xref: /titanic_52/usr/src/common/fs/decompress.c (revision 112f9fc1e25dcac8b980e034e763f96fb9736261)
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 	/*
110 	 * If the file is not long enough to check for a decompression header
111 	 * then return not compressed.
112 	 */
113 	if (filep->fi_inode->i_size < 3)
114 		return (0);
115 	filep->fi_offset = 0;
116 	if ((filep->fi_getblock)(filep) == -1)
117 		return (-1);
118 	filep->fi_offset = 0;
119 	filep->fi_count = 0;
120 	filep->fi_cfoff = 0;
121 	filebytes = (unsigned char *)filep->fi_memp;
122 	if (filebytes[0] != GZIP_ID_BYTE_1 || filebytes[1] != GZIP_ID_BYTE_2 ||
123 	    filebytes[2] != GZIP_CM_DEFLATE)
124 		return (0); /* not compressed */
125 	filep->fi_flags |= FI_COMPRESSED;
126 
127 	dprintf("file %s is compressed\n", filep->fi_path);
128 
129 	/*
130 	 * Allocate decompress scratch buffer
131 	 */
132 	if (free_dcomp_bufs) {
133 		filep->fi_dcscrbuf = scratch_bufs[--free_dcomp_bufs];
134 	} else {
135 		filep->fi_dcscrbuf = bkmem_alloc(DECOMP_BUFSIZE);
136 		decomp_bufcnt++;
137 	}
138 	filep->fi_dcscrused = 0;
139 	zsp = bkmem_alloc(sizeof (*zsp));
140 	filep->fi_dcstream = zsp;
141 	/*
142 	 * Initialize the decompression stream
143 	 */
144 	bzero(zsp, sizeof (*zsp));
145 	zsp->opaque = filep;
146 	zsp->zalloc = cf_alloc;
147 	zsp->zfree = cf_free;
148 	zsp->avail_in = 0;
149 	zsp->next_in = NULL;
150 	zsp->avail_out = 0;
151 	zsp->next_out = NULL;
152 	if (inflateInit2(zsp, -MAX_WBITS) != Z_OK)
153 		return (-1);
154 	return (0);
155 }
156 
157 /*
158  * If the file described by fileid_t struct at *filep is compressed
159  * free any resources associated with the decompression.  (decompression
160  * buffer, etc.).
161  */
162 void
163 cf_close(fileid_t *filep)
164 {
165 	if ((filep->fi_flags & FI_COMPRESSED) == 0)
166 		return;
167 	dprintf("cf_close: %s\n", filep->fi_path);
168 	(void) inflateEnd(filep->fi_dcstream);
169 	bkmem_free(filep->fi_dcstream, sizeof (z_stream));
170 	if (free_dcomp_bufs == MAX_DECOMP_BUFS) {
171 		bkmem_free(filep->fi_dcscrbuf, DECOMP_BUFSIZE);
172 	} else {
173 		scratch_bufs[free_dcomp_bufs++] = filep->fi_dcscrbuf;
174 	}
175 }
176 
177 void
178 cf_rewind(fileid_t *filep)
179 {
180 	z_stream *zsp;
181 
182 	dprintf("cf_rewind: %s\n", filep->fi_path);
183 	zsp = filep->fi_dcstream;
184 	zsp->avail_in = 0;
185 	zsp->next_in = NULL;
186 	(void) inflateReset(zsp);
187 	filep->fi_cfoff = 0;
188 }
189 
190 #define	FLG_FHCRC	0x02	/* crc field present */
191 #define	FLG_FEXTRA	0x04	/* "extra" field present */
192 #define	FLG_FNAME	0x08	/* file name field present */
193 #define	FLG_FCOMMENT	0x10	/* comment field present */
194 
195 /*
196  * Calculate the header length of a gzip compressed file
197  */
198 static int
199 cf_headerlen(unsigned char *hp)
200 {
201 	unsigned char flag;
202 	int xlen, hlen = 0;
203 
204 	hlen += 3;	/* skip ID bytes and compression method */
205 	flag = hp[hlen];
206 	hlen += 7;	/* skip flag, mtime(4 bytes), xfl, and os */
207 	if (flag & FLG_FEXTRA) {
208 		xlen = hp[hlen++];
209 		xlen += hp[hlen++] << 8;
210 		hlen += xlen;	/* skip extra field */
211 	}
212 	if (flag & FLG_FNAME)
213 		hlen += strlen((char *)&hp[hlen]) + 1;	/* skip file name */
214 	if (flag & FLG_FCOMMENT)
215 		hlen += strlen((char *)&hp[hlen]) + 1;	/* skip comment */
216 	if (flag & FLG_FHCRC)
217 		hlen += 2;	/* skip crc */
218 	return (hlen);
219 }
220 
221 /*
222  * Read at the current uncompressed offset from the compressed file described
223  * by *filep.  Will return decompressed data.
224  */
225 int
226 cf_read(fileid_t *filep, caddr_t buf, size_t count)
227 {
228 	z_stream *zsp;
229 	struct inode *ip;
230 	int err = Z_OK;
231 	int hlen, infbytes;
232 	off_t soff;
233 	caddr_t smemp;
234 
235 	dprintf("cf_read: %s ", filep->fi_path);
236 	dprintf("%lx bytes\n", count);
237 	zsp = filep->fi_dcstream;
238 	ip = filep->fi_inode;
239 	dprintf("   reading at offset %lx\n", zsp->total_out);
240 	zsp->next_out = (unsigned char *)buf;
241 	zsp->avail_out = count;
242 	while (zsp->avail_out != 0) {
243 		if (zsp->avail_in == 0 && filep->fi_cfoff < ip->i_size) {
244 			/*
245 			 * read a block of the file to inflate
246 			 */
247 			soff = filep->fi_offset;
248 			smemp = filep->fi_memp;
249 			filep->fi_memp = NULL;
250 			filep->fi_offset = filep->fi_cfoff;
251 			filep->fi_count = 0;
252 			if ((*filep->fi_getblock)(filep) == -1)
253 				return (-1);
254 			filep->fi_offset = soff;
255 			zsp->next_in = (unsigned char *)filep->fi_memp;
256 			zsp->avail_in = filep->fi_count;
257 			filep->fi_memp = smemp;
258 			/*
259 			 * If we are reading the first block of the file, we
260 			 * need to skip over the header bytes before inflating
261 			 */
262 			if (filep->fi_cfoff == 0) {
263 				hlen = cf_headerlen(zsp->next_in);
264 				zsp->next_in += hlen;
265 				zsp->avail_in -= hlen;
266 			}
267 			filep->fi_cfoff += filep->fi_count;
268 		}
269 		infbytes = zsp->avail_out;
270 		dprintf("attempting inflate of %x bytes to buf at: %lx\n",
271 		    zsp->avail_out, (unsigned long)zsp->next_out);
272 		err = inflate(zsp, Z_NO_FLUSH);
273 		infbytes -= zsp->avail_out;
274 		dprintf("inflated %x bytes, errcode=%d\n", infbytes, err);
275 		/*
276 		 * break out if we hit end of the compressed file
277 		 * or the end of the compressed byte stream
278 		 */
279 		if (filep->fi_cfoff >= ip->i_size || err == Z_STREAM_END)
280 			break;
281 	}
282 	dprintf("cf_read: returned %lx bytes\n", count - zsp->avail_out);
283 	return (count - zsp->avail_out);
284 }
285 
286 /*
287  * Seek to the location specified by addr
288  */
289 void
290 cf_seek(fileid_t *filep, off_t addr, int whence)
291 {
292 	z_stream *zsp;
293 	int readsz;
294 
295 	dprintf("cf_seek: %s ", filep->fi_path);
296 	dprintf("to %lx\n", addr);
297 	zsp = filep->fi_dcstream;
298 	if (whence == SEEK_CUR)
299 		addr += zsp->total_out;
300 	/*
301 	 * To seek backwards, must rewind and seek forwards
302 	 */
303 	if (addr < zsp->total_out) {
304 		cf_rewind(filep);
305 		filep->fi_offset = 0;
306 	} else {
307 		addr -= zsp->total_out;
308 	}
309 	while (addr > 0) {
310 		readsz = MIN(addr, SEEKBUFSIZE);
311 		(void) cf_read(filep, seek_scrbuf, readsz);
312 		addr -= readsz;
313 	}
314 }
315