1 /*- 2 * Copyright (c) 1986, 1992, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #ifndef lint 35 static const char copyright[] = 36 "@(#) Copyright (c) 1986, 1992, 1993\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38 #endif /* not lint */ 39 40 #ifndef lint 41 #if 0 42 static char sccsid[] = "@(#)savecore.c 8.3 (Berkeley) 1/2/94"; 43 #endif 44 static const char rcsid[] = 45 "$FreeBSD$"; 46 #endif /* not lint */ 47 48 #include <sys/param.h> 49 #include <sys/stat.h> 50 #include <sys/mount.h> 51 #include <sys/syslog.h> 52 #include <sys/sysctl.h> 53 54 #include <vm/vm.h> 55 #include <vm/vm_param.h> 56 #include <vm/pmap.h> 57 58 #include <dirent.h> 59 #include <fcntl.h> 60 #include <nlist.h> 61 #include <paths.h> 62 #include <stdio.h> 63 #include <stdlib.h> 64 #include <string.h> 65 #include <unistd.h> 66 #include "zopen.h" 67 68 #ifdef __alpha__ 69 #define ok(number) ALPHA_K0SEG_TO_PHYS(number) 70 #endif 71 72 #ifdef __i386__ 73 #define ok(number) ((number) - KERNBASE) 74 #endif 75 76 struct nlist current_nl[] = { /* Namelist for currently running system. */ 77 #define X_DUMPLO 0 78 { "_dumplo" }, 79 #define X_TIME 1 80 { "_time_second" }, 81 #define X_DUMPSIZE 2 82 { "_dumpsize" }, 83 #define X_VERSION 3 84 { "_version" }, 85 #define X_PANICSTR 4 86 { "_panicstr" }, 87 #define X_DUMPMAG 5 88 { "_dumpmag" }, 89 { "" }, 90 }; 91 int cursyms[] = { X_DUMPLO, X_VERSION, X_DUMPMAG, -1 }; 92 int dumpsyms[] = { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 }; 93 94 struct nlist dump_nl[] = { /* Name list for dumped system. */ 95 { "_dumplo" }, /* Entries MUST be the same as */ 96 { "_time_second" }, /* those in current_nl[]. */ 97 { "_dumpsize" }, 98 { "_version" }, 99 { "_panicstr" }, 100 { "_dumpmag" }, 101 { "" }, 102 }; 103 104 /* Types match kernel declarations. */ 105 off_t dumplo; /* where dump starts on dumpdev */ 106 int dumpmag; /* magic number in dump */ 107 int dumpsize; /* amount of memory dumped */ 108 109 char *kernel; /* user-specified kernel */ 110 char *savedir; /* directory to save dumps in */ 111 char ddname[MAXPATHLEN]; /* name of dump device */ 112 dev_t dumpdev; /* dump device */ 113 int dumpfd; /* read/write descriptor on char dev */ 114 time_t now; /* current date */ 115 char panic_mesg[1024]; /* panic message */ 116 int panicstr; /* flag: dump was caused by panic */ 117 char vers[1024]; /* version of kernel that crashed */ 118 119 int clear, compress, force, verbose; /* flags */ 120 121 void check_kmem __P((void)); 122 int check_space __P((void)); 123 void clear_dump __P((void)); 124 void DumpRead __P((int fd, void *bp, int size, off_t off, int flag)); 125 void DumpWrite __P((int fd, void *bp, int size, off_t off, int flag)); 126 int dump_exists __P((void)); 127 void find_dev __P((dev_t)); 128 int get_crashtime __P((void)); 129 void get_dumpsize __P((void)); 130 void kmem_setup __P((void)); 131 void log __P((int, char *, ...)); 132 void Lseek __P((int, off_t, int)); 133 int Open __P((const char *, int rw)); 134 int Read __P((int, void *, int)); 135 void save_core __P((void)); 136 void usage __P((void)); 137 void Write __P((int, void *, int)); 138 139 int 140 main(argc, argv) 141 int argc; 142 char *argv[]; 143 { 144 int ch; 145 146 openlog("savecore", LOG_PERROR, LOG_DAEMON); 147 148 while ((ch = getopt(argc, argv, "cdfN:vz")) != -1) 149 switch(ch) { 150 case 'c': 151 clear = 1; 152 break; 153 case 'd': /* Not documented. */ 154 case 'v': 155 verbose = 1; 156 break; 157 case 'f': 158 force = 1; 159 break; 160 case 'N': 161 kernel = optarg; 162 break; 163 case 'z': 164 compress = 1; 165 break; 166 case '?': 167 default: 168 usage(); 169 } 170 argc -= optind; 171 argv += optind; 172 173 if (!clear) { 174 if (argc != 1 && argc != 2) 175 usage(); 176 savedir = argv[0]; 177 } 178 if (argc == 2) 179 kernel = argv[1]; 180 181 (void)time(&now); 182 kmem_setup(); 183 184 if (clear) { 185 clear_dump(); 186 exit(0); 187 } 188 189 if (!dump_exists() && !force) 190 exit(1); 191 192 check_kmem(); 193 194 if (panicstr) 195 syslog(LOG_ALERT, "reboot after panic: %s", panic_mesg); 196 else 197 syslog(LOG_ALERT, "reboot"); 198 199 get_dumpsize(); 200 201 if ((!get_crashtime() || !check_space()) && !force) 202 exit(1); 203 204 save_core(); 205 206 clear_dump(); 207 exit(0); 208 } 209 210 void 211 kmem_setup() 212 { 213 FILE *fp; 214 int kmem, i; 215 const char *dump_sys; 216 int mib[2]; 217 size_t len; 218 long kdumplo; /* block number where dump starts on dumpdev */ 219 220 /* 221 * Some names we need for the currently running system, others for 222 * the system that was running when the dump was made. The values 223 * obtained from the current system are used to look for things in 224 * /dev/kmem that cannot be found in the dump_sys namelist, but are 225 * presumed to be the same (since the disk partitions are probably 226 * the same!) 227 */ 228 if ((nlist(getbootfile(), current_nl)) == -1) 229 syslog(LOG_ERR, "%s: nlist: %m", getbootfile()); 230 for (i = 0; cursyms[i] != -1; i++) 231 if (current_nl[cursyms[i]].n_value == 0) { 232 syslog(LOG_ERR, "%s: %s not in namelist", 233 getbootfile(), current_nl[cursyms[i]].n_name); 234 exit(1); 235 } 236 237 dump_sys = kernel ? kernel : getbootfile(); 238 if ((nlist(dump_sys, dump_nl)) == -1) 239 syslog(LOG_ERR, "%s: nlist: %m", dump_sys); 240 for (i = 0; dumpsyms[i] != -1; i++) 241 if (dump_nl[dumpsyms[i]].n_value == 0) { 242 syslog(LOG_ERR, "%s: %s not in namelist", 243 dump_sys, dump_nl[dumpsyms[i]].n_name); 244 exit(1); 245 } 246 247 mib[0] = CTL_KERN; 248 mib[1] = KERN_DUMPDEV; 249 len = sizeof dumpdev; 250 if (sysctl(mib, 2, &dumpdev, &len, NULL, 0) == -1) { 251 syslog(LOG_ERR, "sysctl: kern.dumpdev: %m"); 252 exit(1); 253 } 254 if (dumpdev == NODEV) { 255 syslog(LOG_WARNING, "no core dump (no dumpdev)"); 256 exit(1); 257 } 258 259 kmem = Open(_PATH_KMEM, O_RDONLY); 260 Lseek(kmem, (off_t)current_nl[X_DUMPLO].n_value, L_SET); 261 (void)Read(kmem, &kdumplo, sizeof(kdumplo)); 262 dumplo = (off_t)kdumplo * DEV_BSIZE; 263 if (verbose) 264 (void)printf("dumplo = %lld (%ld * %d)\n", 265 (long long)dumplo, kdumplo, DEV_BSIZE); 266 Lseek(kmem, (off_t)current_nl[X_DUMPMAG].n_value, L_SET); 267 (void)Read(kmem, &dumpmag, sizeof(dumpmag)); 268 find_dev(dumpdev); 269 dumpfd = Open(ddname, O_RDWR); 270 fp = fdopen(kmem, "r"); 271 if (fp == NULL) { 272 syslog(LOG_ERR, "%s: fdopen: %m", _PATH_KMEM); 273 exit(1); 274 } 275 if (kernel) 276 return; 277 (void)fseek(fp, (off_t)current_nl[X_VERSION].n_value, L_SET); 278 (void)fgets(vers, sizeof(vers), fp); 279 280 /* Don't fclose(fp), we use dumpfd later. */ 281 } 282 283 void 284 check_kmem() 285 { 286 char core_vers[1024], *p; 287 288 DumpRead(dumpfd, core_vers, sizeof(core_vers), 289 (off_t)(dumplo + ok(dump_nl[X_VERSION].n_value)), L_SET); 290 core_vers[sizeof(core_vers) - 1] = '\0'; 291 p = strchr(core_vers, '\n'); 292 if (p) 293 p[1] = '\0'; 294 if (strcmp(vers, core_vers) && kernel == 0) 295 syslog(LOG_WARNING, 296 "warning: %s version mismatch:\n\t\"%s\"\nand\t\"%s\"\n", 297 getbootfile(), vers, core_vers); 298 DumpRead(dumpfd, &panicstr, sizeof(panicstr), 299 (off_t)(dumplo + ok(dump_nl[X_PANICSTR].n_value)), L_SET); 300 if (panicstr) { 301 DumpRead(dumpfd, panic_mesg, sizeof(panic_mesg), 302 (off_t)(dumplo + ok(panicstr)), L_SET); 303 } 304 } 305 306 /* 307 * Clear the magic number in the dump header. 308 */ 309 void 310 clear_dump() 311 { 312 int newdumpmag; 313 314 newdumpmag = 0; 315 DumpWrite(dumpfd, &newdumpmag, sizeof(newdumpmag), 316 (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET); 317 close(dumpfd); 318 } 319 320 /* 321 * Check if a dump exists by looking for a magic number in the dump 322 * header. 323 */ 324 int 325 dump_exists() 326 { 327 int newdumpmag; 328 329 DumpRead(dumpfd, &newdumpmag, sizeof(newdumpmag), 330 (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET); 331 if (newdumpmag != dumpmag) { 332 if (verbose) 333 syslog(LOG_WARNING, "magic number mismatch (%x != %x)", 334 newdumpmag, dumpmag); 335 syslog(LOG_WARNING, "no core dump"); 336 return (0); 337 } 338 return (1); 339 } 340 341 char buf[1024 * 1024]; 342 343 /* 344 * Save the core dump. 345 */ 346 void 347 save_core() 348 { 349 register FILE *fp; 350 register int bounds, ifd, nr, nw; 351 char path[MAXPATHLEN]; 352 mode_t oumask; 353 354 /* 355 * Get the current number and update the bounds file. Do the update 356 * now, because may fail later and don't want to overwrite anything. 357 */ 358 (void)snprintf(path, sizeof(path), "%s/bounds", savedir); 359 if ((fp = fopen(path, "r")) == NULL) 360 goto err1; 361 if (fgets(buf, sizeof(buf), fp) == NULL) { 362 if (ferror(fp)) 363 err1: syslog(LOG_WARNING, "%s: %m", path); 364 bounds = 0; 365 } else 366 bounds = atoi(buf); 367 if (fp != NULL) 368 (void)fclose(fp); 369 if ((fp = fopen(path, "w")) == NULL) 370 syslog(LOG_ERR, "%s: %m", path); 371 else { 372 (void)fprintf(fp, "%d\n", bounds + 1); 373 (void)fclose(fp); 374 } 375 376 /* Create the core file. */ 377 oumask = umask(S_IRWXG|S_IRWXO); /* Restrict access to the core file.*/ 378 (void)snprintf(path, sizeof(path), "%s/vmcore.%d%s", 379 savedir, bounds, compress ? ".Z" : ""); 380 if (compress) 381 fp = zopen(path, "w", 0); 382 else 383 fp = fopen(path, "w"); 384 if (fp == NULL) { 385 syslog(LOG_ERR, "%s: %m", path); 386 exit(1); 387 } 388 (void)umask(oumask); 389 390 /* Seek to the start of the core. */ 391 Lseek(dumpfd, (off_t)dumplo, L_SET); 392 393 /* Copy the core file. */ 394 syslog(LOG_NOTICE, "writing %score to %s", 395 compress ? "compressed " : "", path); 396 for (; dumpsize > 0; dumpsize -= nr) { 397 (void)printf("%6dK\r", dumpsize / 1024); 398 (void)fflush(stdout); 399 nr = read(dumpfd, buf, MIN(dumpsize, sizeof(buf))); 400 if (nr <= 0) { 401 if (nr == 0) 402 syslog(LOG_WARNING, 403 "WARNING: EOF on dump device"); 404 else 405 syslog(LOG_ERR, "%s: %m", ddname); 406 goto err2; 407 } 408 nw = fwrite(buf, 1, nr, fp); 409 if (nw != nr) { 410 syslog(LOG_ERR, "%s: %m", path); 411 err2: syslog(LOG_WARNING, 412 "WARNING: vmcore may be incomplete"); 413 (void)printf("\n"); 414 exit(1); 415 } 416 } 417 418 (void)fclose(fp); 419 420 /* Copy the kernel. */ 421 ifd = Open(kernel ? kernel : getbootfile(), O_RDONLY); 422 (void)snprintf(path, sizeof(path), "%s/kernel.%d%s", 423 savedir, bounds, compress ? ".Z" : ""); 424 if (compress) 425 fp = zopen(path, "w", 0); 426 else 427 fp = fopen(path, "w"); 428 if (fp == NULL) { 429 syslog(LOG_ERR, "%s: %m", path); 430 exit(1); 431 } 432 syslog(LOG_NOTICE, "writing %skernel to %s", 433 compress ? "compressed " : "", path); 434 while ((nr = read(ifd, buf, sizeof(buf))) > 0) { 435 nw = fwrite(buf, 1, nr, fp); 436 if (nw != nr) { 437 syslog(LOG_ERR, "%s: %m", path); 438 syslog(LOG_WARNING, 439 "WARNING: kernel may be incomplete"); 440 exit(1); 441 } 442 } 443 if (nr < 0) { 444 syslog(LOG_ERR, "%s: %m", kernel ? kernel : getbootfile()); 445 syslog(LOG_WARNING, 446 "WARNING: kernel may be incomplete"); 447 exit(1); 448 } 449 (void)fclose(fp); 450 close(ifd); 451 } 452 453 /* 454 * Verify that the specified device node exists and matches the 455 * specified device. 456 */ 457 int 458 verify_dev(name, dev) 459 char *name; 460 register dev_t dev; 461 { 462 struct stat sb; 463 464 if (lstat(name, &sb) == -1) 465 return (-1); 466 if (!S_ISCHR(sb.st_mode) || sb.st_rdev != dev) 467 return (-1); 468 return (0); 469 } 470 471 /* 472 * Find the dump device. 473 * 474 * 1) try devname(3); see if it returns something sensible 475 * 2) scan /dev for the desired node 476 * 3) as a last resort, try to create the node we need 477 */ 478 void 479 find_dev(dev) 480 register dev_t dev; 481 { 482 struct dirent *ent; 483 char *dn, *dnp; 484 DIR *d; 485 486 strcpy(ddname, _PATH_DEV); 487 dnp = ddname + sizeof _PATH_DEV - 1; 488 if ((dn = devname(dev, S_IFCHR)) != NULL) { 489 strcpy(dnp, dn); 490 if (verify_dev(ddname, dev) == 0) 491 return; 492 } 493 if ((d = opendir(_PATH_DEV)) != NULL) { 494 while ((ent = readdir(d))) { 495 strcpy(dnp, ent->d_name); 496 if (verify_dev(ddname, dev) == 0) { 497 closedir(d); 498 return; 499 } 500 } 501 closedir(d); 502 } 503 strcpy(dnp, "dump"); 504 if (mknod(ddname, S_IFCHR|S_IRUSR|S_IWUSR, dev) == 0) 505 return; 506 syslog(LOG_ERR, "can't find device %d/%#x", major(dev), minor(dev)); 507 exit(1); 508 } 509 510 /* 511 * Extract the date and time of the crash from the dump header, and 512 * make sure it looks sane (within one week of current date and time). 513 */ 514 int 515 get_crashtime() 516 { 517 time_t dumptime; /* Time the dump was taken. */ 518 519 DumpRead(dumpfd, &dumptime, sizeof(dumptime), 520 (off_t)(dumplo + ok(dump_nl[X_TIME].n_value)), L_SET); 521 if (dumptime == 0) { 522 if (verbose) 523 syslog(LOG_ERR, "dump time is zero"); 524 return (0); 525 } 526 (void)printf("savecore: system went down at %s", ctime(&dumptime)); 527 #define LEEWAY (7 * 86400) 528 if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) { 529 (void)printf("dump time is unreasonable\n"); 530 return (0); 531 } 532 return (1); 533 } 534 535 /* 536 * Extract the size of the dump from the dump header. 537 */ 538 void 539 get_dumpsize() 540 { 541 /* Read the dump size. */ 542 DumpRead(dumpfd, &dumpsize, sizeof(dumpsize), 543 (off_t)(dumplo + ok(dump_nl[X_DUMPSIZE].n_value)), L_SET); 544 dumpsize *= getpagesize(); 545 } 546 547 /* 548 * Check that sufficient space is available on the disk that holds the 549 * save directory. 550 */ 551 int 552 check_space() 553 { 554 register FILE *fp; 555 const char *tkernel; 556 off_t minfree, spacefree, totfree, kernelsize, needed; 557 struct stat st; 558 struct statfs fsbuf; 559 char buf[100], path[MAXPATHLEN]; 560 561 tkernel = kernel ? kernel : getbootfile(); 562 if (stat(tkernel, &st) < 0) { 563 syslog(LOG_ERR, "%s: %m", tkernel); 564 exit(1); 565 } 566 kernelsize = st.st_blocks * S_BLKSIZE; 567 568 if (statfs(savedir, &fsbuf) < 0) { 569 syslog(LOG_ERR, "%s: %m", savedir); 570 exit(1); 571 } 572 spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024; 573 totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024; 574 575 (void)snprintf(path, sizeof(path), "%s/minfree", savedir); 576 if ((fp = fopen(path, "r")) == NULL) 577 minfree = 0; 578 else { 579 if (fgets(buf, sizeof(buf), fp) == NULL) 580 minfree = 0; 581 else 582 minfree = atoi(buf); 583 (void)fclose(fp); 584 } 585 586 needed = (dumpsize + kernelsize) / 1024; 587 if (((minfree > 0) ? spacefree : totfree) - needed < minfree) { 588 syslog(LOG_WARNING, 589 "no dump, not enough free space on device (%lld available, need %lld)", 590 (long long)(minfree > 0 ? spacefree : totfree), 591 (long long)needed); 592 return (0); 593 } 594 if (spacefree - needed < 0) 595 syslog(LOG_WARNING, 596 "dump performed, but free space threshold crossed"); 597 return (1); 598 } 599 600 int 601 Open(name, rw) 602 const char *name; 603 int rw; 604 { 605 int fd; 606 607 if ((fd = open(name, rw, 0)) < 0) { 608 syslog(LOG_ERR, "%s: %m", name); 609 exit(1); 610 } 611 return (fd); 612 } 613 614 int 615 Read(fd, bp, size) 616 int fd, size; 617 void *bp; 618 { 619 int nr; 620 621 nr = read(fd, bp, size); 622 if (nr != size) { 623 syslog(LOG_ERR, "read: %m"); 624 exit(1); 625 } 626 return (nr); 627 } 628 629 void 630 Lseek(fd, off, flag) 631 int fd, flag; 632 off_t off; 633 { 634 off_t ret; 635 636 ret = lseek(fd, off, flag); 637 if (ret == -1) { 638 syslog(LOG_ERR, "lseek: %m"); 639 exit(1); 640 } 641 } 642 643 /* 644 * DumpWrite and DumpRead block io requests to the * dump device. 645 */ 646 #define DUMPBUFSIZE 8192 647 void 648 DumpWrite(fd, bp, size, off, flag) 649 int fd, size, flag; 650 void *bp; 651 off_t off; 652 { 653 unsigned char buf[DUMPBUFSIZE], *p, *q; 654 off_t pos; 655 int i, j; 656 657 if (flag != L_SET) { 658 syslog(LOG_ERR, "lseek: not LSET"); 659 exit(2); 660 } 661 q = bp; 662 while (size) { 663 pos = off & ~(DUMPBUFSIZE - 1); 664 Lseek(fd, pos, flag); 665 (void)Read(fd, buf, sizeof(buf)); 666 j = off & (DUMPBUFSIZE - 1); 667 p = buf + j; 668 i = size; 669 if (i > DUMPBUFSIZE - j) 670 i = DUMPBUFSIZE - j; 671 memcpy(p, q, i); 672 Lseek(fd, pos, flag); 673 (void)Write(fd, buf, sizeof(buf)); 674 size -= i; 675 q += i; 676 off += i; 677 } 678 } 679 680 void 681 DumpRead(fd, bp, size, off, flag) 682 int fd, size, flag; 683 void *bp; 684 off_t off; 685 { 686 unsigned char buf[DUMPBUFSIZE], *p, *q; 687 off_t pos; 688 int i, j; 689 690 if (flag != L_SET) { 691 syslog(LOG_ERR, "lseek: not LSET"); 692 exit(2); 693 } 694 q = bp; 695 while (size) { 696 pos = off & ~(DUMPBUFSIZE - 1); 697 Lseek(fd, pos, flag); 698 (void)Read(fd, buf, sizeof(buf)); 699 j = off & (DUMPBUFSIZE - 1); 700 p = buf + j; 701 i = size; 702 if (i > DUMPBUFSIZE - j) 703 i = DUMPBUFSIZE - j; 704 memcpy(q, p, i); 705 size -= i; 706 q += i; 707 off += i; 708 } 709 } 710 711 void 712 Write(fd, bp, size) 713 int fd, size; 714 void *bp; 715 { 716 int n; 717 718 if ((n = write(fd, bp, size)) < size) { 719 syslog(LOG_ERR, "write: %m"); 720 exit(1); 721 } 722 } 723 724 void 725 usage() 726 { 727 (void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory"); 728 exit(1); 729 } 730