savecore.c revision 1.31
1/*	$NetBSD: savecore.c,v 1.31 1997/08/25 19:31:53 kleink 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#ifndef lint
37static char copyright[] =
38"@(#) 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.3 (Berkeley) 1/2/94";
45#else
46static char rcsid[] = "$NetBSD: savecore.c,v 1.31 1997/08/25 19:31:53 kleink 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 <tzfile.h>
65#include <unistd.h>
66#include <limits.h>
67#include <kvm.h>
68
69extern FILE *zopen __P((const char *fname, const char *mode, int bits));
70
71#define KREAD(kd, addr, p)\
72	(kvm_read(kd, addr, (char *)(p), sizeof(*(p))) != sizeof(*(p)))
73
74struct nlist current_nl[] = {	/* Namelist for currently running system. */
75#define X_DUMPDEV	0
76	{ "_dumpdev" },
77#define X_DUMPLO	1
78	{ "_dumplo" },
79#define X_TIME		2
80	{ "_time" },
81#define	X_DUMPSIZE	3
82	{ "_dumpsize" },
83#define X_VERSION	4
84	{ "_version" },
85#define X_PANICSTR	5
86	{ "_panicstr" },
87#define	X_DUMPMAG	6
88	{ "_dumpmag" },
89	{ NULL },
90};
91int cursyms[] = { X_DUMPDEV, X_DUMPLO, X_VERSION, X_DUMPMAG, -1 };
92int dumpsyms[] = { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 };
93
94struct nlist dump_nl[] = {	/* Name list for dumped system. */
95	{ "_dumpdev" },		/* Entries MUST be the same as */
96	{ "_dumplo" },		/*	those in current_nl[].  */
97	{ "_time" },
98	{ "_dumpsize" },
99	{ "_version" },
100	{ "_panicstr" },
101	{ "_dumpmag" },
102	{ NULL },
103};
104
105/* Types match kernel declarations. */
106long	dumplo;				/* where dump starts on dumpdev */
107int	dumpmag;			/* magic number in dump */
108int	dumpsize;			/* amount of memory dumped */
109
110char	*kernel;
111char	*dirname;			/* directory to save dumps in */
112char	*ddname;			/* name of dump device */
113dev_t	dumpdev;			/* dump device */
114int	dumpfd;				/* read/write descriptor on block dev */
115kvm_t	*kd_dump;			/* kvm descriptor on block dev	*/
116time_t	now;				/* current date */
117char	panic_mesg[1024];
118long	panicstr;
119char	vers[1024];
120
121int	clear, compress, force, verbose;	/* flags */
122
123void	 check_kmem __P((void));
124int	 check_space __P((void));
125void	 clear_dump __P((void));
126int	 Create __P((char *, int));
127int	 dump_exists __P((void));
128char	*find_dev __P((dev_t, int));
129int	 get_crashtime __P((void));
130void	 kmem_setup __P((void));
131void	 log __P((int, char *, ...));
132void	 Lseek __P((int, off_t, int));
133int	 Open __P((char *, int rw));
134char	*rawname __P((char *s));
135void	 save_core __P((void));
136void	 usage __P((void));
137void	 Write __P((int, void *, int));
138
139int
140main(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		dirname = 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	if ((!get_crashtime() || !check_space()) && !force)
200		exit(1);
201
202	save_core();
203
204	clear_dump();
205	exit(0);
206}
207
208void
209kmem_setup()
210{
211	kvm_t	*kd_kern;
212	char	errbuf[_POSIX2_LINE_MAX];
213	int	i, hdrsz;
214	char	*dump_sys;
215
216	/*
217	 * Some names we need for the currently running system, others for
218	 * the system that was running when the dump was made.  The values
219	 * obtained from the current system are used to look for things in
220	 * /dev/kmem that cannot be found in the dump_sys namelist, but are
221	 * presumed to be the same (since the disk partitions are probably
222	 * the same!)
223	 */
224	kd_kern = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
225	if (kd_kern == NULL) {
226		syslog(LOG_ERR, "%s: kvm_openfiles: %s", _PATH_UNIX, errbuf);
227		exit(1);
228	}
229	if (kvm_nlist(kd_kern, current_nl) == -1)
230		syslog(LOG_ERR, "%s: kvm_nlist: %s", _PATH_UNIX,
231			kvm_geterr(kd_kern));
232
233	for (i = 0; cursyms[i] != -1; i++)
234		if (current_nl[cursyms[i]].n_value == 0) {
235			syslog(LOG_ERR, "%s: %s not in namelist",
236			    _PATH_UNIX, current_nl[cursyms[i]].n_name);
237			exit(1);
238		}
239
240	if (KREAD(kd_kern, current_nl[X_DUMPDEV].n_value, &dumpdev) != 0) {
241		if (verbose)
242		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
243		exit(1);
244	}
245	if (dumpdev == NODEV) {
246		syslog(LOG_WARNING, "no core dump (no dumpdev)");
247		exit(1);
248	}
249	if (KREAD(kd_kern, current_nl[X_DUMPLO].n_value, &dumplo) != 0) {
250		if (verbose)
251		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
252		exit(1);
253	}
254	dumplo *= DEV_BSIZE;
255	if (verbose)
256		(void)printf("dumplo = %d (%d * %d)\n",
257		    dumplo, dumplo / DEV_BSIZE, DEV_BSIZE);
258	if (KREAD(kd_kern, current_nl[X_DUMPMAG].n_value, &dumpmag) != 0) {
259		if (verbose)
260		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
261		exit(1);
262	}
263
264	if (kernel == NULL) {
265		(void)kvm_read(kd_kern, current_nl[X_VERSION].n_value,
266			vers, sizeof(vers));
267		vers[sizeof(vers) - 1] = '\0';
268	}
269
270	ddname = find_dev(dumpdev, S_IFBLK);
271	dumpfd = Open(ddname, O_RDWR);
272
273	dump_sys = kernel ? kernel : _PATH_UNIX;
274
275	kd_dump = kvm_openfiles(dump_sys, ddname, NULL, O_RDWR, errbuf);
276	if (kd_dump == NULL) {
277		syslog(LOG_ERR, "%s: kvm_openfiles: %s", dump_sys, errbuf);
278		exit(1);
279	}
280
281	if (kvm_nlist(kd_dump, dump_nl) == -1)
282		syslog(LOG_ERR, "%s: kvm_nlist: %s", dump_sys,
283			kvm_geterr(kd_dump));
284
285	for (i = 0; dumpsyms[i] != -1; i++)
286		if (dump_nl[dumpsyms[i]].n_value == 0) {
287			syslog(LOG_ERR, "%s: %s not in namelist",
288			    dump_sys, dump_nl[dumpsyms[i]].n_name);
289			exit(1);
290		}
291	hdrsz = kvm_dump_mkheader(kd_dump, (off_t)dumplo);
292
293	/*
294	 * If 'hdrsz' == 0, kvm_dump_mkheader() failed on the magic-number
295	 * checks, ergo no dump is present...
296	 */
297	if (hdrsz == 0) {
298		syslog(LOG_WARNING, "no core dump");
299		exit(1);
300	}
301	if (hdrsz == -1) {
302		syslog(LOG_ERR, "%s: kvm_dump_mkheader: %s", dump_sys,
303			kvm_geterr(kd_dump));
304		exit(1);
305	}
306	dumplo += hdrsz;
307	kvm_close(kd_kern);
308}
309
310void
311check_kmem()
312{
313	register char	*cp;
314	register long	panicloc;
315	char core_vers[1024];
316
317	(void)kvm_read(kd_dump, dump_nl[X_VERSION].n_value, core_vers,
318		sizeof(core_vers));
319	core_vers[sizeof(core_vers) - 1] = '\0';
320
321	if (strcmp(vers, core_vers) && kernel == 0)
322		syslog(LOG_WARNING,
323		    "warning: %s version mismatch:\n\t%s\nand\t%s\n",
324		    _PATH_UNIX, vers, core_vers);
325
326	if (KREAD(kd_dump, dump_nl[X_PANICSTR].n_value, &panicstr) != 0) {
327		if (verbose)
328		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
329		return;
330	}
331	if (panicstr) {
332		cp       = panic_mesg;
333		panicloc = panicstr;
334		do {
335			if (KREAD(kd_dump, panicloc, cp) != 0) {
336				if (verbose)
337				    syslog(LOG_WARNING, "kvm_read: %s",
338					   kvm_geterr(kd_dump));
339				break;
340			}
341			panicloc++;
342		} while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)-1]);
343		panic_mesg[sizeof(panic_mesg) - 1] = '\0';
344	}
345}
346
347int
348dump_exists()
349{
350	int newdumpmag;
351
352	if (KREAD(kd_dump, dump_nl[X_DUMPMAG].n_value, &newdumpmag) != 0) {
353		if (verbose)
354		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
355		return (0);
356	}
357
358	/* Read the dump size. */
359	if (KREAD(kd_dump, dump_nl[X_DUMPSIZE].n_value, &dumpsize) != 0) {
360		if (verbose)
361		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
362		return (0);
363	}
364	dumpsize *= getpagesize();
365
366	/*
367	 * Return zero if core dump doesn't seem to be there, and note
368	 * it for syslog.  This check and return happens after the dump size
369	 * is read, so dumpsize is whether or not the core is valid (for -f).
370	 */
371	if (newdumpmag != dumpmag) {
372		if (verbose)
373			syslog(LOG_WARNING, "magic number mismatch (%x != %x)",
374			    newdumpmag, dumpmag);
375		syslog(LOG_WARNING, "no core dump");
376		return (0);
377	}
378	return (1);
379}
380
381void
382clear_dump()
383{
384	if (kvm_dump_inval(kd_dump) == -1)
385		syslog(LOG_ERR, "%s: kvm_clear_dump: %s", ddname,
386			kvm_geterr(kd_dump));
387
388}
389
390char buf[1024 * 1024];
391
392void
393save_core()
394{
395	register FILE *fp;
396	register int bounds, ifd, nr, nw, ofd;
397	char *rawp, path[MAXPATHLEN];
398
399	/*
400	 * Get the current number and update the bounds file.  Do the update
401	 * now, because may fail later and don't want to overwrite anything.
402	 */
403	umask(002);
404	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
405	if ((fp = fopen(path, "r")) == NULL)
406		goto err1;
407	if (fgets(buf, sizeof(buf), fp) == NULL) {
408		if (ferror(fp))
409err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
410		bounds = 0;
411	} else
412		bounds = atoi(buf);
413	if (fp != NULL)
414		(void)fclose(fp);
415	if ((fp = fopen(path, "w")) == NULL)
416		syslog(LOG_ERR, "%s: %m", path);
417	else {
418		(void)fprintf(fp, "%d\n", bounds + 1);
419		(void)fclose(fp);
420	}
421	(void)fclose(fp);
422
423	/* Create the core file. */
424	(void)snprintf(path, sizeof(path), "%s/netbsd.%d.core%s",
425	    dirname, bounds, compress ? ".Z" : "");
426	if (compress) {
427		if ((fp = zopen(path, "w", 0)) == NULL) {
428			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
429			exit(1);
430		}
431	} else {
432		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
433		fp  = fdopen(ofd, "w");
434		if (fp == NULL) {
435			syslog(LOG_ERR, "%s: fdopen: %s", path);
436			exit(1);
437		}
438	}
439
440	/* Open the raw device. */
441	rawp = rawname(ddname);
442	if ((ifd = open(rawp, O_RDONLY)) == -1) {
443		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
444		ifd = dumpfd;
445	}
446
447	/* Seek to the start of the core. */
448	Lseek(ifd, (off_t)dumplo, SEEK_SET);
449
450	if (kvm_dump_wrtheader(kd_dump, fp, dumpsize) == -1) {
451		syslog(LOG_ERR, "kvm_dump_wrtheader: %s : %s", path,
452			kvm_geterr(kd_dump));
453		exit(1);
454	}
455
456	/* Copy the core file. */
457	syslog(LOG_NOTICE, "writing %score to %s",
458	    compress ? "compressed " : "", path);
459	for (; dumpsize > 0; dumpsize -= nr) {
460		(void)printf("%6dK\r", dumpsize / 1024);
461		(void)fflush(stdout);
462		nr = read(ifd, buf, MIN(dumpsize, sizeof(buf)));
463		if (nr <= 0) {
464			if (nr == 0)
465				syslog(LOG_WARNING,
466				    "WARNING: EOF on dump device");
467			else
468				syslog(LOG_ERR, "%s: %m", rawp);
469			goto err2;
470		}
471		nw = fwrite(buf, 1, nr, fp);
472		if (nw != nr) {
473			syslog(LOG_ERR, "%s: %s",
474			    path, strerror(nw == 0 ? EIO : errno));
475err2:			syslog(LOG_WARNING,
476			    "WARNING: core may be incomplete");
477			(void)printf("\n");
478			exit(1);
479		}
480	}
481	(void)close(ifd);
482	(void)fclose(fp);
483
484	/* Copy the kernel. */
485	ifd = Open(kernel ? kernel : _PATH_UNIX, O_RDONLY);
486	(void)snprintf(path, sizeof(path), "%s/netbsd.%d%s",
487	    dirname, bounds, compress ? ".Z" : "");
488	if (compress) {
489		if ((fp = zopen(path, "w", 0)) == NULL) {
490			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
491			exit(1);
492		}
493	} else
494		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
495	syslog(LOG_NOTICE, "writing %skernel to %s",
496	    compress ? "compressed " : "", path);
497	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
498		if (compress)
499			nw = fwrite(buf, 1, nr, fp);
500		else
501			nw = write(ofd, buf, nr);
502		if (nw != nr) {
503			syslog(LOG_ERR, "%s: %s",
504			    path, strerror(nw == 0 ? EIO : errno));
505			syslog(LOG_WARNING,
506			    "WARNING: kernel may be incomplete");
507			exit(1);
508		}
509	}
510	if (nr < 0) {
511		syslog(LOG_ERR, "%s: %s",
512		    kernel ? kernel : _PATH_UNIX, strerror(errno));
513		syslog(LOG_WARNING,
514		    "WARNING: kernel may be incomplete");
515		exit(1);
516	}
517	if (compress)
518		(void)fclose(fp);
519	else
520		(void)close(ofd);
521}
522
523char *
524find_dev(dev, type)
525	register dev_t dev;
526	register int type;
527{
528	register DIR *dfd;
529	struct dirent *dir;
530	struct stat sb;
531	char *dp, devname[MAXPATHLEN + 1];
532
533	if ((dfd = opendir(_PATH_DEV)) == NULL) {
534		syslog(LOG_ERR, "%s: %s", _PATH_DEV, strerror(errno));
535		exit(1);
536	}
537	(void)strcpy(devname, _PATH_DEV);
538	while ((dir = readdir(dfd))) {
539		(void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name);
540		if (lstat(devname, &sb)) {
541			syslog(LOG_ERR, "%s: %s", devname, strerror(errno));
542			continue;
543		}
544		if ((sb.st_mode & S_IFMT) != type)
545			continue;
546		if (dev == sb.st_rdev) {
547			closedir(dfd);
548			if ((dp = strdup(devname)) == NULL) {
549				syslog(LOG_ERR, "%s", strerror(errno));
550				exit(1);
551			}
552			return (dp);
553		}
554	}
555	closedir(dfd);
556	syslog(LOG_ERR, "can't find device %d/%d", major(dev), minor(dev));
557	exit(1);
558}
559
560char *
561rawname(s)
562	char *s;
563{
564	char *sl, name[MAXPATHLEN];
565
566	if ((sl = strrchr(s, '/')) == NULL || sl[1] == '0') {
567		syslog(LOG_ERR,
568		    "can't make raw dump device name from %s", s);
569		return (s);
570	}
571	(void)snprintf(name, sizeof(name), "%.*s/r%s", sl - s, s, sl + 1);
572	if ((sl = strdup(name)) == NULL) {
573		syslog(LOG_ERR, "%s", strerror(errno));
574		exit(1);
575	}
576	return (sl);
577}
578
579int
580get_crashtime()
581{
582	time_t dumptime;			/* Time the dump was taken. */
583
584	if (KREAD(kd_dump, dump_nl[X_TIME].n_value, &dumptime) != 0) {
585		if (verbose)
586		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
587		return (0);
588	}
589	if (dumptime == 0) {
590		if (verbose)
591			syslog(LOG_ERR, "dump time is zero");
592		return (0);
593	}
594	(void)printf("savecore: system went down at %s", ctime(&dumptime));
595#define	LEEWAY	(7 * SECSPERDAY)
596	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
597		(void)printf("dump time is unreasonable\n");
598		return (0);
599	}
600	return (1);
601}
602
603int
604check_space()
605{
606	register FILE *fp;
607	char *tkernel;
608	off_t minfree, spacefree, kernelsize, needed;
609	struct stat st;
610	struct statfs fsbuf;
611	char buf[100], path[MAXPATHLEN];
612
613	tkernel = kernel ? kernel : _PATH_UNIX;
614	if (stat(tkernel, &st) < 0) {
615		syslog(LOG_ERR, "%s: %m", tkernel);
616		exit(1);
617	}
618	kernelsize = st.st_blocks * S_BLKSIZE;
619	if (statfs(dirname, &fsbuf) < 0) {
620		syslog(LOG_ERR, "%s: %m", dirname);
621		exit(1);
622	}
623 	spacefree = (fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
624
625	(void)snprintf(path, sizeof(path), "%s/minfree", dirname);
626	if ((fp = fopen(path, "r")) == NULL)
627		minfree = 0;
628	else {
629		if (fgets(buf, sizeof(buf), fp) == NULL)
630			minfree = 0;
631		else
632			minfree = atoi(buf);
633		(void)fclose(fp);
634	}
635
636	needed = (dumpsize + kernelsize) / 1024;
637 	if (minfree > 0 && spacefree - needed < minfree) {
638		syslog(LOG_WARNING,
639		    "no dump, not enough free space on device");
640		return (0);
641	}
642	if (spacefree - needed < minfree)
643		syslog(LOG_WARNING,
644		    "dump performed, but free space threshold crossed");
645	return (1);
646}
647
648int
649Open(name, rw)
650	char *name;
651	int rw;
652{
653	int fd;
654
655	if ((fd = open(name, rw, 0)) < 0) {
656		syslog(LOG_ERR, "%s: %m", name);
657		exit(1);
658	}
659	return (fd);
660}
661
662void
663Lseek(fd, off, flag)
664	int fd, flag;
665	off_t off;
666{
667	off_t ret;
668
669	ret = lseek(fd, off, flag);
670	if (ret == -1) {
671		syslog(LOG_ERR, "lseek: %m");
672		exit(1);
673	}
674}
675
676int
677Create(file, mode)
678	char *file;
679	int mode;
680{
681	register int fd;
682
683	fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode);
684	if (fd < 0) {
685		syslog(LOG_ERR, "%s: %m", file);
686		exit(1);
687	}
688	return (fd);
689}
690
691void
692Write(fd, bp, size)
693	int fd, size;
694	void *bp;
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()
706{
707	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
708	exit(1);
709}
710