#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/vnode.h>
#include <sys/bootvfs.h>
#include <sys/filep.h>
#include <sys/kobj.h>
#include <zlib.h>
#include <sys/sunddi.h>
#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 int bootrd_debug;
extern void *bkmem_alloc(size_t);
extern void bkmem_free(void *, size_t);
caddr_t scratch_bufs[MAX_DECOMP_BUFS];
int decomp_bufcnt;
int free_dcomp_bufs;
char seek_scrbuf[SEEKBUFSIZE];
int cf_debug = 0;
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);
}
void
cf_free(void *opaque __unused, void *addr __unused)
{
}
int
cf_check_compressed(fileid_t *filep)
{
unsigned char *filebytes;
z_stream *zsp;
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);
filep->fi_flags |= FI_COMPRESSED;
if (cf_debug)
kobj_printf("file %s is compressed\n", filep->fi_path);
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;
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) {
if (cf_debug)
kobj_printf("inflateInit2() failed\n");
return (-1);
}
return (0);
}
void
cf_close(fileid_t *filep)
{
if ((filep->fi_flags & FI_COMPRESSED) == 0)
return;
if (cf_debug)
kobj_printf("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;
if (cf_debug)
kobj_printf("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
#define FLG_FEXTRA 0x04
#define FLG_FNAME 0x08
#define FLG_FCOMMENT 0x10
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;
if (cf_debug)
kobj_printf("cf_read: %s %lx bytes\n", filep->fi_path, count);
zsp = filep->fi_dcstream;
ip = filep->fi_inode;
if (cf_debug)
kobj_printf(" 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) {
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;
if (cf_debug) {
kobj_printf("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;
if (cf_debug) {
kobj_printf("inflated %x bytes, errcode=%d\n",
infbytes, err);
}
if (filep->fi_cfoff >= ip->i_size || err == Z_STREAM_END)
break;
}
if (cf_debug) {
kobj_printf("cf_read: returned %lx bytes\n",
count - zsp->avail_out);
}
return (count - zsp->avail_out);
}
void
cf_seek(fileid_t *filep, off_t addr, int whence)
{
z_stream *zsp;
int readsz;
if (cf_debug)
kobj_printf("cf_seek: %s to %lx\n", filep->fi_path, addr);
zsp = filep->fi_dcstream;
if (whence == SEEK_CUR)
addr += zsp->total_out;
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;
}
}