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