Index: sbin/savecore/savecore.c =================================================================== --- sbin/savecore/savecore.c +++ sbin/savecore/savecore.c @@ -68,6 +68,7 @@ #include #include #include +#include #include #include @@ -105,7 +106,7 @@ static cap_channel_t *capsyslog; static fileargs_t *capfa; -static bool checkfor, compress, uncompress, clear, force, keep; /* flags */ +static bool checkfor, compress, uncompress, clear, force, keep, livecore; /* flags */ static int verbose; static int nfound, nsaved, nerr; /* statistics */ static int maxdumps; @@ -220,6 +221,7 @@ break; } xo_emit_h(xo, "{P: }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str); + xo_emit_h(xo, "{P: }{Lwc:Livedump}{:livedump/%s}\n", livecore ? "yes" : "no"); xo_flush_h(xo); } @@ -354,6 +356,12 @@ (void)unlinkat(savedirfd, path, 0); (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); (void)unlinkat(savedirfd, path, 0); + (void)snprintf(path, sizeof(path), "livecore.%d", bounds); + (void)unlinkat(savedirfd, path, 0); + (void)snprintf(path, sizeof(path), "livecore.%d.gz", bounds); + (void)unlinkat(savedirfd, path, 0); + (void)snprintf(path, sizeof(path), "livecore.%d.zst", bounds); + (void)unlinkat(savedirfd, path, 0); } static void @@ -369,6 +377,11 @@ (void)unlinkat(savedirfd, "vmcore_encrypted.last.gz", 0); (void)unlinkat(savedirfd, "textdump.tar.last", 0); (void)unlinkat(savedirfd, "textdump.tar.last.gz", 0); + (void)unlinkat(savedirfd, "livecore.last", 0); + (void)unlinkat(savedirfd, "livecore.last.gz", 0); + (void)unlinkat(savedirfd, "livecore.last.zst", 0); + (void)unlinkat(savedirfd, "livecore_encrypted.last", 0); + (void)unlinkat(savedirfd, "livecore_encrypted.last.gz", 0); } /* @@ -692,6 +705,64 @@ return (0); } +static int +DoLivedumpFile(int fd, off_t dumpsize, char *buf, const char *filename, FILE *fp) +{ + off_t offset; + int nr, nw, wl; + int error; + bool needmove; + + /* + * The livedump output file will initially have the following layout: + * +---------------------+---------------+--------------------+ + * | dump key (optional) | dump contents | kernel dump header | + * +---------------------+---------------+--------------------+ + * ^ + * fd position + * + * We have already read they key and header, so this routine will + * shrink it down to just the dump contents. This means shifting the + * contents to overwrite the key if it is present, and truncating the + * final result to dumpsize. + * + * NB: fd and fp reference the same file. + */ + offset = 0; + needmove = ftell(fp) != 0; /* TODO: Handle -1 */ + while (needmove && offset < dumpsize) { + wl = MIN(dumpsize - offset, BUFFERSIZE); + nr = read(fd, buf, wl); + if (nr != wl) { + if (nr == 0) + logmsg(LOG_WARNING, + "WARNING: EOF on dump device"); + else + logmsg(LOG_ERR, "read error: %m"); + nerr++; + return (-1); + } + nw = pwrite(fd, buf, wl, offset); + if (nw != wl) { + logmsg(LOG_ERR, + "write error on %s file: %m", filename); + logmsg(LOG_WARNING, + "WARNING: dump may be incomplete"); + nerr++; + return (-1); + } + offset += wl; + if (verbose) { + printf("%llu\r", (unsigned long long)offset); + fflush(stdout); + } + } + + /* Remove the vestigial kernel dump header. */ + error = ftruncate(fd, dumpsize); + return (error); +} + static void DoFile(const char *savedir, int savedirfd, const char *device) { @@ -741,6 +812,38 @@ return; } + if (livecore) { + /* Invoke the live dump, saved to a temporary file. */ + fdcore = openat(savedirfd, "livedump.tmp", + O_RDWR | O_CREAT | O_TRUNC); + if (fdcore < 0) { + logmsg(LOG_ERR, "error opening temp file: %m"); + exit(1); + } + printf("opened temp file, fd=%d\n", fdcore); + if (ioctl(fddev, MEM_INVOKE_DUMP, &fdcore) == -1) { + logmsg(LOG_ERR, + "failed to invoke live-dump on system: %m"); + exit(1); + } + + /* Seek to the end of the file, minus the size of the header. */ + lasthd = lseek(fdcore, -sizeof(kdhl), SEEK_END); + if (lasthd == -1) { + printf("lseek failed!\n"); + exit(1); + } + + if (read(fdcore, &kdhl, sizeof(kdhl)) != sizeof(kdhl)) { + printf("failed to read enough!\n"); + logmsg(LOG_ERR, "Failed to read kernel dump header"); + goto closefd; + } + /* Reset cursor */ + (void)lseek(fdcore, 0, SEEK_SET); + goto validate; + } + error = ioctl(fddev, DIOCGMEDIASIZE, &mediasize); if (!error) error = ioctl(fddev, DIOCGSECTORSIZE, §orsize); @@ -776,6 +879,8 @@ goto closefd; } memcpy(&kdhl, temp, sizeof(kdhl)); + +validate: iscompressed = istextdump = false; if (compare_magic(&kdhl, TEXTDUMPMAGIC)) { if (verbose) @@ -862,27 +967,37 @@ dumpextent = dtoh64(kdhl.dumpextent); dumplength = dtoh64(kdhl.dumplength); dumpkeysize = dtoh32(kdhl.dumpkeysize); - firsthd = lasthd - dumpextent - sectorsize - dumpkeysize; - if (lseek(fddev, firsthd, SEEK_SET) != firsthd || - read(fddev, temp, sectorsize) != (ssize_t)sectorsize) { - logmsg(LOG_ERR, - "error reading first dump header at offset %lld in %s: %m", - (long long)firsthd, device); - nerr++; - goto closefd; + + /* Get the first dump header. */ + if (!livecore) { + firsthd = lasthd - dumpextent - sectorsize - dumpkeysize; + if (lseek(fddev, firsthd, SEEK_SET) != firsthd || + read(fddev, temp, sectorsize) != (ssize_t)sectorsize) { + logmsg(LOG_ERR, + "error reading first dump header at offset %lld in %s: %m", + (long long)firsthd, device); + nerr++; + goto closefd; + } + memcpy(&kdhf, temp, sizeof(kdhf)); } - memcpy(&kdhf, temp, sizeof(kdhf)); if (verbose >= 2) { - printf("First dump headers:\n"); - printheader(xostdout, &kdhf, device, bounds, -1); + if (!livecore) { + printf("First dump headers:\n"); + printheader(xostdout, &kdhf, device, bounds, -1); + } printf("\nLast dump headers:\n"); printheader(xostdout, &kdhl, device, bounds, -1); printf("\n"); } - if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) { + /* + * Check that the two dump headers are consistent with one another. + * Livedumps contain only one header. + */ + if (!livecore && memcmp(&kdhl, &kdhf, sizeof(kdhl))) { logmsg(LOG_ERR, "first and last dump headers disagree on %s", device); nerr++; @@ -908,7 +1023,8 @@ if (verbose) printf("Checking for available free space\n"); - if (!check_space(savedir, savedirfd, dumplength, bounds)) { + /* Live cores are already written to the filesystem. */ + if (!livecore && !check_space(savedir, savedirfd, dumplength, bounds)) { nerr++; goto closefd; } @@ -933,16 +1049,28 @@ if (compress) snprintf(corename, sizeof(corename), "%s.%d.gz", istextdump ? "textdump.tar" : - (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); + (livecore ? (isencrypted ? "livecore_encrypted" : "livecore") : + (isencrypted ? "vmcore_encrypted" : "vmcore")), bounds); else if (iscompressed && !isencrypted && !uncompress) - snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds, + snprintf(corename, sizeof(corename), "%s.%d.%s", + livecore ? "livecore" : "vmcore", bounds, (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst"); else snprintf(corename, sizeof(corename), "%s.%d", istextdump ? "textdump.tar" : - (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); - fdcore = openat(savedirfd, corename, O_WRONLY | O_CREAT | O_TRUNC, - 0600); + (livecore ? (isencrypted ? "livecore_encrypted" : "livecore") : + (isencrypted ? "vmcore_encrypted" : "vmcore")), bounds); + if (livecore) { + if (verbose) + printf("renaming livedump.tmp to %s\n", corename); + if (renameat(savedirfd, "livedump.tmp", savedirfd, corename) != 0) { + logmsg(LOG_ERR, "renameat failed: %m"); + exit(1); + } + } else { + fdcore = openat(savedirfd, corename, O_WRONLY | O_CREAT | O_TRUNC, + 0600); + } if (fdcore < 0) { logmsg(LOG_ERR, "open(%s): %m", corename); fclose(info); @@ -961,7 +1089,7 @@ nerr++; goto closefd; } - fdcore = -1; + //fdcore = -1; xostyle = xo_get_style(NULL); xoinfo = xo_create_to_file(info, xostyle, 0); @@ -990,7 +1118,9 @@ goto closeall; } - if (read(fddev, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { + /* For live dumps we are reading from the file, not the device. */ + if (read(livecore ? fdcore : fddev, dumpkey, dumpkeysize) != + (ssize_t)dumpkeysize) { logmsg(LOG_ERR, "Unable to read kernel dump key: %m."); nerr++; goto closeall; @@ -1013,6 +1143,9 @@ if (DoTextdumpFile(fddev, dumplength, lasthd, buf, device, corename, core) < 0) goto closeall; + } else if (livecore) { + if (DoLivedumpFile(fdcore, dumplength, buf, corename, core) < 0) + goto closeall; } else { if (DoRegularFile(fddev, dumplength, sectorsize, !(compress || iscompressed || isencrypted), @@ -1045,12 +1178,16 @@ if ((iscompressed && !uncompress) || compress) { snprintf(linkname, sizeof(linkname), "%s.last.%s", istextdump ? "textdump.tar" : - (isencrypted ? "vmcore_encrypted" : "vmcore"), + (livecore ? + (isencrypted ? "livecore" : "livecore_encrypted") : + (isencrypted ? "vmcore_encrypted" : "vmcore")), (kdhl.compression == KERNELDUMP_COMP_ZSTD) ? "zst" : "gz"); } else { snprintf(linkname, sizeof(linkname), "%s.last", istextdump ? "textdump.tar" : - (isencrypted ? "vmcore_encrypted" : "vmcore")); + (livecore ? + (isencrypted ? "livecore" : "livecore_encrypted") : + (isencrypted ? "vmcore_encrypted" : "vmcore"))); } if (symlinkat(corename, savedirfd, linkname) == -1) { logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", @@ -1063,7 +1200,7 @@ printf("dump saved\n"); nuke: - if (!keep) { + if (!keep && !livecore) { if (verbose) printf("clearing dump header\n"); memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic)); @@ -1214,6 +1351,7 @@ xo_error("%s\n%s\n%s\n", "usage: savecore -c [-v] [device ...]", " savecore -C [-v] [device ...]", + " savecore -L [-fuv] [-m maxdumps] [directory]", " savecore [-fkvz] [-m maxdumps] [directory [device ...]]"); exit(1); } @@ -1226,7 +1364,7 @@ char **devs; int i, ch, error, savedirfd; - checkfor = compress = clear = force = keep = false; + checkfor = compress = clear = force = keep = livecore = false; verbose = 0; nfound = nsaved = nerr = 0; savedir = "."; @@ -1238,7 +1376,7 @@ if (argc < 0) exit(1); - while ((ch = getopt(argc, argv, "Ccfkm:uvz")) != -1) + while ((ch = getopt(argc, argv, "CcfkLm:uvz")) != -1) switch(ch) { case 'C': checkfor = true; @@ -1252,6 +1390,9 @@ case 'k': keep = true; break; + case 'L': + livecore = true; + break; case 'm': maxdumps = atoi(optarg); if (maxdumps <= 0) { @@ -1280,6 +1421,8 @@ usage(); if (compress && uncompress) usage(); + if (livecore && (checkfor || clear || compress || keep)) + usage(); argc -= optind; argv += optind; if (argc >= 1 && !checkfor && !clear) { @@ -1292,7 +1435,15 @@ argc--; argv++; } - if (argc == 0) + if (livecore) { + if (argc > 0) + usage(); + + /* Always need /dev/mem to invoke the dump */ + devs = malloc(sizeof(char *)); + devs[0] = strdup("/dev/mem"); + argc++; + } else if (argc == 0) devs = enum_dumpdevs(&argc); else devs = devify(argc, argv); @@ -1305,6 +1456,10 @@ (void)cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL, CAP_FSTATAT, CAP_FSTATFS, CAP_PREAD, CAP_SYMLINKAT, CAP_FTRUNCATE, CAP_UNLINKAT, CAP_WRITE); + if (livecore) + /* Rename rights are needed. */ + cap_rights_set(&rights, CAP_RENAMEAT_SOURCE, + CAP_RENAMEAT_TARGET); if (caph_rights_limit(savedirfd, &rights) < 0) { logmsg(LOG_ERR, "cap_rights_limit(): %m"); exit(1);