diff options
author | Conrad Meyer <cem@FreeBSD.org> | 2019-05-06 18:24:07 +0000 |
---|---|---|
committer | Conrad Meyer <cem@FreeBSD.org> | 2019-05-06 18:24:07 +0000 |
commit | 6b6e2954dd65ec08e73efb0f2e8bfb2278a07dc6 (patch) | |
tree | 0a05af7fff614a4db1f169f9b92918086e7d676f /sys/kern/kern_shutdown.c | |
parent | 46068e86c3d22eb492cf9de0cb3e43e58be4422f (diff) | |
download | src-6b6e2954dd65ec08e73efb0f2e8bfb2278a07dc6.tar.gz src-6b6e2954dd65ec08e73efb0f2e8bfb2278a07dc6.zip |
List-ify kernel dump device configuration
Allow users to specify multiple dump configurations in a prioritized list.
This enables fallback to secondary device(s) if primary dump fails. E.g.,
one might configure a preference for netdump, but fallback to disk dump as a
second choice if netdump is unavailable.
This change does not list-ify netdump configuration, which is tracked
separately from ordinary disk dumps internally; only one netdump
configuration can be made at a time, for now. It also does not implement
IPv6 netdump.
savecore(8) is already capable of scanning and iterating multiple devices
from /etc/fstab or passed on the command line.
This change doesn't update the rc or loader variables 'dumpdev' in any way;
it can still be set to configure a single dump device, and rc.d/savecore
still uses it as a single device. Only dumpon(8) is updated to be able to
configure the more complicated configurations for now.
As part of revving the ABI, unify netdump and disk dump configuration ioctl
/ structure, and leave room for ipv6 netdump as a future possibility.
Backwards-compatibility ioctls are added to smooth ABI transition,
especially for developers who may not keep kernel and userspace perfectly
synced.
Reviewed by: markj, scottl (earlier version)
Relnotes: maybe
Sponsored by: Dell EMC Isilon
Differential Revision: https://reviews.freebsd.org/D19996
Notes
Notes:
svn path=/head/; revision=347192
Diffstat (limited to 'sys/kern/kern_shutdown.c')
-rw-r--r-- | sys/kern/kern_shutdown.c | 246 |
1 files changed, 189 insertions, 57 deletions
diff --git a/sys/kern/kern_shutdown.c b/sys/kern/kern_shutdown.c index 2ac99440e783..f2e98144e38b 100644 --- a/sys/kern/kern_shutdown.c +++ b/sys/kern/kern_shutdown.c @@ -43,6 +43,7 @@ __FBSDID("$FreeBSD$"); #include "opt_ekcd.h" #include "opt_kdb.h" #include "opt_panic.h" +#include "opt_printf.h" #include "opt_sched.h" #include "opt_watchdog.h" @@ -53,6 +54,7 @@ __FBSDID("$FreeBSD$"); #include <sys/conf.h> #include <sys/compressor.h> #include <sys/cons.h> +#include <sys/disk.h> #include <sys/eventhandler.h> #include <sys/filedesc.h> #include <sys/jail.h> @@ -69,6 +71,7 @@ __FBSDID("$FreeBSD$"); #include <sys/reboot.h> #include <sys/resourcevar.h> #include <sys/rwlock.h> +#include <sys/sbuf.h> #include <sys/sched.h> #include <sys/smp.h> #include <sys/sysctl.h> @@ -209,7 +212,16 @@ const char *panicstr; int dumping; /* system is dumping */ int rebooting; /* system is rebooting */ -static struct dumperinfo dumper; /* our selected dumper */ +/* + * Used to serialize between sysctl kern.shutdown.dumpdevname and list + * modifications via ioctl. + */ +static struct mtx dumpconf_list_lk; +MTX_SYSINIT(dumper_configs, &dumpconf_list_lk, "dumper config list", MTX_DEF); + +/* Our selected dumper(s). */ +static TAILQ_HEAD(dumpconflist, dumperinfo) dumper_configs = + TAILQ_HEAD_INITIALIZER(dumper_configs); /* Context information for dump-debuggers. */ static struct pcb dumppcb; /* Registers. */ @@ -364,7 +376,7 @@ doadump(boolean_t textdump) error = 0; if (dumping) return (EBUSY); - if (dumper.dumper == NULL) + if (TAILQ_EMPTY(&dumper_configs)) return (ENXIO); savectx(&dumppcb); @@ -375,11 +387,18 @@ doadump(boolean_t textdump) #ifdef DDB if (textdump && textdump_pending) { coredump = FALSE; - textdump_dumpsys(&dumper); + textdump_dumpsys(TAILQ_FIRST(&dumper_configs)); } #endif - if (coredump) - error = dumpsys(&dumper); + if (coredump) { + struct dumperinfo *di; + + TAILQ_FOREACH(di, &dumper_configs, di_next) { + error = dumpsys(di); + if (error == 0) + break; + } + } dumping--; return (error); @@ -952,9 +971,35 @@ kthread_shutdown(void *arg, int howto) printf("done\n"); } -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 +dumpdevname_sysctl_handler(SYSCTL_HANDLER_ARGS) +{ + char buf[256]; + struct dumperinfo *di; + struct sbuf sb; + int error; + + error = sysctl_wire_old_buffer(req, 0); + if (error != 0) + return (error); + + sbuf_new_for_sysctl(&sb, buf, sizeof(buf), req); + + mtx_lock(&dumpconf_list_lk); + TAILQ_FOREACH(di, &dumper_configs, di_next) { + if (di != TAILQ_FIRST(&dumper_configs)) + sbuf_putc(&sb, ','); + sbuf_cat(&sb, di->di_devname); + } + mtx_unlock(&dumpconf_list_lk); + + error = sbuf_finish(&sb); + sbuf_delete(&sb); + return (error); +} +SYSCTL_PROC(_kern_shutdown, OID_AUTO, dumpdevname, CTLTYPE_STRING | CTLFLAG_RD, + &dumper_configs, 0, dumpdevname_sysctl_handler, "A", + "Device(s) for kernel dumps"); static int _dump_append(struct dumperinfo *di, void *virtual, vm_offset_t physical, size_t length); @@ -1092,31 +1137,67 @@ kerneldumpcomp_destroy(struct dumperinfo *di) free(kdcomp, M_DUMPER); } +/* + * Must not be present on global list. + */ +static void +free_single_dumper(struct dumperinfo *di) +{ + + if (di == NULL) + return; + + if (di->blockbuf != NULL) { + explicit_bzero(di->blockbuf, di->blocksize); + free(di->blockbuf, M_DUMPER); + } + + kerneldumpcomp_destroy(di); + +#ifdef EKCD + if (di->kdcrypto != NULL) { + explicit_bzero(di->kdcrypto, sizeof(*di->kdcrypto) + + di->kdcrypto->kdc_dumpkeysize); + free(di->kdcrypto, M_EKCD); + } +#endif + + explicit_bzero(di, sizeof(*di)); + free(di, M_DUMPER); +} + /* Registration of dumpers */ int -set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, - uint8_t compression, uint8_t encryption, const uint8_t *key, - uint32_t encryptedkeysize, const uint8_t *encryptedkey) +dumper_insert(const struct dumperinfo *di_template, const char *devname, + const struct diocskerneldump_arg *kda) { - size_t wantcopy; + struct dumperinfo *newdi, *listdi; + bool inserted; + uint8_t index; int error; - error = priv_check(td, PRIV_SETDUMPER); + index = kda->kda_index; + MPASS(index != KDA_REMOVE && index != KDA_REMOVE_DEV && + index != KDA_REMOVE_ALL); + + error = priv_check(curthread, PRIV_SETDUMPER); if (error != 0) return (error); - if (dumper.dumper != NULL) - return (EBUSY); - dumper = *di; - dumper.blockbuf = NULL; - dumper.kdcrypto = NULL; - dumper.kdcomp = NULL; + newdi = malloc(sizeof(*newdi) + strlen(devname) + 1, M_DUMPER, M_WAITOK + | M_ZERO); + memcpy(newdi, di_template, sizeof(*newdi)); + newdi->blockbuf = NULL; + newdi->kdcrypto = NULL; + newdi->kdcomp = NULL; + strcpy(newdi->di_devname, devname); - if (encryption != KERNELDUMP_ENC_NONE) { + if (kda->kda_encryption != KERNELDUMP_ENC_NONE) { #ifdef EKCD - dumper.kdcrypto = kerneldumpcrypto_create(di->blocksize, - encryption, key, encryptedkeysize, encryptedkey); - if (dumper.kdcrypto == NULL) { + newdi->kdcrypto = kerneldumpcrypto_create(di_template->blocksize, + kda->kda_encryption, kda->kda_key, + kda->kda_encryptedkeysize, kda->kda_encryptedkey); + if (newdi->kdcrypto == NULL) { error = EINVAL; goto cleanup; } @@ -1125,66 +1206,117 @@ set_dumper(struct dumperinfo *di, const char *devname, struct thread *td, goto cleanup; #endif } - - wantcopy = strlcpy(dumpdevname, devname, sizeof(dumpdevname)); - if (wantcopy >= sizeof(dumpdevname)) { - printf("set_dumper: device name truncated from '%s' -> '%s'\n", - devname, dumpdevname); - } - - if (compression != KERNELDUMP_COMP_NONE) { + if (kda->kda_compression != KERNELDUMP_COMP_NONE) { /* * We currently can't support simultaneous encryption and - * compression. + * compression because our only encryption mode is an unpadded + * block cipher, go figure. This is low hanging fruit to fix. */ - if (encryption != KERNELDUMP_ENC_NONE) { + if (kda->kda_encryption != KERNELDUMP_ENC_NONE) { error = EOPNOTSUPP; goto cleanup; } - dumper.kdcomp = kerneldumpcomp_create(&dumper, compression); - if (dumper.kdcomp == NULL) { + newdi->kdcomp = kerneldumpcomp_create(newdi, + kda->kda_compression); + if (newdi->kdcomp == NULL) { error = EINVAL; goto cleanup; } } - dumper.blockbuf = malloc(di->blocksize, M_DUMPER, M_WAITOK | M_ZERO); + newdi->blockbuf = malloc(newdi->blocksize, M_DUMPER, M_WAITOK | M_ZERO); + + /* Add the new configuration to the queue */ + mtx_lock(&dumpconf_list_lk); + inserted = false; + TAILQ_FOREACH(listdi, &dumper_configs, di_next) { + if (index == 0) { + TAILQ_INSERT_BEFORE(listdi, newdi, di_next); + inserted = true; + break; + } + index--; + } + if (!inserted) + TAILQ_INSERT_TAIL(&dumper_configs, newdi, di_next); + mtx_unlock(&dumpconf_list_lk); + return (0); cleanup: - (void)clear_dumper(td); + free_single_dumper(newdi); return (error); } -int -clear_dumper(struct thread *td) +static bool +dumper_config_match(const struct dumperinfo *di, const char *devname, + const struct diocskerneldump_arg *kda) { - int error; + if (kda->kda_index == KDA_REMOVE_ALL) + return (true); - error = priv_check(td, PRIV_SETDUMPER); - if (error != 0) - return (error); + if (strcmp(di->di_devname, devname) != 0) + return (false); -#ifdef NETDUMP - netdump_mbuf_drain(); -#endif + /* + * Allow wildcard removal of configs matching a device on g_dev_orphan. + */ + if (kda->kda_index == KDA_REMOVE_DEV) + return (true); + if (di->kdcomp != NULL) { + if (di->kdcomp->kdc_format != kda->kda_compression) + return (false); + } else if (kda->kda_compression != KERNELDUMP_COMP_NONE) + return (false); #ifdef EKCD - if (dumper.kdcrypto != NULL) { - explicit_bzero(dumper.kdcrypto, sizeof(*dumper.kdcrypto) + - dumper.kdcrypto->kdc_dumpkeysize); - free(dumper.kdcrypto, M_EKCD); - } + if (di->kdcrypto != NULL) { + if (di->kdcrypto->kdc_encryption != kda->kda_encryption) + return (false); + /* + * Do we care to verify keys match to delete? It seems weird + * to expect multiple fallback dump configurations on the same + * device that only differ in crypto key. + */ + } else #endif + if (kda->kda_encryption != KERNELDUMP_ENC_NONE) + return (false); - kerneldumpcomp_destroy(&dumper); + return (true); +} + +int +dumper_remove(const char *devname, const struct diocskerneldump_arg *kda) +{ + struct dumperinfo *di, *sdi; + bool found; + int error; - if (dumper.blockbuf != NULL) { - explicit_bzero(dumper.blockbuf, dumper.blocksize); - free(dumper.blockbuf, M_DUMPER); + error = priv_check(curthread, PRIV_SETDUMPER); + if (error != 0) + return (error); + + /* + * Try to find a matching configuration, and kill it. + * + * NULL 'kda' indicates remove any configuration matching 'devname', + * which may remove multiple configurations in atypical configurations. + */ + found = false; + mtx_lock(&dumpconf_list_lk); + TAILQ_FOREACH_SAFE(di, &dumper_configs, di_next, sdi) { + if (dumper_config_match(di, devname, kda)) { + found = true; + TAILQ_REMOVE(&dumper_configs, di, di_next); + free_single_dumper(di); + } } - explicit_bzero(&dumper, sizeof(dumper)); - dumpdevname[0] = '\0'; + mtx_unlock(&dumpconf_list_lk); + + /* Only produce ENOENT if a more targeted match didn't match. */ + if (!found && kda->kda_index == KDA_REMOVE) + return (ENOENT); return (0); } |