freebsd-skq/sys/kern/kern_gzio.c
markj 25e41e0131 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
2015-03-09 03:50:53 +00:00

224 lines
5.8 KiB
C

/*-
* Copyright (c) 2014 Mark Johnston <markj@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/gzio.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <net/zutil.h>
#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);
}