xref: /illumos-gate/usr/src/common/fs/decompress.c (revision eb9a1df2aeb866bf1de4494433b6d7e5fa07b3ae)
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 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Decompression module for stand alone file systems.
29  */
30 
31 #include <sys/param.h>
32 #include <sys/sysmacros.h>
33 #include <sys/vnode.h>
34 #include <sys/bootvfs.h>
35 #include <sys/filep.h>
36 #include <zlib.h>
37 
38 #ifdef	_BOOT
39 #include "../common/util.h"
40 #else
41 #include <sys/sunddi.h>
42 #endif
43 
44 #define	MAX_DECOMP_BUFS		8
45 #define	GZIP_ID_BYTE_1		0x1f
46 #define	GZIP_ID_BYTE_2		0x8b
47 #define	GZIP_CM_DEFLATE		0x08
48 #define	SEEKBUFSIZE		8192
49 
50 extern void prom_printf(const char *fmt, ...);
51 
52 #ifdef	_BOOT
53 #define	dprintf	if (cf_debug) prom_printf
54 #else
55 #define	dprintf	if (cf_debug) prom_printf
56 
57 #endif
58 
59 extern int bootrd_debug;
60 extern void *bkmem_alloc(size_t);
61 extern void bkmem_free(void *, size_t);
62 
63 caddr_t scratch_bufs[MAX_DECOMP_BUFS];	/* array of free scratch mem bufs */
64 int decomp_bufcnt;			/* total no, of allocated decomp bufs */
65 int free_dcomp_bufs;			/* no. of free decomp bufs */
66 char seek_scrbuf[SEEKBUFSIZE];		/* buffer for seeking */
67 int cf_debug = 0;			/* non-zero enables debug prints */
68 
69 void *
70 cf_alloc(void *opaque, unsigned int items, unsigned int size)
71 {
72 	fileid_t *filep;
73 	unsigned int nbytes;
74 	caddr_t ptr;
75 
76 	filep = (fileid_t *)opaque;
77 	nbytes = roundup(items * size, sizeof (long));
78 	if (nbytes > (DECOMP_BUFSIZE - filep->fi_dcscrused)) {
79 		ptr = bkmem_alloc(nbytes);
80 	} else {
81 		ptr = &filep->fi_dcscrbuf[filep->fi_dcscrused];
82 		filep->fi_dcscrused += nbytes;
83 	}
84 	bzero(ptr, nbytes);
85 	return (ptr);
86 }
87 
88 /*
89  * Decompression scratch memory free routine, does nothing since we free
90  * the entire scratch area all at once on file close.
91  */
92 /* ARGSUSED */
93 void
94 cf_free(void *opaque, void *addr)
95 {
96 }
97 
98 /*
99  * Read the first block of the file described by filep and determine if
100  * the file is gzip-compressed.  If so, the compressed flag will be set
101  * in the fileid_t struct pointed to by filep and it will be initialized
102  * for doing decompression on reads to the file.
103  */
104 int
105 cf_check_compressed(fileid_t *filep)
106 {
107 	unsigned char *filebytes;
108 	z_stream *zsp;
109 
110 	/*
111 	 * checking for a dcfs compressed file first would involve:
112 	 *
113 	 *	if (filep->fi_inode->i_cflags & ICOMPRESS)
114 	 *		filep->fi_flags |= FI_COMPRESSED;
115 	 */
116 
117 	/*
118 	 * If the file is not long enough to check for a
119 	 * decompression header then return not compressed.
120 	 */
121 	if (filep->fi_inode->i_size < 3)
122 		return (0);
123 	filep->fi_offset = 0;
124 	if ((filep->fi_getblock)(filep) == -1)
125 		return (-1);
126 	filep->fi_offset = 0;
127 	filep->fi_count = 0;
128 	filep->fi_cfoff = 0;
129 	filebytes = (unsigned char *)filep->fi_memp;
130 	if (filebytes[0] != GZIP_ID_BYTE_1 ||
131 	    filebytes[1] != GZIP_ID_BYTE_2 ||
132 	    filebytes[2] != GZIP_CM_DEFLATE)
133 		return (0); /* not compressed */
134 	filep->fi_flags |= FI_COMPRESSED;
135 
136 	dprintf("file %s is compressed\n", filep->fi_path);
137 
138 	/*
139 	 * Allocate decompress scratch buffer
140 	 */
141 	if (free_dcomp_bufs) {
142 		filep->fi_dcscrbuf = scratch_bufs[--free_dcomp_bufs];
143 	} else {
144 		filep->fi_dcscrbuf = bkmem_alloc(DECOMP_BUFSIZE);
145 		decomp_bufcnt++;
146 	}
147 	filep->fi_dcscrused = 0;
148 	zsp = bkmem_alloc(sizeof (*zsp));
149 	filep->fi_dcstream = zsp;
150 	/*
151 	 * Initialize the decompression stream. Adding 16 to the window size
152 	 * indicates that zlib should expect a gzip header.
153 	 */
154 	bzero(zsp, sizeof (*zsp));
155 	zsp->opaque = filep;
156 	zsp->zalloc = cf_alloc;
157 	zsp->zfree = cf_free;
158 	zsp->avail_in = 0;
159 	zsp->next_in = NULL;
160 	zsp->avail_out = 0;
161 	zsp->next_out = NULL;
162 	if (inflateInit2(zsp, MAX_WBITS | 0x20) != Z_OK) {
163 		dprintf("inflateInit2() failed\n");
164 		return (-1);
165 	}
166 	return (0);
167 }
168 
169 /*
170  * If the file described by fileid_t struct at *filep is compressed
171  * free any resources associated with the decompression.  (decompression
172  * buffer, etc.).
173  */
174 void
175 cf_close(fileid_t *filep)
176 {
177 	if ((filep->fi_flags & FI_COMPRESSED) == 0)
178 		return;
179 	dprintf("cf_close: %s\n", filep->fi_path);
180 	(void) inflateEnd(filep->fi_dcstream);
181 	bkmem_free(filep->fi_dcstream, sizeof (z_stream));
182 	if (free_dcomp_bufs == MAX_DECOMP_BUFS) {
183 		bkmem_free(filep->fi_dcscrbuf, DECOMP_BUFSIZE);
184 	} else {
185 		scratch_bufs[free_dcomp_bufs++] = filep->fi_dcscrbuf;
186 	}
187 }
188 
189 void
190 cf_rewind(fileid_t *filep)
191 {
192 	z_stream *zsp;
193 
194 	dprintf("cf_rewind: %s\n", filep->fi_path);
195 	zsp = filep->fi_dcstream;
196 	zsp->avail_in = 0;
197 	zsp->next_in = NULL;
198 	(void) inflateReset(zsp);
199 	filep->fi_cfoff = 0;
200 }
201 
202 #define	FLG_FHCRC	0x02	/* crc field present */
203 #define	FLG_FEXTRA	0x04	/* "extra" field present */
204 #define	FLG_FNAME	0x08	/* file name field present */
205 #define	FLG_FCOMMENT	0x10	/* comment field present */
206 
207 /*
208  * Read at the current uncompressed offset from the compressed file described
209  * by *filep.  Will return decompressed data.
210  */
211 int
212 cf_read(fileid_t *filep, caddr_t buf, size_t count)
213 {
214 	z_stream *zsp;
215 	struct inode *ip;
216 	int err = Z_OK;
217 	int infbytes;
218 	off_t soff;
219 	caddr_t smemp;
220 
221 	dprintf("cf_read: %s ", filep->fi_path);
222 	dprintf("%lx bytes\n", count);
223 	zsp = filep->fi_dcstream;
224 	ip = filep->fi_inode;
225 	dprintf("   reading at offset %lx\n", zsp->total_out);
226 	zsp->next_out = (unsigned char *)buf;
227 	zsp->avail_out = count;
228 	while (zsp->avail_out != 0) {
229 		if (zsp->avail_in == 0 && filep->fi_cfoff < ip->i_size) {
230 			/*
231 			 * read a block of the file to inflate
232 			 */
233 			soff = filep->fi_offset;
234 			smemp = filep->fi_memp;
235 			filep->fi_memp = NULL;
236 			filep->fi_offset = filep->fi_cfoff;
237 			filep->fi_count = 0;
238 			if ((*filep->fi_getblock)(filep) == -1)
239 				return (-1);
240 			filep->fi_offset = soff;
241 			zsp->next_in = (unsigned char *)filep->fi_memp;
242 			zsp->avail_in = filep->fi_count;
243 			filep->fi_memp = smemp;
244 			filep->fi_cfoff += filep->fi_count;
245 		}
246 		infbytes = zsp->avail_out;
247 		dprintf("attempting inflate of %x bytes to buf at: %lx\n",
248 		    zsp->avail_out, (unsigned long)zsp->next_out);
249 		err = inflate(zsp, Z_NO_FLUSH);
250 		infbytes -= zsp->avail_out;
251 		dprintf("inflated %x bytes, errcode=%d\n", infbytes, err);
252 		/*
253 		 * break out if we hit end of the compressed file
254 		 * or the end of the compressed byte stream
255 		 */
256 		if (filep->fi_cfoff >= ip->i_size || err == Z_STREAM_END)
257 			break;
258 	}
259 	dprintf("cf_read: returned %lx bytes\n", count - zsp->avail_out);
260 	return (count - zsp->avail_out);
261 }
262 
263 /*
264  * Seek to the location specified by addr
265  */
266 void
267 cf_seek(fileid_t *filep, off_t addr, int whence)
268 {
269 	z_stream *zsp;
270 	int readsz;
271 
272 	dprintf("cf_seek: %s ", filep->fi_path);
273 	dprintf("to %lx\n", addr);
274 	zsp = filep->fi_dcstream;
275 	if (whence == SEEK_CUR)
276 		addr += zsp->total_out;
277 	/*
278 	 * To seek backwards, must rewind and seek forwards
279 	 */
280 	if (addr < zsp->total_out) {
281 		cf_rewind(filep);
282 		filep->fi_offset = 0;
283 	} else {
284 		addr -= zsp->total_out;
285 	}
286 	while (addr > 0) {
287 		readsz = MIN(addr, SEEKBUFSIZE);
288 		(void) cf_read(filep, seek_scrbuf, readsz);
289 		addr -= readsz;
290 	}
291 }
292