Index: share/man/man5/core.5 =================================================================== --- share/man/man5/core.5 +++ share/man/man5/core.5 @@ -28,7 +28,7 @@ .\" @(#)core.5 8.3 (Berkeley) 12/11/93 .\" $FreeBSD$ .\" -.Dd November 22, 2012 +.Dd November 10, 2014 .Dt CORE 5 .Os .Sh NAME @@ -101,25 +101,23 @@ .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 Index: share/man/man9/Makefile =================================================================== --- share/man/man9/Makefile +++ share/man/man9/Makefile @@ -130,6 +130,7 @@ g_provider_by_name.9 \ groupmember.9 \ g_wither_geom.9 \ + gzio.9 \ hash.9 \ hashinit.9 \ hexdump.9 \ @@ -736,6 +737,10 @@ MLINKS+=g_provider.9 g_destroy_provider.9 \ g_provider.9 g_error_provider.9 \ g_provider.9 g_new_providerf.9 +MLINKS+=gzio.9 gzio_init.9 \ + gzio.9 gzio_write.9 \ + gzio.9 gzio_flush.9 \ + gzio.9 gzio_fini.9 MLINKS+=hash.9 hash32.9 \ hash.9 hash32_buf.9 \ hash.9 hash32_str.9 \ Index: share/man/man9/gzio.9 =================================================================== --- /dev/null +++ share/man/man9/gzio.9 @@ -0,0 +1,118 @@ +.\" Copyright (c) 2015 Mark Johnston +.\" All rights reserved. +.\" +.\" 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$ +.\" +.Dd February 14, 2015 +.Dt GZIO 9 +.Os +.Sh NAME +.Nm gzio +.Nd a callback interface for the zlib compression library +.Sh SYNOPSIS +.Cd "options GZIO" +.Pp +.In sys/gzio.h +.Bd -literal +typedef int (*gzio_cb)(void *base, size_t len, off_t off, void *arg); +.Ed +.Pp +.Ft struct gzio_stream * +.Fn gzio_init "gzio_cb cb" "enum gzio_mode mode" "size_t bufsz" "int level" "void *arg" +.Ft int +.Fn gzio_write "struct gzio_stream *s" "void *data" "u_int len" +.Ft int +.Fn gzio_flush "struct gzio_stream *s" +.Ft void +.Fn gzio_fini "struct gzio_stream *s" +.Sh DESCRIPTION +The +.Nm +interface is a simple wrapper for the zlib library. +It currently only supports compression of a data stream. +A +.Ft struct gzio_stream +stores state for the stream and is allocated and freed using +.Fn gzio_init +and +.Fn gzio_fini +respectively. +As data is written to the stream using +.Fn gzio_write , +the caller-provided callback is invoked, making the processed data available. +The callback is only invoked when the stream's internal buffer is full; to +force the processing of partially-buffered data, use +.Fn gzio_flush . +.Fn gzio_fini +is used to free a flushed stream. +.Pp +The +.Fn gzio_init +function allocates a new stream with callback +.Va cb , +and an internal buffer of size +.Va bufsz . +The +.Va mode +parameter must be +.Dv GZIO_DEFLATE . +The +.Va level +parameter is a zlib parameter which determines the compression level used; +valid values are 0-9 inclusive, and -1, which tells zlib to use its default +compression level. +Larger values give better compression ratios at the cost of CPU time and +memory usage. +Note that all memory used by +.Nm +is allocated in +.Fn gzio_init . +In particular, memory will not be allocated while writing to a +.Nm +stream. +Finally, +.Va arg +is an opaque argument that is passed to +.Va cb . +.Fn gzio_init +will return +.Dv NULL +if an error occurs during stream initialization. +.Pp +The +.Fn gzio_write +and +.Fn gzio_flush +functions write data to be compressed to a +.Dv GZIO_DEFLATE +stream. +If the callback returns a non-zero value, these functions will return it to +the caller. +If an error occurs during compression, +.Dv EIO will be returned. +.Fn gzio_flush +should be called exactly once for a given stream. +.Sh SEE ALSO +.Xr zlib 3 , +.Xr core 5 Index: sys/conf/NOTES =================================================================== --- sys/conf/NOTES +++ sys/conf/NOTES @@ -2889,11 +2889,6 @@ # 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 @@ # 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 Index: sys/conf/options =================================================================== --- sys/conf/options +++ sys/conf/options @@ -87,13 +87,13 @@ 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 Index: sys/kern/imgact_elf.c =================================================================== --- sys/kern/imgact_elf.c +++ sys/kern/imgact_elf.c @@ -33,12 +33,13 @@ #include "opt_capsicum.h" #include "opt_compat.h" -#include "opt_core.h" +#include "opt_gzio.h" #include #include #include #include +#include #include #include #include @@ -68,8 +69,6 @@ #include #include -#include - #include #include #include @@ -104,11 +103,7 @@ 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, @@ -1065,11 +1060,21 @@ 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 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); @@ -1093,42 +1098,58 @@ static void note_procstat_umask(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; -#endif +/* + * Compress and write out a core segment for a user process. + */ 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(char *base, char *buf, u_int len, struct coredump_params *p) +{ + 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); } +#endif /* GZIO */ -/* 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_write(void *base, size_t len, off_t offset, void *arg) +{ + struct coredump_params *p; + + p = (struct coredump_params *)arg; + + return (vn_rdwr_inchunks(UIO_WRITE, p->vp, base, len, offset, + UIO_SYSSPACE, 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) +{ + int error; + +#ifdef GZIO + if (p->gzs != NULL) + error = compress_chunk(base, tmpbuf, len, p); + else #endif -}; + error = core_write(base, len, offset, p); + return (error); +} /* * Drain into a core file. @@ -1136,10 +1157,10 @@ 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 @@ -1152,16 +1173,12 @@ 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(__DECONST(void *, data), len, p->offset, p); if (locked) PROC_LOCK(p->td->td_proc); if (error != 0) @@ -1190,42 +1207,16 @@ 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(¬elst); -#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; @@ -1252,6 +1243,28 @@ 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_write, GZIO_DEFLATE, CORE_BUF_SIZE, + compress_user_cores_gzlevel, ¶ms); + 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. @@ -1261,8 +1274,8 @@ error = EINVAL; goto done; } - error = __elfN(corehdr)(td, vp, cred, seginfo.count, hdr, hdrsize, - ¬elst, notesz, gzfile); + error = __elfN(corehdr)(¶ms, seginfo.count, hdr, hdrsize, ¬elst, + notesz); /* Write the contents of all of the writable segments. */ if (error == 0) { @@ -1273,13 +1286,17 @@ 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, ¶ms, 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, @@ -1288,11 +1305,11 @@ } 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(¬elst)) != NULL) { TAILQ_REMOVE(¬elst, ninfo, link); @@ -1416,29 +1433,19 @@ * 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, ¶ms); + sbuf_set_drain(sb, sbuf_drain_core_output, p); sbuf_start_section(sb, NULL); sbuf_bcat(sb, hdr, hdrsize); TAILQ_FOREACH(ninfo, notelst, link) @@ -2105,58 +2112,6 @@ }; 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) { Index: sys/kern/kern_gzio.c =================================================================== --- sys/kern/kern_gzio.c +++ 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 * - * 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 +__FBSDID("$FreeBSD$"); #include -#include + +#include +#include #include -#include -#include -#include -#include -#include - -#include -#include - -#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 - /* =========================================================================== - * 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); +} Index: sys/kern/kern_sig.c =================================================================== --- sys/kern/kern_sig.c +++ sys/kern/kern_sig.c @@ -38,8 +38,8 @@ __FBSDID("$FreeBSD$"); #include "opt_compat.h" +#include "opt_gzio.h" #include "opt_ktrace.h" -#include "opt_core.h" #include #include @@ -3075,18 +3075,17 @@ SYSCTL_PROC(_debug, OID_AUTO, ncores, CTLTYPE_INT|CTLFLAG_RW, 0, sizeof(int), sysctl_debug_num_cores_check, "I", ""); -#if defined(COMPRESS_USER_CORES) +#ifdef GZIO int compress_user_cores = 1; SYSCTL_INT(_kern, OID_AUTO, compress_user_cores, CTLFLAG_RW, &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, - &compress_user_cores_gzlevel, -1, "Corefile gzip compression level"); + &compress_user_cores_gzlevel, 0, "Corefile gzip compression level"); -#define GZ_SUFFIX ".gz" -#define GZ_SUFFIX_LEN 3 -#endif +#define GZ_SUFFIX ".gz" +#endif /* GZIO */ static char corefilename[MAXPATHLEN] = {"%N.core"}; SYSCTL_STRING(_kern, OID_AUTO, corefile, CTLFLAG_RWTUN, corefilename, @@ -3162,7 +3161,7 @@ } } free(hostname, M_TEMP); -#ifdef COMPRESS_USER_CORES +#ifdef GZIO if (compress) sbuf_printf(&sb, GZ_SUFFIX); #endif @@ -3267,7 +3266,7 @@ static const char comm_name[] = "comm="; static const char core_name[] = "core="; -#ifdef COMPRESS_USER_CORES +#ifdef GZIO compress = compress_user_cores; #else compress = 0; Index: sys/net/zlib.h =================================================================== --- sys/net/zlib.h +++ sys/net/zlib.h @@ -1010,13 +1010,6 @@ 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 Index: sys/sys/gzio.h =================================================================== --- /dev/null +++ sys/sys/gzio.h @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2014 Mark Johnston + * + * 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_ */