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