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