Index: head/sbin/savecore/Makefile =================================================================== --- head/sbin/savecore/Makefile +++ head/sbin/savecore/Makefile @@ -6,8 +6,10 @@ VAR_CRASH_MODE= 0750 CONFSDIR= VAR_CRASH PROG= savecore -LIBADD= xo z +LIBADD= xo z zstd MAN= savecore.8 + +CFLAGS+= -I${SRCTOP}/sys/contrib/zstd/lib .include Index: head/sbin/savecore/savecore.8 =================================================================== --- head/sbin/savecore/savecore.8 +++ head/sbin/savecore/savecore.8 @@ -28,7 +28,7 @@ .\" From: @(#)savecore.8 8.1 (Berkeley) 6/5/93 .\" $FreeBSD$ .\" -.Dd March 17, 2018 +.Dd November 17, 2020 .Dt SAVECORE 8 .Os .Sh NAME @@ -45,7 +45,7 @@ .Op Ar device ... .Nm .Op Fl -libxo -.Op Fl fkvz +.Op Fl fkuvz .Op Fl m Ar maxdumps .Op Ar directory Op Ar device ... .Sh DESCRIPTION @@ -92,6 +92,8 @@ .Ar maxdumps the counter will restart from .Dv 0 . +.It Fl u +Uncompress the dump in case it was compressed by the kernel. .It Fl v Print out some additional debugging information. Specify twice for more information. Index: head/sbin/savecore/savecore.c =================================================================== --- head/sbin/savecore/savecore.c +++ head/sbin/savecore/savecore.c @@ -86,6 +86,9 @@ #include #include #include +#define Z_SOLO +#include +#include #include #include @@ -102,7 +105,7 @@ static cap_channel_t *capsyslog; static fileargs_t *capfa; -static bool checkfor, compress, clear, force, keep; /* flags */ +static bool checkfor, compress, uncompress, clear, force, keep; /* flags */ static int verbose; static int nfound, nsaved, nerr; /* statistics */ static int maxdumps; @@ -441,22 +444,155 @@ #define BLOCKSIZE (1<<12) #define BLOCKMASK (~(BLOCKSIZE-1)) +static size_t +sparsefwrite(const char *buf, size_t nr, FILE *fp) +{ + size_t nw, he, hs; + + for (nw = 0; nw < nr; nw = he) { + /* find a contiguous block of zeroes */ + for (hs = nw; hs < nr; hs += BLOCKSIZE) { + for (he = hs; he < nr && buf[he] == 0; ++he) + /* nothing */ ; + /* is the hole long enough to matter? */ + if (he >= hs + BLOCKSIZE) + break; + } + + /* back down to a block boundary */ + he &= BLOCKMASK; + + /* + * 1) Don't go beyond the end of the buffer. + * 2) If the end of the buffer is less than + * BLOCKSIZE bytes away, we're at the end + * of the file, so just grab what's left. + */ + if (hs + BLOCKSIZE > nr) + hs = he = nr; + + /* + * At this point, we have a partial ordering: + * nw <= hs <= he <= nr + * If hs > nw, buf[nw..hs] contains non-zero + * data. If he > hs, buf[hs..he] is all zeroes. + */ + if (hs > nw) + if (fwrite(buf + nw, hs - nw, 1, fp) != 1) + break; + if (he > hs) + if (fseeko(fp, he - hs, SEEK_CUR) == -1) + break; + } + + return (nw); +} + +static char *zbuf; +static size_t zbufsize; + +static size_t +GunzipWrite(z_stream *z, char *in, size_t insize, FILE *fp) +{ + static bool firstblock = true; /* XXX not re-entrable/usable */ + const size_t hdrlen = 10; + size_t nw = 0; + int rv; + + z->next_in = in; + z->avail_in = insize; + /* + * Since contrib/zlib for some reason is compiled + * without GUNZIP define, we need to skip the gzip + * header manually. Kernel puts minimal 10 byte + * header, see sys/kern/subr_compressor.c:gz_reset(). + */ + if (firstblock) { + z->next_in += hdrlen; + z->avail_in -= hdrlen; + firstblock = false; + } + do { + z->next_out = zbuf; + z->avail_out = zbufsize; + rv = inflate(z, Z_NO_FLUSH); + if (rv != Z_OK && rv != Z_STREAM_END) { + logmsg(LOG_ERR, "decompression failed: %s", z->msg); + return (-1); + } + nw += sparsefwrite(zbuf, zbufsize - z->avail_out, fp); + } while (z->avail_in > 0 && rv != Z_STREAM_END); + + return (nw); +} + +static size_t +ZstdWrite(ZSTD_DCtx *Zctx, char *in, size_t insize, FILE *fp) +{ + ZSTD_inBuffer Zin; + ZSTD_outBuffer Zout; + size_t nw = 0; + int rv; + + Zin.src = in; + Zin.size = insize; + Zin.pos = 0; + do { + Zout.dst = zbuf; + Zout.size = zbufsize; + Zout.pos = 0; + rv = ZSTD_decompressStream(Zctx, &Zout, &Zin); + if (ZSTD_isError(rv)) { + logmsg(LOG_ERR, "decompression failed: %s", + ZSTD_getErrorName(rv)); + return (-1); + } + nw += sparsefwrite(zbuf, Zout.pos, fp); + } while (Zin.pos < Zin.size && rv != 0); + + return (nw); +} + static int -DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse, char *buf, - const char *device, const char *filename, FILE *fp) +DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse, + uint8_t compression, char *buf, const char *device, + const char *filename, FILE *fp) { - int he, hs, nr, nw, wl; + size_t nr, nw, wl; off_t dmpcnt, origsize; + z_stream z; /* gzip */ + ZSTD_DCtx *Zctx; /* zstd */ dmpcnt = 0; origsize = dumpsize; - he = 0; + if (compression == KERNELDUMP_COMP_GZIP) { + memset(&z, 0, sizeof(z)); + z.zalloc = Z_NULL; + z.zfree = Z_NULL; + if (inflateInit2(&z, -MAX_WBITS) != Z_OK) { + logmsg(LOG_ERR, "failed to initialize zlib: %s", z.msg); + return (-1); + } + zbufsize = BUFFERSIZE; + } else if (compression == KERNELDUMP_COMP_ZSTD) { + if ((Zctx = ZSTD_createDCtx()) == NULL) { + logmsg(LOG_ERR, "failed to initialize zstd"); + return (-1); + } + zbufsize = ZSTD_DStreamOutSize(); + } + if (zbufsize > 0) + if ((zbuf = malloc(zbufsize)) == NULL) { + logmsg(LOG_ERR, "failed to alloc decompression buffer"); + return (-1); + } + while (dumpsize > 0) { wl = BUFFERSIZE; - if (wl > dumpsize) + if (wl > (size_t)dumpsize) wl = dumpsize; nr = read(fd, buf, roundup(wl, sectorsize)); - if (nr != (int)roundup(wl, sectorsize)) { + if (nr != roundup(wl, sectorsize)) { if (nr == 0) logmsg(LOG_WARNING, "WARNING: EOF on dump device"); @@ -465,48 +601,16 @@ nerr++; return (-1); } - if (!sparse) { + if (compression == KERNELDUMP_COMP_GZIP) + nw = GunzipWrite(&z, buf, nr, fp); + else if (compression == KERNELDUMP_COMP_ZSTD) + nw = ZstdWrite(Zctx, buf, nr, fp); + else if (!sparse) nw = fwrite(buf, 1, wl, fp); - } else { - for (nw = 0; nw < nr; nw = he) { - /* find a contiguous block of zeroes */ - for (hs = nw; hs < nr; hs += BLOCKSIZE) { - for (he = hs; he < nr && buf[he] == 0; - ++he) - /* nothing */ ; - /* is the hole long enough to matter? */ - if (he >= hs + BLOCKSIZE) - break; - } - - /* back down to a block boundary */ - he &= BLOCKMASK; - - /* - * 1) Don't go beyond the end of the buffer. - * 2) If the end of the buffer is less than - * BLOCKSIZE bytes away, we're at the end - * of the file, so just grab what's left. - */ - if (hs + BLOCKSIZE > nr) - hs = he = nr; - - /* - * At this point, we have a partial ordering: - * nw <= hs <= he <= nr - * If hs > nw, buf[nw..hs] contains non-zero - * data. If he > hs, buf[hs..he] is all zeroes. - */ - if (hs > nw) - if (fwrite(buf + nw, hs - nw, 1, fp) - != 1) - break; - if (he > hs) - if (fseeko(fp, he - hs, SEEK_CUR) == -1) - break; - } - } - if (nw != wl) { + else + nw = sparsefwrite(buf, wl, fp); + if ((compression == KERNELDUMP_COMP_NONE && nw != wl) || + (compression != KERNELDUMP_COMP_NONE && nw < 0)) { logmsg(LOG_ERR, "write error on %s file: %m", filename); logmsg(LOG_WARNING, @@ -692,11 +796,14 @@ } switch (kdhl.compression) { case KERNELDUMP_COMP_NONE: + uncompress = false; break; case KERNELDUMP_COMP_GZIP: case KERNELDUMP_COMP_ZSTD: if (compress && verbose) printf("dump is already compressed\n"); + if (uncompress && verbose) + printf("dump to be uncompressed\n"); compress = false; iscompressed = true; break; @@ -820,7 +927,7 @@ snprintf(corename, sizeof(corename), "%s.%d.gz", istextdump ? "textdump.tar" : (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); - else if (iscompressed && !isencrypted) + else if (iscompressed && !isencrypted && !uncompress) snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds, (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst"); else @@ -901,8 +1008,9 @@ goto closeall; } else { if (DoRegularFile(fddev, dumplength, sectorsize, - !(compress || iscompressed || isencrypted), buf, device, - corename, core) < 0) { + !(compress || iscompressed || isencrypted), + uncompress ? kdhl.compression : KERNELDUMP_COMP_NONE, + buf, device, corename, core) < 0) { goto closeall; } } @@ -927,7 +1035,7 @@ "key.last"); } } - if (compress || iscompressed) { + if ((iscompressed && !uncompress) || compress) { snprintf(linkname, sizeof(linkname), "%s.last.%s", istextdump ? "textdump.tar" : (isencrypted ? "vmcore_encrypted" : "vmcore"), @@ -1123,7 +1231,7 @@ if (argc < 0) exit(1); - while ((ch = getopt(argc, argv, "Ccfkm:vz")) != -1) + while ((ch = getopt(argc, argv, "Ccfkm:uvz")) != -1) switch(ch) { case 'C': checkfor = true; @@ -1144,6 +1252,9 @@ exit(1); } break; + case 'u': + uncompress = true; + break; case 'v': verbose++; break; @@ -1159,6 +1270,8 @@ if (clear && (compress || keep)) usage(); if (maxdumps > 0 && (checkfor || clear)) + usage(); + if (compress && uncompress) usage(); argc -= optind; argv += optind;