Changeset View
Changeset View
Standalone View
Standalone View
sys/kern/kern_gzio.c
/* | /*- | ||||
* $Id: kern_gzio.c,v 1.6 2008-10-18 22:54:45 lbazinet Exp $ | * Copyright (c) 2014 Mark Johnston <markj@FreeBSD.org> | ||||
* | * | ||||
* core_gzip.c -- gzip routines used in compressing user process cores | * 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 file is derived from src/lib/libz/gzio.c in FreeBSD. | * 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. | |||||
*/ | */ | ||||
/* gzio.c -- IO on .gz files | #include <sys/cdefs.h> | ||||
* Copyright (C) 1995-1998 Jean-loup Gailly. | __FBSDID("$FreeBSD$"); | ||||
* For conditions of distribution and use, see copyright notice in zlib.h | |||||
* | |||||
*/ | |||||
/* @(#) $FreeBSD$ */ | |||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/proc.h> | |||||
#include <sys/gzio.h> | |||||
#include <sys/kernel.h> | |||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/vnode.h> | |||||
#include <sys/syslog.h> | |||||
#include <sys/endian.h> | |||||
#include <net/zutil.h> | #include <net/zutil.h> | ||||
#include <sys/libkern.h> | |||||
#include <sys/vnode.h> | #define KERN_GZ_HDRLEN 10 /* gzip header length */ | ||||
#include <sys/mount.h> | #define KERN_GZ_TRAILERLEN 8 /* gzip trailer length */ | ||||
#define KERN_GZ_MAGIC1 0x1f /* first magic byte */ | |||||
#define KERN_GZ_MAGIC2 0x8b /* second magic byte */ | |||||
#define GZ_HEADER_LEN 10 | MALLOC_DEFINE(M_GZIO, "gzio", "zlib state"); | ||||
#ifndef Z_BUFSIZE | struct gzio_stream { | ||||
# ifdef MAXSEG_64K | uint8_t * gz_buffer; /* output buffer */ | ||||
# define Z_BUFSIZE 4096 /* minimize memory usage for 16-bit DOS */ | size_t gz_bufsz; /* total buffer size */ | ||||
# else | off_t gz_off; /* offset into the output stream */ | ||||
# define Z_BUFSIZE 16384 | enum gzio_mode gz_mode; /* stream mode */ | ||||
# endif | uint32_t gz_crc; /* stream CRC32 */ | ||||
#endif | gzio_cb gz_cb; /* output callback */ | ||||
#ifndef Z_PRINTF_BUFSIZE | void * gz_arg; /* private callback arg */ | ||||
# define Z_PRINTF_BUFSIZE 4096 | z_stream gz_stream; /* zlib state */ | ||||
#endif | }; | ||||
#define ALLOC(size) malloc(size, M_TEMP, M_WAITOK | M_ZERO) | static void * gz_alloc(void *, u_int, u_int); | ||||
#define TRYFREE(p) {if (p) free(p, M_TEMP);} | static void gz_free(void *, void *); | ||||
static int gz_write(struct gzio_stream *, void *, u_int, int); | |||||
static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */ | struct gzio_stream * | ||||
gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg) | |||||
/* gzip flag byte */ | |||||
#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ | |||||
#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ | |||||
#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ | |||||
#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ | |||||
#define COMMENT 0x10 /* bit 4 set: file comment present */ | |||||
#define RESERVED 0xE0 /* bits 5..7: reserved */ | |||||
typedef struct gz_stream { | |||||
z_stream stream; | |||||
int z_err; /* error code for last stream operation */ | |||||
int z_eof; /* set if end of input file */ | |||||
struct vnode *file; /* vnode pointer of .gz file */ | |||||
Byte *inbuf; /* input buffer */ | |||||
Byte *outbuf; /* output buffer */ | |||||
uLong crc; /* crc32 of uncompressed data */ | |||||
char *msg; /* error message */ | |||||
char *path; /* path name for debugging only */ | |||||
int transparent; /* 1 if input file is not a .gz file */ | |||||
char mode; /* 'w' or 'r' */ | |||||
long startpos; /* start of compressed data in file (header skipped) */ | |||||
off_t outoff; /* current offset in output file */ | |||||
int flags; | |||||
} gz_stream; | |||||
local int do_flush OF((gzFile file, int flush)); | |||||
local int destroy OF((gz_stream *s)); | |||||
local void putU32 OF((gz_stream *file, uint32_t x)); | |||||
local void *gz_alloc OF((void *notused, u_int items, u_int size)); | |||||
local void gz_free OF((void *notused, void *ptr)); | |||||
/* =========================================================================== | |||||
Opens a gzip (.gz) file for reading or writing. The mode parameter | |||||
is as in fopen ("rb" or "wb"). The file is given either by file descriptor | |||||
or path name (if fd == -1). | |||||
gz_open return NULL if the file could not be opened or if there was | |||||
insufficient memory to allocate the (de)compression state; errno | |||||
can be checked to distinguish the two cases (if errno is zero, the | |||||
zlib error is Z_MEM_ERROR). | |||||
*/ | |||||
gzFile gz_open (path, mode, vp) | |||||
const char *path; | |||||
const char *mode; | |||||
struct vnode *vp; | |||||
{ | { | ||||
int err; | struct gzio_stream *s; | ||||
int level = Z_DEFAULT_COMPRESSION; /* compression level */ | uint8_t *hdr; | ||||
int strategy = Z_DEFAULT_STRATEGY; /* compression strategy */ | |||||
const char *p = mode; | |||||
gz_stream *s; | |||||
char fmode[80]; /* copy of mode, without the compression level */ | |||||
char *m = fmode; | |||||
ssize_t resid; | |||||
int error; | int error; | ||||
char buf[GZ_HEADER_LEN + 1]; | |||||
if (!path || !mode) return Z_NULL; | if (bufsz < KERN_GZ_HDRLEN) | ||||
return (NULL); | |||||
if (mode != GZIO_DEFLATE) | |||||
return (NULL); | |||||
s = (gz_stream *)ALLOC(sizeof(gz_stream)); | s = gz_alloc(NULL, 1, sizeof(*s)); | ||||
if (!s) return Z_NULL; | 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->stream.zalloc = (alloc_func)gz_alloc; | s->gz_stream.zalloc = gz_alloc; | ||||
s->stream.zfree = (free_func)gz_free; | s->gz_stream.zfree = gz_free; | ||||
s->stream.opaque = (voidpf)0; | s->gz_stream.opaque = NULL; | ||||
s->stream.next_in = s->inbuf = Z_NULL; | s->gz_stream.next_in = Z_NULL; | ||||
s->stream.next_out = s->outbuf = Z_NULL; | s->gz_stream.avail_in = 0; | ||||
s->stream.avail_in = s->stream.avail_out = 0; | |||||
s->file = NULL; | |||||
s->z_err = Z_OK; | |||||
s->z_eof = 0; | |||||
s->crc = 0; | |||||
s->msg = NULL; | |||||
s->transparent = 0; | |||||
s->outoff = 0; | |||||
s->flags = 0; | |||||
s->path = (char*)ALLOC(strlen(path)+1); | error = deflateInit2(&s->gz_stream, level, Z_DEFLATED, -MAX_WBITS, | ||||
if (s->path == NULL) { | DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); | ||||
return destroy(s), (gzFile)Z_NULL; | if (error != 0) | ||||
} | goto fail; | ||||
strcpy(s->path, path); /* do this early for debugging */ | |||||
s->mode = '\0'; | s->gz_stream.avail_out = s->gz_bufsz; | ||||
do { | s->gz_stream.next_out = s->gz_buffer; | ||||
if (*p == 'r') s->mode = 'r'; | |||||
if (*p == 'w' || *p == 'a') s->mode = 'w'; | |||||
if (*p >= '0' && *p <= '9') { | |||||
level = *p - '0'; | |||||
} else if (*p == 'f') { | |||||
strategy = Z_FILTERED; | |||||
} else if (*p == 'h') { | |||||
strategy = Z_HUFFMAN_ONLY; | |||||
} else { | |||||
*m++ = *p; /* copy the mode */ | |||||
} | |||||
} while (*p++ && m != fmode + sizeof(fmode)); | |||||
if (s->mode != 'w') { | /* Write the gzip header to the output buffer. */ | ||||
log(LOG_ERR, "gz_open: mode is not w (%c)\n", s->mode); | hdr = s->gz_buffer; | ||||
return destroy(s), (gzFile)Z_NULL; | 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; | |||||
err = deflateInit2(&(s->stream), level, | return (s); | ||||
Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, strategy); | |||||
/* windowBits is passed < 0 to suppress zlib header */ | |||||
s->stream.next_out = s->outbuf = (Byte*)ALLOC(Z_BUFSIZE); | fail: | ||||
if (err != Z_OK || s->outbuf == Z_NULL) { | gz_free(NULL, s->gz_buffer); | ||||
return destroy(s), (gzFile)Z_NULL; | gz_free(NULL, s); | ||||
return (NULL); | |||||
} | } | ||||
s->stream.avail_out = Z_BUFSIZE; | int | ||||
s->file = vp; | gzio_write(struct gzio_stream *s, void *data, u_int len) | ||||
{ | |||||
/* Write a very simple .gz header: | return (gz_write(s, data, len, Z_NO_FLUSH)); | ||||
*/ | |||||
snprintf(buf, sizeof(buf), "%c%c%c%c%c%c%c%c%c%c", gz_magic[0], | |||||
gz_magic[1], Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, | |||||
0 /*xflags*/, OS_CODE); | |||||
if ((error = vn_rdwr(UIO_WRITE, s->file, buf, GZ_HEADER_LEN, s->outoff, | |||||
UIO_SYSSPACE, IO_UNIT, curproc->p_ucred, | |||||
NOCRED, &resid, curthread))) { | |||||
s->outoff += GZ_HEADER_LEN - resid; | |||||
return destroy(s), (gzFile)Z_NULL; | |||||
} | } | ||||
s->outoff += GZ_HEADER_LEN; | |||||
s->startpos = 10L; | |||||
return (gzFile)s; | int | ||||
} | gzio_flush(struct gzio_stream *s) | ||||
/* =========================================================================== | |||||
* Cleanup then free the given gz_stream. Return a zlib error code. | |||||
Try freeing in the reverse order of allocations. | |||||
*/ | |||||
local int destroy (s) | |||||
gz_stream *s; | |||||
{ | { | ||||
int err = Z_OK; | |||||
if (!s) return Z_STREAM_ERROR; | return (gz_write(s, NULL, 0, Z_FINISH)); | ||||
TRYFREE(s->msg); | |||||
if (s->stream.state != NULL) { | |||||
if (s->mode == 'w') { | |||||
err = deflateEnd(&(s->stream)); | |||||
} | } | ||||
} | |||||
if (s->z_err < 0) err = s->z_err; | |||||
TRYFREE(s->inbuf); | void | ||||
TRYFREE(s->outbuf); | gzio_fini(struct gzio_stream *s) | ||||
TRYFREE(s->path); | |||||
TRYFREE(s); | |||||
return err; | |||||
} | |||||
/* =========================================================================== | |||||
Writes the given number of uncompressed bytes into the compressed file. | |||||
gzwrite returns the number of bytes actually written (0 in case of error). | |||||
*/ | |||||
int ZEXPORT gzwrite (file, buf, len) | |||||
gzFile file; | |||||
const voidp buf; | |||||
unsigned len; | |||||
{ | { | ||||
gz_stream *s = (gz_stream*)file; | |||||
off_t curoff; | |||||
size_t resid; | |||||
int error; | |||||
if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR; | (void)deflateEnd(&s->gz_stream); | ||||
gz_free(NULL, s->gz_buffer); | |||||
s->stream.next_in = (Bytef*)buf; | gz_free(NULL, s); | ||||
s->stream.avail_in = len; | |||||
curoff = s->outoff; | |||||
while (s->stream.avail_in != 0) { | |||||
if (s->stream.avail_out == 0) { | |||||
s->stream.next_out = s->outbuf; | |||||
error = vn_rdwr_inchunks(UIO_WRITE, s->file, s->outbuf, Z_BUFSIZE, | |||||
curoff, UIO_SYSSPACE, IO_UNIT, | |||||
curproc->p_ucred, NOCRED, &resid, curthread); | |||||
if (error) { | |||||
log(LOG_ERR, "gzwrite: vn_rdwr return %d\n", error); | |||||
curoff += Z_BUFSIZE - resid; | |||||
s->z_err = Z_ERRNO; | |||||
break; | |||||
} | } | ||||
curoff += Z_BUFSIZE; | |||||
s->stream.avail_out = Z_BUFSIZE; | |||||
} | |||||
s->z_err = deflate(&(s->stream), Z_NO_FLUSH); | |||||
if (s->z_err != Z_OK) { | |||||
log(LOG_ERR, | |||||
"gzwrite: deflate returned error %d\n", s->z_err); | |||||
break; | |||||
} | |||||
} | |||||
s->crc = ~crc32_raw(buf, len, ~s->crc); | static void * | ||||
s->outoff = curoff; | gz_alloc(void *arg __unused, u_int n, u_int sz) | ||||
return (int)(len - s->stream.avail_in); | |||||
} | |||||
/* =========================================================================== | |||||
Flushes all pending output into the compressed file. The parameter | |||||
flush is as in the deflate() function. | |||||
*/ | |||||
local int do_flush (file, flush) | |||||
gzFile file; | |||||
int flush; | |||||
{ | { | ||||
uInt len; | |||||
int done = 0; | |||||
gz_stream *s = (gz_stream*)file; | |||||
off_t curoff = s->outoff; | |||||
size_t resid; | |||||
int error; | |||||
if (s == NULL || s->mode != 'w') return Z_STREAM_ERROR; | /* | ||||
* Memory for zlib state is allocated using M_NODUMP since it may be | |||||
if (s->stream.avail_in) { | * used to compress a kernel dump, and we don't want zlib to attempt to | ||||
log(LOG_WARNING, "do_flush: avail_in non-zero on entry\n"); | * compress its own state. | ||||
} | |||||
s->stream.avail_in = 0; /* should be zero already anyway */ | |||||
for (;;) { | |||||
len = Z_BUFSIZE - s->stream.avail_out; | |||||
if (len != 0) { | |||||
error = vn_rdwr_inchunks(UIO_WRITE, s->file, s->outbuf, len, curoff, | |||||
UIO_SYSSPACE, IO_UNIT, curproc->p_ucred, | |||||
NOCRED, &resid, curthread); | |||||
if (error) { | |||||
s->z_err = Z_ERRNO; | |||||
s->outoff = curoff + len - resid; | |||||
return Z_ERRNO; | |||||
} | |||||
s->stream.next_out = s->outbuf; | |||||
s->stream.avail_out = Z_BUFSIZE; | |||||
curoff += len; | |||||
} | |||||
if (done) break; | |||||
s->z_err = deflate(&(s->stream), flush); | |||||
/* Ignore the second of two consecutive flushes: */ | |||||
if (len == 0 && s->z_err == Z_BUF_ERROR) s->z_err = Z_OK; | |||||
/* deflate has finished flushing only when it hasn't used up | |||||
* all the available space in the output buffer: | |||||
*/ | */ | ||||
done = (s->stream.avail_out != 0 || s->z_err == Z_STREAM_END); | return (malloc(n * sz, M_GZIO, M_WAITOK | M_ZERO | M_NODUMP)); | ||||
if (s->z_err != Z_OK && s->z_err != Z_STREAM_END) break; | |||||
} | } | ||||
s->outoff = curoff; | |||||
return s->z_err == Z_STREAM_END ? Z_OK : s->z_err; | static void | ||||
} | gz_free(void *arg __unused, void *ptr) | ||||
int ZEXPORT gzflush (file, flush) | |||||
gzFile file; | |||||
int flush; | |||||
{ | { | ||||
gz_stream *s = (gz_stream*)file; | |||||
int err = do_flush (file, flush); | |||||
if (err) return err; | free(ptr, M_GZIO); | ||||
return s->z_err == Z_STREAM_END ? Z_OK : s->z_err; | |||||
} | } | ||||
static int | |||||
/* =========================================================================== | gz_write(struct gzio_stream *s, void *buf, u_int len, int zflag) | ||||
Outputs a long in LSB order to the given file | |||||
*/ | |||||
local void putU32 (s, x) | |||||
gz_stream *s; | |||||
uint32_t x; | |||||
{ | { | ||||
uint32_t xx; | uint8_t trailer[KERN_GZ_TRAILERLEN]; | ||||
off_t curoff = s->outoff; | size_t room; | ||||
ssize_t resid; | int error, zerror; | ||||
#if BYTE_ORDER == BIG_ENDIAN | KASSERT(zflag == Z_FINISH || zflag == Z_NO_FLUSH, | ||||
xx = bswap32(x); | ("unexpected flag %d", zflag)); | ||||
#else | KASSERT(s->gz_mode == GZIO_DEFLATE, | ||||
xx = x; | ("invalid stream mode %d", s->gz_mode)); | ||||
#endif | |||||
vn_rdwr(UIO_WRITE, s->file, (caddr_t)&xx, sizeof(xx), curoff, | |||||
UIO_SYSSPACE, IO_UNIT, curproc->p_ucred, | |||||
NOCRED, &resid, curthread); | |||||
s->outoff += sizeof(xx) - resid; | |||||
} | |||||
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; | ||||
Flushes all pending output if necessary, closes the compressed file | do { | ||||
and deallocates all the (de)compression state. | zerror = deflate(&s->gz_stream, zflag); | ||||
*/ | if (zerror != Z_OK && zerror != Z_STREAM_END) { | ||||
int ZEXPORT gzclose (file) | error = EIO; | ||||
gzFile file; | break; | ||||
{ | |||||
int err; | |||||
gz_stream *s = (gz_stream*)file; | |||||
if (s == NULL) return Z_STREAM_ERROR; | |||||
if (s->mode == 'w') { | |||||
err = do_flush (file, Z_FINISH); | |||||
if (err != Z_OK) { | |||||
log(LOG_ERR, "gzclose: do_flush failed (err %d)\n", err); | |||||
return destroy((gz_stream*)file); | |||||
} | } | ||||
#if 0 | |||||
printf("gzclose: putting crc: %lld total: %lld\n", | |||||
(long long)s->crc, (long long)s->stream.total_in); | |||||
printf("sizeof uLong = %d\n", (int)sizeof(uLong)); | |||||
#endif | |||||
putU32 (s, s->crc); | |||||
putU32 (s, (uint32_t) s->stream.total_in); | |||||
} | |||||
return destroy((gz_stream*)file); | |||||
} | |||||
if (s->gz_stream.avail_out == 0 || zerror == Z_STREAM_END) { | |||||
/* | /* | ||||
* Space allocation and freeing routines for use by zlib routines when called | * Our output buffer is full or there's nothing left | ||||
* from gzip modules. | * to produce, so we're flushing the buffer. | ||||
*/ | */ | ||||
static void * | len = s->gz_bufsz - s->gz_stream.avail_out; | ||||
gz_alloc(void *notused __unused, u_int items, u_int size) | if (zerror == Z_STREAM_END) { | ||||
{ | /* | ||||
void *ptr; | * Try to pack as much of the trailer into the | ||||
* output buffer as we can. | |||||
MALLOC(ptr, void *, items * size, M_TEMP, M_NOWAIT | M_ZERO); | */ | ||||
return ptr; | ((uint32_t *)trailer)[0] = s->gz_crc; | ||||
((uint32_t *)trailer)[1] = | |||||
s->gz_stream.total_in; | |||||
room = MIN(KERN_GZ_TRAILERLEN, | |||||
rpaulo: Can 'room' be zero? | |||||
markjAuthorUnsubmitted Not Done Inline ActionsYes. That case (room < 2) is handled by the if statement right at the end of the loop. It's a bit ugly, but I want to minimize invocations of the callback, so I try to pack the trailer into the output buffer whenever possible (which is most of the time) instead of always invoking the callback once for the trailer. markj: Yes. That case (room < 2) is handled by the if statement right at the end of the loop. It's a… | |||||
rpauloUnsubmitted Not Done Inline ActionsGot it. rpaulo: Got it. | |||||
s->gz_bufsz - len); | |||||
memcpy(s->gz_buffer + len, trailer, room); | |||||
len += room; | |||||
} | } | ||||
static void | error = s->gz_cb(s->gz_buffer, len, s->gz_off, | ||||
gz_free(void *opaque __unused, void *ptr) | s->gz_arg); | ||||
{ | if (error != 0) | ||||
FREE(ptr, M_TEMP); | break; | ||||
s->gz_off += len; | |||||
s->gz_stream.next_out = s->gz_buffer; | |||||
s->gz_stream.avail_out = s->gz_bufsz; | |||||
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); | |||||
} |
Can 'room' be zero?