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