# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2020 Joyent, Inc. # Copyright 2024 MNX Cloud, Inc. # PROG = dumpadm MANIFEST = dumpadm.xml SVCMETHOD= svc-dumpadm OBJS = main.o dconf.o minfree.o utils.o swap.o include ../Makefile.cmd include ../Makefile.esssbin CFLAGS += $(CCVERBOSE) CFLAGS += -I../../lib/libzutil/common CSTD = $(CSTD_GNU99) FILEMODE = 0555 ROOTMANIFESTDIR = $(ROOTSVCSYSTEM) LDLIBS += -ldiskmgt -lzfs -luuid -lzutil .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) install: all $(ROOTSBINPROG) $(ROOTMANIFEST) $(ROOTSVCMETHOD) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) include ../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright 2020 Joyent, Inc. * Copyright 2024 MNX Cloud, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dconf.h" #include "minfree.h" #include "utils.h" #include "swap.h" typedef struct dc_token { const char *tok_name; int (*tok_parse)(dumpconf_t *, char *); int (*tok_print)(const dumpconf_t *, FILE *); } dc_token_t; static int print_device(const dumpconf_t *, FILE *); static int print_savdir(const dumpconf_t *, FILE *); static int print_content(const dumpconf_t *, FILE *); static int print_enable(const dumpconf_t *, FILE *); static int print_csave(const dumpconf_t *, FILE *); static const dc_token_t tokens[] = { { "DUMPADM_DEVICE", dconf_str2device, print_device }, { "DUMPADM_SAVDIR", dconf_str2savdir, print_savdir }, { "DUMPADM_CONTENT", dconf_str2content, print_content }, { "DUMPADM_ENABLE", dconf_str2enable, print_enable }, { "DUMPADM_CSAVE", dconf_str2csave, print_csave }, { NULL, NULL, NULL } }; static const char DC_STR_ON[] = "on"; /* On string */ static const char DC_STR_OFF[] = "off"; /* Off string */ static const char DC_STR_YES[] = "yes"; /* Enable on string */ static const char DC_STR_NO[] = "no"; /* Enable off string */ static const char DC_STR_SWAP[] = "swap"; /* Default dump device */ static const char DC_STR_NONE[] = "none"; /* The pages included in the dump */ static const char DC_STR_KERNEL[] = "kernel"; /* Kernel only */ static const char DC_STR_CURPROC[] = "curproc"; /* Kernel + current process */ static const char DC_STR_ALL[] = "all"; /* All pages */ /* * Permissions and ownership for the configuration file: */ #define DC_OWNER 0 /* Uid 0 (root) */ #define DC_GROUP 1 /* Gid 1 (other) */ #define DC_PERM (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) /* Mode 0644 */ static void dconf_init(dumpconf_t *dcp, int dcmode) { struct utsname ut; /* * Default device for dumps is 'swap' (appropriate swap device), * and default savecore directory is /var/crash/`uname -n`, * which is compatible with pre-dumpadm behavior. */ (void) strcpy(dcp->dc_device, DC_STR_SWAP); (void) strcpy(dcp->dc_savdir, "/var/crash"); if (uname(&ut) != -1) { (void) strcat(dcp->dc_savdir, "/"); (void) strcat(dcp->dc_savdir, ut.nodename); } /* * Default is contents kernel, savecore enabled on reboot, * savecore saves compressed core files. */ dcp->dc_cflags = DUMP_KERNEL; dcp->dc_enable = DC_ON; dcp->dc_csave = DC_COMPRESSED; dcp->dc_mode = dcmode; dcp->dc_conf_fp = NULL; dcp->dc_conf_fd = -1; dcp->dc_dump_fd = -1; dcp->dc_readonly = false; dcp->dc_parsable = false; dcp->dc_headers = true; } int dconf_open(dumpconf_t *dcp, const char *dpath, const char *fpath, int dcmode) { char buf[BUFSIZ]; int line; const char *fpmode = "r+"; dconf_init(dcp, dcmode); if ((dcp->dc_dump_fd = open(dpath, O_RDWR)) == -1) { warn(gettext("failed to open %s"), dpath); return (-1); } if ((dcp->dc_conf_fd = open(fpath, O_RDWR | O_CREAT, DC_PERM)) == -1) { /* * Attempt to open the file read-only. */ if ((dcp->dc_conf_fd = open(fpath, O_RDONLY)) == -1) { warn(gettext("failed to open %s"), fpath); return (-1); } dcp->dc_readonly = true; fpmode = "r"; } if ((dcp->dc_conf_fp = fdopen(dcp->dc_conf_fd, fpmode)) == NULL) { warn(gettext("failed to open stream for %s"), fpath); return (-1); } /* * If we're in override mode, the current kernel settings override the * default settings and anything invalid in the configuration file. */ if (dcmode == DC_OVERRIDE) (void) dconf_getdev(dcp); for (line = 1; fgets(buf, BUFSIZ, dcp->dc_conf_fp) != NULL; line++) { char name[BUFSIZ], value[BUFSIZ]; const dc_token_t *tokp; int len; if (buf[0] == '#' || buf[0] == '\n') continue; /* * Look for "name=value", with optional whitespace on either * side, terminated by a newline, and consuming the whole line. */ /* LINTED - unbounded string specifier */ if (sscanf(buf, " %[^=]=%s \n%n", name, value, &len) == 2 && name[0] != '\0' && value[0] != '\0' && len == strlen(buf)) { /* * Locate a matching token in the tokens[] table, * and invoke its parsing function. */ for (tokp = tokens; tokp->tok_name != NULL; tokp++) { if (strcmp(name, tokp->tok_name) == 0) { if (tokp->tok_parse(dcp, value) == -1) { warn(gettext("\"%s\", line %d: " "warning: invalid %s\n"), fpath, line, name); } break; } } /* * If we hit the end of the tokens[] table, * no matching token was found. */ if (tokp->tok_name == NULL) { warn(gettext("\"%s\", line %d: warning: " "invalid token: %s\n"), fpath, line, name); } } else { warn(gettext("\"%s\", line %d: syntax error\n"), fpath, line); } } /* * If we're not in override mode, the current kernel settings * override the settings read from the configuration file. */ if (dcmode == DC_CURRENT) return (dconf_getdev(dcp)); return (0); } int dconf_getdev(dumpconf_t *dcp) { int status = 0; if ((dcp->dc_cflags = ioctl(dcp->dc_dump_fd, DIOCGETCONF, 0)) == -1) { warn(gettext("failed to get kernel dump settings")); status = -1; } if (ioctl(dcp->dc_dump_fd, DIOCGETDEV, dcp->dc_device) == -1) { if (errno != ENODEV) { warn(gettext("failed to get dump device")); status = -1; } else dcp->dc_device[0] = '\0'; } return (status); } int dconf_close(dumpconf_t *dcp) { if (fclose(dcp->dc_conf_fp) == 0) { (void) close(dcp->dc_dump_fd); return (0); } return (-1); } int dconf_write(dumpconf_t *dcp) { const dc_token_t *tokp; if (fseeko(dcp->dc_conf_fp, (off_t)0, SEEK_SET) == -1) { warn(gettext("failed to seek config file")); return (-1); } if (ftruncate(dcp->dc_conf_fd, (off_t)0) == -1) { warn(gettext("failed to truncate config file")); return (-1); } (void) fputs("#\n# dumpadm.conf\n#\n" "# Configuration parameters for system crash dump.\n" "# Do NOT edit this file by hand -- use dumpadm(8) instead.\n" "#\n", dcp->dc_conf_fp); for (tokp = tokens; tokp->tok_name != NULL; tokp++) { if (fprintf(dcp->dc_conf_fp, "%s=", tokp->tok_name) == -1 || tokp->tok_print(dcp, dcp->dc_conf_fp) == -1) { warn(gettext("failed to write token")); return (-1); } } if (fflush(dcp->dc_conf_fp) != 0) warn(gettext("warning: failed to flush config file")); if (fsync(dcp->dc_conf_fd) == -1) warn(gettext("warning: failed to sync config file to disk")); if (fchmod(dcp->dc_conf_fd, DC_PERM) == -1) warn(gettext("warning: failed to reset mode on config file")); if (fchown(dcp->dc_conf_fd, DC_OWNER, DC_GROUP) == -1) warn(gettext("warning: failed to reset owner on config file")); return (0); } static int open_stat64(const char *path, struct stat64 *stp) { int fd = open64(path, O_RDONLY); if (fd >= 0) { int status = fstat64(fd, stp); (void) close(fd); return (status); } return (-1); } static int dconf_swap_compare(const swapent_t *s1, const swapent_t *s2) { struct stat64 st1, st2; int prefer_s1 = -1; /* Return value to move s1 left (s1 < s2) */ int prefer_s2 = 1; /* Return value to move s2 left (s1 > s2) */ /* * First try: open and fstat each swap entry. If either system * call fails, arbitrarily prefer the other entry. */ if (open_stat64(s1->ste_path, &st1) == -1) return (prefer_s2); if (open_stat64(s2->ste_path, &st2) == -1) return (prefer_s1); /* * Second try: if both entries are block devices, or if * neither is a block device, prefer the larger. */ if (S_ISBLK(st1.st_mode) == S_ISBLK(st2.st_mode)) { if (st2.st_size > st1.st_size) return (prefer_s2); return (prefer_s1); } /* * Third try: prefer the entry that is a block device. */ if (S_ISBLK(st2.st_mode)) return (prefer_s2); return (prefer_s1); } static int dconf_dev_ioctl(dumpconf_t *dcp, int cmd) { if (ioctl(dcp->dc_dump_fd, cmd, dcp->dc_device) == 0) return (0); switch (errno) { case ENOTSUP: warn(gettext("dumps not supported on %s\n"), dcp->dc_device); break; case EBUSY: warn(gettext("device %s is already in use\n"), dcp->dc_device); break; case EBADR: /* ZFS pool is too fragmented to support a dump device */ warn(gettext("device %s is too fragmented to be used as " "a dump device\n"), dcp->dc_device); break; default: /* * NOTE: The stmsboot(8) command's boot-up script parses this * error to get the dump device name. If you change the format * of this message, make sure that stmsboot(8) is in sync. */ warn(gettext("cannot use %s as dump device"), dcp->dc_device); } return (-1); } int dconf_update(dumpconf_t *dcp, int checkinuse) { int oconf; int error; char *msg; error = 0; if (checkinuse && (dm_inuse(dcp->dc_device, &msg, DM_WHO_DUMP, &error) || error)) { if (error != 0) { warn(gettext("failed to determine if %s is" " in use"), dcp->dc_device); } else { warn(msg); free(msg); return (-1); } } /* * Save the existing dump configuration in case something goes wrong. */ if ((oconf = ioctl(dcp->dc_dump_fd, DIOCGETCONF, 0)) == -1) { warn(gettext("failed to get kernel dump configuration")); return (-1); } oconf &= DUMP_CONTENT; dcp->dc_cflags &= DUMP_CONTENT; if (ioctl(dcp->dc_dump_fd, DIOCSETCONF, dcp->dc_cflags) == -1) { warn(gettext("failed to update kernel dump configuration")); return (-1); } if (strcmp(dcp->dc_device, DC_STR_SWAP) == 0) { swaptbl_t *swt; int i; if ((swt = swap_list()) == NULL) goto err; if (swt->swt_n == 0) { warn(gettext("no swap devices are available\n")); free(swt); goto err; } qsort(&swt->swt_ent[0], swt->swt_n, sizeof (swapent_t), (int (*)(const void *, const void *))dconf_swap_compare); /* * Iterate through the prioritized list of swap entries, * trying to configure one as the dump device. */ for (i = 0; i < swt->swt_n; i++) { if (ioctl(dcp->dc_dump_fd, DIOCSETDEV, swt->swt_ent[i].ste_path) == 0) { (void) strcpy(dcp->dc_device, swt->swt_ent[i].ste_path); break; } } if (i == swt->swt_n) { warn(gettext("no swap devices could be configured " "as the dump device\n")); free(swt); goto err; } free(swt); } else if (strcmp(dcp->dc_device, DC_STR_NONE) == 0) { if (ioctl(dcp->dc_dump_fd, DIOCRMDEV, NULL) == -1) { warn(gettext("failed to remove dump device")); return (-1); } } else if (dcp->dc_device[0] != '\0') { /* * If we're not in forcible update mode, then fail the change * if the selected device cannot be used as the dump device, * or if it is not big enough to hold the dump. */ if (dcp->dc_mode == DC_CURRENT) { struct stat64 st; uint64_t d; if (dconf_dev_ioctl(dcp, DIOCTRYDEV) == -1) goto err; if (open_stat64(dcp->dc_device, &st) == -1) { warn(gettext("failed to access %s"), dcp->dc_device); goto err; } if ((error = zvol_check_dump_config( dcp->dc_device)) > 0) goto err; if (ioctl(dcp->dc_dump_fd, DIOCGETDUMPSIZE, &d) == -1) { warn(gettext("failed to get kernel dump size")); goto err; } if (st.st_size < d) { warn(gettext("dump device %s is too small to " "hold a system dump\ndump size %llu " "bytes, device size %lld bytes\n"), dcp->dc_device, d, st.st_size); goto err; } } if (dconf_dev_ioctl(dcp, DIOCSETDEV) == -1) goto err; } /* * Now that we've updated the dump device, we need to issue another * ioctl to re-read the config flags to determine whether we * obtained DUMP_EXCL access on our dump device. */ if ((dcp->dc_cflags = ioctl(dcp->dc_dump_fd, DIOCGETCONF, 0)) == -1) { warn(gettext("failed to re-read kernel dump configuration")); return (-1); } return (0); err: (void) ioctl(dcp->dc_dump_fd, DIOCSETCONF, oconf); return (-1); } int dconf_write_uuid(dumpconf_t *dcp) { char uuidstr[36 + 1]; uuid_t uu; int err; uuid_generate(uu); uuid_unparse(uu, uuidstr); err = ioctl(dcp->dc_dump_fd, DIOCSETUUID, uuidstr); if (err) warn(gettext("kernel image uuid write failed")); return (err == 0); } int dconf_get_dumpsize(dumpconf_t *dcp) { char buf[32]; uint64_t d; if (ioctl(dcp->dc_dump_fd, DIOCGETDUMPSIZE, &d) == -1) { warn(gettext("failed to get kernel dump size")); return (-1); } if (dcp->dc_parsable) (void) snprintf(buf, sizeof (buf), "%llu", d); else zfs_nicenum(d, buf, sizeof (buf)); if (dcp->dc_headers) (void) printf(gettext("Estimated dump size: ")); (void) printf("%s\n", buf); return (0); } void dconf_print(dumpconf_t *dcp, FILE *fp) { u_longlong_t min; char *content; if (dcp->dc_cflags & DUMP_ALL) content = gettext("all"); else if (dcp->dc_cflags & DUMP_CURPROC) content = gettext("kernel and current process"); else content = gettext("kernel"); (void) fprintf(fp, gettext(" Dump content: %s pages\n"), content); if (dcp->dc_device[0] != '\0') { (void) fprintf(fp, gettext(" Dump device: %s (%s)\n"), dcp->dc_device, (dcp->dc_cflags & DUMP_EXCL) ? gettext("dedicated") : gettext("swap")); } else { (void) fprintf(fp, gettext(" Dump device: none " "(dumps disabled)\n")); } (void) fprintf(fp, gettext("Savecore directory: %s"), dcp->dc_savdir); if (minfree_read(dcp->dc_savdir, &min) == 0) { if (min < 1024 || (min % 1024) != 0) (void) fprintf(fp, gettext(" (minfree = %lluKB)"), min); else (void) fprintf(fp, gettext(" (minfree = %lluMB)"), min / 1024); } (void) fprintf(fp, gettext("\n")); (void) fprintf(fp, gettext(" Savecore enabled: %s\n"), (dcp->dc_enable == DC_OFF) ? gettext("no") : gettext("yes")); (void) fprintf(fp, gettext(" Save compressed: %s\n"), (dcp->dc_csave == DC_UNCOMPRESSED) ? gettext("off") : gettext("on")); } int dconf_str2device(dumpconf_t *dcp, char *buf) { if (strcasecmp(buf, DC_STR_SWAP) == 0) { (void) strcpy(dcp->dc_device, DC_STR_SWAP); return (0); } if (strcasecmp(buf, DC_STR_NONE) == 0) { (void) strcpy(dcp->dc_device, DC_STR_NONE); return (0); } if (valid_abspath(buf)) { (void) strcpy(dcp->dc_device, buf); return (0); } return (-1); } int dconf_str2savdir(dumpconf_t *dcp, char *buf) { if (valid_abspath(buf)) { (void) strcpy(dcp->dc_savdir, buf); return (0); } return (-1); } int dconf_str2content(dumpconf_t *dcp, char *buf) { if (strcasecmp(buf, DC_STR_KERNEL) == 0) { dcp->dc_cflags = (dcp->dc_cflags & ~DUMP_CONTENT) | DUMP_KERNEL; return (0); } if (strcasecmp(buf, DC_STR_CURPROC) == 0) { dcp->dc_cflags = (dcp->dc_cflags & ~DUMP_CONTENT) | DUMP_CURPROC; return (0); } if (strcasecmp(buf, DC_STR_ALL) == 0) { dcp->dc_cflags = (dcp->dc_cflags & ~DUMP_CONTENT) | DUMP_ALL; return (0); } warn(gettext("invalid dump content type -- %s\n"), buf); return (-1); } int dconf_str2enable(dumpconf_t *dcp, char *buf) { if (strcasecmp(buf, DC_STR_YES) == 0) { dcp->dc_enable = DC_ON; return (0); } if (strcasecmp(buf, DC_STR_NO) == 0) { dcp->dc_enable = DC_OFF; return (0); } warn(gettext("invalid enable value -- %s\n"), buf); return (-1); } int dconf_str2csave(dumpconf_t *dcp, char *buf) { if (strcasecmp(buf, DC_STR_ON) == 0) { dcp->dc_csave = DC_COMPRESSED; return (0); } if (strcasecmp(buf, DC_STR_OFF) == 0) { dcp->dc_csave = DC_UNCOMPRESSED; return (0); } warn(gettext("invalid save compressed value -- %s\n"), buf); return (-1); } static int print_content(const dumpconf_t *dcp, FILE *fp) { const char *content; if (dcp->dc_cflags & DUMP_ALL) content = DC_STR_ALL; else if (dcp->dc_cflags & DUMP_CURPROC) content = DC_STR_CURPROC; else content = DC_STR_KERNEL; return (fprintf(fp, "%s\n", content)); } static int print_device(const dumpconf_t *dcp, FILE *fp) { return (fprintf(fp, "%s\n", (dcp->dc_device[0] != '\0') ? dcp->dc_device : DC_STR_SWAP)); } static int print_enable(const dumpconf_t *dcp, FILE *fp) { return (fprintf(fp, "%s\n", (dcp->dc_enable == DC_OFF) ? DC_STR_NO : DC_STR_YES)); } static int print_csave(const dumpconf_t *dcp, FILE *fp) { return (fprintf(fp, "%s\n", (dcp->dc_csave == DC_COMPRESSED) ? DC_STR_ON : DC_STR_OFF)); } static int print_savdir(const dumpconf_t *dcp, FILE *fp) { return (fprintf(fp, "%s\n", dcp->dc_savdir)); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright 2024 MNX Cloud, Inc. */ #ifndef _DCONF_H #define _DCONF_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct dumpconf { char dc_device[MAXPATHLEN]; /* Dump device path */ char dc_savdir[MAXPATHLEN]; /* Savecore dir path */ int dc_cflags; /* Config flags (see ) */ int dc_enable; /* Run savecore on boot? (see below) */ int dc_csave; /* Save dump compressed? (see below) */ int dc_mode; /* Mode flags (see below) */ FILE *dc_conf_fp; /* File pointer for config file */ int dc_conf_fd; /* File descriptor for config file */ int dc_dump_fd; /* File descriptor for dump device */ bool dc_readonly; /* Readonly conf file */ bool dc_parsable; /* Parsable values */ bool dc_headers; /* Print headers */ } dumpconf_t; /* * Values for dc_enable (run savecore on boot) property: */ #define DC_OFF 0 /* Savecore disabled */ #define DC_ON 1 /* Savecore enabled */ /* * Values for dc_csave (savecore compressed) property: */ #define DC_UNCOMPRESSED 0 /* Savecore uncompresses the dump */ #define DC_COMPRESSED 1 /* Savecore leaves dump compressed */ /* * Values for dconf_open mode: */ #define DC_CURRENT 1 /* Kernel overrides file settings */ #define DC_OVERRIDE 2 /* File+defaults override kernel */ extern int dconf_open(dumpconf_t *, const char *, const char *, int); extern int dconf_getdev(dumpconf_t *); extern int dconf_close(dumpconf_t *); extern int dconf_write(dumpconf_t *); extern int dconf_update(dumpconf_t *, int); extern void dconf_print(dumpconf_t *, FILE *); extern int dconf_write_uuid(dumpconf_t *); extern int dconf_get_dumpsize(dumpconf_t *); extern int dconf_str2device(dumpconf_t *, char *); extern int dconf_str2savdir(dumpconf_t *, char *); extern int dconf_str2content(dumpconf_t *, char *); extern int dconf_str2enable(dumpconf_t *, char *); extern int dconf_str2csave(dumpconf_t *, char *); #ifdef __cplusplus } #endif #endif /* _DCONF_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright 2024 MNX Cloud, Inc. */ #include #include #include #include #include #include #include "dconf.h" #include "minfree.h" #include "utils.h" static const char USAGE[] = "\ Usage: %s [-Henpuy] [-c kernel | curproc | all ]\n\ [-d dump-device | swap | none ] [-m min {k|m|%%} ] [-s savecore-dir]\n\ [-r root-dir] [-z on|off]\n"; static const char OPTS[] = "Heinpuyc:d:m:s:r:z:"; static const char PATH_DEVICE[] = "/dev/dump"; static const char PATH_CONFIG[] = "/etc/dumpadm.conf"; int main(int argc, char *argv[]) { const char *pname = getpname(argv[0]); u_longlong_t minf; struct stat st; int c; int dflag = 0; /* for checking in use during -d ops */ int eflag = 0; /* print estimated dump size */ int dcmode = DC_CURRENT; /* kernel settings override unless -u */ int modified = 0; /* have we modified the dump config? */ char *minfstr = NULL; /* string value of -m argument */ dumpconf_t dc; /* current configuration */ int chrooted = 0; int douuid = 0; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* * Take an initial lap through argv hunting for -r root-dir, * so that we can chroot before opening the configuration file. * We also handle -u and any bad options at this point. */ while (optind < argc) { while ((c = getopt(argc, argv, OPTS)) != (int)EOF) { if (c == 'r' && ++chrooted && chroot(optarg) == -1) die(gettext("failed to chroot to %s"), optarg); else if (c == 'u') dcmode = DC_OVERRIDE; else if (c == '?') { (void) fprintf(stderr, gettext(USAGE), pname); return (E_USAGE); } } if (optind < argc) { warn(gettext("illegal argument -- %s\n"), argv[optind]); (void) fprintf(stderr, gettext(USAGE), pname); return (E_USAGE); } } if (geteuid() != 0) die(gettext("you must be root to use %s\n"), pname); /* * If no config file exists yet, we're going to create an empty one, * so set the modified flag to force writing out the file. */ if (access(PATH_CONFIG, F_OK) == -1) modified++; /* * Now open and read in the initial values from the config file. * If it doesn't exist, we create an empty file and dc is * initialized with the default values. */ if (dconf_open(&dc, PATH_DEVICE, PATH_CONFIG, dcmode) == -1) return (E_ERROR); /* * Take another lap through argv, processing options and * modifying the dumpconf_t as appropriate. */ for (optind = 1; optind < argc; optind++) { while ((c = getopt(argc, argv, OPTS)) != (int)EOF) { switch (c) { case 'H': dc.dc_headers = false; break; case 'c': if (dconf_str2content(&dc, optarg) == -1) return (E_USAGE); modified++; break; case 'd': if (dconf_str2device(&dc, optarg) == -1) return (E_USAGE); dflag++; modified++; break; case 'e': eflag++; break; case 'i': /* undocumented option */ if (chrooted) { warn(gettext("-i and -r cannot be " "used together\n")); return (E_USAGE); } douuid++; break; case 'm': minfstr = optarg; break; case 'n': dc.dc_enable = DC_OFF; modified++; break; case 'p': dc.dc_parsable = true; break; case 's': if (stat(optarg, &st) == -1 || !S_ISDIR(st.st_mode)) { warn(gettext("%s is missing or not a " "directory\n"), optarg); return (E_USAGE); } if (dconf_str2savdir(&dc, optarg) == -1) return (E_USAGE); modified++; break; case 'y': dc.dc_enable = DC_ON; modified++; break; case 'z': if (dconf_str2csave(&dc, optarg) == -1) return (E_USAGE); modified++; break; } } } if (eflag) { if (modified < 2 && douuid == 0 && chrooted == 0 && dcmode == DC_CURRENT && minfstr == NULL) return (dconf_get_dumpsize(&dc) ? E_SUCCESS : E_ERROR); else die(gettext("-e cannot be used with other options\n")); } if (dc.dc_parsable) { die(gettext("-p can only be used with -e\n")); } if (!dc.dc_headers) { die(gettext("-H can only be used with -e\n")); } if (douuid) return (dconf_write_uuid(&dc) ? E_SUCCESS : E_ERROR); if (minfstr != NULL) { if (minfree_compute(dc.dc_savdir, minfstr, &minf) == -1) return (E_USAGE); if (minfree_write(dc.dc_savdir, minf) == -1) return (E_ERROR); } if (dcmode == DC_OVERRIDE) { /* * In override mode, we try to force an update. If this * fails, we re-load the kernel configuration and write that * out to the file in order to force the file in sync. * * We allow the file to be read-only but print a warning to the * user that indicates it hasn't been updated. */ if (dconf_update(&dc, 0) == -1) (void) dconf_getdev(&dc); if (dc.dc_readonly) warn(gettext("kernel settings updated, but " "%s is read-only\n"), PATH_CONFIG); else if (dconf_write(&dc) == -1) return (E_ERROR); } else if (modified) { /* * If we're modifying the configuration, then try * to update it, and write out the file if successful. */ if (dc.dc_readonly) { warn(gettext("failed to update settings: %s is " "read-only\n"), PATH_CONFIG); return (E_ERROR); } if (dconf_update(&dc, dflag) == -1 || dconf_write(&dc) == -1) return (E_ERROR); } if (dcmode == DC_CURRENT) dconf_print(&dc, stdout); if (dconf_close(&dc) == -1) warn(gettext("failed to close configuration file")); return (E_SUCCESS); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include #include #include #include #include "utils.h" static FILE * minfree_open(const char *dir, int oflags, const char *fmode) { char path[MAXPATHLEN]; int fd; (void) snprintf(path, sizeof (path), "%s/minfree", dir); if ((fd = open(path, oflags, S_IRUSR | S_IWUSR)) >= 0) return (fdopen(fd, fmode)); return (NULL); } int minfree_read(const char *dir, unsigned long long *ullp) { FILE *fp = minfree_open(dir, O_RDONLY, "r"); if (fp != NULL) { char buf[BUFSIZ]; int status = -1; if (fgets(buf, BUFSIZ, fp) != NULL) { if (valid_str2ull(buf, ullp)) status = 0; else warn(gettext("\"%s/minfree\": invalid minfree " "value -- %s\n"), dir, buf); } (void) fclose(fp); return (status); } return (-1); } int minfree_write(const char *dir, unsigned long long ull) { FILE *fp = minfree_open(dir, O_WRONLY | O_CREAT | O_TRUNC, "w"); if (fp != NULL) { int status = fprintf(fp, "%llu\n", ull); (void) fclose(fp); return (status); } return (-1); } int minfree_compute(const char *dir, char *s, unsigned long long *ullp) { size_t len = strlen(s); unsigned long long m = 1; struct statvfs64 fsb; int pct; switch (s[len - 1]) { case '%': s[len - 1] = '\0'; if (!valid_str2int(s, &pct) || pct > 100) { warn(gettext("invalid minfree %% -- %s\n"), s); return (-1); } if (statvfs64(dir, &fsb) == -1) { warn(gettext("failed to statvfs %s"), dir); return (-1); } *ullp = fsb.f_blocks * fsb.f_frsize * (u_longlong_t)pct / 100ULL / 1024ULL; return (0); case 'm': case 'M': m = 1024ULL; /*FALLTHRU*/ case 'k': case 'K': s[len - 1] = '\0'; if (valid_str2ull(s, ullp)) { *ullp *= m; return (0); } warn(gettext("invalid minfree value -- %s\n"), s); return (-1); default: warn(gettext("expected m, k, or %% unit after " "minfree -- %s\n"), s); return (-1); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _MINFREE_H #define _MINFREE_H #ifdef __cplusplus extern "C" { #endif extern int minfree_read(const char *, unsigned long long *); extern int minfree_write(const char *, unsigned long long); extern int minfree_compute(const char *, char *, unsigned long long *); #ifdef __cplusplus } #endif #endif /* _MINFREE_H */ #!/sbin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. # . /lib/svc/share/smf_include.sh . /lib/svc/share/fs_include.sh # # mksavedir # Make sure that $DUMPADM_SAVDIR is set and exists. # mksavedir () { [ -z "$DUMPADM_SAVDIR" ] && DUMPADM_SAVDIR=/var/crash/`uname -n` [ -d "$DUMPADM_SAVDIR" ] || /bin/mkdir -m 0700 -p $DUMPADM_SAVDIR } # # We haven't run savecore on a dump device yet # savedev=none # # If we previously crashed early in boot before dumpadm was used to configure # an alternate dump device, then the dump is in the primary swap partition, # which was configured as the dump device by the first swapadd early in boot. # Thus before we run dumpadm to configure the dump device, we first run # savecore to check the swap partition for a dump; this is run in the # foreground to reduce the chances of overwriting the dump. # # This does not apply for zfs root systems that use a zvol for dump; # for such systems the dedicated dump device is appointed during startup # of the filesystem/usr:default instance before any swap is added. # Therefore we must check that the dump device is a swap device here - # if not then we'll run savecore here in the foreground and prevent # our dependent services coming online until we're done. # rootiszfs=0 alreadydedicated=0 readmnttab / /dev/null | grep "Dump device:" | \ grep '(dedicated)' > /dev/null 2>&1; then alreadydedicated=1 fi fi fi if [ -x /usr/bin/savecore -a \ \( ! $rootiszfs -eq 1 -o $alreadydedicated -eq 0 \) ]; then [ -r /etc/dumpadm.conf ] && . /etc/dumpadm.conf if [ "x$DUMPADM_ENABLE" != xno ] && mksavedir; then /usr/bin/savecore $DUMPADM_SAVDIR shift $# set -- `/sbin/dumpadm 2>/dev/null | /bin/grep 'device:'` savedev=${3:-none} else # # dumpadm -n is in effect, but we can still run savecore # to raise an event with initial panic detail extracted # from the dump header. # /usr/bin/savecore -c fi fi if [ ! -x /usr/bin/savecore ]; then echo "WARNING: /usr/bin/savecore is missing or not executable" >& 2 fi # # Now run dumpadm to configure the dump device based on the settings # previously saved by dumpadm. See dumpadm(8) for instructions on # how to modify the dump settings. # if [ -x /sbin/dumpadm ]; then /sbin/dumpadm -u || $SMF_EXIT_ERR_CONFIG else echo "WARNING: /sbin/dumpadm is missing or not executable" >& 2 exit $SMF_EXIT_ERR_CONFIG fi if [ -r /etc/dumpadm.conf ]; then . /etc/dumpadm.conf else echo "WARNING: /etc/dumpadm.conf is missing or unreadable" >& 2 exit $SMF_EXIT_ERR_CONFIG fi # # If the savecore executable is absent then we're done # if [ ! -x /usr/bin/savecore ]; then exit $SMF_EXIT_ERR_CONFIG fi # # Now that dumpadm has reconfigured /dev/dump, we need to run savecore again # because the dump device may have changed. If the earlier savecore had # saved the dump, savecore will just exit immediately. # isswap=0 swapchanged=0 if /sbin/swap -l 2>/dev/null | grep "^${DUMPADM_DEVICE} " \ >/dev/null 2>&1; then isswap=1 if [ "x$savedev" != "x$DUMPADM_DEVICE" ]; then swapchanged=1 fi fi if [ "x$DUMPADM_ENABLE" != xno ]; then if [ $isswap -eq 1 ]; then # # If the dump device is part of swap, we only need to run # savecore a second time if the device is different from the # swap device on which we initially ran savecore. # if [ $swapchanged -eq 1 ]; then mksavedir && /usr/bin/savecore $DUMPADM_SAVDIR & fi else # # The dump device couldn't have been dedicated before we # ran dumpadm, so we must execute savecore again. # mksavedir && /usr/bin/savecore $DUMPADM_SAVDIR & fi else # # savecore not enabled. Check whether a valid dump is # present on the device and raise an event to signal that, # but avoid sending a duplicate event from the savecore -c # earlier. # if [ $isswap -eq 0 -o $swapchanged -eq 1 ]; then /usr/bin/savecore -c fi fi exit $SMF_EXIT_OK /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include "utils.h" #include "swap.h" swaptbl_t * swap_list(void) { swaptbl_t *swt; int n, i; char *p; if ((n = swapctl(SC_GETNSWP, NULL)) == -1) { warn(gettext("failed to get swap table size")); return (NULL); } swt = malloc(sizeof (int) + n * sizeof (swapent_t) + n * MAXPATHLEN); if (swt == NULL) { warn(gettext("failed to allocate swap table")); return (NULL); } swt->swt_n = n; p = (char *)swt + (sizeof (int) + n * sizeof (swapent_t)); for (i = 0; i < n; i++) { swt->swt_ent[i].ste_path = p; p += MAXPATHLEN; } if ((n = swapctl(SC_LIST, swt)) == -1) { warn(gettext("failed to get swap table")); free(swt); return (NULL); } swt->swt_n = n; /* Number of entries filled in */ n = 0; /* Number of valid entries */ /* * Shrink the array of swapent_t structures by stripping out * all those which are ST_INDEL or ST_DOINGDEL. */ for (i = 0; i < swt->swt_n; i++) { if (!(swt->swt_ent[i].ste_flags & (ST_INDEL | ST_DOINGDEL))) { /* * If i is ahead of the valid count (n), copy the * ith entry back to the nth entry so valid entries * fill the initial part of swt_ent[]. */ if (i > n) { (void) memcpy(&swt->swt_ent[n], &swt->swt_ent[i], sizeof (swapent_t)); } /* * If the pathname isn't absolute, assume it begins * with /dev as swap(8) does. */ if (swt->swt_ent[n].ste_path[0] != '/') { char buf[MAXPATHLEN]; (void) snprintf(buf, sizeof (buf), "/dev/%s", swt->swt_ent[n].ste_path); (void) strcpy(swt->swt_ent[n].ste_path, buf); } n++; } } swt->swt_n = n; /* Update swt_n with number of valid entries */ return (swt); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _SWAP_H #define _SWAP_H #include #include #ifdef __cplusplus extern "C" { #endif extern swaptbl_t *swap_list(void); #ifdef __cplusplus } #endif #endif /* _SWAP_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include #include #include #include "utils.h" static const char PNAME_FMT[] = "%s: "; static const char ERRNO_FMT[] = ": %s\n"; static const char *pname; /*PRINTFLIKE1*/ void warn(const char *format, ...) { int err = errno; va_list alist; if (pname != NULL) (void) fprintf(stderr, gettext(PNAME_FMT), pname); va_start(alist, format); (void) vfprintf(stderr, format, alist); va_end(alist); if (strchr(format, '\n') == NULL) (void) fprintf(stderr, gettext(ERRNO_FMT), strerror(err)); } /*PRINTFLIKE1*/ void die(const char *format, ...) { int err = errno; va_list alist; if (pname != NULL) (void) fprintf(stderr, gettext(PNAME_FMT), pname); va_start(alist, format); (void) vfprintf(stderr, format, alist); va_end(alist); if (strchr(format, '\n') == NULL) (void) fprintf(stderr, gettext(ERRNO_FMT), strerror(err)); exit(E_ERROR); } const char * getpname(const char *arg0) { const char *p = strrchr(arg0, '/'); if (p == NULL) p = arg0; else p++; pname = p; return (p); } int valid_abspath(const char *p) { if (p[0] != '/') { warn(gettext("pathname is not an absolute path -- %s\n"), p); return (0); } if (strlen(p) > MAXPATHLEN) { warn(gettext("pathname is too long -- %s\n"), p); return (0); } return (1); } int valid_str2int(const char *p, int *ip) { int i; char *q; errno = 0; i = (int)strtol(p, &q, 10); if (errno != 0 || q == p || i < 0 || (*q != '\0' && *q != '\n')) return (0); *ip = i; return (1); } int valid_str2ull(const char *p, unsigned long long *ullp) { long long ll; char *q; errno = 0; ll = strtoll(p, &q, 10); if (errno != 0 || q == p || ll < 0LL || (*q != '\0' && *q != '\n')) return (0); *ullp = (unsigned long long)ll; return (1); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1998 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _UTILS_H #define _UTILS_H #include #ifdef __cplusplus extern "C" { #endif #define E_SUCCESS 0 /* Exit status for success */ #define E_ERROR 1 /* Exit status for error */ #define E_USAGE 2 /* Exit status for usage error */ extern void warn(const char *, ...); extern void die(const char *, ...); extern const char *getpname(const char *); extern int valid_abspath(const char *); extern int valid_str2int(const char *, int *); extern int valid_str2ull(const char *, unsigned long long *); #ifdef __cplusplus } #endif #endif /* _UTILS_H */