xref: /illumos-gate/usr/src/common/fs/decompress.c (revision 150d2c5288c645a1c1a7d2bee61199a3729406c7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Decompression module for stand alone file systems.
30  */
31 
32 #include <sys/param.h>
33 #include <sys/sysmacros.h>
34 #include <sys/vnode.h>
35 #include <sys/bootvfs.h>
36 #include <sys/filep.h>
37 #include <zmod/zlib.h>
38 
39 #ifdef	_BOOT
40 #include "../common/util.h"
41 #else
42 #include <sys/sunddi.h>
43 #endif
44 
45 #define	MAX_DECOMP_BUFS		8
46 #define	GZIP_ID_BYTE_1		0x1f
47 #define	GZIP_ID_BYTE_2		0x8b
48 #define	GZIP_CM_DEFLATE		0x08
49 #define	SEEKBUFSIZE		8192
50 
51 #ifdef	_BOOT
52 #define	dprintf	if (cf_debug) printf
53 #else
54 #define	dprintf	if (cf_debug) printf
55 
56 #endif
57 
58 extern int bootrd_debug;
59 extern void *bkmem_alloc(size_t);
60 extern void bkmem_free(void *, size_t);
61 
62 caddr_t scratch_bufs[MAX_DECOMP_BUFS];	/* array of free scratch mem bufs */
63 int decomp_bufcnt;			/* total no, of allocated decomp bufs */
64 int free_dcomp_bufs;			/* no. of free decomp bufs */
65 char seek_scrbuf[SEEKBUFSIZE];		/* buffer for seeking */
66 int cf_debug;				/* non-zero enables debug prints */
67 
68 void *
69 cf_alloc(void *opaque, unsigned int items, unsigned int size)
70 {
71 	fileid_t *filep;
72 	unsigned int nbytes;
73 	caddr_t ptr;
74 
75 	filep = (fileid_t *)opaque;
76 	nbytes = roundup(items * size, sizeof (long));
77 	if (nbytes > (DECOMP_BUFSIZE - filep->fi_dcscrused)) {
78 		ptr = bkmem_alloc(nbytes);
79 	} else {
80 		ptr = &filep->fi_dcscrbuf[filep->fi_dcscrused];
81 		filep->fi_dcscrused += nbytes;
82 	}
83 	bzero(ptr, nbytes);
84 	return (ptr);
85 }
86 
87 /*
88  * Decompression scratch memory free routine, does nothing since we free
89  * the entire scratch area all at once on file close.
90  */
91 /* ARGSUSED */
92 void
93 cf_free(void *opaque, void *addr)
94 {
95 }
96 
97 /*
98  * Read the first block of the file described by filep and determine if
99  * the file is gzip-compressed.  If so, the compressed flag will be set
100  * in the fileid_t struct pointed to by filep and it will be initialized
101  * for doing decompression on reads to the file.
102  */
103 int
104 cf_check_compressed(fileid_t *filep)
105 {
106 	unsigned char *filebytes;
107 	z_stream *zsp;
108 
109 	filep->fi_offset = 0;
110 	if ((filep->fi_getblock)(filep) == -1)
111 		return (-1);
112 	filep->fi_offset = 0;
113 	filep->fi_count = 0;
114 	filep->fi_cfoff = 0;
115 	filebytes = (unsigned char *)filep->fi_memp;
116 	if (filebytes[0] != GZIP_ID_BYTE_1 || filebytes[1] != GZIP_ID_BYTE_2 ||
117 	    filebytes[2] != GZIP_CM_DEFLATE)
118 		return (0); /* not compressed */
119 	filep->fi_flags |= FI_COMPRESSED;
120 
121 	dprintf("file %s is compressed\n", filep->fi_path);
122 
123 	/*
124 	 * Allocate decompress scratch buffer
125 	 */
126 	if (free_dcomp_bufs) {
127 		filep->fi_dcscrbuf = scratch_bufs[--free_dcomp_bufs];
128 	} else {
129 		filep->fi_dcscrbuf = bkmem_alloc(DECOMP_BUFSIZE);
130 		decomp_bufcnt++;
131 	}
132 	filep->fi_dcscrused = 0;
133 	zsp = bkmem_alloc(sizeof (*zsp));
134 	filep->fi_dcstream = zsp;
135 	/*
136 	 * Initialize the decompression stream
137 	 */
138 	bzero(zsp, sizeof (*zsp));
139 	zsp->opaque = filep;
140 	zsp->zalloc = cf_alloc;
141 	zsp->zfree = cf_free;
142 	zsp->avail_in = 0;
143 	zsp->next_in = NULL;
144 	zsp->avail_out = 0;
145 	zsp->next_out = NULL;
146 	if (inflateInit2(zsp, -MAX_WBITS) != Z_OK)
147 		return (-1);
148 	return (0);
149 }
150 
151 /*
152  * If the file described by fileid_t struct at *filep is compressed
153  * free any resources associated with the decompression.  (decompression
154  * buffer, etc.).
155  */
156 void
157 cf_close(fileid_t *filep)
158 {
159 	if ((filep->fi_flags & FI_COMPRESSED) == 0)
160 		return;
161 	dprintf("cf_close: %s\n", filep->fi_path);
162 	(void) inflateEnd(filep->fi_dcstream);
163 	bkmem_free(filep->fi_dcstream, sizeof (z_stream));
164 	if (free_dcomp_bufs == MAX_DECOMP_BUFS) {
165 		bkmem_free(filep->fi_dcscrbuf, DECOMP_BUFSIZE);
166 	} else {
167 		scratch_bufs[free_dcomp_bufs++] = filep->fi_dcscrbuf;
168 	}
169 }
170 
171 void
172 cf_rewind(fileid_t *filep)
173 {
174 	z_stream *zsp;
175 
176 	dprintf("cf_rewind: %s\n", filep->fi_path);
177 	zsp = filep->fi_dcstream;
178 	zsp->avail_in = 0;
179 	zsp->next_in = NULL;
180 	(void) inflateReset(zsp);
181 	filep->fi_cfoff = 0;
182 }
183 
184 #define	FLG_FHCRC	0x02	/* crc field present */
185 #define	FLG_FEXTRA	0x04	/* "extra" field present */
186 #define	FLG_FNAME	0x08	/* file name field present */
187 #define	FLG_FCOMMENT	0x10	/* comment field present */
188 
189 /*
190  * Calculate the header length of a gzip compressed file
191  */
192 static int
193 cf_headerlen(unsigned char *hp)
194 {
195 	unsigned char flag;
196 	int xlen, hlen = 0;
197 
198 	hlen += 3;	/* skip ID bytes and compression method */
199 	flag = hp[hlen];
200 	hlen += 7;	/* skip flag, mtime(4 bytes), xfl, and os */
201 	if (flag & FLG_FEXTRA) {
202 		xlen = hp[hlen++];
203 		xlen += hp[hlen++] << 8;
204 		hlen += xlen;	/* skip extra field */
205 	}
206 	if (flag & FLG_FNAME)
207 		hlen += strlen((char *)&hp[hlen]) + 1;	/* skip file name */
208 	if (flag & FLG_FCOMMENT)
209 		hlen += strlen((char *)&hp[hlen]) + 1;	/* skip comment */
210 	if (flag & FLG_FHCRC)
211 		hlen += 2;	/* skip crc */
212 	return (hlen);
213 }
214 
215 /*
216  * Read at the current uncompressed offset from the compressed file described
217  * by *filep.  Will return decompressed data.
218  */
219 int
220 cf_read(fileid_t *filep, caddr_t buf, size_t count)
221 {
222 	z_stream *zsp;
223 	struct inode *ip;
224 	int err = Z_OK;
225 	int hlen, infbytes;
226 	off_t soff;
227 	caddr_t smemp;
228 
229 	dprintf("cf_read: %s ", filep->fi_path);
230 	dprintf("%lx bytes\n", count);
231 	zsp = filep->fi_dcstream;
232 	ip = filep->fi_inode;
233 	dprintf("   reading at offset %lx\n", zsp->total_out);
234 	zsp->next_out = (unsigned char *)buf;
235 	zsp->avail_out = count;
236 	while (zsp->avail_out != 0) {
237 		if (zsp->avail_in == 0 && filep->fi_cfoff < ip->i_size) {
238 			/*
239 			 * read a block of the file to inflate
240 			 */
241 			soff = filep->fi_offset;
242 			smemp = filep->fi_memp;
243 			filep->fi_memp = NULL;
244 			filep->fi_offset = filep->fi_cfoff;
245 			filep->fi_count = 0;
246 			if ((*filep->fi_getblock)(filep) == -1)
247 				return (-1);
248 			filep->fi_offset = soff;
249 			zsp->next_in = (unsigned char *)filep->fi_memp;
250 			zsp->avail_in = filep->fi_count;
251 			filep->fi_memp = smemp;
252 			/*
253 			 * If we are reading the first block of the file, we
254 			 * need to skip over the header bytes before inflating
255 			 */
256 			if (filep->fi_cfoff == 0) {
257 				hlen = cf_headerlen(zsp->next_in);
258 				zsp->next_in += hlen;
259 				zsp->avail_in -= hlen;
260 			}
261 			filep->fi_cfoff += filep->fi_count;
262 		}
263 		infbytes = zsp->avail_out;
264 		dprintf("attempting inflate of %x bytes to buf at: %lx\n",
265 		    zsp->avail_out, (unsigned long)zsp->next_out);
266 		err = inflate(zsp, Z_NO_FLUSH);
267 		infbytes -= zsp->avail_out;
268 		dprintf("inflated %x bytes, errcode=%d\n", infbytes, err);
269 		/*
270 		 * break out if we hit end of the compressed file
271 		 * or the end of the compressed byte stream
272 		 */
273 		if (filep->fi_cfoff >= ip->i_size || err == Z_STREAM_END)
274 			break;
275 	}
276 	dprintf("cf_read: returned %lx bytes\n", count - zsp->avail_out);
277 	return (count - zsp->avail_out);
278 }
279 
280 /*
281  * Seek to the location specified by addr
282  */
283 void
284 cf_seek(fileid_t *filep, off_t addr, int whence)
285 {
286 	z_stream *zsp;
287 	int readsz;
288 
289 	dprintf("cf_seek: %s ", filep->fi_path);
290 	dprintf("to %lx\n", addr);
291 	zsp = filep->fi_dcstream;
292 	if (whence == SEEK_CUR)
293 		addr += zsp->total_out;
294 	/*
295 	 * To seek backwards, must rewind and seek forwards
296 	 */
297 	if (addr < zsp->total_out) {
298 		cf_rewind(filep);
299 		filep->fi_offset = 0;
300 	} else {
301 		addr -= zsp->total_out;
302 	}
303 	while (addr > 0) {
304 		readsz = MIN(addr, SEEKBUFSIZE);
305 		(void) cf_read(filep, seek_scrbuf, readsz);
306 		addr -= readsz;
307 	}
308 }
309