aboutsummaryrefslogtreecommitdiff
path: root/sys/kern/kern_shutdown.c
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2017-10-25 00:51:00 +0000
committerMark Johnston <markj@FreeBSD.org>2017-10-25 00:51:00 +0000
commit64a16434d8dbd5432e2f1a49007e53a449d43830 (patch)
treee9cdb0a8780646580c9a0d35fd94332c6a2443ad /sys/kern/kern_shutdown.c
parent7b79d6d61ac82b21e2acc396a4ea66d0e92c79d9 (diff)
downloadsrc-64a16434d8dbd5432e2f1a49007e53a449d43830.tar.gz
src-64a16434d8dbd5432e2f1a49007e53a449d43830.zip
Add support for compressed kernel dumps.
When using a kernel built with the GZIO config option, dumpon -z can be used to configure gzip compression using the in-kernel copy of zlib. This is useful on systems with large amounts of RAM, which require a correspondingly large dump device. Recovery of compressed dumps is also faster since fewer bytes need to be copied from the dump device. Because we have no way of knowing the final size of a compressed dump until it is written, the kernel will always attempt to dump when compression is configured, regardless of the dump device size. If the dump is aborted because we run out of space, an error is reported on the console. savecore(8) is modified to handle compressed dumps and save them to vmcore.<index>.gz, as it does when given the -z option. A new rc.conf variable, dumpon_flags, is added. Its value is added to the boot-time dumpon(8) invocation that occurs when a dump device is configured in rc.conf. Reviewed by: cem (earlier version) Discussed with: def, rgrimes Relnotes: yes Sponsored by: Dell EMC Isilon Differential Revision: https://reviews.freebsd.org/D11723
Notes
Notes: svn path=/head/; revision=324965
Diffstat (limited to 'sys/kern/kern_shutdown.c')
-rw-r--r--sys/kern/kern_shutdown.c297
1 files changed, 260 insertions, 37 deletions
diff --git a/sys/kern/kern_shutdown.c b/sys/kern/kern_shutdown.c
index 36c275809add..c73df509249e 100644
--- a/sys/kern/kern_shutdown.c
+++ b/sys/kern/kern_shutdown.c
@@ -39,6 +39,7 @@ __FBSDID("$FreeBSD$");
#include "opt_ddb.h"
#include "opt_ekcd.h"
+#include "opt_gzio.h"
#include "opt_kdb.h"
#include "opt_panic.h"
#include "opt_sched.h"
@@ -52,6 +53,7 @@ __FBSDID("$FreeBSD$");
#include <sys/cons.h>
#include <sys/eventhandler.h>
#include <sys/filedesc.h>
+#include <sys/gzio.h>
#include <sys/jail.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
@@ -162,6 +164,24 @@ struct kerneldumpcrypto {
};
#endif
+#ifdef GZIO
+struct kerneldumpgz {
+ struct gzio_stream *kdgz_stream;
+ uint8_t *kdgz_buf;
+ size_t kdgz_resid;
+};
+
+static struct kerneldumpgz *kerneldumpgz_create(struct dumperinfo *di,
+ uint8_t compression);
+static void kerneldumpgz_destroy(struct dumperinfo *di);
+static int kerneldumpgz_write_cb(void *cb, size_t len, off_t off, void *arg);
+
+static int kerneldump_gzlevel = 6;
+SYSCTL_INT(_kern, OID_AUTO, kerneldump_gzlevel, CTLFLAG_RWTUN,
+ &kerneldump_gzlevel, 0,
+ "Kernel crash dump gzip compression level");
+#endif /* GZIO */
+
/*
* Variable panicstr contains argument to first call to panic; used as flag
* to indicate that the kernel has already called panic.
@@ -857,6 +877,9 @@ static char dumpdevname[sizeof(((struct cdev*)NULL)->si_name)];
SYSCTL_STRING(_kern_shutdown, OID_AUTO, dumpdevname, CTLFLAG_RD,
dumpdevname, 0, "Device for kernel dumps");
+static int _dump_append(struct dumperinfo *di, void *virtual,
+ vm_offset_t physical, size_t length);
+
#ifdef EKCD
static struct kerneldumpcrypto *
kerneldumpcrypto_create(size_t blocksize, uint8_t encryption,
@@ -947,11 +970,45 @@ kerneldumpcrypto_dumpkeysize(const struct kerneldumpcrypto *kdc)
}
#endif /* EKCD */
+#ifdef GZIO
+static struct kerneldumpgz *
+kerneldumpgz_create(struct dumperinfo *di, uint8_t compression)
+{
+ struct kerneldumpgz *kdgz;
+
+ if (compression != KERNELDUMP_COMP_GZIP)
+ return (NULL);
+ kdgz = malloc(sizeof(*kdgz), M_DUMPER, M_WAITOK | M_ZERO);
+ kdgz->kdgz_stream = gzio_init(kerneldumpgz_write_cb, GZIO_DEFLATE,
+ di->maxiosize, kerneldump_gzlevel, di);
+ if (kdgz->kdgz_stream == NULL) {
+ free(kdgz, M_DUMPER);
+ return (NULL);
+ }
+ kdgz->kdgz_buf = malloc(di->maxiosize, M_DUMPER, M_WAITOK | M_NODUMP);
+ return (kdgz);
+}
+
+static void
+kerneldumpgz_destroy(struct dumperinfo *di)
+{
+ struct kerneldumpgz *kdgz;
+
+ kdgz = di->kdgz;
+ if (kdgz == NULL)
+ return;
+ gzio_fini(kdgz->kdgz_stream);
+ explicit_bzero(kdgz->kdgz_buf, di->maxiosize);
+ free(kdgz->kdgz_buf, M_DUMPER);
+ free(kdgz, M_DUMPER);
+}
+#endif /* GZIO */
+
/* Registration of dumpers */
int
set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
- uint8_t encryption, const uint8_t *key, uint32_t encryptedkeysize,
- const uint8_t *encryptedkey)
+ uint8_t compression, uint8_t encryption, const uint8_t *key,
+ uint32_t encryptedkeysize, const uint8_t *encryptedkey)
{
size_t wantcopy;
int error;
@@ -969,6 +1026,7 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
dumper = *di;
dumper.blockbuf = NULL;
dumper.kdc = NULL;
+ dumper.kdgz = NULL;
if (encryption != KERNELDUMP_ENC_NONE) {
#ifdef EKCD
@@ -987,7 +1045,28 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td,
wantcopy = strlcpy(dumpdevname, devname, sizeof(dumpdevname));
if (wantcopy >= sizeof(dumpdevname)) {
printf("set_dumper: device name truncated from '%s' -> '%s'\n",
- devname, dumpdevname);
+ devname, dumpdevname);
+ }
+
+ if (compression != KERNELDUMP_COMP_NONE) {
+#ifdef GZIO
+ /*
+ * We currently can't support simultaneous encryption and
+ * compression.
+ */
+ if (encryption != KERNELDUMP_ENC_NONE) {
+ error = EOPNOTSUPP;
+ goto cleanup;
+ }
+ dumper.kdgz = kerneldumpgz_create(&dumper, compression);
+ if (dumper.kdgz == NULL) {
+ error = EINVAL;
+ goto cleanup;
+ }
+#else
+ error = EOPNOTSUPP;
+ goto cleanup;
+#endif
}
dumper.blockbuf = malloc(di->blocksize, M_DUMPER, M_WAITOK | M_ZERO);
@@ -1000,6 +1079,11 @@ cleanup:
free(dumper.kdc, M_EKCD);
}
#endif
+
+#ifdef GZIO
+ kerneldumpgz_destroy(&dumper);
+#endif
+
if (dumper.blockbuf != NULL) {
explicit_bzero(dumper.blockbuf, dumper.blocksize);
free(dumper.blockbuf, M_DUMPER);
@@ -1090,22 +1174,57 @@ dump_encrypted_write(struct dumperinfo *di, void *virtual,
}
static int
-dump_write_key(struct dumperinfo *di, vm_offset_t physical, off_t offset)
+dump_write_key(struct dumperinfo *di, off_t offset)
{
struct kerneldumpcrypto *kdc;
kdc = di->kdc;
if (kdc == NULL)
return (0);
-
- return (dump_write(di, kdc->kdc_dumpkey, physical, offset,
+ return (dump_write(di, kdc->kdc_dumpkey, 0, offset,
kdc->kdc_dumpkeysize));
}
#endif /* EKCD */
+#ifdef GZIO
+static int
+kerneldumpgz_write_cb(void *base, size_t length, off_t offset, void *arg)
+{
+ struct dumperinfo *di;
+ size_t resid, rlength;
+ int error;
+
+ di = arg;
+
+ if (length % di->blocksize != 0) {
+ /*
+ * This must be the final write after flushing the compression
+ * stream. Write as many full blocks as possible and stash the
+ * residual data in the dumper's block buffer. It will be
+ * padded and written in dump_finish().
+ */
+ rlength = rounddown(length, di->blocksize);
+ if (rlength != 0) {
+ error = _dump_append(di, base, 0, rlength);
+ if (error != 0)
+ return (error);
+ }
+ resid = length - rlength;
+ memmove(di->blockbuf, (uint8_t *)base + rlength, resid);
+ di->kdgz->kdgz_resid = resid;
+ return (EAGAIN);
+ }
+ return (_dump_append(di, base, 0, length));
+}
+#endif /* GZIO */
+
+/*
+ * Write a kerneldumpheader at the specified offset. The header structure is 512
+ * bytes in size, but we must pad to the device sector size.
+ */
static int
dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh,
- vm_offset_t physical, off_t offset)
+ off_t offset)
{
void *buf;
size_t hdrsz;
@@ -1122,7 +1241,7 @@ dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh,
memcpy(buf, kdh, hdrsz);
}
- return (dump_write(di, buf, physical, offset, di->blocksize));
+ return (dump_write(di, buf, 0, offset, di->blocksize));
}
/*
@@ -1132,19 +1251,30 @@ dump_write_header(struct dumperinfo *di, struct kerneldumpheader *kdh,
#define SIZEOF_METADATA (64 * 1024)
/*
- * Do some preliminary setup for a kernel dump: verify that we have enough space
- * on the dump device, write the leading header, and optionally write the crypto
- * key.
+ * Do some preliminary setup for a kernel dump: initialize state for encryption,
+ * if requested, and make sure that we have enough space on the dump device.
+ *
+ * We set things up so that the dump ends before the last sector of the dump
+ * device, at which the trailing header is written.
+ *
+ * +-----------+------+-----+----------------------------+------+
+ * | | lhdr | key | ... kernel dump ... | thdr |
+ * +-----------+------+-----+----------------------------+------+
+ * 1 blk opt <------- dump extent --------> 1 blk
+ *
+ * Dumps written using dump_append() start at the beginning of the extent.
+ * Uncompressed dumps will use the entire extent, but compressed dumps typically
+ * will not. The true length of the dump is recorded in the leading and trailing
+ * headers once the dump has been completed.
*/
int
dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh)
{
- uint64_t dumpsize;
+ uint64_t dumpextent;
uint32_t keysize;
- int error;
#ifdef EKCD
- error = kerneldumpcrypto_init(di->kdc);
+ int error = kerneldumpcrypto_init(di->kdc);
if (error != 0)
return (error);
keysize = kerneldumpcrypto_dumpkeysize(di->kdc);
@@ -1152,30 +1282,36 @@ dump_start(struct dumperinfo *di, struct kerneldumpheader *kdh)
keysize = 0;
#endif
- dumpsize = dtoh64(kdh->dumplength) + 2 * di->blocksize + keysize;
- if (di->mediasize < SIZEOF_METADATA + dumpsize)
- return (E2BIG);
-
- di->dumpoff = di->mediaoffset + di->mediasize - dumpsize;
-
- error = dump_write_header(di, kdh, 0, di->dumpoff);
- if (error != 0)
- return (error);
- di->dumpoff += di->blocksize;
-
-#ifdef EKCD
- error = dump_write_key(di, 0, di->dumpoff);
- if (error != 0)
- return (error);
- di->dumpoff += keysize;
+ dumpextent = dtoh64(kdh->dumpextent);
+ if (di->mediasize < SIZEOF_METADATA + dumpextent + 2 * di->blocksize +
+ keysize) {
+#ifdef GZIO
+ if (di->kdgz != NULL) {
+ /*
+ * 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) in the hope that the
+ * compressed dump will fit. If that doesn't turn out to
+ * be enouch, the bounds checking in dump_write()
+ * will catch us and cause the dump to fail.
+ */
+ dumpextent = di->mediasize - SIZEOF_METADATA -
+ 2 * di->blocksize - keysize;
+ kdh->dumpextent = htod64(dumpextent);
+ } else
#endif
+ return (E2BIG);
+ }
+
+ /* The offset at which to begin writing the dump. */
+ di->dumpoff = di->mediaoffset + di->mediasize - di->blocksize -
+ dumpextent;
return (0);
}
-/* Write to the dump device at the current dump offset. */
-int
-dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical,
+static int
+_dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical,
size_t length)
{
int error;
@@ -1192,7 +1328,33 @@ dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical,
return (error);
}
-/* Perform a raw write to the dump device at the specified offset. */
+/*
+ * Write to the dump device starting at dumpoff. When compression is enabled,
+ * writes to the device will be performed using a callback that gets invoked
+ * when the compression stream's output buffer is full.
+ */
+int
+dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical,
+ size_t length)
+{
+#ifdef GZIO
+ void *buf;
+
+ if (di->kdgz != NULL) {
+ /* Bounce through a buffer to avoid gzip CRC errors. */
+ if (length > di->maxiosize)
+ return (EINVAL);
+ buf = di->kdgz->kdgz_buf;
+ memmove(buf, virtual, length);
+ return (gzio_write(di->kdgz->kdgz_stream, buf, length));
+ }
+#endif
+ return (_dump_append(di, virtual, physical, length));
+}
+
+/*
+ * Write to the dump device at the specified offset.
+ */
int
dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical,
off_t offset, size_t length)
@@ -1206,15 +1368,71 @@ dump_write(struct dumperinfo *di, void *virtual, vm_offset_t physical,
}
/*
- * Write the trailing kernel dump header and signal to the lower layers that the
- * dump has completed.
+ * Perform kernel dump finalization: flush the compression stream, if necessary,
+ * write the leading and trailing kernel dump headers now that we know the true
+ * length of the dump, and optionally write the encryption key following the
+ * leading header.
*/
int
dump_finish(struct dumperinfo *di, struct kerneldumpheader *kdh)
{
+ uint64_t extent;
+ uint32_t keysize;
int error;
- error = dump_write_header(di, kdh, 0, di->dumpoff);
+ extent = dtoh64(kdh->dumpextent);
+
+#ifdef EKCD
+ keysize = kerneldumpcrypto_dumpkeysize(di->kdc);
+#else
+ keysize = 0;
+#endif
+
+#ifdef GZIO
+ if (di->kdgz != NULL) {
+ error = gzio_flush(di->kdgz->kdgz_stream);
+ if (error == EAGAIN) {
+ /* We have residual data in di->blockbuf. */
+ error = dump_write(di, di->blockbuf, 0, di->dumpoff,
+ di->blocksize);
+ di->dumpoff += di->kdgz->kdgz_resid;
+ di->kdgz->kdgz_resid = 0;
+ }
+ if (error != 0)
+ return (error);
+
+ /*
+ * We now know the size of the compressed dump, so update the
+ * header accordingly and recompute parity.
+ */
+ kdh->dumplength = htod64(di->dumpoff -
+ (di->mediaoffset + di->mediasize - di->blocksize - extent));
+ kdh->parity = 0;
+ kdh->parity = kerneldump_parity(kdh);
+
+ gzio_reset(di->kdgz->kdgz_stream);
+ }
+#endif
+
+ /*
+ * Write kerneldump headers at the beginning and end of the dump extent.
+ * Write the key after the leading header.
+ */
+ error = dump_write_header(di, kdh,
+ di->mediaoffset + di->mediasize - 2 * di->blocksize - extent -
+ keysize);
+ if (error != 0)
+ return (error);
+
+#ifdef EKCD
+ error = dump_write_key(di,
+ di->mediaoffset + di->mediasize - di->blocksize - extent - keysize);
+ if (error != 0)
+ return (error);
+#endif
+
+ error = dump_write_header(di, kdh,
+ di->mediaoffset + di->mediasize - di->blocksize);
if (error != 0)
return (error);
@@ -1234,6 +1452,7 @@ dump_init_header(const struct dumperinfo *di, struct kerneldumpheader *kdh,
kdh->version = htod32(KERNELDUMPVERSION);
kdh->architectureversion = htod32(archver);
kdh->dumplength = htod64(dumplen);
+ kdh->dumpextent = kdh->dumplength;
kdh->dumptime = htod64(time_second);
#ifdef EKCD
kdh->dumpkeysize = htod32(kerneldumpcrypto_dumpkeysize(di->kdc));
@@ -1247,6 +1466,10 @@ dump_init_header(const struct dumperinfo *di, struct kerneldumpheader *kdh,
kdh->versionstring[dstsize - 2] = '\n';
if (panicstr != NULL)
strlcpy(kdh->panicstring, panicstr, sizeof(kdh->panicstring));
+#ifdef GZIO
+ if (di->kdgz != NULL)
+ kdh->compression = KERNELDUMP_COMP_GZIP;
+#endif
kdh->parity = kerneldump_parity(kdh);
}