savecore.c revision 1.49
1/* $NetBSD: savecore.c,v 1.49 2001/01/02 21:39:37 joda Exp $ */ 2 3/*- 4 * Copyright (c) 1986, 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 36#include <sys/cdefs.h> 37#ifndef lint 38__COPYRIGHT("@(#) Copyright (c) 1986, 1992, 1993\n\ 39 The Regents of the University of California. All rights reserved.\n"); 40#endif /* not lint */ 41 42#ifndef lint 43#if 0 44static char sccsid[] = "@(#)savecore.c 8.5 (Berkeley) 4/28/95"; 45#else 46__RCSID("$NetBSD: savecore.c,v 1.49 2001/01/02 21:39:37 joda Exp $"); 47#endif 48#endif /* not lint */ 49 50#include <sys/param.h> 51#include <sys/stat.h> 52#include <sys/mount.h> 53#include <sys/syslog.h> 54#include <sys/time.h> 55 56#include <dirent.h> 57#include <errno.h> 58#include <fcntl.h> 59#include <nlist.h> 60#include <paths.h> 61#include <stdio.h> 62#include <stdlib.h> 63#include <string.h> 64#include <time.h> 65#include <tzfile.h> 66#include <unistd.h> 67#include <limits.h> 68#include <kvm.h> 69 70extern FILE *zopen(const char *fname, const char *mode); 71 72#define KREAD(kd, addr, p)\ 73 (kvm_read(kd, addr, (char *)(p), sizeof(*(p))) != sizeof(*(p))) 74 75struct nlist current_nl[] = { /* Namelist for currently running system. */ 76#define X_DUMPDEV 0 77 { "_dumpdev" }, 78#define X_DUMPLO 1 79 { "_dumplo" }, 80#define X_TIME 2 81 { "_time" }, 82#define X_DUMPSIZE 3 83 { "_dumpsize" }, 84#define X_VERSION 4 85 { "_version" }, 86#define X_PANICSTR 5 87 { "_panicstr" }, 88#define X_DUMPMAG 6 89 { "_dumpmag" }, 90 { NULL }, 91}; 92int cursyms[] = { X_DUMPDEV, X_DUMPLO, X_VERSION, X_DUMPMAG, -1 }; 93int dumpsyms[] = { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 }; 94 95struct nlist dump_nl[] = { /* Name list for dumped system. */ 96 { "_dumpdev" }, /* Entries MUST be the same as */ 97 { "_dumplo" }, /* those in current_nl[]. */ 98 { "_time" }, 99 { "_dumpsize" }, 100 { "_version" }, 101 { "_panicstr" }, 102 { "_dumpmag" }, 103 { NULL }, 104}; 105 106/* Types match kernel declarations. */ 107long dumplo; /* where dump starts on dumpdev */ 108int dumpmag; /* magic number in dump */ 109int dumpsize; /* amount of memory dumped */ 110 111char *kernel; /* name of used kernel */ 112char *dirname; /* directory to save dumps in */ 113char *ddname; /* name of dump device */ 114dev_t dumpdev; /* dump device */ 115int dumpfd; /* read/write descriptor on block dev */ 116kvm_t *kd_dump; /* kvm descriptor on block dev */ 117time_t now; /* current date */ 118char panic_mesg[1024]; 119long panicstr; 120char vers[1024]; 121 122int clear, compress, force, verbose; /* flags */ 123 124void check_kmem(void); 125int check_space(void); 126void clear_dump(void); 127int Create(char *, int); 128int dump_exists(void); 129char *find_dev(dev_t, int); 130int get_crashtime(void); 131void kmem_setup(void); 132void log(int, char *, ...); 133void Lseek(int, off_t, int); 134int main(int, char *[]); 135int Open(char *, int rw); 136char *rawname(char *s); 137void save_core(void); 138void usage(void); 139void Write(int, void *, int); 140 141int 142main(int argc, char *argv[]) 143{ 144 int ch; 145 146 dirname = NULL; 147 kernel = NULL; 148 149 openlog("savecore", LOG_PERROR, LOG_DAEMON); 150 151 while ((ch = getopt(argc, argv, "cdfN:vz")) != -1) 152 switch(ch) { 153 case 'c': 154 clear = 1; 155 break; 156 case 'd': /* Not documented. */ 157 case 'v': 158 verbose = 1; 159 break; 160 case 'f': 161 force = 1; 162 break; 163 case 'N': 164 kernel = optarg; 165 break; 166 case 'z': 167 compress = 1; 168 break; 169 case '?': 170 default: 171 usage(); 172 } 173 argc -= optind; 174 argv += optind; 175 176 if (argc != (clear ? 0 : 1)) 177 usage(); 178 179 if (!clear) 180 dirname = argv[0]; 181 182 if (kernel == NULL) { 183 kernel = _PATH_UNIX; 184 } 185 186 (void)time(&now); 187 kmem_setup(); 188 189 if (clear) { 190 clear_dump(); 191 exit(0); 192 } 193 194 if (!dump_exists() && !force) 195 exit(1); 196 197 check_kmem(); 198 199 if (panicstr) 200 syslog(LOG_ALERT, "reboot after panic: %s", panic_mesg); 201 else 202 syslog(LOG_ALERT, "reboot"); 203 204 if ((!get_crashtime() || !check_space()) && !force) 205 exit(1); 206 207 save_core(); 208 209 clear_dump(); 210 exit(0); 211} 212 213void 214kmem_setup(void) 215{ 216 kvm_t *kd_kern; 217 char errbuf[_POSIX2_LINE_MAX]; 218 int i, hdrsz; 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 kernel namelist, but are 225 * presumed to be the same (since the disk partitions are probably 226 * the same!) 227 */ 228 kd_kern = kvm_openfiles(kernel, NULL, NULL, O_RDONLY, errbuf); 229 if (kd_kern == NULL) { 230 syslog(LOG_ERR, "%s: kvm_openfiles: %s", kernel, errbuf); 231 exit(1); 232 } 233 if (kvm_nlist(kd_kern, current_nl) == -1) 234 syslog(LOG_ERR, "%s: kvm_nlist: %s", kernel, 235 kvm_geterr(kd_kern)); 236 237 for (i = 0; cursyms[i] != -1; i++) 238 if (current_nl[cursyms[i]].n_value == 0) { 239 syslog(LOG_ERR, "%s: %s not in namelist", 240 kernel, current_nl[cursyms[i]].n_name); 241 exit(1); 242 } 243 244 if (KREAD(kd_kern, current_nl[X_DUMPDEV].n_value, &dumpdev) != 0) { 245 if (verbose) 246 syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern)); 247 exit(1); 248 } 249 if (dumpdev == NODEV) { 250 syslog(LOG_WARNING, "no core dump (no dumpdev)"); 251 exit(1); 252 } 253 if (KREAD(kd_kern, current_nl[X_DUMPLO].n_value, &dumplo) != 0) { 254 if (verbose) 255 syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern)); 256 exit(1); 257 } 258 if (dumplo == -1) { 259 syslog(LOG_WARNING, "no core dump (invalid dumplo)"); 260 exit(1); 261 } 262 dumplo *= DEV_BSIZE; 263 if (verbose) 264 (void)printf("dumplo = %ld (%ld * %ld)\n", 265 (long)dumplo, (long)(dumplo / DEV_BSIZE), (long)DEV_BSIZE); 266 if (KREAD(kd_kern, current_nl[X_DUMPMAG].n_value, &dumpmag) != 0) { 267 if (verbose) 268 syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern)); 269 exit(1); 270 } 271 272 (void)kvm_read(kd_kern, current_nl[X_VERSION].n_value, vers, 273 sizeof(vers)); 274 vers[sizeof(vers) - 1] = '\0'; 275 276 ddname = find_dev(dumpdev, S_IFBLK); 277 dumpfd = Open(ddname, O_RDWR); 278 279 kd_dump = kvm_openfiles(kernel, ddname, NULL, O_RDWR, errbuf); 280 if (kd_dump == NULL) { 281 syslog(LOG_ERR, "%s: kvm_openfiles: %s", kernel, errbuf); 282 exit(1); 283 } 284 285 if (kvm_nlist(kd_dump, dump_nl) == -1) 286 syslog(LOG_ERR, "%s: kvm_nlist: %s", kernel, 287 kvm_geterr(kd_dump)); 288 289 for (i = 0; dumpsyms[i] != -1; i++) 290 if (dump_nl[dumpsyms[i]].n_value == 0) { 291 syslog(LOG_ERR, "%s: %s not in namelist", 292 kernel, dump_nl[dumpsyms[i]].n_name); 293 exit(1); 294 } 295 hdrsz = kvm_dump_mkheader(kd_dump, (off_t)dumplo); 296 297 /* 298 * If 'hdrsz' == 0, kvm_dump_mkheader() failed on the magic-number 299 * checks, ergo no dump is present... 300 */ 301 if (hdrsz == 0) { 302 syslog(LOG_WARNING, "no core dump"); 303 exit(1); 304 } 305 if (hdrsz == -1) { 306 syslog(LOG_ERR, "%s: kvm_dump_mkheader: %s", kernel, 307 kvm_geterr(kd_dump)); 308 exit(1); 309 } 310 dumplo += hdrsz; 311 kvm_close(kd_kern); 312} 313 314void 315check_kmem(void) 316{ 317 char *cp; 318 long panicloc; 319 char core_vers[1024]; 320 321 (void)kvm_read(kd_dump, dump_nl[X_VERSION].n_value, core_vers, 322 sizeof(core_vers)); 323 core_vers[sizeof(core_vers) - 1] = '\0'; 324 325 if (strcmp(vers, core_vers) && kernel == 0) 326 syslog(LOG_WARNING, 327 "warning: %s version mismatch:\n\t%s\nand\t%s\n", 328 kernel, vers, core_vers); 329 330 if (KREAD(kd_dump, dump_nl[X_PANICSTR].n_value, &panicstr) != 0) { 331 if (verbose) 332 syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump)); 333 return; 334 } 335 if (panicstr) { 336 cp = panic_mesg; 337 panicloc = panicstr; 338 do { 339 if (KREAD(kd_dump, panicloc, cp) != 0) { 340 if (verbose) 341 syslog(LOG_WARNING, "kvm_read: %s", 342 kvm_geterr(kd_dump)); 343 break; 344 } 345 panicloc++; 346 } while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)-1]); 347 panic_mesg[sizeof(panic_mesg) - 1] = '\0'; 348 } 349} 350 351int 352dump_exists(void) 353{ 354 int newdumpmag; 355 356 if (KREAD(kd_dump, dump_nl[X_DUMPMAG].n_value, &newdumpmag) != 0) { 357 if (verbose) 358 syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump)); 359 return (0); 360 } 361 362 /* Read the dump size. */ 363 if (KREAD(kd_dump, dump_nl[X_DUMPSIZE].n_value, &dumpsize) != 0) { 364 if (verbose) 365 syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump)); 366 return (0); 367 } 368 dumpsize *= getpagesize(); 369 370 /* 371 * Return zero if core dump doesn't seem to be there, and note 372 * it for syslog. This check and return happens after the dump size 373 * is read, so dumpsize is whether or not the core is valid (for -f). 374 */ 375 if (newdumpmag != dumpmag) { 376 if (verbose) 377 syslog(LOG_WARNING, 378 "magic number mismatch (0x%x != 0x%x)", 379 newdumpmag, dumpmag); 380 syslog(LOG_WARNING, "no core dump"); 381 return (0); 382 } 383 return (1); 384} 385 386void 387clear_dump(void) 388{ 389 if (kvm_dump_inval(kd_dump) == -1) 390 syslog(LOG_ERR, "%s: kvm_clear_dump: %s", ddname, 391 kvm_geterr(kd_dump)); 392 393} 394 395char buf[1024 * 1024]; 396 397void 398save_core(void) 399{ 400 FILE *fp; 401 int bounds, ifd, nr, nw, ofd; 402 char *rawp, path[MAXPATHLEN]; 403 404 ofd = -1; 405 /* 406 * Get the current number and update the bounds file. Do the update 407 * now, because may fail later and don't want to overwrite anything. 408 */ 409 umask(066); 410 (void)snprintf(path, sizeof(path), "%s/bounds", dirname); 411 if ((fp = fopen(path, "r")) == NULL) 412 goto err1; 413 if (fgets(buf, sizeof(buf), fp) == NULL) { 414 if (ferror(fp)) 415err1: syslog(LOG_WARNING, "%s: %m", path); 416 bounds = 0; 417 } else 418 bounds = atoi(buf); 419 if (fp != NULL) 420 (void)fclose(fp); 421 if ((fp = fopen(path, "w")) == NULL) 422 syslog(LOG_ERR, "%s: %m", path); 423 else { 424 (void)fprintf(fp, "%d\n", bounds + 1); 425 (void)fclose(fp); 426 } 427 428 /* Create the core file. */ 429 (void)snprintf(path, sizeof(path), "%s/netbsd.%d.core%s", 430 dirname, bounds, compress ? ".gz" : ""); 431 if (compress) { 432 if ((fp = zopen(path, "w")) == NULL) { 433 syslog(LOG_ERR, "%s: %m", path); 434 exit(1); 435 } 436 } else { 437 ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 438 fp = fdopen(ofd, "w"); 439 if (fp == NULL) { 440 syslog(LOG_ERR, "%s: fdopen: %m", path); 441 exit(1); 442 } 443 } 444 445 /* Open the raw device. */ 446 rawp = rawname(ddname); 447 if ((ifd = open(rawp, O_RDONLY)) == -1) { 448 syslog(LOG_WARNING, "%s: %m; using block device", rawp); 449 ifd = dumpfd; 450 } 451 452 /* Seek to the start of the core. */ 453 Lseek(ifd, (off_t)dumplo, SEEK_SET); 454 455 if (kvm_dump_wrtheader(kd_dump, fp, dumpsize) == -1) { 456 syslog(LOG_ERR, "kvm_dump_wrtheader: %s : %s", path, 457 kvm_geterr(kd_dump)); 458 exit(1); 459 } 460 461 /* Copy the core file. */ 462 syslog(LOG_NOTICE, "writing %score to %s", 463 compress ? "compressed " : "", path); 464 for (; dumpsize > 0; dumpsize -= nr) { 465 (void)printf("%8dK\r", dumpsize / 1024); 466 (void)fflush(stdout); 467 nr = read(ifd, buf, MIN(dumpsize, sizeof(buf))); 468 if (nr <= 0) { 469 if (nr == 0) 470 syslog(LOG_WARNING, 471 "WARNING: EOF on dump device"); 472 else 473 syslog(LOG_ERR, "%s: %m", rawp); 474 goto err2; 475 } 476 nw = fwrite(buf, 1, nr, fp); 477 if (nw != nr) { 478 syslog(LOG_ERR, "%s: %s", 479 path, strerror(nw == 0 ? EIO : errno)); 480err2: syslog(LOG_WARNING, 481 "WARNING: core may be incomplete"); 482 (void)printf("\n"); 483 exit(1); 484 } 485 } 486 (void)close(ifd); 487 (void)fclose(fp); 488 489 /* Copy the kernel. */ 490 ifd = Open(kernel ? kernel : _PATH_UNIX, O_RDONLY); 491 (void)snprintf(path, sizeof(path), "%s/netbsd.%d%s", 492 dirname, bounds, compress ? ".gz" : ""); 493 if (compress) { 494 if ((fp = zopen(path, "w")) == NULL) { 495 syslog(LOG_ERR, "%s: %m", path); 496 exit(1); 497 } 498 } else 499 ofd = Create(path, S_IRUSR | S_IWUSR); 500 syslog(LOG_NOTICE, "writing %skernel to %s", 501 compress ? "compressed " : "", path); 502 while ((nr = read(ifd, buf, sizeof(buf))) > 0) { 503 if (compress) 504 nw = fwrite(buf, 1, nr, fp); 505 else 506 nw = write(ofd, buf, nr); 507 if (nw != nr) { 508 syslog(LOG_ERR, "%s: %s", 509 path, strerror(nw == 0 ? EIO : errno)); 510 syslog(LOG_WARNING, 511 "WARNING: kernel may be incomplete"); 512 exit(1); 513 } 514 } 515 if (nr < 0) { 516 syslog(LOG_ERR, "%s: %m", kernel ? kernel : _PATH_UNIX); 517 syslog(LOG_WARNING, "WARNING: kernel may be incomplete"); 518 exit(1); 519 } 520 if (compress) 521 (void)fclose(fp); 522 else 523 (void)close(ofd); 524} 525 526char * 527find_dev(dev_t dev, int type) 528{ 529 DIR *dfd; 530 struct dirent *dir; 531 struct stat sb; 532 char *dp, devname[MAXPATHLEN + 1]; 533 534 if ((dfd = opendir(_PATH_DEV)) == NULL) { 535 syslog(LOG_ERR, "%s: %m", _PATH_DEV); 536 exit(1); 537 } 538 (void)strcpy(devname, _PATH_DEV); 539 while ((dir = readdir(dfd))) { 540 (void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name); 541 if (lstat(devname, &sb)) { 542 syslog(LOG_ERR, "%s: %m", devname); 543 continue; 544 } 545 if ((sb.st_mode & S_IFMT) != type) 546 continue; 547 if (dev == sb.st_rdev) { 548 closedir(dfd); 549 if ((dp = strdup(devname)) == NULL) { 550 syslog(LOG_ERR, "%m"); 551 exit(1); 552 } 553 return (dp); 554 } 555 } 556 closedir(dfd); 557 syslog(LOG_ERR, "can't find device %d/%d", major(dev), minor(dev)); 558 exit(1); 559} 560 561char * 562rawname(char *s) 563{ 564 char *sl; 565 char name[MAXPATHLEN]; 566 567 if ((sl = strrchr(s, '/')) == NULL || sl[1] == '0') { 568 syslog(LOG_ERR, 569 "can't make raw dump device name from %s", s); 570 return (s); 571 } 572 (void)snprintf(name, sizeof(name), "%.*s/r%s", (int)(sl - s), s, 573 sl + 1); 574 if ((sl = strdup(name)) == NULL) { 575 syslog(LOG_ERR, "%m"); 576 exit(1); 577 } 578 return (sl); 579} 580 581int 582get_crashtime(void) 583{ 584 struct timeval dtime; 585 time_t dumptime; /* Time the dump was taken. */ 586 587 if (KREAD(kd_dump, dump_nl[X_TIME].n_value, &dtime) != 0) { 588 if (verbose) 589 syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump)); 590 return (0); 591 } 592 dumptime = dtime.tv_sec; 593 if (dumptime == 0) { 594 if (verbose) 595 syslog(LOG_ERR, "dump time is zero"); 596 return (0); 597 } 598 (void)printf("savecore: system went down at %s", ctime(&dumptime)); 599#define LEEWAY (7 * SECSPERDAY) 600 if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) { 601 (void)printf("dump time is unreasonable\n"); 602 return (0); 603 } 604 return (1); 605} 606 607int 608check_space(void) 609{ 610 FILE *fp; 611 off_t minfree, spacefree, kernelsize, needed; 612 struct stat st; 613 struct statfs fsbuf; 614 char mbuf[100], path[MAXPATHLEN]; 615 616#ifdef __GNUC__ 617 (void) &minfree; 618#endif 619 620 if (stat(kernel, &st) < 0) { 621 syslog(LOG_ERR, "%s: %m", kernel); 622 exit(1); 623 } 624 kernelsize = st.st_blocks * S_BLKSIZE; 625 if (statfs(dirname, &fsbuf) < 0) { 626 syslog(LOG_ERR, "%s: %m", dirname); 627 exit(1); 628 } 629 spacefree = fsbuf.f_bavail; 630 spacefree *= fsbuf.f_bsize; 631 spacefree /= 1024; 632 633 (void)snprintf(path, sizeof(path), "%s/minfree", dirname); 634 if ((fp = fopen(path, "r")) == NULL) 635 minfree = 0; 636 else { 637 if (fgets(mbuf, sizeof(mbuf), fp) == NULL) 638 minfree = 0; 639 else 640 minfree = atoi(mbuf); 641 (void)fclose(fp); 642 } 643 644 needed = (dumpsize + kernelsize) / 1024; 645 if (minfree > 0 && spacefree - needed < minfree) { 646 syslog(LOG_WARNING, 647 "no dump, not enough free space in %s", dirname); 648 return (0); 649 } 650 if (spacefree - needed < minfree) 651 syslog(LOG_WARNING, 652 "dump performed, but free space threshold crossed"); 653 return (1); 654} 655 656int 657Open(char *name, int rw) 658{ 659 int fd; 660 661 if ((fd = open(name, rw, 0)) < 0) { 662 syslog(LOG_ERR, "%s: %m", name); 663 exit(1); 664 } 665 return (fd); 666} 667 668void 669Lseek(int fd, off_t off, int flag) 670{ 671 off_t ret; 672 673 ret = lseek(fd, off, flag); 674 if (ret == -1) { 675 syslog(LOG_ERR, "lseek: %m"); 676 exit(1); 677 } 678} 679 680int 681Create(char *file, int mode) 682{ 683 int fd; 684 685 fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode); 686 if (fd < 0) { 687 syslog(LOG_ERR, "%s: %m", file); 688 exit(1); 689 } 690 return (fd); 691} 692 693void 694Write(int fd, void *bp, int size) 695{ 696 int n; 697 698 if ((n = write(fd, bp, size)) < size) { 699 syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO)); 700 exit(1); 701 } 702} 703 704void 705usage(void) 706{ 707 (void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory"); 708 exit(1); 709} 710