Index: sys/kern/kern_dump.c =================================================================== --- sys/kern/kern_dump.c +++ sys/kern/kern_dump.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 2002 Marcel Moolenaar + * Copyright (c) 2015 EMC Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,15 +29,18 @@ __FBSDID("$FreeBSD$"); #include "opt_ddb.h" +#include "opt_gzio.h" #include "opt_watchdog.h" #include #include #include #include +#include #include #include #include +#include #include #include #include @@ -79,12 +83,76 @@ static off_t dumpoff; static char buffer[DEV_BSIZE]; static size_t fragsz; +#ifdef GZIO +static struct gzio_stream *gzs; +static uint8_t *gzbuffer; +#endif static char dumpdevname[sizeof(((struct cdev *)NULL)->si_name)]; SYSCTL_DECL(_kern_shutdown); SYSCTL_STRING(_kern_shutdown, OID_AUTO, dumpdevname, CTLFLAG_RD, dumpdevname, 0, "Device for kernel dumps"); +static int compress_kernel_dumps = 0; + +#ifdef GZIO +static int compress_kernel_dumps_gzlevel = 6; +SYSCTL_INT(_kern, OID_AUTO, compress_kernel_dumps_gzlevel, CTLFLAG_RW, + &compress_kernel_dumps_gzlevel, 0, + "Kernel crash dump compression level"); + +static int sysctl_dump_gz_toggle(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_kern, OID_AUTO, compress_kernel_dumps, CTLFLAG_RW | CTLTYPE_INT, + &compress_kernel_dumps, 0, sysctl_dump_gz_toggle, "I", + "Enable compressed kernel crash dumps"); + +static int dump_gz_configure(struct dumperinfo *); +static void dump_gz_disable(void); +static int dump_gz_write_cb(void *, size_t, off_t, void *); + +static int +sysctl_dump_gz_toggle(SYSCTL_HANDLER_ARGS) +{ + int error, value; + + value = *(int *)arg1; + error = sysctl_handle_int(oidp, &value, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + + if (value == 0) { + compress_kernel_dumps = 0; + dump_gz_disable(); + } else if (compress_kernel_dumps == 0) { + if (strlen(dumpdevname) > 0) + error = dump_gz_configure(&dumper); + if (error == 0) + compress_kernel_dumps = 1; + } + return (error); +} +#endif /* GZIO */ + +/* + * When writing a kernel dump to disk, we also include dump metadata that is + * used by savecore(8) to locate and recover the dump. This metadata is + * represented by the struct kerneldumpheader type. When a kernel dump is + * complete, two copies of the header are written: one to the last sector of + * the dump medium, and one immediately before the beginning of the dump. The + * last header is used to locate the first header, and thus, the dump itself. + * + * When the dump is written without compression, things are simple: we know + * exactly how long the dump will be, so the initial offset in the medium can be + * chosen such that the end of the dump is flush with the terminating header. + * In this case, the "extent" of the dump (the space between the two headers) is + * equal to its length. In the compressed case we don't know the dump length + * a priori, so we write the dump starting at the same offset as we would in the + * uncompressed case. Once the dump is complete, we know its compressed length, + * so the dump headers are updated and written to the medium. In this case, the + * extent tells savecore(8) where to find the beginning of the dump, and the + * length tells it how far into the extent it must read to recover the dump. + */ + int doadump(boolean_t textdump) { @@ -122,10 +190,27 @@ uint64_t length; length = dtoh64(kdh->dumplength); - if (di->mediasize < SIZEOF_METADATA + length + sizeof(*kdh) * 2) - return (E2BIG); + if (di->mediasize < SIZEOF_METADATA + length + 2 * sizeof(*kdh)) { + if (compress_kernel_dumps) + /* + * We don't yet know how much space the compressed dump + * will occupy, so try to use the whole swap partition + * (minus the first 64KB). If that doesn't turn out to + * be enough, the bounds checking in dump_write_raw() + * will catch us. + */ + length = di->mediasize - SIZEOF_METADATA - + 2 * sizeof(*kdh); + else + return (ENOSPC); + } + /* + * The initial offset at which we're going to write the dump (excluding + * the leading kernel dump header). + */ dumpoff = di->mediaoffset + di->mediasize - length - sizeof(*kdh); + kdh->dumpextent = htod64(length); return (0); } @@ -133,12 +218,27 @@ int dump_finish(struct dumperinfo *di, struct kerneldumpheader *kdh) { - uint64_t length; + uint64_t extent; int error; - dumpoff = 0; - - length = dtoh64(kdh->dumplength); + extent = dtoh64(kdh->dumpextent); + +#ifdef GZIO + if (compress_kernel_dumps) { + error = gzio_flush(gzs); + if (error != 0) + return (error); + + /* + * Now that we've completed the compressed dump, we know its + * size, so update the header accordingly and recompute parity. + */ + kdh->dumplength = htod64(dumpoff - + (di->mediaoffset + di->mediasize - extent - sizeof(*kdh))); + kdh->parity = 0; + kdh->parity = kerneldump_parity(kdh); + } +#endif /* Write dump headers at the beginning and end of the dump extent. */ error = dump_write_raw(di, kdh, 0, @@ -146,11 +246,18 @@ if (error != 0) return (error); error = dump_write_raw(di, kdh, 0, - di->mediaoffset + di->mediasize - length - 2 * sizeof(*kdh), - sizeof(*kdh)); + di->mediaoffset + di->mediasize - extent - 2 * sizeof(*kdh), + sizeof(*kdh)); if (error != 0) return (error); + /* Reset dump state. */ +#ifdef GZIO + if (compress_kernel_dumps) + gzio_reset(gzs); +#endif + dumpoff = 0; + /* Tell the dump media driver that we're done. */ return (dump_write_raw(di, NULL, 0, 0, 0)); } @@ -162,6 +269,14 @@ { int error; +#ifdef GZIO + if (compress_kernel_dumps) { + /* Bounce through a buffer to avoid gzip CRC errors. */ + memmove(gzbuffer, virtual, length); + return (gzio_write(gzs, gzbuffer, length)); + } +#endif + error = dump_write_raw(di, virtual, physical, dumpoff, length); if (error == 0) dumpoff += length; @@ -173,6 +288,16 @@ dump_skip(struct dumperinfo *di, size_t gap) { + if (gap > di->maxiosize) + return (EINVAL); + +#ifdef GZIO + if (compress_kernel_dumps) { + memset(gzbuffer, 0, di->maxiosize); + return (gzio_write(gzs, gzbuffer, gap)); + } +#endif + dumpoff += gap; return (0); } @@ -194,6 +319,57 @@ return (di->dumper(di->priv, virtual, physical, offset, length)); } +#ifdef GZIO +static int +dump_gz_configure(struct dumperinfo *di) +{ + + MPASS(gzs == NULL); + gzs = gzio_init(dump_gz_write_cb, GZIO_DEFLATE, di->maxiosize, + compress_kernel_dumps_gzlevel, di); + if (gzs == NULL) + return (EINVAL); + gzbuffer = malloc(di->maxiosize, M_TEMP, M_WAITOK | M_NODUMP); + return (0); +} + +static void +dump_gz_disable(void) +{ + + if (gzs != NULL) { + gzio_fini(gzs); + gzs = NULL; + } + free(gzbuffer, M_TEMP); + gzbuffer = NULL; +} + +/* Write compressed data to the dump medium. */ +static int +dump_gz_write_cb(void *base, size_t length, off_t offset __unused, void *arg) +{ + struct dumperinfo *di; + int error; + + di = (struct dumperinfo *)arg; + + if (dumpoff % di->blocksize != 0) + /* + * A previous write caused us to end up at an unaligned offset. + * Only the final gzio flush should cause that, in which case we + * shouldn't be here. + */ + return (EINVAL); + + error = dump_write_raw(di, base, 0, dumpoff, + roundup(length, di->blocksize)); + if (error == 0) + dumpoff += length; + return (error); +} +#endif /* GZIO */ + /* Register a dumper. */ int set_dumper(struct dumperinfo *di, const char *devname, struct thread *td) @@ -208,6 +384,10 @@ if (di == NULL) { bzero(&dumper, sizeof dumper); dumpdevname[0] = '\0'; +#ifdef GZIO + if (compress_kernel_dumps) + dump_gz_disable(); +#endif return (0); } if (dumper.dumper != NULL) @@ -217,7 +397,11 @@ if (wantcopy >= sizeof(dumpdevname)) printf("set_dumper: device name truncated from '%s' -> '%s'\n", devname, dumpdevname); - return (0); +#ifdef GZIO + if (compress_kernel_dumps) + error = dump_gz_configure(di); +#endif + return (error); } void @@ -228,9 +412,14 @@ bzero(kdh, sizeof(*kdh)); strlcpy(kdh->magic, magic, sizeof(kdh->magic)); strlcpy(kdh->architecture, MACHINE_ARCH, sizeof(kdh->architecture)); + if (compress_kernel_dumps && strcmp(magic, KERNELDUMPMAGIC) == 0) + strlcpy(kdh->magic, GZDUMPMAGIC, sizeof(kdh->magic)); + else + strlcpy(kdh->magic, magic, sizeof(kdh->magic)); kdh->version = htod32(KERNELDUMPVERSION); kdh->architectureversion = htod32(archver); kdh->dumplength = htod64(dumplen); + kdh->dumpextent = kdh->dumplength; kdh->dumptime = htod64(time_second); kdh->blocksize = htod32(blksz); strlcpy(kdh->hostname, prison0.pr_hostname, sizeof(kdh->hostname)); Index: sys/sys/kerneldump.h =================================================================== --- sys/sys/kerneldump.h +++ sys/sys/kerneldump.h @@ -61,10 +61,11 @@ char magic[20]; #define KERNELDUMPMAGIC "FreeBSD Kernel Dump" #define TEXTDUMPMAGIC "FreeBSD Text Dump" +#define GZDUMPMAGIC "FreeBSD GZIP Dump" #define KERNELDUMPMAGIC_CLEARED "Cleared Kernel Dump" char architecture[12]; uint32_t version; -#define KERNELDUMPVERSION 1 +#define KERNELDUMPVERSION 2 uint32_t architectureversion; #define KERNELDUMP_AARCH64_VERSION 1 #define KERNELDUMP_AMD64_VERSION 2 @@ -75,10 +76,11 @@ #define KERNELDUMP_SPARC64_VERSION 1 #define KERNELDUMP_TEXT_VERSION 1 uint64_t dumplength; /* excl headers */ + uint64_t dumpextent; /* space between headers */ uint64_t dumptime; uint32_t blocksize; char hostname[64]; - char versionstring[192]; + char versionstring[184]; char panicstring[192]; uint32_t parity; }; @@ -105,20 +107,20 @@ vm_paddr_t pa_size; }; -int dumpsys_generic(struct dumperinfo *); +int dumpsys_generic(struct dumperinfo *); -void dumpsys_map_chunk(vm_paddr_t, size_t, void **); +void dumpsys_map_chunk(vm_paddr_t, size_t, void **); typedef int dumpsys_callback_t(struct dump_pa *, int, void *); -int dumpsys_foreach_chunk(dumpsys_callback_t, void *); -int dumpsys_cb_dumpdata(struct dump_pa *, int, void *); -int dumpsys_buf_write(struct dumperinfo *, char *, size_t); -int dumpsys_buf_flush(struct dumperinfo *); +int dumpsys_foreach_chunk(dumpsys_callback_t, void *); +int dumpsys_cb_dumpdata(struct dump_pa *, int, void *); +int dumpsys_buf_write(struct dumperinfo *, char *, size_t); +int dumpsys_buf_flush(struct dumperinfo *); -void dumpsys_gen_pa_init(void); +void dumpsys_gen_pa_init(void); struct dump_pa *dumpsys_gen_pa_next(struct dump_pa *); -void dumpsys_gen_wbinv_all(void); -void dumpsys_gen_unmap_chunk(vm_paddr_t, size_t, void *); -int dumpsys_gen_write_aux_headers(struct dumperinfo *); +void dumpsys_gen_wbinv_all(void); +void dumpsys_gen_unmap_chunk(vm_paddr_t, size_t, void *); +int dumpsys_gen_write_aux_headers(struct dumperinfo *); int dump_start(struct dumperinfo *, struct kerneldumpheader *); int dump_finish(struct dumperinfo *, struct kerneldumpheader *);