1 /*- 2 * Copyright (c) 2002 Poul-Henning Kamp 3 * Copyright (c) 2002 Networks Associates Technology, Inc. 4 * All rights reserved. 5 * 6 * This software was developed for the FreeBSD Project by Poul-Henning Kamp 7 * and NAI Labs, the Security Research Division of Network Associates, Inc. 8 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the 9 * DARPA CHATS research program. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. The names of the authors may not be used to endorse or promote 20 * products derived from this software without specific prior written 21 * permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * Copyright (c) 1986, 1992, 1993 36 * The Regents of the University of California. All rights reserved. 37 * 38 * Redistribution and use in source and binary forms, with or without 39 * modification, are permitted provided that the following conditions 40 * are met: 41 * 1. Redistributions of source code must retain the above copyright 42 * notice, this list of conditions and the following disclaimer. 43 * 2. Redistributions in binary form must reproduce the above copyright 44 * notice, this list of conditions and the following disclaimer in the 45 * documentation and/or other materials provided with the distribution. 46 * 3. Neither the name of the University nor the names of its contributors 47 * may be used to endorse or promote products derived from this software 48 * without specific prior written permission. 49 * 50 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 51 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 52 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 53 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 54 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 55 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 56 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 57 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 58 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 59 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 60 * SUCH DAMAGE. 61 */ 62 63 #include <sys/param.h> 64 #include <sys/kerneldump.h> 65 #include <sys/diskslice.h> 66 #include <sys/ioctl.h> 67 #include <sys/mount.h> 68 #include <sys/stat.h> 69 #include <errno.h> 70 #include <fcntl.h> 71 #include <fstab.h> 72 #include <paths.h> 73 #include <stdarg.h> 74 #include <stdio.h> 75 #include <stdlib.h> 76 #include <string.h> 77 #include <syslog.h> 78 #include <time.h> 79 #include <unistd.h> 80 81 /* The size of the buffer used for I/O. */ 82 #define BUFFERSIZE (1024*1024) 83 84 #define STATUS_BAD 0 85 #define STATUS_GOOD 1 86 #define STATUS_UNKNOWN 2 87 88 static int checkfor, compress, clear, force, keep, verbose; /* flags */ 89 static int nfound, nsaved, nerr; /* statistics */ 90 91 extern FILE *zopen(const char *, const char *); 92 93 static void 94 printheader(FILE *f, const struct kerneldumpheader *h, const char *device, 95 int bounds, const int status) 96 { 97 uint64_t dumplen; 98 time_t t; 99 const char *stat_str; 100 101 fprintf(f, "Dump header from device %s\n", device); 102 fprintf(f, " Architecture: %s\n", h->architecture); 103 fprintf(f, " Architecture Version: %u\n", 104 dtoh32(h->architectureversion)); 105 dumplen = dtoh64(h->dumplength); 106 fprintf(f, " Dump Length: %lldB (%lld MB)\n", (long long)dumplen, 107 (long long)(dumplen >> 20)); 108 fprintf(f, " Blocksize: %d\n", dtoh32(h->blocksize)); 109 t = dtoh64(h->dumptime); 110 fprintf(f, " Dumptime: %s", ctime(&t)); 111 fprintf(f, " Hostname: %s\n", h->hostname); 112 fprintf(f, " Magic: %s\n", h->magic); 113 fprintf(f, " Version String: %s", h->versionstring); 114 fprintf(f, " Panic String: %s\n", h->panicstring); 115 fprintf(f, " Dump Parity: %u\n", h->parity); 116 fprintf(f, " Bounds: %d\n", bounds); 117 118 switch(status) { 119 case STATUS_BAD: 120 stat_str = "bad"; 121 break; 122 case STATUS_GOOD: 123 stat_str = "good"; 124 break; 125 default: 126 stat_str = "unknown"; 127 } 128 fprintf(f, " Dump Status: %s\n", stat_str); 129 fflush(f); 130 } 131 132 static int 133 getbounds(void) { 134 FILE *fp; 135 char buf[6]; 136 int ret; 137 138 ret = 0; 139 140 if ((fp = fopen("bounds", "r")) == NULL) { 141 if (verbose) 142 printf("unable to open bounds file, using 0\n"); 143 return (ret); 144 } 145 146 if (fgets(buf, sizeof buf, fp) == NULL) { 147 syslog(LOG_WARNING, "unable to read from bounds, using 0"); 148 fclose(fp); 149 return (ret); 150 } 151 152 errno = 0; 153 ret = (int)strtol(buf, NULL, 10); 154 if (ret == 0 && (errno == EINVAL || errno == ERANGE)) 155 syslog(LOG_WARNING, "invalid value found in bounds, using 0"); 156 return (ret); 157 } 158 159 static void 160 writebounds(int bounds) { 161 FILE *fp; 162 163 if ((fp = fopen("bounds", "w")) == NULL) { 164 syslog(LOG_WARNING, "unable to write to bounds file: %m"); 165 return; 166 } 167 168 if (verbose) 169 printf("bounds number: %d\n", bounds); 170 171 fprintf(fp, "%d\n", bounds); 172 fclose(fp); 173 } 174 175 /* 176 * Check that sufficient space is available on the disk that holds the 177 * save directory. 178 */ 179 static int 180 check_space(const char *savedir, off_t dumpsize) 181 { 182 FILE *fp; 183 off_t minfree, spacefree, totfree, needed; 184 struct statfs fsbuf; 185 char buf[100], path[MAXPATHLEN]; 186 187 if (statfs(savedir, &fsbuf) < 0) { 188 syslog(LOG_ERR, "%s: %m", savedir); 189 exit(1); 190 } 191 spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024; 192 totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024; 193 194 (void)snprintf(path, sizeof(path), "%s/minfree", savedir); 195 if ((fp = fopen(path, "r")) == NULL) 196 minfree = 0; 197 else { 198 if (fgets(buf, sizeof(buf), fp) == NULL) 199 minfree = 0; 200 else 201 minfree = atoi(buf); 202 (void)fclose(fp); 203 } 204 205 needed = dumpsize / 1024 + 2; /* 2 for info file */ 206 if (((minfree > 0) ? spacefree : totfree) - needed < minfree) { 207 syslog(LOG_WARNING, 208 "no dump, not enough free space on device (%lld available, need %lld)", 209 (long long)(minfree > 0 ? spacefree : totfree), 210 (long long)needed); 211 return (0); 212 } 213 if (spacefree - needed < 0) 214 syslog(LOG_WARNING, 215 "dump performed, but free space threshold crossed"); 216 return (1); 217 } 218 219 #define BLOCKSIZE (1<<12) 220 #define BLOCKMASK (~(BLOCKSIZE-1)) 221 222 static void 223 DoFile(const char *savedir, const char *device) 224 { 225 static char *buf = NULL; 226 struct partinfo dpart; 227 struct kerneldumpheader kdhf, kdhl; 228 off_t mediasize, dumpsize, firsthd, lasthd, dmpcnt; 229 FILE *info, *fp, *fpkern; 230 mode_t oumask; 231 int fd, fdinfo, fdkernin, error, wl; 232 int nr, nw, hs, he = 0; 233 int bounds, status; 234 u_int sectorsize; 235 236 bounds = getbounds(); 237 dmpcnt = 0; 238 mediasize = 0; 239 status = STATUS_UNKNOWN; 240 241 if (buf == NULL) { 242 buf = malloc(BUFFERSIZE); 243 if (buf == NULL) { 244 syslog(LOG_ERR, "%m"); 245 return; 246 } 247 } 248 249 if (verbose) 250 printf("checking for kernel dump on device %s\n", device); 251 252 fd = open(device, O_RDWR); 253 if (fd < 0) { 254 syslog(LOG_ERR, "%s: %m", device); 255 return; 256 } 257 258 bzero(&dpart, sizeof(dpart)); 259 error = ioctl(fd, DIOCGPART, &dpart); 260 if (error) { 261 syslog(LOG_ERR, 262 "couldn't find media and/or sector size of %s: %m", device); 263 goto closefd; 264 } 265 mediasize = dpart.media_size; 266 sectorsize = dpart.media_blksize; 267 268 if (verbose) { 269 printf("mediasize = %lld\n", (long long)mediasize); 270 printf("sectorsize = %u\n", sectorsize); 271 } 272 273 lasthd = mediasize - sectorsize; 274 lseek(fd, lasthd, SEEK_SET); 275 error = read(fd, &kdhl, sizeof kdhl); 276 if (error != sizeof kdhl) { 277 syslog(LOG_ERR, 278 "error reading last dump header at offset %lld in %s: %m", 279 (long long)lasthd, device); 280 goto closefd; 281 } 282 if (memcmp(kdhl.magic, KERNELDUMPMAGIC, sizeof kdhl.magic)) { 283 if (verbose) 284 printf("magic mismatch on last dump header on %s\n", 285 device); 286 287 status = STATUS_BAD; 288 if (force == 0) 289 goto closefd; 290 291 if (memcmp(kdhl.magic, KERNELDUMPMAGIC_CLEARED, 292 sizeof kdhl.magic) == 0) { 293 if (verbose) 294 printf("forcing magic on %s\n", device); 295 memcpy(kdhl.magic, KERNELDUMPMAGIC, 296 sizeof kdhl.magic); 297 } else { 298 syslog(LOG_ERR, "unable to force dump - bad magic"); 299 goto closefd; 300 } 301 } 302 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) { 303 syslog(LOG_ERR, 304 "unknown version (%d) in last dump header on %s", 305 dtoh32(kdhl.version), device); 306 307 status = STATUS_BAD; 308 if (force == 0) 309 goto closefd; 310 } 311 312 nfound++; 313 if (clear) 314 goto nuke; 315 316 if (kerneldump_parity(&kdhl)) { 317 syslog(LOG_ERR, 318 "parity error on last dump header on %s", device); 319 nerr++; 320 status = STATUS_BAD; 321 if (force == 0) 322 goto closefd; 323 } 324 dumpsize = dtoh64(kdhl.dumplength); 325 firsthd = lasthd - dumpsize - sizeof kdhf; 326 lseek(fd, firsthd, SEEK_SET); 327 error = read(fd, &kdhf, sizeof kdhf); 328 if (error != sizeof kdhf) { 329 syslog(LOG_ERR, 330 "error reading first dump header at offset %lld in %s: %m", 331 (long long)firsthd, device); 332 nerr++; 333 goto closefd; 334 } 335 336 if (verbose >= 2) { 337 printf("First dump headers:\n"); 338 printheader(stdout, &kdhf, device, bounds, -1); 339 340 printf("\nLast dump headers:\n"); 341 printheader(stdout, &kdhl, device, bounds, -1); 342 printf("\n"); 343 } 344 345 if (memcmp(&kdhl, &kdhf, sizeof kdhl)) { 346 syslog(LOG_ERR, 347 "first and last dump headers disagree on %s", device); 348 nerr++; 349 status = STATUS_BAD; 350 if (force == 0) 351 goto closefd; 352 } else { 353 status = STATUS_GOOD; 354 } 355 356 if (checkfor) { 357 printf("A dump exists on %s\n", device); 358 close(fd); 359 exit(0); 360 } 361 362 if (kdhl.panicstring[0]) 363 syslog(LOG_ALERT, "reboot after panic: %s", kdhl.panicstring); 364 else 365 syslog(LOG_ALERT, "reboot"); 366 367 if (verbose) 368 printf("Checking for available free space\n"); 369 if (!check_space(savedir, dumpsize)) { 370 nerr++; 371 goto closefd; 372 } 373 374 writebounds(bounds + 1); 375 376 /* 377 * Write kernel file. 378 */ 379 fdkernin = open(getbootfile(), O_RDONLY, 0); 380 if (fdkernin < 0) { 381 syslog(LOG_ERR, "%s: %m", getbootfile()); 382 } 383 384 if (compress) { 385 sprintf(buf, "kern.%d.gz", bounds); 386 fpkern = zopen(buf, "w"); 387 } else { 388 sprintf(buf, "kern.%d", bounds); 389 fpkern = fopen(buf, "w"); 390 } 391 if (fpkern == NULL) { 392 syslog(LOG_ERR, "%s: %m", buf); 393 close(fdkernin); 394 } 395 396 syslog(LOG_NOTICE, "writing %skernel to %s", 397 compress ? "compressed " : "", buf); 398 399 while ((nr = read(fdkernin, buf, BUFFERSIZE)) > 0) { 400 nw = fwrite(buf, 1, nr, fpkern); 401 if (nw != nr) { 402 syslog(LOG_ERR, "kern.%d: %m", bounds); 403 syslog(LOG_WARNING, 404 "WARNING: kernel may be incomplete"); 405 exit(1); 406 } 407 } 408 if (nr < 0) { 409 syslog(LOG_ERR, "%s: %m", getbootfile()); 410 syslog(LOG_WARNING, 411 "WARNING: kernel may be incomplete"); 412 exit(1); 413 } 414 fclose(fpkern); 415 close(fdkernin); 416 417 418 sprintf(buf, "info.%d", bounds); 419 420 /* 421 * Create or overwrite any existing dump header files. 422 */ 423 fdinfo = open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0600); 424 if (fdinfo < 0) { 425 syslog(LOG_ERR, "%s: %m", buf); 426 nerr++; 427 goto closefd; 428 } 429 oumask = umask(S_IRWXG|S_IRWXO); /* Restrict access to the core file.*/ 430 if (compress) { 431 sprintf(buf, "vmcore.%d.gz", bounds); 432 fp = zopen(buf, "w"); 433 } else { 434 sprintf(buf, "vmcore.%d", bounds); 435 fp = fopen(buf, "w"); 436 } 437 if (fp == NULL) { 438 syslog(LOG_ERR, "%s: %m", buf); 439 close(fdinfo); 440 nerr++; 441 goto closefd; 442 } 443 (void)umask(oumask); 444 445 info = fdopen(fdinfo, "w"); 446 447 if (info == NULL) { 448 syslog(LOG_ERR, "fdopen failed: %m"); 449 nerr++; 450 goto closefd; 451 } 452 453 if (verbose) 454 printheader(stdout, &kdhl, device, bounds, status); 455 456 printheader(info, &kdhl, device, bounds, status); 457 fclose(info); 458 459 syslog(LOG_NOTICE, "writing %score to %s", 460 compress ? "compressed " : "", buf); 461 462 while (dumpsize > 0) { 463 wl = BUFFERSIZE; 464 if (wl > dumpsize) 465 wl = dumpsize; 466 nr = read(fd, buf, wl); 467 if (nr != wl) { 468 if (nr == 0) 469 syslog(LOG_WARNING, 470 "WARNING: EOF on dump device"); 471 else 472 syslog(LOG_ERR, "read error on %s: %m", device); 473 nerr++; 474 goto closeall; 475 } 476 if (compress) { 477 nw = fwrite(buf, 1, wl, fp); 478 } else { 479 for (nw = 0; nw < nr; nw = he) { 480 /* find a contiguous block of zeroes */ 481 for (hs = nw; hs < nr; hs += BLOCKSIZE) { 482 for (he = hs; he < nr && buf[he] == 0; 483 ++he) 484 /* nothing */ ; 485 /* is the hole long enough to matter? */ 486 if (he >= hs + BLOCKSIZE) 487 break; 488 } 489 490 /* back down to a block boundary */ 491 he &= BLOCKMASK; 492 493 /* 494 * 1) Don't go beyond the end of the buffer. 495 * 2) If the end of the buffer is less than 496 * BLOCKSIZE bytes away, we're at the end 497 * of the file, so just grab what's left. 498 */ 499 if (hs + BLOCKSIZE > nr) 500 hs = he = nr; 501 502 /* 503 * At this point, we have a partial ordering: 504 * nw <= hs <= he <= nr 505 * If hs > nw, buf[nw..hs] contains non-zero data. 506 * If he > hs, buf[hs..he] is all zeroes. 507 */ 508 if (hs > nw) 509 if (fwrite(buf + nw, hs - nw, 1, fp) 510 != 1) 511 break; 512 if (he > hs) 513 if (fseeko(fp, he - hs, SEEK_CUR) == -1) 514 break; 515 } 516 } 517 if (nw != wl) { 518 syslog(LOG_ERR, 519 "write error on vmcore.%d file: %m", bounds); 520 syslog(LOG_WARNING, 521 "WARNING: vmcore may be incomplete"); 522 nerr++; 523 goto closeall; 524 } 525 if (verbose) { 526 dmpcnt += wl; 527 printf("%llu\r", (unsigned long long)dmpcnt); 528 fflush(stdout); 529 } 530 dumpsize -= wl; 531 } 532 if (verbose) 533 printf("\n"); 534 535 if (fclose(fp) < 0) { 536 syslog(LOG_ERR, "error on vmcore.%d: %m", bounds); 537 nerr++; 538 goto closeall; 539 } 540 nsaved++; 541 542 if (verbose) 543 printf("dump saved\n"); 544 545 nuke: 546 if (clear || !keep) { 547 if (verbose) 548 printf("clearing dump header\n"); 549 memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof kdhl.magic); 550 lseek(fd, lasthd, SEEK_SET); 551 error = write(fd, &kdhl, sizeof kdhl); 552 if (error != sizeof kdhl) 553 syslog(LOG_ERR, 554 "error while clearing the dump header: %m"); 555 } 556 close(fd); 557 return; 558 559 closeall: 560 fclose(fp); 561 562 closefd: 563 close(fd); 564 } 565 566 static void 567 usage(void) 568 { 569 fprintf(stderr, "%s\n%s\n%s\n", 570 "usage: savecore -c", 571 " savecore -C [-v] [directory device]", 572 " savecore [-fkvz] [directory [device ...]]"); 573 exit (1); 574 } 575 576 int 577 main(int argc, char **argv) 578 { 579 const char *savedir = "."; 580 struct fstab *fsp; 581 int i, ch, error; 582 583 checkfor = compress = clear = force = keep = verbose = 0; 584 nfound = nsaved = nerr = 0; 585 586 openlog("savecore", LOG_PERROR, LOG_DAEMON); 587 588 while ((ch = getopt(argc, argv, "Ccfkvz")) != -1) 589 switch(ch) { 590 case 'C': 591 checkfor = 1; 592 break; 593 case 'c': 594 clear = 1; 595 break; 596 case 'k': 597 keep = 1; 598 break; 599 case 'v': 600 verbose++; 601 break; 602 case 'f': 603 force = 1; 604 break; 605 case 'z': 606 compress = 1; 607 break; 608 case '?': 609 default: 610 usage(); 611 } 612 if (checkfor && (clear || force || keep)) 613 usage(); 614 argc -= optind; 615 argv += optind; 616 if (argc >= 1) { 617 error = chdir(argv[0]); 618 if (error) { 619 syslog(LOG_ERR, "chdir(%s): %m", argv[0]); 620 exit(1); 621 } 622 savedir = argv[0]; 623 argc--; 624 argv++; 625 } 626 if (argc == 0) { 627 for (;;) { 628 fsp = getfsent(); 629 if (fsp == NULL) 630 break; 631 if (strcmp(fsp->fs_vfstype, "swap") && 632 strcmp(fsp->fs_vfstype, "dump")) 633 continue; 634 DoFile(savedir, fsp->fs_spec); 635 } 636 } else { 637 for (i = 0; i < argc; i++) 638 DoFile(savedir, argv[i]); 639 } 640 641 /* Emit minimal output. */ 642 if (nfound == 0) { 643 if (checkfor) { 644 printf("No dump exists\n"); 645 exit(1); 646 } 647 syslog(LOG_WARNING, "no dumps found"); 648 } 649 else if (nsaved == 0) { 650 if (nerr != 0) 651 syslog(LOG_WARNING, "unsaved dumps found but not saved"); 652 else 653 syslog(LOG_WARNING, "no unsaved dumps found"); 654 } 655 656 return (0); 657 } 658