/*- * Copyright (c) 2014 Mark Johnston * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #define KERN_GZ_HDRLEN 10 /* gzip header length */ #define KERN_GZ_TRAILERLEN 8 /* gzip trailer length */ #define KERN_GZ_MAGIC1 0x1f /* first magic byte */ #define KERN_GZ_MAGIC2 0x8b /* second magic byte */ MALLOC_DEFINE(M_GZIO, "gzio", "zlib state"); struct gzio_stream { uint8_t * gz_buffer; /* output buffer */ size_t gz_bufsz; /* total buffer size */ off_t gz_off; /* offset into the output stream */ enum gzio_mode gz_mode; /* stream mode */ uint32_t gz_crc; /* stream CRC32 */ gzio_cb gz_cb; /* output callback */ void * gz_arg; /* private callback arg */ z_stream gz_stream; /* zlib state */ }; static void * gz_alloc(void *, u_int, u_int); static void gz_free(void *, void *); static int gz_write(struct gzio_stream *, void *, u_int, int); struct gzio_stream * gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg) { struct gzio_stream *s; uint8_t *hdr; int error; if (bufsz < KERN_GZ_HDRLEN) return (NULL); if (mode != GZIO_DEFLATE) return (NULL); s = gz_alloc(NULL, 1, sizeof(*s)); s->gz_bufsz = bufsz; s->gz_buffer = gz_alloc(NULL, 1, s->gz_bufsz); s->gz_mode = mode; s->gz_crc = ~0U; s->gz_cb = cb; s->gz_arg = arg; s->gz_stream.zalloc = gz_alloc; s->gz_stream.zfree = gz_free; s->gz_stream.opaque = NULL; s->gz_stream.next_in = Z_NULL; s->gz_stream.avail_in = 0; error = deflateInit2(&s->gz_stream, level, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (error != 0) goto fail; s->gz_stream.avail_out = s->gz_bufsz; s->gz_stream.next_out = s->gz_buffer; /* Write the gzip header to the output buffer. */ hdr = s->gz_buffer; memset(hdr, 0, KERN_GZ_HDRLEN); hdr[0] = KERN_GZ_MAGIC1; hdr[1] = KERN_GZ_MAGIC2; hdr[2] = Z_DEFLATED; hdr[9] = OS_CODE; s->gz_stream.next_out += KERN_GZ_HDRLEN; s->gz_stream.avail_out -= KERN_GZ_HDRLEN; return (s); fail: gz_free(NULL, s->gz_buffer); gz_free(NULL, s); return (NULL); } int gzio_write(struct gzio_stream *s, void *data, u_int len) { return (gz_write(s, data, len, Z_NO_FLUSH)); } int gzio_flush(struct gzio_stream *s) { return (gz_write(s, NULL, 0, Z_FINISH)); } void gzio_fini(struct gzio_stream *s) { (void)deflateEnd(&s->gz_stream); gz_free(NULL, s->gz_buffer); gz_free(NULL, s); } static void * gz_alloc(void *arg __unused, u_int n, u_int sz) { /* * Memory for zlib state is allocated using M_NODUMP since it may be * used to compress a kernel dump, and we don't want zlib to attempt to * compress its own state. */ return (malloc(n * sz, M_GZIO, M_WAITOK | M_ZERO | M_NODUMP)); } static void gz_free(void *arg __unused, void *ptr) { free(ptr, M_GZIO); } static int gz_write(struct gzio_stream *s, void *buf, u_int len, int zflag) { uint8_t trailer[KERN_GZ_TRAILERLEN]; size_t room; int error, zerror; KASSERT(zflag == Z_FINISH || zflag == Z_NO_FLUSH, ("unexpected flag %d", zflag)); KASSERT(s->gz_mode == GZIO_DEFLATE, ("invalid stream mode %d", s->gz_mode)); if (len > 0) { s->gz_stream.avail_in = len; s->gz_stream.next_in = buf; s->gz_crc = crc32_raw(buf, len, s->gz_crc); } else s->gz_crc ^= ~0U; error = 0; do { zerror = deflate(&s->gz_stream, zflag); if (zerror != Z_OK && zerror != Z_STREAM_END) { error = EIO; break; } if (s->gz_stream.avail_out == 0 || zerror == Z_STREAM_END) { /* * Our output buffer is full or there's nothing left * to produce, so we're flushing the buffer. */ len = s->gz_bufsz - s->gz_stream.avail_out; if (zerror == Z_STREAM_END) { /* * Try to pack as much of the trailer into the * output buffer as we can. */ ((uint32_t *)trailer)[0] = s->gz_crc; ((uint32_t *)trailer)[1] = s->gz_stream.total_in; room = MIN(KERN_GZ_TRAILERLEN, s->gz_bufsz - len); memcpy(s->gz_buffer + len, trailer, room); len += room; } error = s->gz_cb(s->gz_buffer, len, s->gz_off, s->gz_arg); if (error != 0) break; s->gz_off += len; s->gz_stream.next_out = s->gz_buffer; s->gz_stream.avail_out = s->gz_bufsz; /* * If we couldn't pack the trailer into the output * buffer, write it out now. */ if (zerror == Z_STREAM_END && room < KERN_GZ_TRAILERLEN) error = s->gz_cb(trailer + room, KERN_GZ_TRAILERLEN - room, s->gz_off, s->gz_arg); } } while (zerror != Z_STREAM_END && (zflag == Z_FINISH || s->gz_stream.avail_in > 0)); return (error); }