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 <zmod/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
50extern 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
59extern int bootrd_debug;
60extern void *bkmem_alloc(size_t);
61extern void bkmem_free(void *, size_t);
62
63caddr_t scratch_bufs[MAX_DECOMP_BUFS];	/* array of free scratch mem bufs */
64int decomp_bufcnt;			/* total no, of allocated decomp bufs */
65int free_dcomp_bufs;			/* no. of free decomp bufs */
66char seek_scrbuf[SEEKBUFSIZE];		/* buffer for seeking */
67int cf_debug = 0;			/* non-zero enables debug prints */
68
69void *
70cf_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 */
93void
94cf_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 */
104int
105cf_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 */
174void
175cf_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
189void
190cf_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 */
211int
212cf_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 */
266void
267cf_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