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

View File

@ -2889,11 +2889,6 @@ options SHMMNI=33
# a single process at one time. # a single process at one time.
options SHMSEG=9 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 # Set the amount of time (in seconds) the system will wait before
# rebooting automatically when a kernel panic occurs. If set to (-1), # rebooting automatically when a kernel panic occurs. If set to (-1),
# the system will wait indefinitely until a key is pressed on the # 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 # Module to enable execution of application via emulators like QEMU
options IMAGACT_BINMISC 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_FREEBSD10 opt_compat.h
COMPAT_LINUXAPI opt_compat.h COMPAT_LINUXAPI opt_compat.h
COMPILING_LINT opt_global.h COMPILING_LINT opt_global.h
COMPRESS_USER_CORES opt_core.h
CY_PCI_FASTINTR CY_PCI_FASTINTR
DEADLKRES opt_watchdog.h DEADLKRES opt_watchdog.h
DIRECTIO DIRECTIO
FILEMON opt_dontuse.h FILEMON opt_dontuse.h
FFCLOCK FFCLOCK
FULL_PREEMPTION opt_sched.h FULL_PREEMPTION opt_sched.h
GZIO opt_gzio.h
IMAGACT_BINMISC opt_dontuse.h IMAGACT_BINMISC opt_dontuse.h
IPI_PREEMPTION opt_sched.h IPI_PREEMPTION opt_sched.h
GEOM_AES opt_geom.h GEOM_AES opt_geom.h

View File

@ -33,12 +33,13 @@ __FBSDID("$FreeBSD$");
#include "opt_capsicum.h" #include "opt_capsicum.h"
#include "opt_compat.h" #include "opt_compat.h"
#include "opt_core.h" #include "opt_gzio.h"
#include <sys/param.h> #include <sys/param.h>
#include <sys/capsicum.h> #include <sys/capsicum.h>
#include <sys/exec.h> #include <sys/exec.h>
#include <sys/fcntl.h> #include <sys/fcntl.h>
#include <sys/gzio.h>
#include <sys/imgact.h> #include <sys/imgact.h>
#include <sys/imgact_elf.h> #include <sys/imgact_elf.h>
#include <sys/jail.h> #include <sys/jail.h>
@ -69,8 +70,6 @@ __FBSDID("$FreeBSD$");
#include <sys/eventhandler.h> #include <sys/eventhandler.h>
#include <sys/user.h> #include <sys/user.h>
#include <net/zlib.h>
#include <vm/vm.h> #include <vm/vm.h>
#include <vm/vm_kern.h> #include <vm/vm_kern.h>
#include <vm/vm_param.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, SYSCTL_NODE(_kern, OID_AUTO, __CONCAT(elf, __ELF_WORD_SIZE), CTLFLAG_RW, 0,
""); "");
#ifdef COMPRESS_USER_CORES #define CORE_BUF_SIZE (16 * 1024)
static int compress_core(gzFile, char *, char *, unsigned int,
struct thread * td);
#endif
#define CORE_BUF_SIZE (16 * 1024)
int __elfN(fallback_brand) = -1; int __elfN(fallback_brand) = -1;
SYSCTL_INT(__CONCAT(_kern_elf, __ELF_WORD_SIZE), OID_AUTO, SYSCTL_INT(__CONCAT(_kern_elf, __ELF_WORD_SIZE), OID_AUTO,
@ -1067,11 +1062,23 @@ struct note_info {
TAILQ_HEAD(note_info_list, 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_put_phdr(vm_map_entry_t, void *);
static void cb_size_segment(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 void each_writable_segment(struct thread *, segment_callback, void *);
static int __elfN(corehdr)(struct thread *, struct vnode *, struct ucred *, static int __elfN(corehdr)(struct coredump_params *, int, void *, size_t,
int, void *, size_t, struct note_info_list *, size_t, gzFile); struct note_info_list *, size_t);
static void __elfN(prepare_notes)(struct thread *, struct note_info_list *, static void __elfN(prepare_notes)(struct thread *, struct note_info_list *,
size_t *); size_t *);
static void __elfN(puthdr)(struct thread *, void *, size_t, int, 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_umask(void *, struct sbuf *, size_t *);
static void note_procstat_vmmap(void *, struct sbuf *, size_t *); static void note_procstat_vmmap(void *, struct sbuf *, size_t *);
#ifdef COMPRESS_USER_CORES #ifdef GZIO
extern int compress_user_cores;
extern int compress_user_cores_gzlevel; extern int compress_user_cores_gzlevel;
#endif
/*
* Write out a core segment to the compression stream.
*/
static int static int
core_output(struct vnode *vp, void *base, size_t len, off_t offset, compress_chunk(struct coredump_params *p, char *base, char *buf, u_int len)
struct ucred *active_cred, struct ucred *file_cred, {
struct thread *td, char *core_buf, gzFile gzfile) { u_int chunk_len;
int error; int error;
if (gzfile) {
#ifdef COMPRESS_USER_CORES while (len > 0) {
error = compress_core(gzfile, base, core_buf, len, td); chunk_len = MIN(len, CORE_BUF_SIZE);
#else copyin(base, buf, chunk_len);
panic("shouldn't be here"); error = gzio_write(p->gzs, buf, chunk_len);
#endif if (error != 0)
} else { break;
error = vn_rdwr_inchunks(UIO_WRITE, vp, base, len, offset, base += chunk_len;
UIO_USERSPACE, IO_UNIT | IO_DIRECT | IO_RANGELOCKED, len -= chunk_len;
active_cred, file_cred, NULL, td);
} }
return (error); return (error);
} }
/* Coredump output parameters for sbuf drain routine. */ static int
struct sbuf_drain_core_params { core_gz_write(void *base, size_t len, off_t offset, void *arg)
off_t offset; {
struct ucred *active_cred;
struct ucred *file_cred; return (core_write((struct coredump_params *)arg, base, len, offset,
struct thread *td; UIO_SYSSPACE));
struct vnode *vp; }
#ifdef COMPRESS_USER_CORES #endif /* GZIO */
gzFile gzfile;
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 #endif
}; return (core_write(p, base, len, offset, UIO_USERSPACE));
}
/* /*
* Drain into a core file. * Drain into a core file.
@ -1138,10 +1163,10 @@ struct sbuf_drain_core_params {
static int static int
sbuf_drain_core_output(void *arg, const char *data, int len) sbuf_drain_core_output(void *arg, const char *data, int len)
{ {
struct sbuf_drain_core_params *p; struct coredump_params *p;
int error, locked; 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 * 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); locked = PROC_LOCKED(p->td->td_proc);
if (locked) if (locked)
PROC_UNLOCK(p->td->td_proc); PROC_UNLOCK(p->td->td_proc);
#ifdef COMPRESS_USER_CORES #ifdef GZIO
if (p->gzfile != Z_NULL) if (p->gzs != NULL)
error = compress_core(p->gzfile, NULL, __DECONST(char *, data), error = gzio_write(p->gzs, __DECONST(char *, data), len);
len, p->td);
else else
#endif #endif
error = vn_rdwr_inchunks(UIO_WRITE, p->vp, error = core_write(p, __DECONST(void *, data), len, p->offset,
__DECONST(void *, data), len, p->offset, UIO_SYSSPACE, UIO_SYSSPACE);
IO_UNIT | IO_DIRECT | IO_RANGELOCKED, p->active_cred,
p->file_cred, NULL, p->td);
if (locked) if (locked)
PROC_LOCK(p->td->td_proc); PROC_LOCK(p->td->td_proc);
if (error != 0) if (error != 0)
@ -1192,42 +1214,16 @@ __elfN(coredump)(struct thread *td, struct vnode *vp, off_t limit, int flags)
int error = 0; int error = 0;
struct sseg_closure seginfo; struct sseg_closure seginfo;
struct note_info_list notelst; struct note_info_list notelst;
struct coredump_params params;
struct note_info *ninfo; struct note_info *ninfo;
void *hdr; void *hdr, *tmpbuf;
size_t hdrsize, notesz, coresize; size_t hdrsize, notesz, coresize;
boolean_t compress;
gzFile gzfile = Z_NULL; compress = (flags & IMGACT_CORE_COMPRESS) != 0;
char *core_buf = NULL;
#ifdef COMPRESS_USER_CORES
char gzopen_flags[8];
char *p;
int doing_compress = flags & IMGACT_CORE_COMPRESS;
#endif
hdr = NULL; hdr = NULL;
TAILQ_INIT(&notelst); 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. */ /* Size the program segments. */
seginfo.count = 0; seginfo.count = 0;
seginfo.size = 0; seginfo.size = 0;
@ -1254,6 +1250,28 @@ __elfN(coredump)(struct thread *td, struct vnode *vp, off_t limit, int flags)
goto done; 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, * Allocate memory for building the header, fill it up,
* and write it out following the notes. * 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; error = EINVAL;
goto done; goto done;
} }
error = __elfN(corehdr)(td, vp, cred, seginfo.count, hdr, hdrsize, error = __elfN(corehdr)(&params, seginfo.count, hdr, hdrsize, &notelst,
&notelst, notesz, gzfile); notesz);
/* Write the contents of all of the writable segments. */ /* Write the contents of all of the writable segments. */
if (error == 0) { 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; php = (Elf_Phdr *)((char *)hdr + sizeof(Elf_Ehdr)) + 1;
offset = round_page(hdrsize + notesz); offset = round_page(hdrsize + notesz);
for (i = 0; i < seginfo.count; i++) { for (i = 0; i < seginfo.count; i++) {
error = core_output(vp, (caddr_t)(uintptr_t)php->p_vaddr, error = core_output((caddr_t)(uintptr_t)php->p_vaddr,
php->p_filesz, offset, cred, NOCRED, curthread, core_buf, gzfile); php->p_filesz, offset, &params, tmpbuf);
if (error != 0) if (error != 0)
break; break;
offset += php->p_filesz; offset += php->p_filesz;
php++; php++;
} }
#ifdef GZIO
if (error == 0 && compress)
error = gzio_flush(params.gzs);
#endif
} }
if (error) { if (error) {
log(LOG_WARNING, log(LOG_WARNING,
@ -1290,11 +1312,11 @@ __elfN(coredump)(struct thread *td, struct vnode *vp, off_t limit, int flags)
} }
done: done:
#ifdef COMPRESS_USER_CORES #ifdef GZIO
if (core_buf) if (compress) {
free(core_buf, M_TEMP); free(tmpbuf, M_TEMP);
if (gzfile) gzio_fini(params.gzs);
gzclose(gzfile); }
#endif #endif
while ((ninfo = TAILQ_FIRST(&notelst)) != NULL) { while ((ninfo = TAILQ_FIRST(&notelst)) != NULL) {
TAILQ_REMOVE(&notelst, ninfo, link); TAILQ_REMOVE(&notelst, ninfo, link);
@ -1419,29 +1441,19 @@ each_writable_segment(td, func, closure)
* the page boundary. * the page boundary.
*/ */
static int static int
__elfN(corehdr)(struct thread *td, struct vnode *vp, struct ucred *cred, __elfN(corehdr)(struct coredump_params *p, int numsegs, void *hdr,
int numsegs, void *hdr, size_t hdrsize, struct note_info_list *notelst, size_t hdrsize, struct note_info_list *notelst, size_t notesz)
size_t notesz, gzFile gzfile)
{ {
struct sbuf_drain_core_params params;
struct note_info *ninfo; struct note_info *ninfo;
struct sbuf *sb; struct sbuf *sb;
int error; int error;
/* Fill in the header. */ /* Fill in the header. */
bzero(hdr, hdrsize); 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); 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_start_section(sb, NULL);
sbuf_bcat(sb, hdr, hdrsize); sbuf_bcat(sb, hdr, hdrsize);
TAILQ_FOREACH(ninfo, notelst, link) TAILQ_FOREACH(ninfo, notelst, link)
@ -2108,58 +2120,6 @@ static struct execsw __elfN(execsw) = {
}; };
EXEC_SET(__CONCAT(elf, __ELF_WORD_SIZE), __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 static vm_prot_t
__elfN(trans_prot)(Elf_Word flags) __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 #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 */ int error;
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; 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;
err = deflateInit2(&(s->stream), level, hdr[2] = Z_DEFLATED;
Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, strategy); hdr[9] = OS_CODE;
/* windowBits is passed < 0 to suppress zlib header */ 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); return (s);
if (err != Z_OK || s->outbuf == Z_NULL) {
return destroy(s), (gzFile)Z_NULL;
}
s->stream.avail_out = Z_BUFSIZE; fail:
s->file = vp; gz_free(NULL, s->gz_buffer);
gz_free(NULL, s);
/* Write a very simple .gz header: return (NULL);
*/
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_write(struct gzio_stream *s, void *data, u_int len)
* 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, data, len, Z_NO_FLUSH));
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;
} }
int
/* =========================================================================== gzio_flush(struct gzio_stream *s)
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; return (gz_write(s, NULL, 0, Z_FINISH));
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);
} }
void
/* =========================================================================== gzio_fini(struct gzio_stream *s)
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; (void)deflateEnd(&s->gz_stream);
gz_free(NULL, s->gz_buffer);
if (s->stream.avail_in) { gz_free(NULL, s);
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;
} }
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 * 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 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$"); __FBSDID("$FreeBSD$");
#include "opt_compat.h" #include "opt_compat.h"
#include "opt_gzio.h"
#include "opt_ktrace.h" #include "opt_ktrace.h"
#include "opt_core.h"
#include <sys/param.h> #include <sys/param.h>
#include <sys/ctype.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, SYSCTL_PROC(_debug, OID_AUTO, ncores, CTLTYPE_INT|CTLFLAG_RW,
0, sizeof(int), sysctl_debug_num_cores_check, "I", ""); 0, sizeof(int), sysctl_debug_num_cores_check, "I", "");
#if defined(COMPRESS_USER_CORES) #define GZ_SUFFIX ".gz"
int compress_user_cores = 1;
SYSCTL_INT(_kern, OID_AUTO, compress_user_cores, CTLFLAG_RW, #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"); &compress_user_cores, 0, "Compression of user corefiles");
int compress_user_cores_gzlevel = -1; /* default level */ int compress_user_cores_gzlevel = 6;
SYSCTL_INT(_kern, OID_AUTO, compress_user_cores_gzlevel, CTLFLAG_RW, SYSCTL_INT(_kern, OID_AUTO, compress_user_cores_gzlevel, CTLFLAG_RWTUN,
&compress_user_cores_gzlevel, -1, "Corefile gzip compression level"); &compress_user_cores_gzlevel, 0, "Corefile gzip compression level");
#else
#define GZ_SUFFIX ".gz" static int compress_user_cores = 0;
#define GZ_SUFFIX_LEN 3
#endif #endif
static char corefilename[MAXPATHLEN] = {"%N.core"}; 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); free(hostname, M_TEMP);
#ifdef COMPRESS_USER_CORES
if (compress) if (compress)
sbuf_printf(&sb, GZ_SUFFIX); sbuf_printf(&sb, GZ_SUFFIX);
#endif
if (sbuf_error(&sb) != 0) { if (sbuf_error(&sb) != 0) {
log(LOG_ERR, "pid %ld (%s), uid (%lu): corename is too " log(LOG_ERR, "pid %ld (%s), uid (%lu): corename is too "
"long\n", (long)pid, comm, (u_long)uid); "long\n", (long)pid, comm, (u_long)uid);
@ -3260,18 +3259,12 @@ coredump(struct thread *td)
char *name; /* name of corefile */ char *name; /* name of corefile */
void *rl_cookie; void *rl_cookie;
off_t limit; off_t limit;
int compress;
char *data = NULL; char *data = NULL;
char *fullpath, *freepath = NULL; char *fullpath, *freepath = NULL;
size_t len; size_t len;
static const char comm_name[] = "comm="; static const char comm_name[] = "comm=";
static const char core_name[] = "core="; static const char core_name[] = "core=";
#ifdef COMPRESS_USER_CORES
compress = compress_user_cores;
#else
compress = 0;
#endif
PROC_LOCK_ASSERT(p, MA_OWNED); PROC_LOCK_ASSERT(p, MA_OWNED);
MPASS((p->p_flag & P_HADTHREADS) == 0 || p->p_singlethread == td); MPASS((p->p_flag & P_HADTHREADS) == 0 || p->p_singlethread == td);
_STOPEVENT(p, S_CORE, 0); _STOPEVENT(p, S_CORE, 0);
@ -3297,8 +3290,8 @@ coredump(struct thread *td)
} }
PROC_UNLOCK(p); PROC_UNLOCK(p);
error = corefile_open(p->p_comm, cred->cr_uid, p->p_pid, td, compress, error = corefile_open(p->p_comm, cred->cr_uid, p->p_pid, td,
&vp, &name); compress_user_cores, &vp, &name);
if (error != 0) if (error != 0)
return (error); return (error);
@ -3337,7 +3330,7 @@ coredump(struct thread *td)
if (p->p_sysent->sv_coredump != NULL) { if (p->p_sysent->sv_coredump != NULL) {
error = p->p_sysent->sv_coredump(td, vp, limit, error = p->p_sysent->sv_coredump(td, vp, limit,
compress ? IMGACT_CORE_COMPRESS : 0); compress_user_cores ? IMGACT_CORE_COMPRESS : 0);
} else { } else {
error = ENOSYS; 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() */ 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 #ifdef __cplusplus
} }
#endif #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_ */