aboutsummaryrefslogtreecommitdiff
path: root/sys/kern/kern_gzio.c
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2015-03-09 03:50:53 +0000
committerMark Johnston <markj@FreeBSD.org>2015-03-09 03:50:53 +0000
commitaa14e9b7c9d90c2db81e29b3c68bcee6129b8b4c (patch)
treec2951caec9b898eb9457779108d4f0b71d4607bb /sys/kern/kern_gzio.c
parent10b92369a1d53e5ac4a0c81011605eb60295824d (diff)
downloadsrc-aa14e9b7c9d90c2db81e29b3c68bcee6129b8b4c.tar.gz
src-aa14e9b7c9d90c2db81e29b3c68bcee6129b8b4c.zip
Reimplement support for userland core dump compression using a new interface
in kern_gzio.c. The old gzio interface was somewhat inflexible and has not worked properly since r272535: currently, the gzio functions are called with a range lock held on the output vnode, but kern_gzio.c does not pass the IO_RANGELOCKED flag to vn_rdwr() calls, resulting in deadlock when vn_rdwr() attempts to reacquire the range lock. Moreover, the new gzio interface can be used to implement kernel core compression. This change also modifies the kernel configuration options needed to enable userland core dump compression support: gzio is now an option rather than a device, and the COMPRESS_USER_CORES option is removed. Core dump compression is enabled using the kern.compress_user_cores sysctl/tunable. Differential Revision: https://reviews.freebsd.org/D1832 Reviewed by: rpaulo Discussed with: kib
Notes
Notes: svn path=/head/; revision=279801
Diffstat (limited to 'sys/kern/kern_gzio.c')
-rw-r--r--sys/kern/kern_gzio.c559
1 files changed, 191 insertions, 368 deletions
diff --git a/sys/kern/kern_gzio.c b/sys/kern/kern_gzio.c
index 15dc3015ca25..a4974a729c3f 100644
--- a/sys/kern/kern_gzio.c
+++ b/sys/kern/kern_gzio.c
@@ -1,400 +1,223 @@
-/*
- * $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
- * Copyright (C) 1995-1998 Jean-loup Gailly.
- * For conditions of distribution and use, see copyright notice in zlib.h
- *
- */
-
-/* @(#) $FreeBSD$ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
#include <sys/param.h>
-#include <sys/proc.h>
+
+#include <sys/gzio.h>
+#include <sys/kernel.h>
#include <sys/malloc.h>
-#include <sys/vnode.h>
-#include <sys/syslog.h>
-#include <sys/endian.h>
-#include <net/zutil.h>
-#include <sys/libkern.h>
-
-#include <sys/vnode.h>
-#include <sys/mount.h>
-
-#define GZ_HEADER_LEN 10
-
-#ifndef Z_BUFSIZE
-# ifdef MAXSEG_64K
-# define Z_BUFSIZE 4096 /* minimize memory usage for 16-bit DOS */
-# else
-# define Z_BUFSIZE 16384
-# endif
-#endif
-#ifndef Z_PRINTF_BUFSIZE
-# define Z_PRINTF_BUFSIZE 4096
-#endif
-
-#define ALLOC(size) malloc(size, M_TEMP, M_WAITOK | M_ZERO)
-#define TRYFREE(p) {if (p) free(p, M_TEMP);}
-
-static int gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
-
-/* 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;
- int level = Z_DEFAULT_COMPRESSION; /* compression level */
- 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;
- char buf[GZ_HEADER_LEN + 1];
-
- if (!path || !mode) return Z_NULL;
-
- s = (gz_stream *)ALLOC(sizeof(gz_stream));
- if (!s) return Z_NULL;
-
- s->stream.zalloc = (alloc_func)gz_alloc;
- s->stream.zfree = (free_func)gz_free;
- s->stream.opaque = (voidpf)0;
- s->stream.next_in = s->inbuf = Z_NULL;
- s->stream.next_out = s->outbuf = Z_NULL;
- 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);
- if (s->path == NULL) {
- return destroy(s), (gzFile)Z_NULL;
- }
- strcpy(s->path, path); /* do this early for debugging */
-
- s->mode = '\0';
- do {
- 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') {
- log(LOG_ERR, "gz_open: mode is not w (%c)\n", s->mode);
- return destroy(s), (gzFile)Z_NULL;
- }
-
- err = deflateInit2(&(s->stream), level,
- 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);
- if (err != Z_OK || s->outbuf == Z_NULL) {
- return destroy(s), (gzFile)Z_NULL;
- }
-
- s->stream.avail_out = Z_BUFSIZE;
- s->file = vp;
-
- /* Write a very simple .gz header:
- */
- 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;
-}
+#include <net/zutil.h>
- /* ===========================================================================
- * 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;
+#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)
{
- int err = Z_OK;
-
- if (!s) return Z_STREAM_ERROR;
-
- 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);
- TRYFREE(s->outbuf);
- TRYFREE(s->path);
- TRYFREE(s);
- return err;
+ 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);
}
-
-/* ===========================================================================
- 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;
+int
+gzio_write(struct gzio_stream *s, void *data, u_int 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;
-
- s->stream.next_in = (Bytef*)buf;
- 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);
- s->outoff = curoff;
-
- 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;
-
- if (s->stream.avail_in) {
- log(LOG_WARNING, "do_flush: avail_in non-zero on entry\n");
- }
-
- 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);
-
- 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;
+ return (gz_write(s, data, len, Z_NO_FLUSH));
}
-int ZEXPORT gzflush (file, flush)
- gzFile file;
- int flush;
+int
+gzio_flush(struct gzio_stream *s)
{
- gz_stream *s = (gz_stream*)file;
- int err = do_flush (file, flush);
- if (err) return err;
- return s->z_err == Z_STREAM_END ? Z_OK : s->z_err;
+ return (gz_write(s, NULL, 0, Z_FINISH));
}
-
-/* ===========================================================================
- Outputs a long in LSB order to the given file
-*/
-local void putU32 (s, x)
- gz_stream *s;
- uint32_t x;
+void
+gzio_fini(struct gzio_stream *s)
{
- uint32_t xx;
- off_t curoff = s->outoff;
- ssize_t resid;
-
-#if BYTE_ORDER == BIG_ENDIAN
- xx = bswap32(x);
-#else
- xx = x;
-#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;
-}
-
-/* ===========================================================================
- Flushes all pending output if necessary, closes the compressed file
- and deallocates all the (de)compression state.
-*/
-int ZEXPORT gzclose (file)
- gzFile file;
-{
- 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);
+ (void)deflateEnd(&s->gz_stream);
+ gz_free(NULL, s->gz_buffer);
+ gz_free(NULL, s);
}
-/*
- * Space allocation and freeing routines for use by zlib routines when called
- * from gzip modules.
- */
static void *
-gz_alloc(void *notused __unused, u_int items, u_int size)
+gz_alloc(void *arg __unused, u_int n, u_int sz)
{
- void *ptr;
- MALLOC(ptr, void *, items * size, M_TEMP, M_NOWAIT | M_ZERO);
- return ptr;
+ /*
+ * 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 *opaque __unused, void *ptr)
+gz_free(void *arg __unused, void *ptr)
{
- FREE(ptr, M_TEMP);
+
+ 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);
+}