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
This commit is contained in:
Mark Johnston 2015-03-09 03:50:53 +00:00
parent 10b92369a1
commit aa14e9b7c9
8 changed files with 368 additions and 553 deletions

View File

@ -28,7 +28,7 @@
.\" @(#)core.5 8.3 (Berkeley) 12/11/93
.\" $FreeBSD$
.\"
.Dd November 22, 2012
.Dd March 8, 2015
.Dt CORE 5
.Os
.Sh NAME
@ -101,25 +101,23 @@ variable
.Va kern.sugid_coredump
to 1.
.Pp
Corefiles can be compressed by the kernel if the following items
are included in the kernel configuration file:
Corefiles can be compressed by the kernel if the following item
is included in the kernel configuration file:
.Bl -tag -width "1234567890" -compact -offset "12345"
.It options
COMPRESS_USER_CORES
.It devices
gzio
GZIO
.El
.Pp
When COMPRESS_USER_CORES is included the following sysctls can control
if core files will be compressed:
When the GZIO option is included, the following sysctls control whether core
files will be compressed:
.Bl -tag -width "kern.compress_user_cores_gzlevel" -compact -offset "12345"
.It Em kern.compress_user_cores_gzlevel
Gzip compression level.
Defaults to -1.
Defaults to 6.
.It Em kern.compress_user_cores
Actually compress user cores.
Core files will have the suffix
.Em .gz
Compressed core files will have a suffix of
.Ql .gz
appended to them.
.El
.Sh EXAMPLES

View File

@ -2889,11 +2889,6 @@ options SHMMNI=33
# a single process at one time.
options SHMSEG=9
# Compress user core dumps.
options COMPRESS_USER_CORES
# required to compress file output from kernel for COMPRESS_USER_CORES.
device gzio
# Set the amount of time (in seconds) the system will wait before
# rebooting automatically when a kernel panic occurs. If set to (-1),
# the system will wait indefinitely until a key is pressed on the
@ -2983,3 +2978,7 @@ options RANDOM_DEBUG # Debugging messages
# Module to enable execution of application via emulators like QEMU
options IMAGACT_BINMISC
# zlib I/O stream support
# This enables support for compressed core dumps.
options GZIO

View File

@ -87,13 +87,13 @@ COMPAT_FREEBSD9 opt_compat.h
COMPAT_FREEBSD10 opt_compat.h
COMPAT_LINUXAPI opt_compat.h
COMPILING_LINT opt_global.h
COMPRESS_USER_CORES opt_core.h
CY_PCI_FASTINTR
DEADLKRES opt_watchdog.h
DIRECTIO
FILEMON opt_dontuse.h
FFCLOCK
FULL_PREEMPTION opt_sched.h
GZIO opt_gzio.h
IMAGACT_BINMISC opt_dontuse.h
IPI_PREEMPTION opt_sched.h
GEOM_AES opt_geom.h

View File

@ -33,12 +33,13 @@ __FBSDID("$FreeBSD$");
#include "opt_capsicum.h"
#include "opt_compat.h"
#include "opt_core.h"
#include "opt_gzio.h"
#include <sys/param.h>
#include <sys/capsicum.h>
#include <sys/exec.h>
#include <sys/fcntl.h>
#include <sys/gzio.h>
#include <sys/imgact.h>
#include <sys/imgact_elf.h>
#include <sys/jail.h>
@ -69,8 +70,6 @@ __FBSDID("$FreeBSD$");
#include <sys/eventhandler.h>
#include <sys/user.h>
#include <net/zlib.h>
#include <vm/vm.h>
#include <vm/vm_kern.h>
#include <vm/vm_param.h>
@ -105,11 +104,7 @@ static Elf_Word __elfN(untrans_prot)(vm_prot_t);
SYSCTL_NODE(_kern, OID_AUTO, __CONCAT(elf, __ELF_WORD_SIZE), CTLFLAG_RW, 0,
"");
#ifdef COMPRESS_USER_CORES
static int compress_core(gzFile, char *, char *, unsigned int,
struct thread * td);
#endif
#define CORE_BUF_SIZE (16 * 1024)
#define CORE_BUF_SIZE (16 * 1024)
int __elfN(fallback_brand) = -1;
SYSCTL_INT(__CONCAT(_kern_elf, __ELF_WORD_SIZE), OID_AUTO,
@ -1067,11 +1062,23 @@ struct note_info {
TAILQ_HEAD(note_info_list, note_info);
/* Coredump output parameters. */
struct coredump_params {
off_t offset;
struct ucred *active_cred;
struct ucred *file_cred;
struct thread *td;
struct vnode *vp;
struct gzio_stream *gzs;
};
static void cb_put_phdr(vm_map_entry_t, void *);
static void cb_size_segment(vm_map_entry_t, void *);
static int core_write(struct coredump_params *, void *, size_t, off_t,
enum uio_seg);
static void each_writable_segment(struct thread *, segment_callback, void *);
static int __elfN(corehdr)(struct thread *, struct vnode *, struct ucred *,
int, void *, size_t, struct note_info_list *, size_t, gzFile);
static int __elfN(corehdr)(struct coredump_params *, int, void *, size_t,
struct note_info_list *, size_t);
static void __elfN(prepare_notes)(struct thread *, struct note_info_list *,
size_t *);
static void __elfN(puthdr)(struct thread *, void *, size_t, int, size_t);
@ -1095,42 +1102,60 @@ static void note_procstat_rlimit(void *, struct sbuf *, size_t *);
static void note_procstat_umask(void *, struct sbuf *, size_t *);
static void note_procstat_vmmap(void *, struct sbuf *, size_t *);
#ifdef COMPRESS_USER_CORES
extern int compress_user_cores;
#ifdef GZIO
extern int compress_user_cores_gzlevel;
#endif
/*
* Write out a core segment to the compression stream.
*/
static int
core_output(struct vnode *vp, void *base, size_t len, off_t offset,
struct ucred *active_cred, struct ucred *file_cred,
struct thread *td, char *core_buf, gzFile gzfile) {
compress_chunk(struct coredump_params *p, char *base, char *buf, u_int len)
{
u_int chunk_len;
int error;
if (gzfile) {
#ifdef COMPRESS_USER_CORES
error = compress_core(gzfile, base, core_buf, len, td);
#else
panic("shouldn't be here");
#endif
} else {
error = vn_rdwr_inchunks(UIO_WRITE, vp, base, len, offset,
UIO_USERSPACE, IO_UNIT | IO_DIRECT | IO_RANGELOCKED,
active_cred, file_cred, NULL, td);
while (len > 0) {
chunk_len = MIN(len, CORE_BUF_SIZE);
copyin(base, buf, chunk_len);
error = gzio_write(p->gzs, buf, chunk_len);
if (error != 0)
break;
base += chunk_len;
len -= chunk_len;
}
return (error);
}
/* Coredump output parameters for sbuf drain routine. */
struct sbuf_drain_core_params {
off_t offset;
struct ucred *active_cred;
struct ucred *file_cred;
struct thread *td;
struct vnode *vp;
#ifdef COMPRESS_USER_CORES
gzFile gzfile;
static int
core_gz_write(void *base, size_t len, off_t offset, void *arg)
{
return (core_write((struct coredump_params *)arg, base, len, offset,
UIO_SYSSPACE));
}
#endif /* GZIO */
static int
core_write(struct coredump_params *p, void *base, size_t len, off_t offset,
enum uio_seg seg)
{
return (vn_rdwr_inchunks(UIO_WRITE, p->vp, base, len, offset,
seg, IO_UNIT | IO_DIRECT | IO_RANGELOCKED,
p->active_cred, p->file_cred, NULL, p->td));
}
static int
core_output(void *base, size_t len, off_t offset, struct coredump_params *p,
void *tmpbuf)
{
#ifdef GZIO
if (p->gzs != NULL)
return (compress_chunk(p, base, tmpbuf, len));
#endif
};
return (core_write(p, base, len, offset, UIO_USERSPACE));
}
/*
* Drain into a core file.
@ -1138,10 +1163,10 @@ struct sbuf_drain_core_params {
static int
sbuf_drain_core_output(void *arg, const char *data, int len)
{
struct sbuf_drain_core_params *p;
struct coredump_params *p;
int error, locked;
p = (struct sbuf_drain_core_params *)arg;
p = (struct coredump_params *)arg;
/*
* Some kern_proc out routines that print to this sbuf may
@ -1154,16 +1179,13 @@ sbuf_drain_core_output(void *arg, const char *data, int len)
locked = PROC_LOCKED(p->td->td_proc);
if (locked)
PROC_UNLOCK(p->td->td_proc);
#ifdef COMPRESS_USER_CORES
if (p->gzfile != Z_NULL)
error = compress_core(p->gzfile, NULL, __DECONST(char *, data),
len, p->td);
#ifdef GZIO
if (p->gzs != NULL)
error = gzio_write(p->gzs, __DECONST(char *, data), len);
else
#endif
error = vn_rdwr_inchunks(UIO_WRITE, p->vp,
__DECONST(void *, data), len, p->offset, UIO_SYSSPACE,
IO_UNIT | IO_DIRECT | IO_RANGELOCKED, p->active_cred,
p->file_cred, NULL, p->td);
error = core_write(p, __DECONST(void *, data), len, p->offset,
UIO_SYSSPACE);
if (locked)
PROC_LOCK(p->td->td_proc);
if (error != 0)
@ -1192,42 +1214,16 @@ __elfN(coredump)(struct thread *td, struct vnode *vp, off_t limit, int flags)
int error = 0;
struct sseg_closure seginfo;
struct note_info_list notelst;
struct coredump_params params;
struct note_info *ninfo;
void *hdr;
void *hdr, *tmpbuf;
size_t hdrsize, notesz, coresize;
boolean_t compress;
gzFile gzfile = Z_NULL;
char *core_buf = NULL;
#ifdef COMPRESS_USER_CORES
char gzopen_flags[8];
char *p;
int doing_compress = flags & IMGACT_CORE_COMPRESS;
#endif
compress = (flags & IMGACT_CORE_COMPRESS) != 0;
hdr = NULL;
TAILQ_INIT(&notelst);
#ifdef COMPRESS_USER_CORES
if (doing_compress) {
p = gzopen_flags;
*p++ = 'w';
if (compress_user_cores_gzlevel >= 0 &&
compress_user_cores_gzlevel <= 9)
*p++ = '0' + compress_user_cores_gzlevel;
*p = 0;
gzfile = gz_open("", gzopen_flags, vp);
if (gzfile == Z_NULL) {
error = EFAULT;
goto done;
}
core_buf = malloc(CORE_BUF_SIZE, M_TEMP, M_WAITOK | M_ZERO);
if (!core_buf) {
error = ENOMEM;
goto done;
}
}
#endif
/* Size the program segments. */
seginfo.count = 0;
seginfo.size = 0;
@ -1254,6 +1250,28 @@ __elfN(coredump)(struct thread *td, struct vnode *vp, off_t limit, int flags)
goto done;
}
/* Set up core dump parameters. */
params.offset = 0;
params.active_cred = cred;
params.file_cred = NOCRED;
params.td = td;
params.vp = vp;
params.gzs = NULL;
tmpbuf = NULL;
#ifdef GZIO
/* Create a compression stream if necessary. */
if (compress) {
params.gzs = gzio_init(core_gz_write, GZIO_DEFLATE,
CORE_BUF_SIZE, compress_user_cores_gzlevel, &params);
if (params.gzs == NULL) {
error = EFAULT;
goto done;
}
tmpbuf = malloc(CORE_BUF_SIZE, M_TEMP, M_WAITOK | M_ZERO);
}
#endif
/*
* Allocate memory for building the header, fill it up,
* and write it out following the notes.
@ -1263,8 +1281,8 @@ __elfN(coredump)(struct thread *td, struct vnode *vp, off_t limit, int flags)
error = EINVAL;
goto done;
}
error = __elfN(corehdr)(td, vp, cred, seginfo.count, hdr, hdrsize,
&notelst, notesz, gzfile);
error = __elfN(corehdr)(&params, seginfo.count, hdr, hdrsize, &notelst,
notesz);
/* Write the contents of all of the writable segments. */
if (error == 0) {
@ -1275,13 +1293,17 @@ __elfN(coredump)(struct thread *td, struct vnode *vp, off_t limit, int flags)
php = (Elf_Phdr *)((char *)hdr + sizeof(Elf_Ehdr)) + 1;
offset = round_page(hdrsize + notesz);
for (i = 0; i < seginfo.count; i++) {
error = core_output(vp, (caddr_t)(uintptr_t)php->p_vaddr,
php->p_filesz, offset, cred, NOCRED, curthread, core_buf, gzfile);
error = core_output((caddr_t)(uintptr_t)php->p_vaddr,
php->p_filesz, offset, &params, tmpbuf);
if (error != 0)
break;
offset += php->p_filesz;
php++;
}
#ifdef GZIO
if (error == 0 && compress)
error = gzio_flush(params.gzs);
#endif
}
if (error) {
log(LOG_WARNING,
@ -1290,11 +1312,11 @@ __elfN(coredump)(struct thread *td, struct vnode *vp, off_t limit, int flags)
}
done:
#ifdef COMPRESS_USER_CORES
if (core_buf)
free(core_buf, M_TEMP);
if (gzfile)
gzclose(gzfile);
#ifdef GZIO
if (compress) {
free(tmpbuf, M_TEMP);
gzio_fini(params.gzs);
}
#endif
while ((ninfo = TAILQ_FIRST(&notelst)) != NULL) {
TAILQ_REMOVE(&notelst, ninfo, link);
@ -1419,29 +1441,19 @@ each_writable_segment(td, func, closure)
* the page boundary.
*/
static int
__elfN(corehdr)(struct thread *td, struct vnode *vp, struct ucred *cred,
int numsegs, void *hdr, size_t hdrsize, struct note_info_list *notelst,
size_t notesz, gzFile gzfile)
__elfN(corehdr)(struct coredump_params *p, int numsegs, void *hdr,
size_t hdrsize, struct note_info_list *notelst, size_t notesz)
{
struct sbuf_drain_core_params params;
struct note_info *ninfo;
struct sbuf *sb;
int error;
/* Fill in the header. */
bzero(hdr, hdrsize);
__elfN(puthdr)(td, hdr, hdrsize, numsegs, notesz);
__elfN(puthdr)(p->td, hdr, hdrsize, numsegs, notesz);
params.offset = 0;
params.active_cred = cred;
params.file_cred = NOCRED;
params.td = td;
params.vp = vp;
#ifdef COMPRESS_USER_CORES
params.gzfile = gzfile;
#endif
sb = sbuf_new(NULL, NULL, CORE_BUF_SIZE, SBUF_FIXEDLEN);
sbuf_set_drain(sb, sbuf_drain_core_output, &params);
sbuf_set_drain(sb, sbuf_drain_core_output, p);
sbuf_start_section(sb, NULL);
sbuf_bcat(sb, hdr, hdrsize);
TAILQ_FOREACH(ninfo, notelst, link)
@ -2108,58 +2120,6 @@ static struct execsw __elfN(execsw) = {
};
EXEC_SET(__CONCAT(elf, __ELF_WORD_SIZE), __elfN(execsw));
#ifdef COMPRESS_USER_CORES
/*
* Compress and write out a core segment for a user process.
*
* 'inbuf' is the starting address of a VM segment in the process' address
* space that is to be compressed and written out to the core file. 'dest_buf'
* is a buffer in the kernel's address space. The segment is copied from
* 'inbuf' to 'dest_buf' first before being processed by the compression
* routine gzwrite(). This copying is necessary because the content of the VM
* segment may change between the compression pass and the crc-computation pass
* in gzwrite(). This is because realtime threads may preempt the UNIX kernel.
*
* If inbuf is NULL it is assumed that data is already copied to 'dest_buf'.
*/
static int
compress_core (gzFile file, char *inbuf, char *dest_buf, unsigned int len,
struct thread *td)
{
int len_compressed;
int error = 0;
unsigned int chunk_len;
while (len) {
if (inbuf != NULL) {
chunk_len = (len > CORE_BUF_SIZE) ? CORE_BUF_SIZE : len;
copyin(inbuf, dest_buf, chunk_len);
inbuf += chunk_len;
} else {
chunk_len = len;
}
len_compressed = gzwrite(file, dest_buf, chunk_len);
EVENTHANDLER_INVOKE(app_coredump_progress, td, len_compressed);
if ((unsigned int)len_compressed != chunk_len) {
log(LOG_WARNING,
"compress_core: length mismatch (0x%x returned, "
"0x%x expected)\n", len_compressed, chunk_len);
EVENTHANDLER_INVOKE(app_coredump_error, td,
"compress_core: length mismatch %x -> %x",
chunk_len, len_compressed);
error = EFAULT;
break;
}
len -= chunk_len;
maybe_yield();
}
return (error);
}
#endif /* COMPRESS_USER_CORES */
static vm_prot_t
__elfN(trans_prot)(Elf_Word flags)
{

View File

@ -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 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 */
#define GZ_HEADER_LEN 10
MALLOC_DEFINE(M_GZIO, "gzio", "zlib state");
#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
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 */
};
#define ALLOC(size) malloc(size, M_TEMP, M_WAITOK | M_ZERO)
#define TRYFREE(p) {if (p) free(p, M_TEMP);}
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);
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;
struct gzio_stream *
gzio_init(gzio_cb cb, enum gzio_mode mode, size_t bufsz, int level, void *arg)
{
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];
struct gzio_stream *s;
uint8_t *hdr;
int error;
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));
if (!s) return Z_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->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->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;
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 */
error = deflateInit2(&s->gz_stream, level, Z_DEFLATED, -MAX_WBITS,
DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (error != 0)
goto fail;
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));
s->gz_stream.avail_out = s->gz_bufsz;
s->gz_stream.next_out = s->gz_buffer;
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 */
/* 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;
s->stream.next_out = s->outbuf = (Byte*)ALLOC(Z_BUFSIZE);
if (err != Z_OK || s->outbuf == Z_NULL) {
return destroy(s), (gzFile)Z_NULL;
}
return (s);
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;
fail:
gz_free(NULL, s->gz_buffer);
gz_free(NULL, s);
return (NULL);
}
/* ===========================================================================
* 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
gzio_write(struct gzio_stream *s, void *data, u_int len)
{
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;
return (gz_write(s, data, len, Z_NO_FLUSH));
}
/* ===========================================================================
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_flush(struct gzio_stream *s)
{
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);
return (gz_write(s, NULL, 0, Z_FINISH));
}
/* ===========================================================================
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;
void
gzio_fini(struct gzio_stream *s)
{
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;
(void)deflateEnd(&s->gz_stream);
gz_free(NULL, s->gz_buffer);
gz_free(NULL, s);
}
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;
return s->z_err == Z_STREAM_END ? Z_OK : s->z_err;
}
/* ===========================================================================
Outputs a long in LSB order to the given file
*/
local void putU32 (s, x)
gz_stream *s;
uint32_t x;
{
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);
}
/*
* 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);
}

View File

@ -38,8 +38,8 @@
__FBSDID("$FreeBSD$");
#include "opt_compat.h"
#include "opt_gzio.h"
#include "opt_ktrace.h"
#include "opt_core.h"
#include <sys/param.h>
#include <sys/ctype.h>
@ -3075,17 +3075,18 @@ sysctl_debug_num_cores_check (SYSCTL_HANDLER_ARGS)
SYSCTL_PROC(_debug, OID_AUTO, ncores, CTLTYPE_INT|CTLFLAG_RW,
0, sizeof(int), sysctl_debug_num_cores_check, "I", "");
#if defined(COMPRESS_USER_CORES)
int compress_user_cores = 1;
SYSCTL_INT(_kern, OID_AUTO, compress_user_cores, CTLFLAG_RW,
#define GZ_SUFFIX ".gz"
#ifdef GZIO
static int compress_user_cores = 1;
SYSCTL_INT(_kern, OID_AUTO, compress_user_cores, CTLFLAG_RWTUN,
&compress_user_cores, 0, "Compression of user corefiles");
int compress_user_cores_gzlevel = -1; /* default level */
SYSCTL_INT(_kern, OID_AUTO, compress_user_cores_gzlevel, CTLFLAG_RW,
&compress_user_cores_gzlevel, -1, "Corefile gzip compression level");
#define GZ_SUFFIX ".gz"
#define GZ_SUFFIX_LEN 3
int compress_user_cores_gzlevel = 6;
SYSCTL_INT(_kern, OID_AUTO, compress_user_cores_gzlevel, CTLFLAG_RWTUN,
&compress_user_cores_gzlevel, 0, "Corefile gzip compression level");
#else
static int compress_user_cores = 0;
#endif
static char corefilename[MAXPATHLEN] = {"%N.core"};
@ -3162,10 +3163,8 @@ corefile_open(const char *comm, uid_t uid, pid_t pid, struct thread *td,
}
}
free(hostname, M_TEMP);
#ifdef COMPRESS_USER_CORES
if (compress)
sbuf_printf(&sb, GZ_SUFFIX);
#endif
if (sbuf_error(&sb) != 0) {
log(LOG_ERR, "pid %ld (%s), uid (%lu): corename is too "
"long\n", (long)pid, comm, (u_long)uid);
@ -3260,18 +3259,12 @@ coredump(struct thread *td)
char *name; /* name of corefile */
void *rl_cookie;
off_t limit;
int compress;
char *data = NULL;
char *fullpath, *freepath = NULL;
size_t len;
static const char comm_name[] = "comm=";
static const char core_name[] = "core=";
#ifdef COMPRESS_USER_CORES
compress = compress_user_cores;
#else
compress = 0;
#endif
PROC_LOCK_ASSERT(p, MA_OWNED);
MPASS((p->p_flag & P_HADTHREADS) == 0 || p->p_singlethread == td);
_STOPEVENT(p, S_CORE, 0);
@ -3297,8 +3290,8 @@ coredump(struct thread *td)
}
PROC_UNLOCK(p);
error = corefile_open(p->p_comm, cred->cr_uid, p->p_pid, td, compress,
&vp, &name);
error = corefile_open(p->p_comm, cred->cr_uid, p->p_pid, td,
compress_user_cores, &vp, &name);
if (error != 0)
return (error);
@ -3337,7 +3330,7 @@ coredump(struct thread *td)
if (p->p_sysent->sv_coredump != NULL) {
error = p->p_sysent->sv_coredump(td, vp, limit,
compress ? IMGACT_CORE_COMPRESS : 0);
compress_user_cores ? IMGACT_CORE_COMPRESS : 0);
} else {
error = ENOSYS;
}

View File

@ -1010,13 +1010,6 @@ extern int EXPORT inflateInit2_ OF((z_streamp strm, int windowBits,
uLongf *get_crc_table OF((void)); /* can be used by asm versions of crc32() */
#ifdef _KERNEL
struct vnode;
extern gzFile gz_open OF((const char *path, const char *mode,
struct vnode *vp));
#endif
#ifdef __cplusplus
}
#endif

49
sys/sys/gzio.h Normal file
View File

@ -0,0 +1,49 @@
/*-
* 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.
*
* $FreeBSD$
*/
#ifndef _SYS__GZIO_H_
#define _SYS__GZIO_H_
#ifdef _KERNEL
enum gzio_mode {
GZIO_DEFLATE,
};
typedef int (*gzio_cb)(void *, size_t, off_t, void *);
struct gzio_stream;
struct gzio_stream *gzio_init(gzio_cb cb, enum gzio_mode, size_t, int, void *);
int gzio_write(struct gzio_stream *, void *, u_int);
int gzio_flush(struct gzio_stream *);
void gzio_fini(struct gzio_stream *);
#endif /* _KERNEL */
#endif /* _SYS__GZIO_H_ */