diff --git a/sbin/dumpon/dumpon.8 b/sbin/dumpon/dumpon.8 index 572bd030aef..e6aace1bbbc 100644 --- a/sbin/dumpon/dumpon.8 +++ b/sbin/dumpon/dumpon.8 @@ -28,7 +28,7 @@ .\" From: @(#)swapon.8 8.1 (Berkeley) 6/5/93 .\" $FreeBSD$ .\" -.Dd October 24, 2017 +.Dd February 13, 2018 .Dt DUMPON 8 .Os .Sh NAME @@ -39,6 +39,7 @@ .Op Fl v .Op Fl k Ar public_key_file .Op Fl z +.Op Fl Z .Ar special_file .Nm .Op Fl v @@ -116,8 +117,10 @@ kernel option. .Pp The .Fl z -option configures the kernel to compress the dump in gzip format before writing -it to the dump device. +and +.Fl Z +options configure the kernel to compress the dump before writing it to +the dump device. This reduces the amount of space required for the dump and accelerates recovery with .Xr savecore 8 @@ -126,9 +129,21 @@ When compression is enabled, the .Nm utility will not verify that the dump device is sufficiently large for a full dump. -This flag requires a kernel compiled with the +The +.Fl z +and +.Fl Z +options cause the dump to be written in +.Xr gzip 1 +and +.Xr zstd 1 +format, respectively. +These flags require a kernel compiled with the .Dv GZIO -kernel option. +or +.Dv ZSTDIO +kernel options. +.Pp .Pp The .Fl l @@ -269,15 +284,17 @@ The core was decrypted properly if .Xr kgdb 1 does not print any errors. .Sh SEE ALSO +.Xr gzip 1 , .Xr kgdb 1 , +.Xr zstd 1 , .Xr ddb 4 , .Xr fstab 5 , .Xr rc.conf 5 , .Xr config 8 , +.Xr decryptcore 8 , .Xr init 8 , .Xr loader 8 , .Xr rc 8 , -.Xr decryptcore 8 , .Xr savecore 8 , .Xr swapon 8 , .Xr panic 9 diff --git a/sbin/dumpon/dumpon.c b/sbin/dumpon/dumpon.c index 19fdb3f5d57..0ae95e6e184 100644 --- a/sbin/dumpon/dumpon.c +++ b/sbin/dumpon/dumpon.c @@ -73,7 +73,7 @@ static void usage(void) { fprintf(stderr, "%s\n%s\n%s\n", - "usage: dumpon [-v] [-k public_key_file] [-z] special_file", + "usage: dumpon [-v] [-k public_key_file] [-Zz] special_file", " dumpon [-v] off", " dumpon [-v] -l"); exit(EX_USAGE); @@ -192,12 +192,12 @@ main(int argc, char *argv[]) int ch; int i, fd; int do_listdumpdev = 0; - bool enable, gzip; + bool enable, gzip, zstd; - gzip = false; + gzip = zstd = false; pubkeyfile = NULL; - while ((ch = getopt(argc, argv, "k:lvz")) != -1) + while ((ch = getopt(argc, argv, "k:lvZz")) != -1) switch((char)ch) { case 'k': pubkeyfile = optarg; @@ -208,6 +208,9 @@ main(int argc, char *argv[]) case 'v': verbose = 1; break; + case 'Z': + zstd = true; + break; case 'z': gzip = true; break; @@ -215,6 +218,9 @@ main(int argc, char *argv[]) usage(); } + if (gzip && zstd) + errx(EX_USAGE, "The -z and -Z options are mutually exclusive."); + argc -= optind; argv += optind; @@ -254,7 +260,7 @@ main(int argc, char *argv[]) if (fd < 0) err(EX_OSFILE, "%s", dumpdev); - if (!gzip) + if (!gzip && !zstd) check_size(fd, dumpdev); bzero(&kda, sizeof(kda)); @@ -268,8 +274,11 @@ main(int argc, char *argv[]) #endif kda.kda_enable = 1; - kda.kda_compression = gzip ? KERNELDUMP_COMP_GZIP : - KERNELDUMP_COMP_NONE; + kda.kda_compression = KERNELDUMP_COMP_NONE; + if (zstd) + kda.kda_compression = KERNELDUMP_COMP_ZSTD; + else if (gzip) + kda.kda_compression = KERNELDUMP_COMP_GZIP; i = ioctl(fd, DIOCSKERNELDUMP, &kda); explicit_bzero(kda.kda_encryptedkey, kda.kda_encryptedkeysize); free(kda.kda_encryptedkey); diff --git a/sbin/savecore/savecore.c b/sbin/savecore/savecore.c index 14257421361..5414b191045 100644 --- a/sbin/savecore/savecore.c +++ b/sbin/savecore/savecore.c @@ -109,6 +109,7 @@ printheader(xo_handle_t *xo, const struct kerneldumpheader *h, uint64_t dumplen; time_t t; const char *stat_str; + const char *comp_str; xo_flush_h(xo); xo_emit_h(xo, "{Lwc:Dump header from device}{:dump_device/%s}\n", @@ -123,10 +124,21 @@ printheader(xo_handle_t *xo, const struct kerneldumpheader *h, (long long)dumplen); xo_emit_h(xo, "{P: }{Lwc:Blocksize}{:blocksize/%d}\n", dtoh32(h->blocksize)); - xo_emit_h(xo, "{P: }{Lwc:Compression}{:compression/%s}\n", - h->compression == KERNELDUMP_COMP_GZIP ? - "gzip" : "none"); - + switch (h->compression) { + case KERNELDUMP_COMP_NONE: + comp_str = "none"; + break; + case KERNELDUMP_COMP_GZIP: + comp_str = "gzip"; + break; + case KERNELDUMP_COMP_ZSTD: + comp_str = "zstd"; + break; + default: + comp_str = "???"; + break; + } + xo_emit_h(xo, "{P: }{Lwc:Compression}{:compression/%s}\n", comp_str); t = dtoh64(h->dumptime); xo_emit_h(xo, "{P: }{Lwc:Dumptime}{:dumptime/%s}", ctime(&t)); xo_emit_h(xo, "{P: }{Lwc:Hostname}{:hostname/%s}\n", h->hostname); @@ -249,6 +261,8 @@ saved_dump_size(int bounds) dumpsize += file_size(path); (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds); dumpsize += file_size(path); + (void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds); + dumpsize += file_size(path); (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds); dumpsize += file_size(path); (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); @@ -268,6 +282,8 @@ saved_dump_remove(int bounds) (void)unlink(path); (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds); (void)unlink(path); + (void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds); + (void)unlink(path); (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds); (void)unlink(path); (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); @@ -282,6 +298,7 @@ symlinks_remove(void) (void)unlink("key.last"); (void)unlink("vmcore.last"); (void)unlink("vmcore.last.gz"); + (void)unlink("vmcore.last.zstd"); (void)unlink("vmcore_encrypted.last"); (void)unlink("vmcore_encrypted.last.gz"); (void)unlink("textdump.tar.last"); @@ -615,6 +632,7 @@ DoFile(const char *savedir, const char *device) case KERNELDUMP_COMP_NONE: break; case KERNELDUMP_COMP_GZIP: + case KERNELDUMP_COMP_ZSTD: if (compress && verbose) printf("dump is already compressed\n"); compress = false; @@ -743,7 +761,8 @@ DoFile(const char *savedir, const char *device) (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); fp = zopen(corename, "w"); } else if (iscompressed && !isencrypted) { - snprintf(corename, sizeof(corename), "vmcore.%d.gz", bounds); + snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds, + (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst"); fp = fopen(corename, "w"); } else { snprintf(corename, sizeof(corename), "%s.%d", @@ -845,9 +864,10 @@ DoFile(const char *savedir, const char *device) } } if (compress || iscompressed) { - snprintf(linkname, sizeof(linkname), "%s.last.gz", + snprintf(linkname, sizeof(linkname), "%s.last.%s", istextdump ? "textdump.tar" : - (isencrypted ? "vmcore_encrypted" : "vmcore")); + (isencrypted ? "vmcore_encrypted" : "vmcore"), + (kdhl.compression == KERNELDUMP_COMP_ZSTD) ? "zst" : "gz"); } else { snprintf(linkname, sizeof(linkname), "%s.last", istextdump ? "textdump.tar" : diff --git a/share/man/man5/core.5 b/share/man/man5/core.5 index e050cf80d56..e8c747ecd22 100644 --- a/share/man/man5/core.5 +++ b/share/man/man5/core.5 @@ -28,7 +28,7 @@ .\" @(#)core.5 8.3 (Berkeley) 12/11/93 .\" $FreeBSD$ .\" -.Dd January 8, 2018 +.Dd February 13, 2018 .Dt CORE 5 .Os .Sh NAME @@ -112,10 +112,17 @@ The following sysctl control core file compression: .Bl -tag -width "kern.compress_user_cores_level" -compact -offset "12345" .It Em kern.compress_user_cores Enable compression of user cores. -A value of 1 configures gzip compression. -gzip-compressed core files will have a suffix of +A value of 1 configures +.Xr gzip 1 +compression, +and a value of 2 configures +.Xr zstd 1 +compression. +Compressed core files will have a suffix of .Ql .gz -appended to their filenames. +or +.Ql .zst +appended to their filenames depending on the selected format. .It Em kern.compress_user_cores_level Compression level. Defaults to 6. diff --git a/sys/conf/NOTES b/sys/conf/NOTES index 6c7386992f1..d3ca05b7aa4 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -3018,6 +3018,10 @@ options IMAGACT_BINMISC # This enables support for compressed core dumps. options GZIO +# zstd I/O stream support +# This enables support for Zstd compressed core dumps. +options ZSTDIO + # BHND(4) drivers options BHND_LOGLEVEL # Logging threshold level diff --git a/sys/conf/files b/sys/conf/files index 15cb532b3ec..4caf4bae44c 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -3836,7 +3836,8 @@ kern/subr_bus_dma.c standard kern/subr_bufring.c standard kern/subr_capability.c standard kern/subr_clock.c standard -kern/subr_compressor.c standard +kern/subr_compressor.c standard \ + compile-with "${NORMAL_C} -I$S/contrib/zstd/lib/freebsd" kern/subr_counter.c standard kern/subr_devstat.c standard kern/subr_disk.c standard diff --git a/sys/conf/options b/sys/conf/options index b784079e231..adef51b5067 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -222,6 +222,7 @@ TURNSTILE_PROFILING UMTX_PROFILING UMTX_CHAINS opt_global.h VERBOSE_SYSINIT +ZSTDIO opt_zstdio.h # POSIX kernel options P1003_1B_MQUEUE opt_posix.h diff --git a/sys/kern/kern_shutdown.c b/sys/kern/kern_shutdown.c index 0979b308128..aa60eecc5fa 100644 --- a/sys/kern/kern_shutdown.c +++ b/sys/kern/kern_shutdown.c @@ -174,6 +174,7 @@ struct kerneldumpcrypto { #endif struct kerneldumpcomp { + uint8_t kdc_format; struct compressor *kdc_stream; uint8_t *kdc_buf; size_t kdc_resid; @@ -987,12 +988,23 @@ static struct kerneldumpcomp * kerneldumpcomp_create(struct dumperinfo *di, uint8_t compression) { struct kerneldumpcomp *kdcomp; + int format; - if (compression != KERNELDUMP_COMP_GZIP) + switch (compression) { + case KERNELDUMP_COMP_GZIP: + format = COMPRESS_GZIP; + break; + case KERNELDUMP_COMP_ZSTD: + format = COMPRESS_ZSTD; + break; + default: return (NULL); + } + kdcomp = malloc(sizeof(*kdcomp), M_DUMPER, M_WAITOK | M_ZERO); + kdcomp->kdc_format = compression; kdcomp->kdc_stream = compressor_init(kerneldumpcomp_write_cb, - COMPRESS_GZIP, di->maxiosize, kerneldump_gzlevel, di); + format, di->maxiosize, kerneldump_gzlevel, di); if (kdcomp->kdc_stream == NULL) { free(kdcomp, M_DUMPER); return (NULL); @@ -1293,7 +1305,7 @@ dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh) * will occupy, so try to use the whole swap partition * (minus the first 64KB) in the hope that the * compressed dump will fit. If that doesn't turn out to - * be enouch, the bounds checking in dump_write() + * be enough, the bounds checking in dump_write() * will catch us and cause the dump to fail. */ dumpextent = di->mediasize - SIZEOF_METADATA - @@ -1463,7 +1475,7 @@ dump_init_header(const struct dumperinfo *di, struct kerneldumpheader *kdh, if (panicstr != NULL) strlcpy(kdh->panicstring, panicstr, sizeof(kdh->panicstring)); if (di->kdcomp != NULL) - kdh->compression = KERNELDUMP_COMP_GZIP; + kdh->compression = di->kdcomp->kdc_format; kdh->parity = kerneldump_parity(kdh); } diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c index 24c1cbdf161..a71de7a5848 100644 --- a/sys/kern/kern_sig.c +++ b/sys/kern/kern_sig.c @@ -3253,7 +3253,8 @@ 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", ""); -#define GZ_SUFFIX ".gz" +#define GZIP_SUFFIX ".gz" +#define ZSTD_SUFFIX ".zst" int compress_user_cores = 0; @@ -3273,7 +3274,9 @@ sysctl_compress_user_cores(SYSCTL_HANDLER_ARGS) } SYSCTL_PROC(_kern, OID_AUTO, compress_user_cores, CTLTYPE_INT | CTLFLAG_RWTUN, 0, sizeof(int), sysctl_compress_user_cores, "I", - "Enable compression of user corefiles (" __XSTRING(COMPRESS_GZIP) " = gzip)"); + "Enable compression of user corefiles (" + __XSTRING(COMPRESS_GZIP) " = gzip, " + __XSTRING(COMPRESS_ZSTD) " = zstd)"); int compress_user_cores_level = 6; SYSCTL_INT(_kern, OID_AUTO, compress_user_cores_level, CTLFLAG_RWTUN, @@ -3377,7 +3380,9 @@ corefile_open(const char *comm, uid_t uid, pid_t pid, struct thread *td, sx_sunlock(&corefilename_lock); free(hostname, M_TEMP); if (compress == COMPRESS_GZIP) - sbuf_printf(&sb, GZ_SUFFIX); + sbuf_printf(&sb, GZIP_SUFFIX); + else if (compress == COMPRESS_ZSTD) + sbuf_printf(&sb, ZSTD_SUFFIX); if (sbuf_error(&sb) != 0) { log(LOG_ERR, "pid %ld (%s), uid (%lu): corename is too " "long\n", (long)pid, comm, (u_long)uid); diff --git a/sys/kern/subr_compressor.c b/sys/kern/subr_compressor.c index 0ddf2727451..cc8c3e50452 100644 --- a/sys/kern/subr_compressor.c +++ b/sys/kern/subr_compressor.c @@ -2,6 +2,7 @@ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014, 2017 Mark Johnston + * Copyright (c) 2017 Conrad Meyer * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -34,8 +35,10 @@ __FBSDID("$FreeBSD$"); #include "opt_gzio.h" +#include "opt_zstdio.h" #include +#include #include #include @@ -245,6 +248,232 @@ DATA_SET(compressors, gzip_methods); #endif /* GZIO */ +#ifdef ZSTDIO + +#define ZSTD_STATIC_LINKING_ONLY +#include + +struct zstdio_stream { + ZSTD_CCtx *zst_stream; + ZSTD_inBuffer zst_inbuffer; + ZSTD_outBuffer zst_outbuffer; + uint8_t * zst_buffer; /* output buffer */ + size_t zst_maxiosz; /* Max output IO size */ + off_t zst_off; /* offset into the output stream */ + void * zst_static_wkspc; +}; + +static void *zstdio_init(size_t maxiosize, int level); +static void zstdio_reset(void *stream); +static int zstdio_write(void *stream, void *data, size_t len, + compressor_cb_t, void *); +static void zstdio_fini(void *stream); + +static void * +zstdio_init(size_t maxiosize, int level) +{ + ZSTD_CCtx *dump_compressor; + struct zstdio_stream *s; + void *wkspc, *owkspc, *buffer; + size_t wkspc_size, buf_size; + + wkspc_size = ZSTD_estimateCStreamSize(level); + owkspc = wkspc = malloc(wkspc_size + 8, M_COMPRESS, + M_WAITOK | M_NODUMP); + /* Zstd API requires 8-byte alignment. */ + if ((uintptr_t)wkspc % 8 != 0) + wkspc = (void *)roundup2((uintptr_t)wkspc, 8); + + dump_compressor = ZSTD_initStaticCCtx(wkspc, wkspc_size); + if (dump_compressor == NULL) { + free(owkspc, M_COMPRESS); + printf("%s: workspace too small.\n", __func__); + return (NULL); + } + + (void)ZSTD_CCtx_setParameter(dump_compressor, ZSTD_p_checksumFlag, 1); + + buf_size = ZSTD_CStreamOutSize() * 2; + buffer = malloc(buf_size, M_COMPRESS, M_WAITOK | M_NODUMP); + + s = malloc(sizeof(*s), M_COMPRESS, M_NODUMP | M_WAITOK); + s->zst_buffer = buffer; + s->zst_outbuffer.dst = buffer; + s->zst_outbuffer.size = buf_size; + s->zst_maxiosz = maxiosize; + s->zst_stream = dump_compressor; + s->zst_static_wkspc = owkspc; + + zstdio_reset(s); + + return (s); +} + +static void +zstdio_reset(void *stream) +{ + struct zstdio_stream *s; + size_t res; + + s = stream; + res = ZSTD_resetCStream(s->zst_stream, 0); + if (ZSTD_isError(res)) + panic("%s: could not reset stream %p: %s\n", __func__, s, + ZSTD_getErrorName(res)); + + s->zst_off = 0; + s->zst_inbuffer.src = NULL; + s->zst_inbuffer.size = 0; + s->zst_inbuffer.pos = 0; + s->zst_outbuffer.pos = 0; +} + +static int +zst_flush_intermediate(struct zstdio_stream *s, compressor_cb_t cb, void *arg) +{ + size_t bytes_to_dump; + int error; + + /* Flush as many full output blocks as possible. */ + /* XXX: 4096 is arbitrary safe HDD block size for kernel dumps */ + while (s->zst_outbuffer.pos >= 4096) { + bytes_to_dump = rounddown(s->zst_outbuffer.pos, 4096); + + if (bytes_to_dump > s->zst_maxiosz) + bytes_to_dump = s->zst_maxiosz; + + error = cb(s->zst_buffer, bytes_to_dump, s->zst_off, arg); + if (error != 0) + return (error); + + /* + * Shift any non-full blocks up to the front of the output + * buffer. + */ + s->zst_outbuffer.pos -= bytes_to_dump; + memmove(s->zst_outbuffer.dst, + (char *)s->zst_outbuffer.dst + bytes_to_dump, + s->zst_outbuffer.pos); + s->zst_off += bytes_to_dump; + } + return (0); +} + +static int +zstdio_flush(struct zstdio_stream *s, compressor_cb_t cb, void *arg) +{ + size_t rc, lastpos; + int error; + + /* + * Positive return indicates unflushed data remaining; need to call + * endStream again after clearing out room in output buffer. + */ + rc = 1; + lastpos = s->zst_outbuffer.pos; + while (rc > 0) { + rc = ZSTD_endStream(s->zst_stream, &s->zst_outbuffer); + if (ZSTD_isError(rc)) { + printf("%s: ZSTD_endStream failed (%s)\n", __func__, + ZSTD_getErrorName(rc)); + return (EIO); + } + if (lastpos == s->zst_outbuffer.pos) { + printf("%s: did not make forward progress endStream %zu\n", + __func__, lastpos); + return (EIO); + } + + error = zst_flush_intermediate(s, cb, arg); + if (error != 0) + return (error); + + lastpos = s->zst_outbuffer.pos; + } + + /* + * We've already done an intermediate flush, so all full blocks have + * been written. Only a partial block remains. Padding happens in a + * higher layer. + */ + if (s->zst_outbuffer.pos != 0) { + error = cb(s->zst_buffer, s->zst_outbuffer.pos, s->zst_off, + arg); + if (error != 0) + return (error); + } + + return (0); +} + +static int +zstdio_write(void *stream, void *data, size_t len, compressor_cb_t cb, + void *arg) +{ + struct zstdio_stream *s; + size_t lastpos, rc; + int error; + + s = stream; + if (data == NULL) + return (zstdio_flush(s, cb, arg)); + + s->zst_inbuffer.src = data; + s->zst_inbuffer.size = len; + s->zst_inbuffer.pos = 0; + lastpos = 0; + + while (s->zst_inbuffer.pos < s->zst_inbuffer.size) { + rc = ZSTD_compressStream(s->zst_stream, &s->zst_outbuffer, + &s->zst_inbuffer); + if (ZSTD_isError(rc)) { + printf("%s: Compress failed on %p! (%s)\n", + __func__, data, ZSTD_getErrorName(rc)); + return (EIO); + } + + if (lastpos == s->zst_inbuffer.pos) { + /* + * XXX: May need flushStream to make forward progress + */ + printf("ZSTD: did not make forward progress @pos %zu\n", + lastpos); + return (EIO); + } + lastpos = s->zst_inbuffer.pos; + + error = zst_flush_intermediate(s, cb, arg); + if (error != 0) + return (error); + } + return (0); +} + +static void +zstdio_fini(void *stream) +{ + struct zstdio_stream *s; + + s = stream; + if (s->zst_static_wkspc != NULL) + free(s->zst_static_wkspc, M_COMPRESS); + else + ZSTD_freeCCtx(s->zst_stream); + free(s->zst_buffer, M_COMPRESS); + free(s, M_COMPRESS); +} + +static struct compressor_methods zstd_methods = { + .format = COMPRESS_ZSTD, + .init = zstdio_init, + .reset = zstdio_reset, + .write = zstdio_write, + .fini = zstdio_fini, +}; +DATA_SET(compressors, zstd_methods); + +#endif /* ZSTDIO */ + bool compressor_avail(int format) { diff --git a/sys/sys/compressor.h b/sys/sys/compressor.h index 9407b950d5a..5406ef3c329 100644 --- a/sys/sys/compressor.h +++ b/sys/sys/compressor.h @@ -35,6 +35,7 @@ /* Supported formats. */ #define COMPRESS_GZIP 1 +#define COMPRESS_ZSTD 2 typedef int (*compressor_cb_t)(void *, size_t, off_t, void *); diff --git a/sys/sys/kerneldump.h b/sys/sys/kerneldump.h index 607b6848c80..9e2b8aaad02 100644 --- a/sys/sys/kerneldump.h +++ b/sys/sys/kerneldump.h @@ -59,6 +59,7 @@ #define KERNELDUMP_COMP_NONE 0 #define KERNELDUMP_COMP_GZIP 1 +#define KERNELDUMP_COMP_ZSTD 2 #define KERNELDUMP_ENC_NONE 0 #define KERNELDUMP_ENC_AES_256_CBC 1