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