xref: /illumos-gate/usr/src/boot/libsa/bzipfs.c (revision 22028508)
1 /*
2  * Copyright (c) 1998 Michael Smith.
3  * Copyright (c) 2000 Maxim Sobolev
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 
30 #ifndef REGRESSION
31 #include "stand.h"
32 #else
33 #include <stdlib.h>
34 #include <sys/errno.h>
35 #include <sys/fcntl.h>
36 #include <sys/types.h>
37 #include <sys/unistd.h>
38 
39 struct open_file {
40 	int	f_flags;	/* see F_* below */
41 	void	*f_fsdata;	/* file system specific data */
42 };
43 #define	F_READ		0x0001	/* file opened for reading */
44 #define	EOFFSET	(ELAST + 8)	/* relative seek not supported */
45 #define	panic(x, y) abort()
46 
47 static inline uint_t
min(uint_t a,uint_t b)48 min(uint_t a, uint_t b)
49 {
50 	return (a < b ? a : b);
51 }
52 #endif
53 
54 #include <sys/stat.h>
55 #include <string.h>
56 #include <bzlib.h>
57 
58 #define	BZ_BUFSIZE 2048	/* XXX larger? */
59 
60 struct bz_file
61 {
62 	int		bzf_rawfd;
63 	bz_stream	bzf_bzstream;
64 	char		bzf_buf[BZ_BUFSIZE];
65 	int		bzf_endseen;
66 };
67 
68 static int	bzf_fill(struct bz_file *);
69 static int	bzf_open(const char *, struct open_file *);
70 static int	bzf_close(struct open_file *);
71 static int	bzf_read(struct open_file *, void *, size_t, size_t *);
72 static off_t	bzf_seek(struct open_file *, off_t, int);
73 static int	bzf_stat(struct open_file *, struct stat *);
74 
75 #ifndef REGRESSION
76 struct fs_ops bzipfs_fsops = {
77 	.fs_name = "bzip",
78 	.fo_open = bzf_open,
79 	.fo_close = bzf_close,
80 	.fo_read = bzf_read,
81 	.fo_write = null_write,
82 	.fo_seek = bzf_seek,
83 	.fo_stat = bzf_stat,
84 	.fo_readdir = null_readdir
85 };
86 #endif
87 
88 static int
bzf_fill(struct bz_file * bzf)89 bzf_fill(struct bz_file *bzf)
90 {
91 	int		result;
92 	int		req;
93 
94 	req = BZ_BUFSIZE - bzf->bzf_bzstream.avail_in;
95 	result = 0;
96 
97 	/* If we need more */
98 	if (req > 0) {
99 		/* move old data to bottom of buffer */
100 		if (req < BZ_BUFSIZE) {
101 			bcopy(bzf->bzf_buf + req, bzf->bzf_buf,
102 			    BZ_BUFSIZE - req);
103 		}
104 
105 		/* read to fill buffer and update availibility data */
106 		result = read(bzf->bzf_rawfd,
107 		    bzf->bzf_buf + bzf->bzf_bzstream.avail_in, req);
108 		bzf->bzf_bzstream.next_in = bzf->bzf_buf;
109 		if (result >= 0)
110 			bzf->bzf_bzstream.avail_in += result;
111 	}
112 	return (result);
113 }
114 
115 /*
116  * Adapted from get_byte/check_header in libz
117  *
118  * Returns 0 if the header is OK, nonzero if not.
119  */
120 static int
get_byte(struct bz_file * bzf)121 get_byte(struct bz_file *bzf)
122 {
123 	if ((bzf->bzf_bzstream.avail_in == 0) && (bzf_fill(bzf) == -1))
124 		return (-1);
125 	bzf->bzf_bzstream.avail_in--;
126 	return (*(bzf->bzf_bzstream.next_in)++);
127 }
128 
129 static int bz_magic[3] = {'B', 'Z', 'h'}; /* bzip2 magic header */
130 
131 static int
check_header(struct bz_file * bzf)132 check_header(struct bz_file *bzf)
133 {
134 	unsigned int len;
135 	int		 c;
136 
137 	/* Check the bzip2 magic header */
138 	for (len = 0; len < 3; len++) {
139 		c = get_byte(bzf);
140 		if (c != bz_magic[len]) {
141 			return (1);
142 		}
143 	}
144 	/* Check that the block size is valid */
145 	c = get_byte(bzf);
146 	if (c < '1' || c > '9')
147 		return (1);
148 
149 	/* Put back bytes that we've took from the input stream */
150 	bzf->bzf_bzstream.next_in -= 4;
151 	bzf->bzf_bzstream.avail_in += 4;
152 
153 	return (0);
154 }
155 
156 static int
bzf_open(const char * fname,struct open_file * f)157 bzf_open(const char *fname, struct open_file *f)
158 {
159 	static char		*bzfname;
160 	int			rawfd;
161 	struct bz_file	*bzf;
162 	char		*cp;
163 	int			error;
164 	struct stat		sb;
165 
166 	/* Have to be in "just read it" mode */
167 	if (f->f_flags != F_READ)
168 		return (EPERM);
169 
170 	/* If the name already ends in .gz or .bz2, ignore it */
171 	if ((cp = strrchr(fname, '.')) &&
172 	    ((strcmp(cp, ".gz") == 0) ||
173 	    (strcmp(cp, ".bz2") == 0) ||
174 	    (strcmp(cp, ".split") == 0)))
175 		return (ENOENT);
176 
177 	/* Construct new name */
178 	bzfname = malloc(strlen(fname) + 5);
179 	if (bzfname == NULL)
180 		return (ENOMEM);
181 	sprintf(bzfname, "%s.bz2", fname);
182 
183 	/* Try to open the compressed datafile */
184 	rawfd = open(bzfname, O_RDONLY);
185 	free(bzfname);
186 	if (rawfd == -1)
187 		return (ENOENT);
188 
189 	if (fstat(rawfd, &sb) < 0) {
190 		printf("bzf_open: stat failed\n");
191 		close(rawfd);
192 		return (ENOENT);
193 	}
194 	if (!S_ISREG(sb.st_mode)) {
195 		printf("bzf_open: not a file\n");
196 		close(rawfd);
197 		return (EISDIR);		/* best guess */
198 	}
199 
200 	/* Allocate a bz_file structure, populate it */
201 	bzf = malloc(sizeof (struct bz_file));
202 	if (bzf == NULL)
203 		return (ENOMEM);
204 	bzero(bzf, sizeof (struct bz_file));
205 	bzf->bzf_rawfd = rawfd;
206 
207 	/* Verify that the file is bzipped */
208 	if (check_header(bzf)) {
209 		close(bzf->bzf_rawfd);
210 		free(bzf);
211 		return (EFTYPE);
212 	}
213 
214 	/* Initialise the inflation engine */
215 	error = BZ2_bzDecompressInit(&(bzf->bzf_bzstream), 0, 1);
216 	if (error != BZ_OK) {
217 		printf("bzf_open: BZ2_bzDecompressInit returned %d\n", error);
218 		close(bzf->bzf_rawfd);
219 		free(bzf);
220 		return (EIO);
221 	}
222 
223 	/* Looks OK, we'll take it */
224 	f->f_fsdata = bzf;
225 	return (0);
226 }
227 
228 static int
bzf_close(struct open_file * f)229 bzf_close(struct open_file *f)
230 {
231 	struct bz_file	*bzf = (struct bz_file *)f->f_fsdata;
232 
233 	BZ2_bzDecompressEnd(&(bzf->bzf_bzstream));
234 	close(bzf->bzf_rawfd);
235 	free(bzf);
236 	return (0);
237 }
238 
239 static int
bzf_read(struct open_file * f,void * buf,size_t size,size_t * resid)240 bzf_read(struct open_file *f, void *buf, size_t size, size_t *resid)
241 {
242 	struct bz_file	*bzf = (struct bz_file *)f->f_fsdata;
243 	int			error;
244 
245 	bzf->bzf_bzstream.next_out = buf;	/* where and how much */
246 	bzf->bzf_bzstream.avail_out = size;
247 
248 	while (bzf->bzf_bzstream.avail_out && bzf->bzf_endseen == 0) {
249 		if ((bzf->bzf_bzstream.avail_in == 0) &&
250 		    (bzf_fill(bzf) == -1)) {
251 			printf("bzf_read: fill error\n");
252 			return (EIO);
253 		}
254 		if (bzf->bzf_bzstream.avail_in == 0) {
255 			/* oops, unexpected EOF */
256 			printf("bzf_read: unexpected EOF\n");
257 			if (bzf->bzf_bzstream.avail_out == size)
258 				return (EIO);
259 			break;
260 		}
261 
262 		/* decompression pass */
263 		error = BZ2_bzDecompress(&bzf->bzf_bzstream);
264 		if (error == BZ_STREAM_END) {		/* EOF, all done */
265 			bzf->bzf_endseen = 1;
266 			break;
267 		}
268 		if (error != BZ_OK) {	/* argh, decompression error */
269 			printf("bzf_read: BZ2_bzDecompress returned %d\n",
270 			    error);
271 			return (EIO);
272 		}
273 	}
274 	if (resid != NULL)
275 		*resid = bzf->bzf_bzstream.avail_out;
276 	return (0);
277 }
278 
279 static int
bzf_rewind(struct open_file * f)280 bzf_rewind(struct open_file *f)
281 {
282 	struct bz_file	*bzf = (struct bz_file *)f->f_fsdata;
283 	struct bz_file	*bzf_tmp;
284 
285 	/*
286 	 * Since bzip2 does not have an equivalent inflateReset function a crude
287 	 * one needs to be provided. The functions all called in such a way that
288 	 * at any time an error occurs a roll back can be done (effectively
289 	 * making this rewind 'atomic', either the reset occurs successfully
290 	 * or not at all, with no 'undefined' state happening).
291 	 */
292 
293 	/* Allocate a bz_file structure, populate it */
294 	bzf_tmp = malloc(sizeof (struct bz_file));
295 	if (bzf_tmp == NULL)
296 		return (-1);
297 	bzero(bzf_tmp, sizeof (struct bz_file));
298 	bzf_tmp->bzf_rawfd = bzf->bzf_rawfd;
299 
300 	/* Initialise the inflation engine */
301 	if (BZ2_bzDecompressInit(&(bzf_tmp->bzf_bzstream), 0, 1) != BZ_OK) {
302 		free(bzf_tmp);
303 		return (-1);
304 	}
305 
306 	/* Seek back to the beginning of the file */
307 	if (lseek(bzf->bzf_rawfd, 0, SEEK_SET) == -1) {
308 		BZ2_bzDecompressEnd(&(bzf_tmp->bzf_bzstream));
309 		free(bzf_tmp);
310 		return (-1);
311 	}
312 
313 	/* Free old bz_file data */
314 	BZ2_bzDecompressEnd(&(bzf->bzf_bzstream));
315 	free(bzf);
316 
317 	/* Use the new bz_file data */
318 	f->f_fsdata = bzf_tmp;
319 
320 	return (0);
321 }
322 
323 static off_t
bzf_seek(struct open_file * f,off_t offset,int where)324 bzf_seek(struct open_file *f, off_t offset, int where)
325 {
326 	struct bz_file	*bzf = (struct bz_file *)f->f_fsdata;
327 	off_t		target;
328 	char		discard[16];
329 
330 	switch (where) {
331 	case SEEK_SET:
332 		target = offset;
333 		break;
334 	case SEEK_CUR:
335 		target = offset + bzf->bzf_bzstream.total_out_lo32;
336 		break;
337 	default:
338 		errno = EINVAL;
339 		return (-1);
340 	}
341 
342 	/* Can we get there from here? */
343 	if (target < bzf->bzf_bzstream.total_out_lo32 && bzf_rewind(f) != 0) {
344 		errno = EOFFSET;
345 		return (-1);
346 	}
347 
348 	/* if bzf_rewind was called then bzf has changed */
349 	bzf = (struct bz_file *)f->f_fsdata;
350 
351 	/* skip forwards if required */
352 	while (target > bzf->bzf_bzstream.total_out_lo32) {
353 		errno = bzf_read(f, discard, min(sizeof (discard),
354 		    target - bzf->bzf_bzstream.total_out_lo32), NULL);
355 		if (errno)
356 			return (-1);
357 	}
358 	/* This is where we are (be honest if we overshot) */
359 	return (bzf->bzf_bzstream.total_out_lo32);
360 }
361 
362 static int
bzf_stat(struct open_file * f,struct stat * sb)363 bzf_stat(struct open_file *f, struct stat *sb)
364 {
365 	struct bz_file	*bzf = (struct bz_file *)f->f_fsdata;
366 	int		result;
367 
368 	/* stat as normal, but indicate that size is unknown */
369 	if ((result = fstat(bzf->bzf_rawfd, sb)) == 0)
370 		sb->st_size = -1;
371 	return (result);
372 }
373 
374 void
bz_internal_error(int errorcode)375 bz_internal_error(int errorcode)
376 {
377 	panic("bzipfs: critical error %d in bzip2 library occured",
378 	    errorcode);
379 }
380 
381 #ifdef REGRESSION
382 /* Small test case, open and decompress test.bz2 */
383 int
main()384 main()
385 {
386 	struct open_file f;
387 	char buf[1024];
388 	size_t resid;
389 	int err;
390 
391 	memset(&f, '\0', sizeof (f));
392 	f.f_flags = F_READ;
393 	err = bzf_open("test", &f);
394 	if (err != 0)
395 		exit(1);
396 	do {
397 		err = bzf_read(&f, buf, sizeof (buf), &resid);
398 	} while (err == 0 && resid != sizeof (buf));
399 
400 	if (err != 0)
401 		exit(2);
402 	exit(0);
403 }
404 #endif
405