1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 2002 Poul-Henning Kamp 5 * Copyright (c) 2002 Networks Associates Technology, Inc. 6 * All rights reserved. 7 * 8 * This software was developed for the FreeBSD Project by Poul-Henning Kamp 9 * and NAI Labs, the Security Research Division of Network Associates, Inc. 10 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the 11 * DARPA CHATS research program. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. The names of the authors may not be used to endorse or promote 22 * products derived from this software without specific prior written 23 * permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 * 37 * Copyright (c) 1986, 1992, 1993 38 * The Regents of the University of California. All rights reserved. 39 * 40 * Redistribution and use in source and binary forms, with or without 41 * modification, are permitted provided that the following conditions 42 * are met: 43 * 1. Redistributions of source code must retain the above copyright 44 * notice, this list of conditions and the following disclaimer. 45 * 2. Redistributions in binary form must reproduce the above copyright 46 * notice, this list of conditions and the following disclaimer in the 47 * documentation and/or other materials provided with the distribution. 48 * 3. Neither the name of the University nor the names of its contributors 49 * may be used to endorse or promote products derived from this software 50 * without specific prior written permission. 51 * 52 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 53 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 54 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 55 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 56 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 57 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 58 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 59 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 60 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 61 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 62 * SUCH DAMAGE. 63 */ 64 65 #include <sys/cdefs.h> 66 #include <sys/param.h> 67 #include <sys/disk.h> 68 #include <sys/kerneldump.h> 69 #include <sys/memrange.h> 70 #include <sys/mount.h> 71 #include <sys/stat.h> 72 73 #include <capsicum_helpers.h> 74 #include <ctype.h> 75 #include <errno.h> 76 #include <fcntl.h> 77 #include <fstab.h> 78 #include <paths.h> 79 #include <signal.h> 80 #include <stdarg.h> 81 #include <stdbool.h> 82 #include <stdio.h> 83 #include <stdlib.h> 84 #include <string.h> 85 #include <syslog.h> 86 #include <time.h> 87 #include <unistd.h> 88 #define Z_SOLO 89 #include <zlib.h> 90 #include <zstd.h> 91 92 #include <libcasper.h> 93 #include <casper/cap_fileargs.h> 94 #include <casper/cap_syslog.h> 95 96 #include <libxo/xo.h> 97 98 /* The size of the buffer used for I/O. */ 99 #define BUFFERSIZE (1024*1024) 100 101 #define STATUS_BAD 0 102 #define STATUS_GOOD 1 103 #define STATUS_UNKNOWN 2 104 105 static cap_channel_t *capsyslog; 106 static fileargs_t *capfa; 107 static bool checkfor, compress, uncompress, clear, force, keep; /* flags */ 108 static bool livecore; /* flags cont. */ 109 static int verbose; 110 static int nfound, nsaved, nerr; /* statistics */ 111 static int maxdumps; 112 static uint8_t comp_desired; 113 114 extern FILE *zdopen(int, const char *); 115 116 static sig_atomic_t got_siginfo; 117 static void infohandler(int); 118 119 static void 120 logmsg(int pri, const char *fmt, ...) 121 { 122 va_list ap; 123 124 va_start(ap, fmt); 125 if (capsyslog != NULL) 126 cap_vsyslog(capsyslog, pri, fmt, ap); 127 else 128 vsyslog(pri, fmt, ap); 129 va_end(ap); 130 } 131 132 static FILE * 133 xfopenat(int dirfd, const char *path, int flags, const char *modestr, ...) 134 { 135 va_list ap; 136 FILE *fp; 137 mode_t mode; 138 int error, fd; 139 140 if ((flags & O_CREAT) == O_CREAT) { 141 va_start(ap, modestr); 142 mode = (mode_t)va_arg(ap, int); 143 va_end(ap); 144 } else 145 mode = 0; 146 147 fd = openat(dirfd, path, flags, mode); 148 if (fd < 0) 149 return (NULL); 150 fp = fdopen(fd, modestr); 151 if (fp == NULL) { 152 error = errno; 153 (void)close(fd); 154 errno = error; 155 } 156 return (fp); 157 } 158 159 static void 160 printheader(xo_handle_t *xo, const struct kerneldumpheader *h, 161 const char *device, int bounds, const int status) 162 { 163 uint64_t dumplen; 164 time_t t; 165 struct tm tm; 166 char time_str[64]; 167 const char *stat_str; 168 const char *comp_str; 169 170 xo_flush_h(xo); 171 xo_emit_h(xo, "{Lwc:Dump header from device}{:dump_device/%s}\n", 172 device); 173 xo_emit_h(xo, "{P: }{Lwc:Architecture}{:architecture/%s}\n", 174 h->architecture); 175 xo_emit_h(xo, 176 "{P: }{Lwc:Architecture Version}{:architecture_version/%u}\n", 177 dtoh32(h->architectureversion)); 178 dumplen = dtoh64(h->dumplength); 179 xo_emit_h(xo, "{P: }{Lwc:Dump Length}{:dump_length_bytes/%lld}\n", 180 (long long)dumplen); 181 xo_emit_h(xo, "{P: }{Lwc:Blocksize}{:blocksize/%d}\n", 182 dtoh32(h->blocksize)); 183 switch (h->compression) { 184 case KERNELDUMP_COMP_NONE: 185 comp_str = "none"; 186 break; 187 case KERNELDUMP_COMP_GZIP: 188 comp_str = "gzip"; 189 break; 190 case KERNELDUMP_COMP_ZSTD: 191 comp_str = "zstd"; 192 break; 193 default: 194 comp_str = "???"; 195 break; 196 } 197 xo_emit_h(xo, "{P: }{Lwc:Compression}{:compression/%s}\n", comp_str); 198 t = dtoh64(h->dumptime); 199 localtime_r(&t, &tm); 200 if (strftime(time_str, sizeof(time_str), "%F %T %z", &tm) == 0) 201 time_str[0] = '\0'; 202 xo_emit_h(xo, "{P: }{Lwc:Dumptime}{:dumptime/%s}\n", time_str); 203 xo_emit_h(xo, "{P: }{Lwc:Hostname}{:hostname/%s}\n", h->hostname); 204 xo_emit_h(xo, "{P: }{Lwc:Magic}{:magic/%s}\n", h->magic); 205 xo_emit_h(xo, "{P: }{Lwc:Version String}{:version_string/%s}", 206 h->versionstring); 207 xo_emit_h(xo, "{P: }{Lwc:Panic String}{:panic_string/%s}\n", 208 h->panicstring); 209 xo_emit_h(xo, "{P: }{Lwc:Dump Parity}{:dump_parity/%u}\n", h->parity); 210 xo_emit_h(xo, "{P: }{Lwc:Bounds}{:bounds/%d}\n", bounds); 211 212 switch (status) { 213 case STATUS_BAD: 214 stat_str = "bad"; 215 break; 216 case STATUS_GOOD: 217 stat_str = "good"; 218 break; 219 default: 220 stat_str = "unknown"; 221 break; 222 } 223 xo_emit_h(xo, "{P: }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str); 224 xo_flush_h(xo); 225 } 226 227 static int 228 getbounds(int savedirfd) 229 { 230 FILE *fp; 231 char buf[6]; 232 int ret; 233 234 /* 235 * If we are just checking, then we haven't done a chdir to the dump 236 * directory and we should not try to read a bounds file. 237 */ 238 if (checkfor) 239 return (0); 240 241 ret = 0; 242 243 if ((fp = xfopenat(savedirfd, "bounds", O_RDONLY, "r")) == NULL) { 244 if (verbose) 245 printf("unable to open bounds file, using 0\n"); 246 return (ret); 247 } 248 if (fgets(buf, sizeof(buf), fp) == NULL) { 249 if (feof(fp)) 250 logmsg(LOG_WARNING, "bounds file is empty, using 0"); 251 else 252 logmsg(LOG_WARNING, "bounds file: %s", strerror(errno)); 253 fclose(fp); 254 return (ret); 255 } 256 257 errno = 0; 258 ret = (int)strtol(buf, NULL, 10); 259 if (ret == 0 && (errno == EINVAL || errno == ERANGE)) 260 logmsg(LOG_WARNING, "invalid value found in bounds, using 0"); 261 if (maxdumps > 0 && ret == maxdumps) 262 ret = 0; 263 fclose(fp); 264 return (ret); 265 } 266 267 static void 268 writebounds(int savedirfd, int bounds) 269 { 270 FILE *fp; 271 272 if ((fp = xfopenat(savedirfd, "bounds", O_WRONLY | O_CREAT | O_TRUNC, 273 "w", 0644)) == NULL) { 274 logmsg(LOG_WARNING, "unable to write to bounds file: %m"); 275 return; 276 } 277 278 if (verbose) 279 printf("bounds number: %d\n", bounds); 280 281 fprintf(fp, "%d\n", bounds); 282 fclose(fp); 283 } 284 285 static bool 286 writekey(int savedirfd, const char *keyname, uint8_t *dumpkey, 287 uint32_t dumpkeysize) 288 { 289 int fd; 290 291 fd = openat(savedirfd, keyname, O_WRONLY | O_CREAT | O_TRUNC, 0600); 292 if (fd == -1) { 293 logmsg(LOG_ERR, "Unable to open %s to write the key: %m.", 294 keyname); 295 return (false); 296 } 297 298 if (write(fd, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { 299 logmsg(LOG_ERR, "Unable to write the key to %s: %m.", keyname); 300 close(fd); 301 return (false); 302 } 303 304 close(fd); 305 return (true); 306 } 307 308 static int 309 write_header_info(xo_handle_t *xostdout, const struct kerneldumpheader *kdh, 310 int savedirfd, const char *infoname, const char *device, int bounds, 311 int status) 312 { 313 xo_handle_t *xoinfo; 314 FILE *info; 315 316 /* 317 * Create or overwrite any existing dump header files. 318 */ 319 if ((info = xfopenat(savedirfd, infoname, 320 O_WRONLY | O_CREAT | O_TRUNC, "w", 0600)) == NULL) { 321 logmsg(LOG_ERR, "open(%s): %m", infoname); 322 return (-1); 323 } 324 325 xoinfo = xo_create_to_file(info, xo_get_style(NULL), 0); 326 if (xoinfo == NULL) { 327 logmsg(LOG_ERR, "%s: %m", infoname); 328 fclose(info); 329 return (-1); 330 } 331 xo_open_container_h(xoinfo, "crashdump"); 332 333 if (verbose) 334 printheader(xostdout, kdh, device, bounds, status); 335 336 printheader(xoinfo, kdh, device, bounds, status); 337 xo_close_container_h(xoinfo, "crashdump"); 338 xo_flush_h(xoinfo); 339 xo_finish_h(xoinfo); 340 fclose(info); 341 342 return (0); 343 } 344 345 static off_t 346 file_size(int savedirfd, const char *path) 347 { 348 struct stat sb; 349 350 /* Ignore all errors, this file may not exist. */ 351 if (fstatat(savedirfd, path, &sb, 0) == -1) 352 return (0); 353 return (sb.st_size); 354 } 355 356 static off_t 357 saved_dump_size(int savedirfd, int bounds) 358 { 359 char path[32]; 360 off_t dumpsize; 361 362 dumpsize = 0; 363 364 (void)snprintf(path, sizeof(path), "info.%d", bounds); 365 dumpsize += file_size(savedirfd, path); 366 (void)snprintf(path, sizeof(path), "vmcore.%d", bounds); 367 dumpsize += file_size(savedirfd, path); 368 (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds); 369 dumpsize += file_size(savedirfd, path); 370 (void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds); 371 dumpsize += file_size(savedirfd, path); 372 (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds); 373 dumpsize += file_size(savedirfd, path); 374 (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); 375 dumpsize += file_size(savedirfd, path); 376 377 return (dumpsize); 378 } 379 380 static void 381 saved_dump_remove(int savedirfd, int bounds) 382 { 383 char path[32]; 384 385 (void)snprintf(path, sizeof(path), "info.%d", bounds); 386 (void)unlinkat(savedirfd, path, 0); 387 (void)snprintf(path, sizeof(path), "vmcore.%d", bounds); 388 (void)unlinkat(savedirfd, path, 0); 389 (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds); 390 (void)unlinkat(savedirfd, path, 0); 391 (void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds); 392 (void)unlinkat(savedirfd, path, 0); 393 (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds); 394 (void)unlinkat(savedirfd, path, 0); 395 (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds); 396 (void)unlinkat(savedirfd, path, 0); 397 (void)snprintf(path, sizeof(path), "livecore.%d", bounds); 398 (void)unlinkat(savedirfd, path, 0); 399 (void)snprintf(path, sizeof(path), "livecore.%d.gz", bounds); 400 (void)unlinkat(savedirfd, path, 0); 401 (void)snprintf(path, sizeof(path), "livecore.%d.zst", bounds); 402 (void)unlinkat(savedirfd, path, 0); 403 } 404 405 static void 406 symlinks_remove(int savedirfd) 407 { 408 409 (void)unlinkat(savedirfd, "info.last", 0); 410 (void)unlinkat(savedirfd, "key.last", 0); 411 (void)unlinkat(savedirfd, "vmcore.last", 0); 412 (void)unlinkat(savedirfd, "vmcore.last.gz", 0); 413 (void)unlinkat(savedirfd, "vmcore.last.zst", 0); 414 (void)unlinkat(savedirfd, "vmcore_encrypted.last", 0); 415 (void)unlinkat(savedirfd, "vmcore_encrypted.last.gz", 0); 416 (void)unlinkat(savedirfd, "textdump.tar.last", 0); 417 (void)unlinkat(savedirfd, "textdump.tar.last.gz", 0); 418 (void)unlinkat(savedirfd, "livecore.last", 0); 419 (void)unlinkat(savedirfd, "livecore.last.gz", 0); 420 (void)unlinkat(savedirfd, "livecore.last.zst", 0); 421 } 422 423 /* 424 * Check that sufficient space is available on the disk that holds the 425 * save directory. 426 */ 427 static int 428 check_space(const char *savedir, int savedirfd, off_t dumpsize, int bounds) 429 { 430 char buf[100]; 431 struct statfs fsbuf; 432 FILE *fp; 433 off_t available, minfree, spacefree, totfree, needed; 434 435 if (fstatfs(savedirfd, &fsbuf) < 0) { 436 logmsg(LOG_ERR, "%s: %m", savedir); 437 exit(1); 438 } 439 spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024; 440 totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024; 441 442 if ((fp = xfopenat(savedirfd, "minfree", O_RDONLY, "r")) == NULL) 443 minfree = 0; 444 else { 445 if (fgets(buf, sizeof(buf), fp) == NULL) 446 minfree = 0; 447 else { 448 char *endp; 449 450 errno = 0; 451 minfree = strtoll(buf, &endp, 10); 452 if (minfree == 0 && errno != 0) 453 minfree = -1; 454 else { 455 while (*endp != '\0' && isspace(*endp)) 456 endp++; 457 if (*endp != '\0' || minfree < 0) 458 minfree = -1; 459 } 460 if (minfree < 0) 461 logmsg(LOG_WARNING, 462 "`minfree` didn't contain a valid size " 463 "(`%s`). Defaulting to 0", buf); 464 } 465 (void)fclose(fp); 466 } 467 468 available = minfree > 0 ? spacefree - minfree : totfree; 469 needed = dumpsize / 1024 + 2; /* 2 for info file */ 470 needed -= saved_dump_size(savedirfd, bounds); 471 if (available < needed) { 472 logmsg(LOG_WARNING, 473 "no dump: not enough free space on device (need at least " 474 "%jdkB for dump; %jdkB available; %jdkB reserved)", 475 (intmax_t)needed, 476 (intmax_t)available + minfree, 477 (intmax_t)minfree); 478 return (0); 479 } 480 if (spacefree - needed < 0) 481 logmsg(LOG_WARNING, 482 "dump performed, but free space threshold crossed"); 483 return (1); 484 } 485 486 static bool 487 compare_magic(const struct kerneldumpheader *kdh, const char *magic) 488 { 489 490 return (strncmp(kdh->magic, magic, sizeof(kdh->magic)) == 0); 491 } 492 493 #define BLOCKSIZE (1<<12) 494 #define BLOCKMASK (~(BLOCKSIZE-1)) 495 496 static size_t 497 sparsefwrite(const char *buf, size_t nr, FILE *fp) 498 { 499 size_t nw, he, hs; 500 501 for (nw = 0; nw < nr; nw = he) { 502 /* find a contiguous block of zeroes */ 503 for (hs = nw; hs < nr; hs += BLOCKSIZE) { 504 for (he = hs; he < nr && buf[he] == 0; ++he) 505 /* nothing */ ; 506 /* is the hole long enough to matter? */ 507 if (he >= hs + BLOCKSIZE) 508 break; 509 } 510 511 /* back down to a block boundary */ 512 he &= BLOCKMASK; 513 514 /* 515 * 1) Don't go beyond the end of the buffer. 516 * 2) If the end of the buffer is less than 517 * BLOCKSIZE bytes away, we're at the end 518 * of the file, so just grab what's left. 519 */ 520 if (hs + BLOCKSIZE > nr) 521 hs = he = nr; 522 523 /* 524 * At this point, we have a partial ordering: 525 * nw <= hs <= he <= nr 526 * If hs > nw, buf[nw..hs] contains non-zero 527 * data. If he > hs, buf[hs..he] is all zeroes. 528 */ 529 if (hs > nw) 530 if (fwrite(buf + nw, hs - nw, 1, fp) != 1) 531 break; 532 if (he > hs) 533 if (fseeko(fp, he - hs, SEEK_CUR) == -1) 534 break; 535 } 536 537 return (nw); 538 } 539 540 static char *zbuf; 541 static size_t zbufsize; 542 543 static ssize_t 544 GunzipWrite(z_stream *z, char *in, size_t insize, FILE *fp) 545 { 546 static bool firstblock = true; /* XXX not re-entrable/usable */ 547 const size_t hdrlen = 10; 548 size_t nw = 0, w; 549 int rv; 550 551 z->next_in = in; 552 z->avail_in = insize; 553 /* 554 * Since contrib/zlib for some reason is compiled 555 * without GUNZIP define, we need to skip the gzip 556 * header manually. Kernel puts minimal 10 byte 557 * header, see sys/kern/subr_compressor.c:gz_reset(). 558 */ 559 if (firstblock) { 560 z->next_in += hdrlen; 561 z->avail_in -= hdrlen; 562 firstblock = false; 563 } 564 do { 565 z->next_out = zbuf; 566 z->avail_out = zbufsize; 567 rv = inflate(z, Z_NO_FLUSH); 568 if (rv != Z_OK && rv != Z_STREAM_END) { 569 logmsg(LOG_ERR, "decompression failed: %s", z->msg); 570 return (-1); 571 } 572 w = sparsefwrite(zbuf, zbufsize - z->avail_out, fp); 573 if (w < zbufsize - z->avail_out) 574 return (-1); 575 nw += w; 576 } while (z->avail_in > 0 && rv != Z_STREAM_END); 577 578 return (nw); 579 } 580 581 static ssize_t 582 ZstdWrite(ZSTD_DCtx *Zctx, char *in, size_t insize, FILE *fp) 583 { 584 ZSTD_inBuffer Zin; 585 ZSTD_outBuffer Zout; 586 size_t nw = 0, w; 587 int rv; 588 589 Zin.src = in; 590 Zin.size = insize; 591 Zin.pos = 0; 592 do { 593 Zout.dst = zbuf; 594 Zout.size = zbufsize; 595 Zout.pos = 0; 596 rv = ZSTD_decompressStream(Zctx, &Zout, &Zin); 597 if (ZSTD_isError(rv)) { 598 logmsg(LOG_ERR, "decompression failed: %s", 599 ZSTD_getErrorName(rv)); 600 return (-1); 601 } 602 w = sparsefwrite(zbuf, Zout.pos, fp); 603 if (w < Zout.pos) 604 return (-1); 605 nw += w; 606 } while (Zin.pos < Zin.size && rv != 0); 607 608 return (nw); 609 } 610 611 static int 612 DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse, 613 uint8_t compression, char *buf, const char *device, 614 const char *filename, FILE *fp) 615 { 616 size_t nr, wl; 617 ssize_t nw; 618 off_t dmpcnt, origsize; 619 z_stream z; /* gzip */ 620 ZSTD_DCtx *Zctx; /* zstd */ 621 622 dmpcnt = 0; 623 origsize = dumpsize; 624 if (compression == KERNELDUMP_COMP_GZIP) { 625 memset(&z, 0, sizeof(z)); 626 z.zalloc = Z_NULL; 627 z.zfree = Z_NULL; 628 if (inflateInit2(&z, -MAX_WBITS) != Z_OK) { 629 logmsg(LOG_ERR, "failed to initialize zlib: %s", z.msg); 630 return (-1); 631 } 632 zbufsize = BUFFERSIZE; 633 } else if (compression == KERNELDUMP_COMP_ZSTD) { 634 if ((Zctx = ZSTD_createDCtx()) == NULL) { 635 logmsg(LOG_ERR, "failed to initialize zstd"); 636 return (-1); 637 } 638 zbufsize = ZSTD_DStreamOutSize(); 639 } 640 if (zbufsize > 0) 641 if ((zbuf = malloc(zbufsize)) == NULL) { 642 logmsg(LOG_ERR, "failed to alloc decompression buffer"); 643 return (-1); 644 } 645 646 while (dumpsize > 0) { 647 wl = BUFFERSIZE; 648 if (wl > (size_t)dumpsize) 649 wl = dumpsize; 650 nr = read(fd, buf, roundup(wl, sectorsize)); 651 if (nr != roundup(wl, sectorsize)) { 652 if (nr == 0) 653 logmsg(LOG_WARNING, 654 "WARNING: EOF on dump device"); 655 else 656 logmsg(LOG_ERR, "read error on %s: %m", device); 657 nerr++; 658 return (-1); 659 } 660 if (compression == KERNELDUMP_COMP_GZIP) 661 nw = GunzipWrite(&z, buf, nr, fp); 662 else if (compression == KERNELDUMP_COMP_ZSTD) 663 nw = ZstdWrite(Zctx, buf, nr, fp); 664 else if (!sparse) 665 nw = fwrite(buf, 1, wl, fp); 666 else 667 nw = sparsefwrite(buf, wl, fp); 668 if (nw < 0 || (compression == KERNELDUMP_COMP_NONE && 669 (size_t)nw != wl)) { 670 logmsg(LOG_ERR, 671 "write error on %s file: %m", filename); 672 logmsg(LOG_WARNING, 673 "WARNING: vmcore may be incomplete"); 674 nerr++; 675 return (-1); 676 } 677 if (verbose) { 678 dmpcnt += wl; 679 printf("%llu\r", (unsigned long long)dmpcnt); 680 fflush(stdout); 681 } 682 dumpsize -= wl; 683 if (got_siginfo) { 684 printf("%s %.1lf%%\n", filename, (100.0 - (100.0 * 685 (double)dumpsize / (double)origsize))); 686 got_siginfo = 0; 687 } 688 } 689 return (0); 690 } 691 692 /* 693 * Specialized version of dump-reading logic for use with textdumps, which 694 * are written backwards from the end of the partition, and must be reversed 695 * before being written to the file. Textdumps are small, so do a bit less 696 * work to optimize/sparsify. 697 */ 698 static int 699 DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf, 700 const char *device, const char *filename, FILE *fp) 701 { 702 int nr, nw, wl; 703 off_t dmpcnt, totsize; 704 705 totsize = dumpsize; 706 dmpcnt = 0; 707 wl = 512; 708 if ((dumpsize % wl) != 0) { 709 logmsg(LOG_ERR, "textdump uneven multiple of 512 on %s", 710 device); 711 nerr++; 712 return (-1); 713 } 714 while (dumpsize > 0) { 715 nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl); 716 if (nr != wl) { 717 if (nr == 0) 718 logmsg(LOG_WARNING, 719 "WARNING: EOF on dump device"); 720 else 721 logmsg(LOG_ERR, "read error on %s: %m", device); 722 nerr++; 723 return (-1); 724 } 725 nw = fwrite(buf, 1, wl, fp); 726 if (nw != wl) { 727 logmsg(LOG_ERR, 728 "write error on %s file: %m", filename); 729 logmsg(LOG_WARNING, 730 "WARNING: textdump may be incomplete"); 731 nerr++; 732 return (-1); 733 } 734 if (verbose) { 735 dmpcnt += wl; 736 printf("%llu\r", (unsigned long long)dmpcnt); 737 fflush(stdout); 738 } 739 dumpsize -= wl; 740 } 741 return (0); 742 } 743 744 static void 745 DoLiveFile(const char *savedir, int savedirfd, const char *device) 746 { 747 char infoname[32], corename[32], linkname[32], tmpname[32]; 748 struct mem_livedump_arg marg; 749 struct kerneldumpheader kdhl; 750 xo_handle_t *xostdout; 751 off_t dumplength; 752 uint32_t version; 753 int fddev, fdcore; 754 int bounds; 755 int error, status; 756 757 bounds = getbounds(savedirfd); 758 status = STATUS_UNKNOWN; 759 760 xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0); 761 if (xostdout == NULL) { 762 logmsg(LOG_ERR, "xo_create_to_file() failed: %m"); 763 return; 764 } 765 766 /* 767 * Create a temporary file. We will invoke the live dump and its 768 * contents will be written to this fd. After validating and removing 769 * the kernel dump header from the tail-end of this file, it will be 770 * renamed to its definitive filename (e.g. livecore.2.gz). 771 * 772 * If any errors are encountered before the rename, the temporary file 773 * is unlinked. 774 */ 775 strcpy(tmpname, "livecore.tmp.XXXXXX"); 776 fdcore = mkostempsat(savedirfd, tmpname, 0, 0); 777 if (fdcore < 0) { 778 logmsg(LOG_ERR, "error opening temp file: %m"); 779 return; 780 } 781 782 fddev = fileargs_open(capfa, device); 783 if (fddev < 0) { 784 logmsg(LOG_ERR, "%s: %m", device); 785 goto unlinkexit; 786 } 787 788 bzero(&marg, sizeof(marg)); 789 marg.fd = fdcore; 790 marg.compression = comp_desired; 791 if (ioctl(fddev, MEM_KERNELDUMP, &marg) == -1) { 792 logmsg(LOG_ERR, 793 "failed to invoke live-dump on system: %m"); 794 close(fddev); 795 goto unlinkexit; 796 } 797 798 /* Close /dev/mem fd, we are finished with it. */ 799 close(fddev); 800 801 /* Seek to the end of the file, minus the size of the header. */ 802 if (lseek(fdcore, -(off_t)sizeof(kdhl), SEEK_END) == -1) { 803 logmsg(LOG_ERR, "failed to lseek: %m"); 804 goto unlinkexit; 805 } 806 807 if (read(fdcore, &kdhl, sizeof(kdhl)) != sizeof(kdhl)) { 808 logmsg(LOG_ERR, "failed to read kernel dump header: %m"); 809 goto unlinkexit; 810 } 811 /* Reset cursor */ 812 (void)lseek(fdcore, 0, SEEK_SET); 813 814 /* Validate the dump header. */ 815 version = dtoh32(kdhl.version); 816 if (compare_magic(&kdhl, KERNELDUMPMAGIC)) { 817 if (version != KERNELDUMPVERSION) { 818 logmsg(LOG_ERR, 819 "unknown version (%d) in dump header on %s", 820 version, device); 821 goto unlinkexit; 822 } else if (kdhl.compression != comp_desired) { 823 /* This should be impossible. */ 824 logmsg(LOG_ERR, 825 "dump compression (%u) doesn't match request (%u)", 826 kdhl.compression, comp_desired); 827 if (!force) 828 goto unlinkexit; 829 } 830 } else { 831 logmsg(LOG_ERR, "magic mismatch on live dump header"); 832 goto unlinkexit; 833 } 834 if (kerneldump_parity(&kdhl)) { 835 logmsg(LOG_ERR, 836 "parity error on last dump header on %s", device); 837 nerr++; 838 status = STATUS_BAD; 839 if (!force) 840 goto unlinkexit; 841 } else { 842 status = STATUS_GOOD; 843 } 844 845 nfound++; 846 dumplength = dtoh64(kdhl.dumplength); 847 if (dtoh32(kdhl.dumpkeysize) != 0) { 848 logmsg(LOG_ERR, 849 "dump header unexpectedly reported keysize > 0"); 850 goto unlinkexit; 851 } 852 853 /* Remove the vestigial kernel dump header. */ 854 error = ftruncate(fdcore, dumplength); 855 if (error != 0) { 856 logmsg(LOG_ERR, "failed to truncate the core file: %m"); 857 goto unlinkexit; 858 } 859 860 if (verbose >= 2) { 861 printf("\nDump header:\n"); 862 printheader(xostdout, &kdhl, device, bounds, -1); 863 printf("\n"); 864 } 865 logmsg(LOG_ALERT, "livedump"); 866 867 writebounds(savedirfd, bounds + 1); 868 saved_dump_remove(savedirfd, bounds); 869 870 snprintf(corename, sizeof(corename), "livecore.%d", bounds); 871 if (compress) 872 strcat(corename, kdhl.compression == KERNELDUMP_COMP_ZSTD ? 873 ".zst" : ".gz"); 874 875 if (verbose) 876 printf("renaming %s to %s\n", tmpname, corename); 877 if (renameat(savedirfd, tmpname, savedirfd, corename) != 0) { 878 logmsg(LOG_ERR, "renameat failed: %m"); 879 goto unlinkexit; 880 } 881 882 snprintf(infoname, sizeof(infoname), "info.%d", bounds); 883 if (write_header_info(xostdout, &kdhl, savedirfd, infoname, device, 884 bounds, status) != 0) { 885 nerr++; 886 return; 887 } 888 889 logmsg(LOG_NOTICE, "writing %score to %s/%s", 890 compress ? "compressed " : "", savedir, corename); 891 892 if (verbose) 893 printf("\n"); 894 895 symlinks_remove(savedirfd); 896 if (symlinkat(infoname, savedirfd, "info.last") == -1) { 897 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", 898 savedir, "info.last"); 899 } 900 901 snprintf(linkname, sizeof(linkname), "livecore.last"); 902 if (compress) 903 strcat(linkname, kdhl.compression == KERNELDUMP_COMP_ZSTD ? 904 ".zst" : ".gz"); 905 if (symlinkat(corename, savedirfd, linkname) == -1) { 906 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", 907 savedir, linkname); 908 } 909 910 nsaved++; 911 if (verbose) 912 printf("dump saved\n"); 913 914 close(fdcore); 915 return; 916 unlinkexit: 917 funlinkat(savedirfd, tmpname, fdcore, 0); 918 close(fdcore); 919 } 920 921 static void 922 DoFile(const char *savedir, int savedirfd, const char *device) 923 { 924 static char *buf = NULL; 925 xo_handle_t *xostdout; 926 char infoname[32], corename[32], linkname[32], keyname[32]; 927 char *temp = NULL; 928 struct kerneldumpheader kdhf, kdhl; 929 uint8_t *dumpkey; 930 off_t mediasize, dumpextent, dumplength, firsthd, lasthd; 931 FILE *core; 932 int fdcore, fddev, error; 933 int bounds, status; 934 u_int sectorsize; 935 uint32_t dumpkeysize; 936 bool iscompressed, isencrypted, istextdump, ret; 937 938 /* Live kernel dumps are handled separately. */ 939 if (livecore) { 940 DoLiveFile(savedir, savedirfd, device); 941 return; 942 } 943 944 bounds = getbounds(savedirfd); 945 dumpkey = NULL; 946 mediasize = 0; 947 status = STATUS_UNKNOWN; 948 949 xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0); 950 if (xostdout == NULL) { 951 logmsg(LOG_ERR, "xo_create_to_file() failed: %m"); 952 return; 953 } 954 955 if (buf == NULL) { 956 buf = malloc(BUFFERSIZE); 957 if (buf == NULL) { 958 logmsg(LOG_ERR, "%m"); 959 return; 960 } 961 } 962 963 if (verbose) 964 printf("checking for kernel dump on device %s\n", device); 965 966 fddev = fileargs_open(capfa, device); 967 if (fddev < 0) { 968 logmsg(LOG_ERR, "%s: %m", device); 969 return; 970 } 971 972 error = ioctl(fddev, DIOCGMEDIASIZE, &mediasize); 973 if (!error) 974 error = ioctl(fddev, DIOCGSECTORSIZE, §orsize); 975 if (error) { 976 logmsg(LOG_ERR, 977 "couldn't find media and/or sector size of %s: %m", device); 978 goto closefd; 979 } 980 981 if (verbose) { 982 printf("mediasize = %lld bytes\n", (long long)mediasize); 983 printf("sectorsize = %u bytes\n", sectorsize); 984 } 985 986 if (sectorsize < sizeof(kdhl)) { 987 logmsg(LOG_ERR, 988 "Sector size is less the kernel dump header %zu", 989 sizeof(kdhl)); 990 goto closefd; 991 } 992 993 lasthd = mediasize - sectorsize; 994 temp = malloc(sectorsize); 995 if (temp == NULL) { 996 logmsg(LOG_ERR, "%m"); 997 goto closefd; 998 } 999 if (lseek(fddev, lasthd, SEEK_SET) != lasthd || 1000 read(fddev, temp, sectorsize) != (ssize_t)sectorsize) { 1001 logmsg(LOG_ERR, 1002 "error reading last dump header at offset %lld in %s: %m", 1003 (long long)lasthd, device); 1004 goto closefd; 1005 } 1006 memcpy(&kdhl, temp, sizeof(kdhl)); 1007 iscompressed = istextdump = false; 1008 if (compare_magic(&kdhl, TEXTDUMPMAGIC)) { 1009 if (verbose) 1010 printf("textdump magic on last dump header on %s\n", 1011 device); 1012 istextdump = true; 1013 if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) { 1014 logmsg(LOG_ERR, 1015 "unknown version (%d) in last dump header on %s", 1016 dtoh32(kdhl.version), device); 1017 1018 status = STATUS_BAD; 1019 if (!force) 1020 goto closefd; 1021 } 1022 } else if (compare_magic(&kdhl, KERNELDUMPMAGIC)) { 1023 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { 1024 logmsg(LOG_ERR, 1025 "unknown version (%d) in last dump header on %s", 1026 dtoh32(kdhl.version), device); 1027 1028 status = STATUS_BAD; 1029 if (!force) 1030 goto closefd; 1031 } 1032 switch (kdhl.compression) { 1033 case KERNELDUMP_COMP_NONE: 1034 uncompress = false; 1035 break; 1036 case KERNELDUMP_COMP_GZIP: 1037 case KERNELDUMP_COMP_ZSTD: 1038 if (compress && verbose) 1039 printf("dump is already compressed\n"); 1040 if (uncompress && verbose) 1041 printf("dump to be uncompressed\n"); 1042 compress = false; 1043 iscompressed = true; 1044 break; 1045 default: 1046 logmsg(LOG_ERR, "unknown compression type %d on %s", 1047 kdhl.compression, device); 1048 break; 1049 } 1050 } else { 1051 if (verbose) 1052 printf("magic mismatch on last dump header on %s\n", 1053 device); 1054 1055 status = STATUS_BAD; 1056 if (!force) 1057 goto closefd; 1058 1059 if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) { 1060 if (verbose) 1061 printf("forcing magic on %s\n", device); 1062 memcpy(kdhl.magic, KERNELDUMPMAGIC, sizeof(kdhl.magic)); 1063 } else { 1064 logmsg(LOG_ERR, "unable to force dump - bad magic"); 1065 goto closefd; 1066 } 1067 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { 1068 logmsg(LOG_ERR, 1069 "unknown version (%d) in last dump header on %s", 1070 dtoh32(kdhl.version), device); 1071 1072 status = STATUS_BAD; 1073 if (!force) 1074 goto closefd; 1075 } 1076 } 1077 1078 nfound++; 1079 if (clear) 1080 goto nuke; 1081 1082 if (kerneldump_parity(&kdhl)) { 1083 logmsg(LOG_ERR, 1084 "parity error on last dump header on %s", device); 1085 nerr++; 1086 status = STATUS_BAD; 1087 if (!force) 1088 goto closefd; 1089 } 1090 dumpextent = dtoh64(kdhl.dumpextent); 1091 dumplength = dtoh64(kdhl.dumplength); 1092 dumpkeysize = dtoh32(kdhl.dumpkeysize); 1093 firsthd = lasthd - dumpextent - sectorsize - dumpkeysize; 1094 if (lseek(fddev, firsthd, SEEK_SET) != firsthd || 1095 read(fddev, temp, sectorsize) != (ssize_t)sectorsize) { 1096 logmsg(LOG_ERR, 1097 "error reading first dump header at offset %lld in %s: %m", 1098 (long long)firsthd, device); 1099 nerr++; 1100 goto closefd; 1101 } 1102 memcpy(&kdhf, temp, sizeof(kdhf)); 1103 1104 if (verbose >= 2) { 1105 printf("First dump headers:\n"); 1106 printheader(xostdout, &kdhf, device, bounds, -1); 1107 1108 printf("\nLast dump headers:\n"); 1109 printheader(xostdout, &kdhl, device, bounds, -1); 1110 printf("\n"); 1111 } 1112 1113 if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) { 1114 logmsg(LOG_ERR, 1115 "first and last dump headers disagree on %s", device); 1116 nerr++; 1117 status = STATUS_BAD; 1118 if (!force) 1119 goto closefd; 1120 } else { 1121 status = STATUS_GOOD; 1122 } 1123 1124 if (checkfor) { 1125 printf("A dump exists on %s\n", device); 1126 close(fddev); 1127 exit(0); 1128 } 1129 1130 if (kdhl.panicstring[0] != '\0') 1131 logmsg(LOG_ALERT, "reboot after panic: %.*s", 1132 (int)sizeof(kdhl.panicstring), kdhl.panicstring); 1133 else 1134 logmsg(LOG_ALERT, "reboot"); 1135 1136 if (verbose) 1137 printf("Checking for available free space\n"); 1138 1139 if (!check_space(savedir, savedirfd, dumplength, bounds)) { 1140 nerr++; 1141 goto closefd; 1142 } 1143 1144 writebounds(savedirfd, bounds + 1); 1145 1146 saved_dump_remove(savedirfd, bounds); 1147 1148 isencrypted = (dumpkeysize > 0); 1149 if (compress) 1150 snprintf(corename, sizeof(corename), "%s.%d.gz", 1151 istextdump ? "textdump.tar" : 1152 (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); 1153 else if (iscompressed && !isencrypted && !uncompress) 1154 snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds, 1155 (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst"); 1156 else 1157 snprintf(corename, sizeof(corename), "%s.%d", 1158 istextdump ? "textdump.tar" : 1159 (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds); 1160 fdcore = openat(savedirfd, corename, O_WRONLY | O_CREAT | O_TRUNC, 1161 0600); 1162 if (fdcore < 0) { 1163 logmsg(LOG_ERR, "open(%s): %m", corename); 1164 nerr++; 1165 goto closefd; 1166 } 1167 1168 if (compress) 1169 core = zdopen(fdcore, "w"); 1170 else 1171 core = fdopen(fdcore, "w"); 1172 if (core == NULL) { 1173 logmsg(LOG_ERR, "%s: %m", corename); 1174 (void)close(fdcore); 1175 nerr++; 1176 goto closefd; 1177 } 1178 fdcore = -1; 1179 1180 snprintf(infoname, sizeof(infoname), "info.%d", bounds); 1181 if (write_header_info(xostdout, &kdhl, savedirfd, infoname, device, 1182 bounds, status) != 0) { 1183 nerr++; 1184 goto closeall; 1185 } 1186 1187 if (isencrypted) { 1188 dumpkey = calloc(1, dumpkeysize); 1189 if (dumpkey == NULL) { 1190 logmsg(LOG_ERR, "Unable to allocate kernel dump key."); 1191 nerr++; 1192 goto closeall; 1193 } 1194 1195 if (read(fddev, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) { 1196 logmsg(LOG_ERR, "Unable to read kernel dump key: %m."); 1197 nerr++; 1198 goto closeall; 1199 } 1200 1201 snprintf(keyname, sizeof(keyname), "key.%d", bounds); 1202 ret = writekey(savedirfd, keyname, dumpkey, dumpkeysize); 1203 explicit_bzero(dumpkey, dumpkeysize); 1204 if (!ret) { 1205 nerr++; 1206 goto closeall; 1207 } 1208 } 1209 1210 logmsg(LOG_NOTICE, "writing %s%score to %s/%s", 1211 isencrypted ? "encrypted " : "", compress ? "compressed " : "", 1212 savedir, corename); 1213 1214 if (istextdump) { 1215 if (DoTextdumpFile(fddev, dumplength, lasthd, buf, device, 1216 corename, core) < 0) 1217 goto closeall; 1218 } else { 1219 if (DoRegularFile(fddev, dumplength, sectorsize, 1220 !(compress || iscompressed || isencrypted), 1221 uncompress ? kdhl.compression : KERNELDUMP_COMP_NONE, 1222 buf, device, corename, core) < 0) { 1223 goto closeall; 1224 } 1225 } 1226 if (verbose) 1227 printf("\n"); 1228 1229 if (fclose(core) < 0) { 1230 logmsg(LOG_ERR, "error on %s: %m", corename); 1231 nerr++; 1232 goto closefd; 1233 } 1234 1235 symlinks_remove(savedirfd); 1236 if (symlinkat(infoname, savedirfd, "info.last") == -1) { 1237 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", 1238 savedir, "info.last"); 1239 } 1240 if (isencrypted) { 1241 if (symlinkat(keyname, savedirfd, "key.last") == -1) { 1242 logmsg(LOG_WARNING, 1243 "unable to create symlink %s/%s: %m", savedir, 1244 "key.last"); 1245 } 1246 } 1247 if ((iscompressed && !uncompress) || compress) { 1248 snprintf(linkname, sizeof(linkname), "%s.last.%s", 1249 istextdump ? "textdump.tar" : 1250 (isencrypted ? "vmcore_encrypted" : "vmcore"), 1251 (kdhl.compression == KERNELDUMP_COMP_ZSTD) ? "zst" : "gz"); 1252 } else { 1253 snprintf(linkname, sizeof(linkname), "%s.last", 1254 istextdump ? "textdump.tar" : 1255 (isencrypted ? "vmcore_encrypted" : "vmcore")); 1256 } 1257 if (symlinkat(corename, savedirfd, linkname) == -1) { 1258 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m", 1259 savedir, linkname); 1260 } 1261 1262 nsaved++; 1263 1264 if (verbose) 1265 printf("dump saved\n"); 1266 1267 nuke: 1268 if (!keep) { 1269 if (verbose) 1270 printf("clearing dump header\n"); 1271 memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic)); 1272 memcpy(temp, &kdhl, sizeof(kdhl)); 1273 if (lseek(fddev, lasthd, SEEK_SET) != lasthd || 1274 write(fddev, temp, sectorsize) != (ssize_t)sectorsize) 1275 logmsg(LOG_ERR, 1276 "error while clearing the dump header: %m"); 1277 } 1278 xo_close_container_h(xostdout, "crashdump"); 1279 xo_finish_h(xostdout); 1280 free(dumpkey); 1281 free(temp); 1282 close(fddev); 1283 return; 1284 1285 closeall: 1286 fclose(core); 1287 1288 closefd: 1289 free(dumpkey); 1290 free(temp); 1291 close(fddev); 1292 } 1293 1294 /* Prepend "/dev/" to any arguments that don't already have it */ 1295 static char ** 1296 devify(int argc, char **argv) 1297 { 1298 char **devs; 1299 int i, l; 1300 1301 devs = malloc(argc * sizeof(*argv)); 1302 if (devs == NULL) { 1303 logmsg(LOG_ERR, "malloc(): %m"); 1304 exit(1); 1305 } 1306 for (i = 0; i < argc; i++) { 1307 if (strncmp(argv[i], _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) 1308 devs[i] = strdup(argv[i]); 1309 else { 1310 char *fullpath; 1311 1312 fullpath = malloc(PATH_MAX); 1313 if (fullpath == NULL) { 1314 logmsg(LOG_ERR, "malloc(): %m"); 1315 exit(1); 1316 } 1317 l = snprintf(fullpath, PATH_MAX, "%s%s", _PATH_DEV, 1318 argv[i]); 1319 if (l < 0) { 1320 logmsg(LOG_ERR, "snprintf(): %m"); 1321 exit(1); 1322 } else if (l >= PATH_MAX) { 1323 logmsg(LOG_ERR, "device name too long"); 1324 exit(1); 1325 } 1326 devs[i] = fullpath; 1327 } 1328 } 1329 return (devs); 1330 } 1331 1332 static char ** 1333 enum_dumpdevs(int *argcp) 1334 { 1335 struct fstab *fsp; 1336 char **argv; 1337 int argc, n; 1338 1339 /* 1340 * We cannot use getfsent(3) in capability mode, so we must 1341 * scan /etc/fstab and build up a list of candidate devices 1342 * before proceeding. 1343 */ 1344 argc = 0; 1345 n = 8; 1346 argv = malloc(n * sizeof(*argv)); 1347 if (argv == NULL) { 1348 logmsg(LOG_ERR, "malloc(): %m"); 1349 exit(1); 1350 } 1351 for (;;) { 1352 fsp = getfsent(); 1353 if (fsp == NULL) 1354 break; 1355 if (strcmp(fsp->fs_vfstype, "swap") != 0 && 1356 strcmp(fsp->fs_vfstype, "dump") != 0) 1357 continue; 1358 if (argc >= n) { 1359 n *= 2; 1360 argv = realloc(argv, n * sizeof(*argv)); 1361 if (argv == NULL) { 1362 logmsg(LOG_ERR, "realloc(): %m"); 1363 exit(1); 1364 } 1365 } 1366 argv[argc] = strdup(fsp->fs_spec); 1367 if (argv[argc] == NULL) { 1368 logmsg(LOG_ERR, "strdup(): %m"); 1369 exit(1); 1370 } 1371 argc++; 1372 } 1373 *argcp = argc; 1374 return (argv); 1375 } 1376 1377 static void 1378 init_caps(int argc, char **argv) 1379 { 1380 cap_rights_t rights; 1381 cap_channel_t *capcas; 1382 1383 capcas = cap_init(); 1384 if (capcas == NULL) { 1385 logmsg(LOG_ERR, "cap_init(): %m"); 1386 exit(1); 1387 } 1388 /* 1389 * The fileargs capability does not currently provide a way to limit 1390 * ioctls. 1391 */ 1392 (void)cap_rights_init(&rights, CAP_PREAD, CAP_WRITE, CAP_IOCTL); 1393 capfa = fileargs_init(argc, argv, checkfor || keep ? O_RDONLY : O_RDWR, 1394 0, &rights, FA_OPEN); 1395 if (capfa == NULL) { 1396 logmsg(LOG_ERR, "fileargs_init(): %m"); 1397 exit(1); 1398 } 1399 caph_cache_catpages(); 1400 caph_cache_tzdata(); 1401 if (caph_enter_casper() != 0) { 1402 logmsg(LOG_ERR, "caph_enter_casper(): %m"); 1403 exit(1); 1404 } 1405 capsyslog = cap_service_open(capcas, "system.syslog"); 1406 if (capsyslog == NULL) { 1407 logmsg(LOG_ERR, "cap_service_open(system.syslog): %m"); 1408 exit(1); 1409 } 1410 cap_close(capcas); 1411 } 1412 1413 static void 1414 usage(void) 1415 { 1416 xo_error("%s\n%s\n%s\n%s\n", 1417 "usage: savecore -c [-v] [device ...]", 1418 " savecore -C [-v] [device ...]", 1419 " savecore -L [-fvZz] [-m maxdumps] [directory]", 1420 " savecore [-fkuvz] [-m maxdumps] [directory [device ...]]"); 1421 exit(1); 1422 } 1423 1424 int 1425 main(int argc, char **argv) 1426 { 1427 cap_rights_t rights; 1428 const char *savedir; 1429 char **devs; 1430 int i, ch, error, savedirfd; 1431 1432 checkfor = compress = clear = force = keep = livecore = false; 1433 verbose = 0; 1434 nfound = nsaved = nerr = 0; 1435 savedir = "."; 1436 comp_desired = KERNELDUMP_COMP_NONE; 1437 1438 openlog("savecore", LOG_PERROR, LOG_DAEMON); 1439 signal(SIGINFO, infohandler); 1440 1441 argc = xo_parse_args(argc, argv); 1442 if (argc < 0) 1443 exit(1); 1444 1445 while ((ch = getopt(argc, argv, "CcfkLm:uvZz")) != -1) 1446 switch(ch) { 1447 case 'C': 1448 checkfor = true; 1449 break; 1450 case 'c': 1451 clear = true; 1452 break; 1453 case 'f': 1454 force = true; 1455 break; 1456 case 'k': 1457 keep = true; 1458 break; 1459 case 'L': 1460 livecore = true; 1461 break; 1462 case 'm': 1463 maxdumps = atoi(optarg); 1464 if (maxdumps <= 0) { 1465 logmsg(LOG_ERR, "Invalid maxdump value"); 1466 exit(1); 1467 } 1468 break; 1469 case 'u': 1470 uncompress = true; 1471 break; 1472 case 'v': 1473 verbose++; 1474 break; 1475 case 'Z': 1476 /* No on-the-fly compression with zstd at the moment. */ 1477 if (!livecore) 1478 usage(); 1479 compress = true; 1480 comp_desired = KERNELDUMP_COMP_ZSTD; 1481 break; 1482 case 'z': 1483 compress = true; 1484 comp_desired = KERNELDUMP_COMP_GZIP; 1485 break; 1486 case '?': 1487 default: 1488 usage(); 1489 } 1490 if (checkfor && (clear || force || keep)) 1491 usage(); 1492 if (clear && (compress || keep)) 1493 usage(); 1494 if (maxdumps > 0 && (checkfor || clear)) 1495 usage(); 1496 if (compress && uncompress) 1497 usage(); 1498 if (livecore && (checkfor || clear || uncompress || keep)) 1499 usage(); 1500 argc -= optind; 1501 argv += optind; 1502 if (argc >= 1 && !checkfor && !clear) { 1503 error = chdir(argv[0]); 1504 if (error) { 1505 logmsg(LOG_ERR, "chdir(%s): %m", argv[0]); 1506 exit(1); 1507 } 1508 savedir = argv[0]; 1509 argc--; 1510 argv++; 1511 } 1512 if (livecore) { 1513 if (argc > 0) 1514 usage(); 1515 1516 /* Always need /dev/mem to invoke the dump */ 1517 devs = malloc(sizeof(char *)); 1518 devs[0] = strdup("/dev/mem"); 1519 argc++; 1520 } else if (argc == 0) 1521 devs = enum_dumpdevs(&argc); 1522 else 1523 devs = devify(argc, argv); 1524 1525 savedirfd = open(savedir, O_RDONLY | O_DIRECTORY); 1526 if (savedirfd < 0) { 1527 logmsg(LOG_ERR, "open(%s): %m", savedir); 1528 exit(1); 1529 } 1530 (void)cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL, CAP_FSTATAT, 1531 CAP_FSTATFS, CAP_PREAD, CAP_SYMLINKAT, CAP_FTRUNCATE, CAP_UNLINKAT, 1532 CAP_WRITE); 1533 if (livecore) 1534 cap_rights_set(&rights, CAP_RENAMEAT_SOURCE, 1535 CAP_RENAMEAT_TARGET); 1536 if (caph_rights_limit(savedirfd, &rights) < 0) { 1537 logmsg(LOG_ERR, "cap_rights_limit(): %m"); 1538 exit(1); 1539 } 1540 1541 /* Enter capability mode. */ 1542 init_caps(argc, devs); 1543 1544 for (i = 0; i < argc; i++) 1545 DoFile(savedir, savedirfd, devs[i]); 1546 1547 if (nfound == 0) { 1548 if (checkfor) { 1549 if (verbose) 1550 printf("No dump exists\n"); 1551 exit(1); 1552 } 1553 if (verbose) 1554 logmsg(LOG_WARNING, "no dumps found"); 1555 } else if (nsaved == 0) { 1556 if (nerr != 0) { 1557 if (verbose) 1558 logmsg(LOG_WARNING, 1559 "unsaved dumps found but not saved"); 1560 exit(1); 1561 } else if (verbose) 1562 logmsg(LOG_WARNING, "no unsaved dumps found"); 1563 } else if (verbose) { 1564 logmsg(LOG_NOTICE, "%d cores saved in %s\n", nsaved, savedir); 1565 } 1566 1567 return (0); 1568 } 1569 1570 static void 1571 infohandler(int sig __unused) 1572 { 1573 got_siginfo = 1; 1574 } 1575