xref: /illumos-gate/usr/src/common/fs/decompress.c (revision 15deec58)
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. Adding 16 to the window size
143 	 * indicates that zlib should expect a gzip header.
144 	 */
145 	bzero(zsp, sizeof (*zsp));
146 	zsp->opaque = filep;
147 	zsp->zalloc = cf_alloc;
148 	zsp->zfree = cf_free;
149 	zsp->avail_in = 0;
150 	zsp->next_in = NULL;
151 	zsp->avail_out = 0;
152 	zsp->next_out = NULL;
153 	if (inflateInit2(zsp, MAX_WBITS + 16) != Z_OK) {
154 		dprintf("inflateInit2() failed\n");
155 		return (-1);
156 	}
157 	return (0);
158 }
159 
160 /*
161  * If the file described by fileid_t struct at *filep is compressed
162  * free any resources associated with the decompression.  (decompression
163  * buffer, etc.).
164  */
165 void
166 cf_close(fileid_t *filep)
167 {
168 	if ((filep->fi_flags & FI_COMPRESSED) == 0)
169 		return;
170 	dprintf("cf_close: %s\n", filep->fi_path);
171 	(void) inflateEnd(filep->fi_dcstream);
172 	bkmem_free(filep->fi_dcstream, sizeof (z_stream));
173 	if (free_dcomp_bufs == MAX_DECOMP_BUFS) {
174 		bkmem_free(filep->fi_dcscrbuf, DECOMP_BUFSIZE);
175 	} else {
176 		scratch_bufs[free_dcomp_bufs++] = filep->fi_dcscrbuf;
177 	}
178 }
179 
180 void
181 cf_rewind(fileid_t *filep)
182 {
183 	z_stream *zsp;
184 
185 	dprintf("cf_rewind: %s\n", filep->fi_path);
186 	zsp = filep->fi_dcstream;
187 	zsp->avail_in = 0;
188 	zsp->next_in = NULL;
189 	(void) inflateReset(zsp);
190 	filep->fi_cfoff = 0;
191 }
192 
193 #define	FLG_FHCRC	0x02	/* crc field present */
194 #define	FLG_FEXTRA	0x04	/* "extra" field present */
195 #define	FLG_FNAME	0x08	/* file name field present */
196 #define	FLG_FCOMMENT	0x10	/* comment field present */
197 
198 /*
199  * Read at the current uncompressed offset from the compressed file described
200  * by *filep.  Will return decompressed data.
201  */
202 int
203 cf_read(fileid_t *filep, caddr_t buf, size_t count)
204 {
205 	z_stream *zsp;
206 	struct inode *ip;
207 	int err = Z_OK;
208 	int infbytes;
209 	off_t soff;
210 	caddr_t smemp;
211 
212 	dprintf("cf_read: %s ", filep->fi_path);
213 	dprintf("%lx bytes\n", count);
214 	zsp = filep->fi_dcstream;
215 	ip = filep->fi_inode;
216 	dprintf("   reading at offset %lx\n", zsp->total_out);
217 	zsp->next_out = (unsigned char *)buf;
218 	zsp->avail_out = count;
219 	while (zsp->avail_out != 0) {
220 		if (zsp->avail_in == 0 && filep->fi_cfoff < ip->i_size) {
221 			/*
222 			 * read a block of the file to inflate
223 			 */
224 			soff = filep->fi_offset;
225 			smemp = filep->fi_memp;
226 			filep->fi_memp = NULL;
227 			filep->fi_offset = filep->fi_cfoff;
228 			filep->fi_count = 0;
229 			if ((*filep->fi_getblock)(filep) == -1)
230 				return (-1);
231 			filep->fi_offset = soff;
232 			zsp->next_in = (unsigned char *)filep->fi_memp;
233 			zsp->avail_in = filep->fi_count;
234 			filep->fi_memp = smemp;
235 			filep->fi_cfoff += filep->fi_count;
236 		}
237 		infbytes = zsp->avail_out;
238 		dprintf("attempting inflate of %x bytes to buf at: %lx\n",
239 		    zsp->avail_out, (unsigned long)zsp->next_out);
240 		err = inflate(zsp, Z_NO_FLUSH);
241 		infbytes -= zsp->avail_out;
242 		dprintf("inflated %x bytes, errcode=%d\n", infbytes, err);
243 		/*
244 		 * break out if we hit end of the compressed file
245 		 * or the end of the compressed byte stream
246 		 */
247 		if (filep->fi_cfoff >= ip->i_size || err == Z_STREAM_END)
248 			break;
249 	}
250 	dprintf("cf_read: returned %lx bytes\n", count - zsp->avail_out);
251 	return (count - zsp->avail_out);
252 }
253 
254 /*
255  * Seek to the location specified by addr
256  */
257 void
258 cf_seek(fileid_t *filep, off_t addr, int whence)
259 {
260 	z_stream *zsp;
261 	int readsz;
262 
263 	dprintf("cf_seek: %s ", filep->fi_path);
264 	dprintf("to %lx\n", addr);
265 	zsp = filep->fi_dcstream;
266 	if (whence == SEEK_CUR)
267 		addr += zsp->total_out;
268 	/*
269 	 * To seek backwards, must rewind and seek forwards
270 	 */
271 	if (addr < zsp->total_out) {
272 		cf_rewind(filep);
273 		filep->fi_offset = 0;
274 	} else {
275 		addr -= zsp->total_out;
276 	}
277 	while (addr > 0) {
278 		readsz = MIN(addr, SEEKBUFSIZE);
279 		(void) cf_read(filep, seek_scrbuf, readsz);
280 		addr -= readsz;
281 	}
282 }
283