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