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
44static 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
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
122static int	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