| /* |
| * 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; |
| } |
| } |