savecore.c revision 1.50 1 /* $NetBSD: savecore.c,v 1.50 2001/01/11 20:27:12 martin 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
44 static char sccsid[] = "@(#)savecore.c 8.5 (Berkeley) 4/28/95";
45 #else
46 __RCSID("$NetBSD: savecore.c,v 1.50 2001/01/11 20:27:12 martin 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
70 extern 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
75 struct 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 };
92 int cursyms[] = { X_DUMPDEV, X_DUMPLO, X_VERSION, X_DUMPMAG, -1 };
93 int dumpsyms[] = { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 };
94
95 struct 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. */
107 long dumplo; /* where dump starts on dumpdev */
108 int dumpmag; /* magic number in dump */
109 int dumpsize; /* amount of memory dumped */
110
111 char *kernel; /* name of used kernel */
112 char *dirname; /* directory to save dumps in */
113 char *ddname; /* name of dump device */
114 dev_t dumpdev; /* dump device */
115 int dumpfd; /* read/write descriptor on block dev */
116 kvm_t *kd_dump; /* kvm descriptor on block dev */
117 time_t now; /* current date */
118 char panic_mesg[1024];
119 long panicstr;
120 char vers[1024];
121
122 static int clear, compress, force, verbose; /* flags */
123
124 void check_kmem(void);
125 int check_space(void);
126 void clear_dump(void);
127 int Create(char *, int);
128 int dump_exists(void);
129 char *find_dev(dev_t, int);
130 int get_crashtime(void);
131 void kmem_setup(void);
132 void log(int, char *, ...);
133 void Lseek(int, off_t, int);
134 int main(int, char *[]);
135 int Open(char *, int rw);
136 char *rawname(char *s);
137 void save_core(void);
138 void usage(void);
139 void Write(int, void *, int);
140
141 int
142 main(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
213 void
214 kmem_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
314 void
315 check_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
351 int
352 dump_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
386 void
387 clear_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
395 char buf[1024 * 1024];
396
397 void
398 save_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))
415 err1: 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));
480 err2: 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
526 char *
527 find_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
561 char *
562 rawname(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
581 int
582 get_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
607 int
608 check_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
656 int
657 Open(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
668 void
669 Lseek(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
680 int
681 Create(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
693 void
694 Write(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
704 void
705 usage(void)
706 {
707 (void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
708 exit(1);
709 }
710