OpenSolaris_b135/common/fs/decompress.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Decompression module for stand alone file systems.
 */

#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/vnode.h>
#include <sys/bootvfs.h>
#include <sys/filep.h>
#include <zmod/zlib.h>

#ifdef	_BOOT
#include "../common/util.h"
#else
#include <sys/sunddi.h>
#endif

#define	MAX_DECOMP_BUFS		8
#define	GZIP_ID_BYTE_1		0x1f
#define	GZIP_ID_BYTE_2		0x8b
#define	GZIP_CM_DEFLATE		0x08
#define	SEEKBUFSIZE		8192

extern void prom_printf(const char *fmt, ...);

#ifdef	_BOOT
#define	dprintf	if (cf_debug) prom_printf
#else
#define	dprintf	if (cf_debug) prom_printf

#endif

extern int bootrd_debug;
extern void *bkmem_alloc(size_t);
extern void bkmem_free(void *, size_t);

caddr_t scratch_bufs[MAX_DECOMP_BUFS];	/* array of free scratch mem bufs */
int decomp_bufcnt;			/* total no, of allocated decomp bufs */
int free_dcomp_bufs;			/* no. of free decomp bufs */
char seek_scrbuf[SEEKBUFSIZE];		/* buffer for seeking */
int cf_debug = 0;			/* non-zero enables debug prints */

void *
cf_alloc(void *opaque, unsigned int items, unsigned int size)
{
	fileid_t *filep;
	unsigned int nbytes;
	caddr_t ptr;

	filep = (fileid_t *)opaque;
	nbytes = roundup(items * size, sizeof (long));
	if (nbytes > (DECOMP_BUFSIZE - filep->fi_dcscrused)) {
		ptr = bkmem_alloc(nbytes);
	} else {
		ptr = &filep->fi_dcscrbuf[filep->fi_dcscrused];
		filep->fi_dcscrused += nbytes;
	}
	bzero(ptr, nbytes);
	return (ptr);
}

/*
 * Decompression scratch memory free routine, does nothing since we free
 * the entire scratch area all at once on file close.
 */
/* ARGSUSED */
void
cf_free(void *opaque, void *addr)
{
}

/*
 * Read the first block of the file described by filep and determine if
 * the file is gzip-compressed.  If so, the compressed flag will be set
 * in the fileid_t struct pointed to by filep and it will be initialized
 * for doing decompression on reads to the file.
 */
int
cf_check_compressed(fileid_t *filep)
{
	unsigned char *filebytes;
	z_stream *zsp;

	/*
	 * checking for a dcfs compressed file first would involve:
	 *
	 *	if (filep->fi_inode->i_cflags & ICOMPRESS)
	 * 		filep->fi_flags |= FI_COMPRESSED;
	 */

	/*
	 * If the file is not long enough to check for a
	 * decompression header then return not compressed.
	 */
	if (filep->fi_inode->i_size < 3)
		return (0);
	filep->fi_offset = 0;
	if ((filep->fi_getblock)(filep) == -1)
		return (-1);
	filep->fi_offset = 0;
	filep->fi_count = 0;
	filep->fi_cfoff = 0;
	filebytes = (unsigned char *)filep->fi_memp;
	if (filebytes[0] != GZIP_ID_BYTE_1 ||
	    filebytes[1] != GZIP_ID_BYTE_2 ||
	    filebytes[2] != GZIP_CM_DEFLATE)
		return (0); /* not compressed */
	filep->fi_flags |= FI_COMPRESSED;

	dprintf("file %s is compressed\n", filep->fi_path);

	/*
	 * Allocate decompress scratch buffer
	 */
	if (free_dcomp_bufs) {
		filep->fi_dcscrbuf = scratch_bufs[--free_dcomp_bufs];
	} else {
		filep->fi_dcscrbuf = bkmem_alloc(DECOMP_BUFSIZE);
		decomp_bufcnt++;
	}
	filep->fi_dcscrused = 0;
	zsp = bkmem_alloc(sizeof (*zsp));
	filep->fi_dcstream = zsp;
	/*
	 * Initialize the decompression stream. Adding 16 to the window size
	 * indicates that zlib should expect a gzip header.
	 */
	bzero(zsp, sizeof (*zsp));
	zsp->opaque = filep;
	zsp->zalloc = cf_alloc;
	zsp->zfree = cf_free;
	zsp->avail_in = 0;
	zsp->next_in = NULL;
	zsp->avail_out = 0;
	zsp->next_out = NULL;
	if (inflateInit2(zsp, MAX_WBITS | 0x20) != Z_OK) {
		dprintf("inflateInit2() failed\n");
		return (-1);
	}
	return (0);
}

/*
 * If the file described by fileid_t struct at *filep is compressed
 * free any resources associated with the decompression.  (decompression
 * buffer, etc.).
 */
void
cf_close(fileid_t *filep)
{
	if ((filep->fi_flags & FI_COMPRESSED) == 0)
		return;
	dprintf("cf_close: %s\n", filep->fi_path);
	(void) inflateEnd(filep->fi_dcstream);
	bkmem_free(filep->fi_dcstream, sizeof (z_stream));
	if (free_dcomp_bufs == MAX_DECOMP_BUFS) {
		bkmem_free(filep->fi_dcscrbuf, DECOMP_BUFSIZE);
	} else {
		scratch_bufs[free_dcomp_bufs++] = filep->fi_dcscrbuf;
	}
}

void
cf_rewind(fileid_t *filep)
{
	z_stream *zsp;

	dprintf("cf_rewind: %s\n", filep->fi_path);
	zsp = filep->fi_dcstream;
	zsp->avail_in = 0;
	zsp->next_in = NULL;
	(void) inflateReset(zsp);
	filep->fi_cfoff = 0;
}

#define	FLG_FHCRC	0x02	/* crc field present */
#define	FLG_FEXTRA	0x04	/* "extra" field present */
#define	FLG_FNAME	0x08	/* file name field present */
#define	FLG_FCOMMENT	0x10	/* comment field present */

/*
 * Read at the current uncompressed offset from the compressed file described
 * by *filep.  Will return decompressed data.
 */
int
cf_read(fileid_t *filep, caddr_t buf, size_t count)
{
	z_stream *zsp;
	struct inode *ip;
	int err = Z_OK;
	int infbytes;
	off_t soff;
	caddr_t smemp;

	dprintf("cf_read: %s ", filep->fi_path);
	dprintf("%lx bytes\n", count);
	zsp = filep->fi_dcstream;
	ip = filep->fi_inode;
	dprintf("   reading at offset %lx\n", zsp->total_out);
	zsp->next_out = (unsigned char *)buf;
	zsp->avail_out = count;
	while (zsp->avail_out != 0) {
		if (zsp->avail_in == 0 && filep->fi_cfoff < ip->i_size) {
			/*
			 * read a block of the file to inflate
			 */
			soff = filep->fi_offset;
			smemp = filep->fi_memp;
			filep->fi_memp = NULL;
			filep->fi_offset = filep->fi_cfoff;
			filep->fi_count = 0;
			if ((*filep->fi_getblock)(filep) == -1)
				return (-1);
			filep->fi_offset = soff;
			zsp->next_in = (unsigned char *)filep->fi_memp;
			zsp->avail_in = filep->fi_count;
			filep->fi_memp = smemp;
			filep->fi_cfoff += filep->fi_count;
		}
		infbytes = zsp->avail_out;
		dprintf("attempting inflate of %x bytes to buf at: %lx\n",
		    zsp->avail_out, (unsigned long)zsp->next_out);
		err = inflate(zsp, Z_NO_FLUSH);
		infbytes -= zsp->avail_out;
		dprintf("inflated %x bytes, errcode=%d\n", infbytes, err);
		/*
		 * break out if we hit end of the compressed file
		 * or the end of the compressed byte stream
		 */
		if (filep->fi_cfoff >= ip->i_size || err == Z_STREAM_END)
			break;
	}
	dprintf("cf_read: returned %lx bytes\n", count - zsp->avail_out);
	return (count - zsp->avail_out);
}

/*
 * Seek to the location specified by addr
 */
void
cf_seek(fileid_t *filep, off_t addr, int whence)
{
	z_stream *zsp;
	int readsz;

	dprintf("cf_seek: %s ", filep->fi_path);
	dprintf("to %lx\n", addr);
	zsp = filep->fi_dcstream;
	if (whence == SEEK_CUR)
		addr += zsp->total_out;
	/*
	 * To seek backwards, must rewind and seek forwards
	 */
	if (addr < zsp->total_out) {
		cf_rewind(filep);
		filep->fi_offset = 0;
	} else {
		addr -= zsp->total_out;
	}
	while (addr > 0) {
		readsz = MIN(addr, SEEKBUFSIZE);
		(void) cf_read(filep, seek_scrbuf, readsz);
		addr -= readsz;
	}
}