root/usr/src/common/fs/decompress.c
/*
 * 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.
 * Copyright 2025 MNX Cloud, Inc.
 */

/*
 * 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 <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];  /* 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.
 */
void
cf_free(void *opaque __unused, void *addr __unused)
{
}

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

        if (cf_debug)
                kobj_printf("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) {
                if (cf_debug)
                        kobj_printf("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;
        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    /* 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;

        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) {
                        /*
                         * 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;
                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);
                }
                /*
                 * 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;
        }
        if (cf_debug) {
                kobj_printf("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;

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