1/*- 2 * Copyright (c) 2014 Mark Johnston <markj@FreeBSD.org> 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in 11 * the documentation and/or other materials provided with the 12 * distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: releng/11.0/sys/kern/kern_gzio.c 281855 2015-04-22 14:38:58Z rodrigc $"); 29 30#include <sys/param.h> 31 32#include <sys/gzio.h> 33#include <sys/kernel.h> 34#include <sys/malloc.h> 35#include <sys/zutil.h> 36 37#define KERN_GZ_HDRLEN 10 /* gzip header length */ 38#define KERN_GZ_TRAILERLEN 8 /* gzip trailer length */ 39#define KERN_GZ_MAGIC1 0x1f /* first magic byte */ 40#define KERN_GZ_MAGIC2 0x8b /* second magic byte */ 41 42MALLOC_DEFINE(M_GZIO, "gzio", "zlib state"); 43 44struct gzio_stream { 45 uint8_t * gz_buffer; /* output buffer */ 46 size_t gz_bufsz; /* total buffer size */ 47 off_t gz_off; /* offset into the output stream */ 48 enum gzio_mode gz_mode; /* stream mode */ 49 uint32_t gz_crc; /* stream CRC32 */ 50 gzio_cb gz_cb; /* output callback */ 51 void * gz_arg; /* private callback arg */ 52 z_stream gz_stream; /* zlib state */ 53}; 54 55static void * gz_alloc(void *, u_int, u_int); 56static void gz_free(void *, void *); 57static int gz_write(struct gzio_stream *, void *, u_int, int); 58 59struct gzio_stream * 60gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg) 61{ 62 struct gzio_stream *s; 63 uint8_t *hdr; 64 int error; 65 66 if (bufsz < KERN_GZ_HDRLEN) 67 return (NULL); 68 if (mode != GZIO_DEFLATE) 69 return (NULL); 70 71 s = gz_alloc(NULL, 1, sizeof(*s)); 72 s->gz_bufsz = bufsz; 73 s->gz_buffer = gz_alloc(NULL, 1, s->gz_bufsz); 74 s->gz_mode = mode; 75 s->gz_crc = ~0U; 76 s->gz_cb = cb; 77 s->gz_arg = arg; 78 79 s->gz_stream.zalloc = gz_alloc; 80 s->gz_stream.zfree = gz_free; 81 s->gz_stream.opaque = NULL; 82 s->gz_stream.next_in = Z_NULL; 83 s->gz_stream.avail_in = 0; 84 85 error = deflateInit2(&s->gz_stream, level, Z_DEFLATED, -MAX_WBITS, 86 DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); 87 if (error != 0) 88 goto fail; 89 90 s->gz_stream.avail_out = s->gz_bufsz; 91 s->gz_stream.next_out = s->gz_buffer; 92 93 /* Write the gzip header to the output buffer. */ 94 hdr = s->gz_buffer; 95 memset(hdr, 0, KERN_GZ_HDRLEN); 96 hdr[0] = KERN_GZ_MAGIC1; 97 hdr[1] = KERN_GZ_MAGIC2; 98 hdr[2] = Z_DEFLATED; 99 hdr[9] = OS_CODE; 100 s->gz_stream.next_out += KERN_GZ_HDRLEN; 101 s->gz_stream.avail_out -= KERN_GZ_HDRLEN; 102 103 return (s); 104 105fail: 106 gz_free(NULL, s->gz_buffer); 107 gz_free(NULL, s); 108 return (NULL); 109} 110 111int 112gzio_write(struct gzio_stream *s, void *data, u_int len) 113{ 114 115 return (gz_write(s, data, len, Z_NO_FLUSH)); 116} 117 118int 119gzio_flush(struct gzio_stream *s) 120{ 121 122 return (gz_write(s, NULL, 0, Z_FINISH)); 123} 124 125void 126gzio_fini(struct gzio_stream *s) 127{ 128 129 (void)deflateEnd(&s->gz_stream); 130 gz_free(NULL, s->gz_buffer); 131 gz_free(NULL, s); 132} 133 134static void * 135gz_alloc(void *arg __unused, u_int n, u_int sz) 136{ 137 138 /* 139 * Memory for zlib state is allocated using M_NODUMP since it may be 140 * used to compress a kernel dump, and we don't want zlib to attempt to 141 * compress its own state. 142 */ 143 return (malloc(n * sz, M_GZIO, M_WAITOK | M_ZERO | M_NODUMP)); 144} 145 146static void 147gz_free(void *arg __unused, void *ptr) 148{ 149 150 free(ptr, M_GZIO); 151} 152 153static int 154gz_write(struct gzio_stream *s, void *buf, u_int len, int zflag) 155{ 156 uint8_t trailer[KERN_GZ_TRAILERLEN]; 157 size_t room; 158 int error, zerror; 159 160 KASSERT(zflag == Z_FINISH || zflag == Z_NO_FLUSH, 161 ("unexpected flag %d", zflag)); 162 KASSERT(s->gz_mode == GZIO_DEFLATE, 163 ("invalid stream mode %d", s->gz_mode)); 164 165 if (len > 0) { 166 s->gz_stream.avail_in = len; 167 s->gz_stream.next_in = buf; 168 s->gz_crc = crc32_raw(buf, len, s->gz_crc); 169 } else 170 s->gz_crc ^= ~0U; 171 172 error = 0; 173 do { 174 zerror = deflate(&s->gz_stream, zflag); 175 if (zerror != Z_OK && zerror != Z_STREAM_END) { 176 error = EIO; 177 break; 178 } 179 180 if (s->gz_stream.avail_out == 0 || zerror == Z_STREAM_END) { 181 /* 182 * Our output buffer is full or there's nothing left 183 * to produce, so we're flushing the buffer. 184 */ 185 len = s->gz_bufsz - s->gz_stream.avail_out; 186 if (zerror == Z_STREAM_END) { 187 /* 188 * Try to pack as much of the trailer into the 189 * output buffer as we can. 190 */ 191 ((uint32_t *)trailer)[0] = s->gz_crc; 192 ((uint32_t *)trailer)[1] = 193 s->gz_stream.total_in; 194 room = MIN(KERN_GZ_TRAILERLEN, 195 s->gz_bufsz - len); 196 memcpy(s->gz_buffer + len, trailer, room); 197 len += room; 198 } 199 200 error = s->gz_cb(s->gz_buffer, len, s->gz_off, 201 s->gz_arg); 202 if (error != 0) 203 break; 204 205 s->gz_off += len; 206 s->gz_stream.next_out = s->gz_buffer; 207 s->gz_stream.avail_out = s->gz_bufsz; 208 209 /* 210 * If we couldn't pack the trailer into the output 211 * buffer, write it out now. 212 */ 213 if (zerror == Z_STREAM_END && room < KERN_GZ_TRAILERLEN) 214 error = s->gz_cb(trailer + room, 215 KERN_GZ_TRAILERLEN - room, s->gz_off, 216 s->gz_arg); 217 } 218 } while (zerror != Z_STREAM_END && 219 (zflag == Z_FINISH || s->gz_stream.avail_in > 0)); 220 221 return (error); 222} 223