savecore.c revision 1.43
1/*	$NetBSD: savecore.c,v 1.43 2000/10/08 07:04:28 darrenr 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.43 2000/10/08 07:04:28 darrenr 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 __P((const char *fname, const char *mode, int bits));
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 = _PATH_UNIX;
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 __P((void));
125int	 check_space __P((void));
126void	 clear_dump __P((void));
127int	 Create __P((char *, int));
128int	 dump_exists __P((void));
129char	*find_dev __P((dev_t, int));
130int	 get_crashtime __P((void));
131void	 kmem_setup __P((void));
132void	 log __P((int, char *, ...));
133void	 Lseek __P((int, off_t, int));
134int	 main __P((int, char *[]));
135int	 Open __P((char *, int rw));
136char	*rawname __P((char *s));
137void	 save_core __P((void));
138void	 usage __P((void));
139void	 Write __P((int, void *, int));
140
141int
142main(argc, argv)
143	int argc;
144	char *argv[];
145{
146	int ch;
147
148	openlog("savecore", LOG_PERROR, LOG_DAEMON);
149
150	while ((ch = getopt(argc, argv, "cdfN:vz")) != -1)
151		switch(ch) {
152		case 'c':
153			clear = 1;
154			break;
155		case 'd':		/* Not documented. */
156		case 'v':
157			verbose = 1;
158			break;
159		case 'f':
160			force = 1;
161			break;
162		case 'N':
163			kernel = optarg;
164			break;
165		case 'z':
166			compress = 1;
167			break;
168		case '?':
169		default:
170			usage();
171		}
172	argc -= optind;
173	argv += optind;
174
175	if (!clear) {
176		if (argc != 1 && argc != 2)
177			usage();
178		dirname = argv[0];
179	}
180	if (argc == 2)
181		kernel = argv[1];
182
183	(void)time(&now);
184	kmem_setup();
185
186	if (clear) {
187		clear_dump();
188		exit(0);
189	}
190
191	if (!dump_exists() && !force)
192		exit(1);
193
194	check_kmem();
195
196	if (panicstr)
197		syslog(LOG_ALERT, "reboot after panic: %s", panic_mesg);
198	else
199		syslog(LOG_ALERT, "reboot");
200
201	if ((!get_crashtime() || !check_space()) && !force)
202		exit(1);
203
204	save_core();
205
206	clear_dump();
207	exit(0);
208}
209
210void
211kmem_setup()
212{
213	kvm_t	*kd_kern;
214	char	errbuf[_POSIX2_LINE_MAX];
215	int	i, hdrsz;
216
217	/*
218	 * Some names we need for the currently running system, others for
219	 * the system that was running when the dump was made.  The values
220	 * obtained from the current system are used to look for things in
221	 * /dev/kmem that cannot be found in the kernel namelist, but are
222	 * presumed to be the same (since the disk partitions are probably
223	 * the same!)
224	 */
225	kd_kern = kvm_openfiles(kernel, NULL, NULL, O_RDONLY, errbuf);
226	if (kd_kern == NULL) {
227		syslog(LOG_ERR, "%s: kvm_openfiles: %s", _PATH_UNIX, errbuf);
228		exit(1);
229	}
230	if (kvm_nlist(kd_kern, current_nl) == -1)
231		syslog(LOG_ERR, "%s: kvm_nlist: %s", _PATH_UNIX,
232			kvm_geterr(kd_kern));
233
234	for (i = 0; cursyms[i] != -1; i++)
235		if (current_nl[cursyms[i]].n_value == 0) {
236			syslog(LOG_ERR, "%s: %s not in namelist",
237			    _PATH_UNIX, current_nl[cursyms[i]].n_name);
238			exit(1);
239		}
240
241	if (KREAD(kd_kern, current_nl[X_DUMPDEV].n_value, &dumpdev) != 0) {
242		if (verbose)
243		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
244		exit(1);
245	}
246	if (dumpdev == NODEV) {
247		syslog(LOG_WARNING, "no core dump (no dumpdev)");
248		exit(1);
249	}
250	if (KREAD(kd_kern, current_nl[X_DUMPLO].n_value, &dumplo) != 0) {
251		if (verbose)
252		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
253		exit(1);
254	}
255	dumplo *= DEV_BSIZE;
256	if (verbose)
257		(void)printf("dumplo = %ld (%ld * %ld)\n",
258		    (long)dumplo, (long)(dumplo / DEV_BSIZE), (long)DEV_BSIZE);
259	if (KREAD(kd_kern, current_nl[X_DUMPMAG].n_value, &dumpmag) != 0) {
260		if (verbose)
261		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
262		exit(1);
263	}
264
265	if (kernel == NULL) {
266		(void)kvm_read(kd_kern, current_nl[X_VERSION].n_value,
267			vers, sizeof(vers));
268		vers[sizeof(vers) - 1] = '\0';
269	}
270
271	ddname = find_dev(dumpdev, S_IFBLK);
272	dumpfd = Open(ddname, O_RDWR);
273
274	kd_dump = kvm_openfiles(kernel, ddname, NULL, O_RDWR, errbuf);
275	if (kd_dump == NULL) {
276		syslog(LOG_ERR, "%s: kvm_openfiles: %s", kernel, errbuf);
277		exit(1);
278	}
279
280	if (kvm_nlist(kd_dump, dump_nl) == -1)
281		syslog(LOG_ERR, "%s: kvm_nlist: %s", kernel,
282			kvm_geterr(kd_dump));
283
284	for (i = 0; dumpsyms[i] != -1; i++)
285		if (dump_nl[dumpsyms[i]].n_value == 0) {
286			syslog(LOG_ERR, "%s: %s not in namelist",
287			    kernel, dump_nl[dumpsyms[i]].n_name);
288			exit(1);
289		}
290	hdrsz = kvm_dump_mkheader(kd_dump, (off_t)dumplo);
291
292	/*
293	 * If 'hdrsz' == 0, kvm_dump_mkheader() failed on the magic-number
294	 * checks, ergo no dump is present...
295	 */
296	if (hdrsz == 0) {
297		syslog(LOG_WARNING, "no core dump");
298		exit(1);
299	}
300	if (hdrsz == -1) {
301		syslog(LOG_ERR, "%s: kvm_dump_mkheader: %s", kernel,
302			kvm_geterr(kd_dump));
303		exit(1);
304	}
305	dumplo += hdrsz;
306	kvm_close(kd_kern);
307}
308
309void
310check_kmem()
311{
312	char	*cp;
313	long	panicloc;
314	char core_vers[1024];
315
316	(void)kvm_read(kd_dump, dump_nl[X_VERSION].n_value, core_vers,
317		sizeof(core_vers));
318	core_vers[sizeof(core_vers) - 1] = '\0';
319
320	if (strcmp(vers, core_vers) && kernel == 0)
321		syslog(LOG_WARNING,
322		    "warning: %s version mismatch:\n\t%s\nand\t%s\n",
323		    _PATH_UNIX, vers, core_vers);
324
325	if (KREAD(kd_dump, dump_nl[X_PANICSTR].n_value, &panicstr) != 0) {
326		if (verbose)
327		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
328		return;
329	}
330	if (panicstr) {
331		cp       = panic_mesg;
332		panicloc = panicstr;
333		do {
334			if (KREAD(kd_dump, panicloc, cp) != 0) {
335				if (verbose)
336				    syslog(LOG_WARNING, "kvm_read: %s",
337					   kvm_geterr(kd_dump));
338				break;
339			}
340			panicloc++;
341		} while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)-1]);
342		panic_mesg[sizeof(panic_mesg) - 1] = '\0';
343	}
344}
345
346int
347dump_exists()
348{
349	int newdumpmag;
350
351	if (KREAD(kd_dump, dump_nl[X_DUMPMAG].n_value, &newdumpmag) != 0) {
352		if (verbose)
353		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
354		return (0);
355	}
356
357	/* Read the dump size. */
358	if (KREAD(kd_dump, dump_nl[X_DUMPSIZE].n_value, &dumpsize) != 0) {
359		if (verbose)
360		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
361		return (0);
362	}
363	dumpsize *= getpagesize();
364
365	/*
366	 * Return zero if core dump doesn't seem to be there, and note
367	 * it for syslog.  This check and return happens after the dump size
368	 * is read, so dumpsize is whether or not the core is valid (for -f).
369	 */
370	if (newdumpmag != dumpmag) {
371		if (verbose)
372			syslog(LOG_WARNING,
373			    "magic number mismatch (0x%x != 0x%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	FILE *fp;
396	int bounds, ifd, nr, nw, ofd;
397	char *rawp, path[MAXPATHLEN];
398
399	ofd = -1;
400	/*
401	 * Get the current number and update the bounds file.  Do the update
402	 * now, because may fail later and don't want to overwrite anything.
403	 */
404	umask(066);
405	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
406	if ((fp = fopen(path, "r")) == NULL)
407		goto err1;
408	if (fgets(buf, sizeof(buf), fp) == NULL) {
409		if (ferror(fp))
410err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
411		bounds = 0;
412	} else
413		bounds = atoi(buf);
414	if (fp != NULL)
415		(void)fclose(fp);
416	if ((fp = fopen(path, "w")) == NULL)
417		syslog(LOG_ERR, "%s: %m", path);
418	else {
419		(void)fprintf(fp, "%d\n", bounds + 1);
420		(void)fclose(fp);
421	}
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			    strerror(errno));
437			exit(1);
438		}
439	}
440
441	/* Open the raw device. */
442	rawp = rawname(ddname);
443	if ((ifd = open(rawp, O_RDONLY)) == -1) {
444		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
445		ifd = dumpfd;
446	}
447
448	/* Seek to the start of the core. */
449	Lseek(ifd, (off_t)dumplo, SEEK_SET);
450
451	if (kvm_dump_wrtheader(kd_dump, fp, dumpsize) == -1) {
452		syslog(LOG_ERR, "kvm_dump_wrtheader: %s : %s", path,
453			kvm_geterr(kd_dump));
454		exit(1);
455	}
456
457	/* Copy the core file. */
458	syslog(LOG_NOTICE, "writing %score to %s",
459	    compress ? "compressed " : "", path);
460	for (; dumpsize > 0; dumpsize -= nr) {
461		(void)printf("%6dK\r", dumpsize / 1024);
462		(void)fflush(stdout);
463		nr = read(ifd, buf, MIN(dumpsize, sizeof(buf)));
464		if (nr <= 0) {
465			if (nr == 0)
466				syslog(LOG_WARNING,
467				    "WARNING: EOF on dump device");
468			else
469				syslog(LOG_ERR, "%s: %m", rawp);
470			goto err2;
471		}
472		nw = fwrite(buf, 1, nr, fp);
473		if (nw != nr) {
474			syslog(LOG_ERR, "%s: %s",
475			    path, strerror(nw == 0 ? EIO : errno));
476err2:			syslog(LOG_WARNING,
477			    "WARNING: core may be incomplete");
478			(void)printf("\n");
479			exit(1);
480		}
481	}
482	(void)close(ifd);
483	(void)fclose(fp);
484
485	/* Copy the kernel. */
486	ifd = Open(kernel ? kernel : _PATH_UNIX, O_RDONLY);
487	(void)snprintf(path, sizeof(path), "%s/netbsd.%d%s",
488	    dirname, bounds, compress ? ".Z" : "");
489	if (compress) {
490		if ((fp = zopen(path, "w", 0)) == NULL) {
491			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
492			exit(1);
493		}
494	} else
495		ofd = Create(path, S_IRUSR | S_IWUSR);
496	syslog(LOG_NOTICE, "writing %skernel to %s",
497	    compress ? "compressed " : "", path);
498	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
499		if (compress)
500			nw = fwrite(buf, 1, nr, fp);
501		else
502			nw = write(ofd, buf, nr);
503		if (nw != nr) {
504			syslog(LOG_ERR, "%s: %s",
505			    path, strerror(nw == 0 ? EIO : errno));
506			syslog(LOG_WARNING,
507			    "WARNING: kernel may be incomplete");
508			exit(1);
509		}
510	}
511	if (nr < 0) {
512		syslog(LOG_ERR, "%s: %s",
513		    kernel ? kernel : _PATH_UNIX, strerror(errno));
514		syslog(LOG_WARNING,
515		    "WARNING: kernel may be incomplete");
516		exit(1);
517	}
518	if (compress)
519		(void)fclose(fp);
520	else
521		(void)close(ofd);
522}
523
524char *
525find_dev(dev, type)
526	dev_t dev;
527	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: %s", _PATH_DEV, strerror(errno));
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: %s", devname, strerror(errno));
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, "%s", strerror(errno));
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(s)
563	char *s;
564{
565	char *sl, 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, "%s", strerror(errno));
576		exit(1);
577	}
578	return (sl);
579}
580
581int
582get_crashtime()
583{
584	struct timeval time;
585	time_t dumptime;			/* Time the dump was taken. */
586
587	if (KREAD(kd_dump, dump_nl[X_TIME].n_value, &time) != 0) {
588		if (verbose)
589		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
590		return (0);
591	}
592	dumptime = time.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()
609{
610	FILE *fp;
611	off_t minfree, spacefree, kernelsize, needed;
612	struct stat st;
613	struct statfs fsbuf;
614	char buf[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(buf, sizeof(buf), fp) == NULL)
638			minfree = 0;
639		else
640			minfree = atoi(buf);
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(name, rw)
658	char *name;
659	int rw;
660{
661	int fd;
662
663	if ((fd = open(name, rw, 0)) < 0) {
664		syslog(LOG_ERR, "%s: %m", name);
665		exit(1);
666	}
667	return (fd);
668}
669
670void
671Lseek(fd, off, flag)
672	int fd, flag;
673	off_t off;
674{
675	off_t ret;
676
677	ret = lseek(fd, off, flag);
678	if (ret == -1) {
679		syslog(LOG_ERR, "lseek: %m");
680		exit(1);
681	}
682}
683
684int
685Create(file, mode)
686	char *file;
687	int mode;
688{
689	int fd;
690
691	fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode);
692	if (fd < 0) {
693		syslog(LOG_ERR, "%s: %m", file);
694		exit(1);
695	}
696	return (fd);
697}
698
699void
700Write(fd, bp, size)
701	int fd, size;
702	void *bp;
703{
704	int n;
705
706	if ((n = write(fd, bp, size)) < size) {
707		syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO));
708		exit(1);
709	}
710}
711
712void
713usage()
714{
715	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
716	exit(1);
717}
718