1 /*- 2 * Copyright (c) 1986, 1992 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 char copyright[] = 10 "@(#) Copyright (c) 1986, 1992 The Regents of the University of California.\n\ 11 All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)savecore.c 5.32 (Berkeley) 07/15/92"; 16 #endif /* not lint */ 17 18 #include <sys/param.h> 19 #include <sys/file.h> 20 #include <sys/mount.h> 21 #include <sys/stat.h> 22 #include <sys/syslog.h> 23 #include <sys/time.h> 24 25 #include <errno.h> 26 #include <dirent.h> 27 #include <nlist.h> 28 #include <paths.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 34 #define DAY (60L*60L*24L) 35 #define LEEWAY (3*DAY) 36 37 #define ok(number) ((number) - KERNBASE) 38 39 struct nlist current_nl[] = { /* namelist for currently running system */ 40 #define X_DUMPDEV 0 41 { "_dumpdev" }, 42 #define X_DUMPLO 1 43 { "_dumplo" }, 44 #define X_TIME 2 45 { "_time" }, 46 #define X_DUMPSIZE 3 47 { "_dumpsize" }, 48 #define X_VERSION 4 49 { "_version" }, 50 #define X_PANICSTR 5 51 { "_panicstr" }, 52 #define X_DUMPMAG 6 53 { "_dumpmag" }, 54 { "" }, 55 }; 56 57 struct nlist dump_nl[] = { /* name list for dumped system */ 58 { "_dumpdev" }, /* entries MUST be the same as */ 59 { "_dumplo" }, /* those in current_nl[] */ 60 { "_time" }, 61 { "_dumpsize" }, 62 { "_version" }, 63 { "_panicstr" }, 64 { "_dumpmag" }, 65 { "" }, 66 }; 67 68 char *vmunix; 69 char *dirname; /* directory to save dumps in */ 70 char *ddname; /* name of dump device */ 71 int dumpfd; /* read/write descriptor on block dev */ 72 dev_t dumpdev; /* dump device */ 73 time_t dumptime; /* time the dump was taken */ 74 int dumplo; /* where dump starts on dumpdev */ 75 int dumpsize; /* amount of memory dumped */ 76 int dumpmag; /* magic number in dump */ 77 time_t now; /* current date */ 78 char vers[80]; 79 char core_vers[80]; 80 char panic_mesg[80]; 81 int panicstr; 82 int verbose; 83 int force; 84 int clear; 85 86 int dump_exists __P(()); 87 void clear_dump __P(()); 88 char *find_dev __P((dev_t, int)); 89 char *rawname __P((char *s)); 90 void read_kmem __P(()); 91 void check_kmem __P(()); 92 int get_crashtime __P(()); 93 char *path __P((char *)); 94 int check_space __P(()); 95 int read_number __P((char *)); 96 int save_core __P(()); 97 int Open __P((char *, int rw)); 98 int Read __P((int, char *, int)); 99 void Lseek __P((int, off_t, int)); 100 int Create __P((char *, int)); 101 void Write __P((int, char *, int)); 102 void log __P((int, char *, ...)); 103 void Perror __P((int, char *, char *)); 104 void usage __P(()); 105 106 int 107 main(argc, argv) 108 int argc; 109 char *argv[]; 110 { 111 int ch; 112 113 while ((ch = getopt(argc, argv, "cdfv")) != EOF) 114 switch(ch) { 115 case 'c': 116 clear = 1; 117 break; 118 case 'd': /* not documented */ 119 case 'v': 120 verbose = 1; 121 break; 122 case 'f': 123 force = 1; 124 break; 125 case '?': 126 default: 127 usage(); 128 } 129 argc -= optind; 130 argv += optind; 131 132 /* This is wrong, but I want "savecore -c" to work. */ 133 if (!clear) { 134 if (argc != 1 && argc != 2) 135 usage(); 136 dirname = argv[0]; 137 } 138 if (argc == 2) 139 vmunix = argv[1]; 140 141 openlog("savecore", LOG_ODELAY, LOG_AUTH); 142 143 read_kmem(); 144 if (!dump_exists()) { 145 (void)fprintf(stderr, "savecore: no core dump\n"); 146 if (!force) 147 exit(0); 148 } 149 if (clear) { 150 clear_dump(); 151 exit(0); 152 } 153 (void) time(&now); 154 check_kmem(); 155 if (panicstr) 156 log(LOG_CRIT, "reboot after panic: %s\n", panic_mesg); 157 else 158 syslog(LOG_CRIT, "reboot\n"); 159 160 if (access(dirname, W_OK) < 0) { 161 Perror(LOG_ERR, "%s: %m\n", dirname); 162 exit(1); 163 } 164 if ((!get_crashtime() || !check_space()) && !force) 165 exit(1); 166 if (!save_core()) 167 exit(1); 168 clear_dump(); 169 exit(0); 170 } 171 172 int 173 dump_exists() 174 { 175 int word; 176 177 Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET); 178 Read(dumpfd, (char *)&word, sizeof (word)); 179 if (verbose && word != dumpmag) 180 printf("magic number mismatch: %x != %x\n", word, dumpmag); 181 return (word == dumpmag); 182 } 183 184 void 185 clear_dump() 186 { 187 int zero; 188 189 zero = 0; 190 Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET); 191 Write(dumpfd, (char *)&zero, sizeof (zero)); 192 } 193 194 char * 195 find_dev(dev, type) 196 register dev_t dev; 197 register int type; 198 { 199 static char devname[MAXPATHLEN + 1]; 200 register DIR *dfd; 201 struct dirent *dir; 202 struct stat statb; 203 char *dp; 204 205 dfd = opendir(_PATH_DEV); 206 strcpy(devname, _PATH_DEV); 207 while ((dir = readdir(dfd))) { 208 (void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name); 209 if (stat(devname, &statb)) { 210 perror(devname); 211 continue; 212 } 213 if ((statb.st_mode&S_IFMT) != type) 214 continue; 215 if (dev == statb.st_rdev) { 216 closedir(dfd); 217 dp = malloc(strlen(devname)+1); 218 strcpy(dp, devname); 219 return (dp); 220 } 221 } 222 closedir(dfd); 223 log(LOG_ERR, "Can't find device %d/%d\n", major(dev), minor(dev)); 224 exit(1); 225 /*NOTREACHED*/ 226 } 227 228 char * 229 rawname(s) 230 char *s; 231 { 232 static char name[MAXPATHLEN]; 233 char *sl; 234 235 if ((sl = rindex(s, '/')) == NULL || sl[1] == '0') { 236 log(LOG_ERR, "can't make raw dump device name from %s?\n", s); 237 return (s); 238 } 239 (void)snprintf(name, sizeof(name), "%.*s/r%s", sl - s, s, sl + 1); 240 return (name); 241 } 242 243 int cursyms[] = 244 { X_DUMPDEV, X_DUMPLO, X_VERSION, X_DUMPMAG, -1 }; 245 int dumpsyms[] = 246 { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 }; 247 248 void 249 read_kmem() 250 { 251 FILE *fp; 252 int kmem, i; 253 char *dump_sys; 254 255 dump_sys = vmunix ? vmunix : _PATH_UNIX; 256 nlist(_PATH_UNIX, current_nl); 257 nlist(dump_sys, dump_nl); 258 /* 259 * Some names we need for the currently running system, 260 * others for the system that was running when the dump was made. 261 * The values obtained from the current system are used 262 * to look for things in /dev/kmem that cannot be found 263 * in the dump_sys namelist, but are presumed to be the same 264 * (since the disk partitions are probably the same!) 265 */ 266 for (i = 0; cursyms[i] != -1; i++) 267 if (current_nl[cursyms[i]].n_value == 0) { 268 log(LOG_ERR, "%s: %s not in namelist\n", _PATH_UNIX, 269 current_nl[cursyms[i]].n_name); 270 exit(1); 271 } 272 for (i = 0; dumpsyms[i] != -1; i++) 273 if (dump_nl[dumpsyms[i]].n_value == 0) { 274 log(LOG_ERR, "%s: %s not in namelist\n", dump_sys, 275 dump_nl[dumpsyms[i]].n_name); 276 exit(1); 277 } 278 kmem = Open(_PATH_KMEM, O_RDONLY); 279 Lseek(kmem, (off_t)current_nl[X_DUMPDEV].n_value, L_SET); 280 Read(kmem, (char *)&dumpdev, sizeof (dumpdev)); 281 Lseek(kmem, (off_t)current_nl[X_DUMPLO].n_value, L_SET); 282 Read(kmem, (char *)&dumplo, sizeof (dumplo)); 283 if (verbose) 284 printf("dumplo = %d (%d * %d)\n", dumplo, dumplo/DEV_BSIZE, 285 DEV_BSIZE); 286 Lseek(kmem, (off_t)current_nl[X_DUMPMAG].n_value, L_SET); 287 Read(kmem, (char *)&dumpmag, sizeof (dumpmag)); 288 dumplo *= DEV_BSIZE; 289 ddname = find_dev(dumpdev, S_IFBLK); 290 dumpfd = Open(ddname, O_RDWR); 291 fp = fdopen(kmem, "r"); 292 if (fp == NULL) { 293 log(LOG_ERR, "Couldn't fdopen kmem\n"); 294 exit(1); 295 } 296 if (vmunix) 297 return; 298 fseek(fp, (off_t)current_nl[X_VERSION].n_value, L_SET); 299 fgets(vers, sizeof (vers), fp); 300 (void)fclose(fp); 301 } 302 303 void 304 check_kmem() 305 { 306 FILE *fp; 307 register char *cp; 308 309 fp = fdopen(dumpfd, "r"); 310 if (fp == NULL) { 311 log(LOG_ERR, "Can't fdopen dumpfd\n"); 312 exit(1); 313 } 314 fseek(fp, (off_t)(dumplo+ok(dump_nl[X_VERSION].n_value)), L_SET); 315 fgets(core_vers, sizeof (core_vers), fp); 316 if (strcmp(vers, core_vers) && vmunix == 0) { 317 log(LOG_WARNING, "Warning: %s version mismatch:\n", _PATH_UNIX); 318 log(LOG_WARNING, "\t%s\n", vers); 319 log(LOG_WARNING, "and\t%s\n", core_vers); 320 } 321 fseek(fp, (off_t)(dumplo + ok(dump_nl[X_PANICSTR].n_value)), L_SET); 322 fread((char *)&panicstr, sizeof (panicstr), 1, fp); 323 if (panicstr) { 324 fseek(fp, dumplo + ok(panicstr), L_SET); 325 cp = panic_mesg; 326 do 327 *cp = getc(fp); 328 while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)]); 329 } 330 /* don't fclose(fp); we want the file descriptor */ 331 } 332 333 int 334 get_crashtime() 335 { 336 337 Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_TIME].n_value)), L_SET); 338 Read(dumpfd, (char *)&dumptime, sizeof dumptime); 339 if (dumptime == 0) { 340 if (verbose) 341 printf("Dump time is zero.\n"); 342 return (0); 343 } 344 printf("System went down at %s", ctime(&dumptime)); 345 if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) { 346 printf("dump time is unreasonable\n"); 347 return (0); 348 } 349 return (1); 350 } 351 352 char * 353 path(file) 354 char *file; 355 { 356 register char *cp = malloc(strlen(file) + strlen(dirname) + 2); 357 358 (void) strcpy(cp, dirname); 359 (void) strcat(cp, "/"); 360 (void) strcat(cp, file); 361 return (cp); 362 } 363 364 int 365 check_space() 366 { 367 long minfree, spacefree; 368 struct statfs fsbuf; 369 370 if (statfs(dirname, &fsbuf) < 0) { 371 Perror(LOG_ERR, "%s: %m\n", dirname); 372 exit(1); 373 } 374 spacefree = fsbuf.f_bavail * fsbuf.f_bsize / 1024; 375 minfree = read_number("minfree"); 376 if (minfree > 0 && spacefree - dumpsize < minfree) { 377 log(LOG_WARNING, "Dump omitted, not enough space on device\n"); 378 return (0); 379 } 380 if (spacefree - dumpsize < minfree) 381 log(LOG_WARNING, 382 "Dump performed, but free space threshold crossed\n"); 383 return (1); 384 } 385 386 int 387 read_number(fn) 388 char *fn; 389 { 390 char lin[80]; 391 register FILE *fp; 392 393 fp = fopen(path(fn), "r"); 394 if (fp == NULL) 395 return (0); 396 if (fgets(lin, 80, fp) == NULL) { 397 (void)fclose(fp); 398 return (0); 399 } 400 (void)fclose(fp); 401 return (atoi(lin)); 402 } 403 404 #define BUFSIZE (256*1024) /* 1/4 Mb */ 405 406 int 407 save_core() 408 { 409 register int n; 410 register char *cp; 411 register int ifd, ofd, bounds, ret, stat; 412 char *bfile; 413 register FILE *fp; 414 415 cp = malloc(BUFSIZE); 416 if (cp == 0) { 417 log(LOG_ERR, "savecore: Can't allocate i/o buffer.\n"); 418 return (0); 419 } 420 bounds = read_number("bounds"); 421 ifd = Open(vmunix ? vmunix : _PATH_UNIX, O_RDONLY); 422 (void)sprintf(cp, "vmunix.%d", bounds); 423 ofd = Create(path(cp), 0644); 424 while((n = Read(ifd, cp, BUFSIZE)) > 0) 425 Write(ofd, cp, n); 426 close(ifd); 427 close(ofd); 428 if ((ifd = open(rawname(ddname), O_RDONLY)) == -1) { 429 log(LOG_WARNING, "Can't open %s (%m); using block device", 430 rawname(ddname)); 431 ifd = dumpfd; 432 } 433 Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPSIZE].n_value)), L_SET); 434 Read(dumpfd, (char *)&dumpsize, sizeof (dumpsize)); 435 (void)sprintf(cp, "vmcore.%d", bounds); 436 ofd = Create(path(cp), 0644); 437 Lseek(ifd, (off_t)dumplo, L_SET); 438 dumpsize *= NBPG; 439 log(LOG_NOTICE, "Saving %d bytes of image in vmcore.%d\n", 440 dumpsize, bounds); 441 stat = 1; 442 while (dumpsize > 0) { 443 n = read(ifd, cp, dumpsize > BUFSIZE ? BUFSIZE : dumpsize); 444 if (n <= 0) { 445 if (n == 0) 446 log(LOG_WARNING, 447 "WARNING: EOF on dump device; %s\n", 448 "vmcore may be incomplete"); 449 else 450 Perror(LOG_ERR, "read from dumpdev: %m", 451 "read"); 452 stat = 0; 453 break; 454 } 455 if ((ret = write(ofd, cp, n)) < n) { 456 if (ret < 0) 457 Perror(LOG_ERR, "write: %m", "write"); 458 else 459 log(LOG_ERR, "short write: wrote %d of %d\n", 460 ret, n); 461 log(LOG_WARNING, "WARNING: vmcore may be incomplete\n"); 462 stat = 0; 463 break; 464 } 465 dumpsize -= n; 466 (void)fprintf(stderr, "%6dK\r", dumpsize / 1024); 467 } 468 fputc('\n', stderr); 469 close(ifd); 470 close(ofd); 471 bfile = path("bounds"); 472 fp = fopen(bfile, "w"); 473 if (fp) { 474 (void)fprintf(fp, "%d\n", bounds+1); 475 (void)fclose(fp); 476 } else 477 Perror(LOG_ERR, "Can't create bounds file %s: %m", bfile); 478 free(cp); 479 return (stat); 480 } 481 482 /* 483 * Versions of std routines that exit on error. 484 */ 485 int 486 Open(name, rw) 487 char *name; 488 int rw; 489 { 490 int fd; 491 492 fd = open(name, rw); 493 if (fd < 0) { 494 Perror(LOG_ERR, "%s: %m", name); 495 exit(1); 496 } 497 return (fd); 498 } 499 500 int 501 Read(fd, buff, size) 502 int fd; 503 char *buff; 504 int size; 505 { 506 int ret; 507 508 ret = read(fd, buff, size); 509 if (ret < 0) { 510 Perror(LOG_ERR, "read: %m", "read"); 511 exit(1); 512 } 513 return (ret); 514 } 515 516 void 517 Lseek(fd, off, flag) 518 int fd; 519 off_t off; 520 int flag; 521 { 522 long ret; 523 524 ret = lseek(fd, (off_t)off, flag); 525 if (ret == -1) { 526 Perror(LOG_ERR, "lseek: %m", "lseek"); 527 exit(1); 528 } 529 } 530 531 int 532 Create(file, mode) 533 char *file; 534 int mode; 535 { 536 register int fd; 537 538 fd = creat(file, mode); 539 if (fd < 0) { 540 Perror(LOG_ERR, "%s: %m", file); 541 exit(1); 542 } 543 return (fd); 544 } 545 546 void 547 Write(fd, buf, size) 548 int fd; 549 char *buf; 550 int size; 551 { 552 int n; 553 554 if ((n = write(fd, buf, size)) < size) { 555 if (n < 0) 556 Perror(LOG_ERR, "write: %m", "write"); 557 else 558 log(LOG_ERR, "short write: wrote %d of %d\n", n, size); 559 exit(1); 560 } 561 } 562 563 #if __STDC__ 564 #include <stdarg.h> 565 #else 566 #include <varargs.h> 567 #endif 568 569 void 570 #if __STDC__ 571 log(int level, char *fmt, ...) 572 #else 573 log(level, fmt, va_alist) 574 int level; 575 char *fmt; 576 va_dcl 577 #endif 578 { 579 va_list ap; 580 581 #if __STDC__ 582 va_start(ap, fmt); 583 #else 584 va_start(ap); 585 #endif 586 587 (void)vfprintf(stderr, fmt, ap); 588 vsyslog(level, fmt, ap); 589 va_end(ap); 590 } 591 592 void 593 Perror(level, msg, s) 594 int level; 595 char *msg, *s; 596 { 597 int oerrno = errno; 598 599 perror(s); 600 errno = oerrno; 601 syslog(level, msg, s); 602 } 603 604 void 605 usage() 606 { 607 (void)fprintf(stderr, "usage: savecore [-cfv] dirname [system]\n"); 608 exit(1); 609 } 610