savecore.c revision 1.32
1/*	$NetBSD: savecore.c,v 1.32 1997/09/15 11:08:45 lukem 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.3 (Berkeley) 1/2/94";
45#else
46__RCSID("$NetBSD: savecore.c,v 1.32 1997/09/15 11:08:45 lukem 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	 main __P((int, char *[]));
134int	 Open __P((char *, int rw));
135char	*rawname __P((char *s));
136void	 save_core __P((void));
137void	 usage __P((void));
138void	 Write __P((int, void *, int));
139
140int
141main(argc, argv)
142	int argc;
143	char *argv[];
144{
145	int ch;
146
147	openlog("savecore", LOG_PERROR, LOG_DAEMON);
148
149	while ((ch = getopt(argc, argv, "cdfN:vz")) != -1)
150		switch(ch) {
151		case 'c':
152			clear = 1;
153			break;
154		case 'd':		/* Not documented. */
155		case 'v':
156			verbose = 1;
157			break;
158		case 'f':
159			force = 1;
160			break;
161		case 'N':
162			kernel = optarg;
163			break;
164		case 'z':
165			compress = 1;
166			break;
167		case '?':
168		default:
169			usage();
170		}
171	argc -= optind;
172	argv += optind;
173
174	if (!clear) {
175		if (argc != 1 && argc != 2)
176			usage();
177		dirname = argv[0];
178	}
179	if (argc == 2)
180		kernel = argv[1];
181
182	(void)time(&now);
183	kmem_setup();
184
185	if (clear) {
186		clear_dump();
187		exit(0);
188	}
189
190	if (!dump_exists() && !force)
191		exit(1);
192
193	check_kmem();
194
195	if (panicstr)
196		syslog(LOG_ALERT, "reboot after panic: %s", panic_mesg);
197	else
198		syslog(LOG_ALERT, "reboot");
199
200	if ((!get_crashtime() || !check_space()) && !force)
201		exit(1);
202
203	save_core();
204
205	clear_dump();
206	exit(0);
207}
208
209void
210kmem_setup()
211{
212	kvm_t	*kd_kern;
213	char	errbuf[_POSIX2_LINE_MAX];
214	int	i, hdrsz;
215	char	*dump_sys;
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 dump_sys namelist, but are
222	 * presumed to be the same (since the disk partitions are probably
223	 * the same!)
224	 */
225	kd_kern = kvm_openfiles(NULL, 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	dump_sys = kernel ? kernel : _PATH_UNIX;
275
276	kd_dump = kvm_openfiles(dump_sys, ddname, NULL, O_RDWR, errbuf);
277	if (kd_dump == NULL) {
278		syslog(LOG_ERR, "%s: kvm_openfiles: %s", dump_sys, errbuf);
279		exit(1);
280	}
281
282	if (kvm_nlist(kd_dump, dump_nl) == -1)
283		syslog(LOG_ERR, "%s: kvm_nlist: %s", dump_sys,
284			kvm_geterr(kd_dump));
285
286	for (i = 0; dumpsyms[i] != -1; i++)
287		if (dump_nl[dumpsyms[i]].n_value == 0) {
288			syslog(LOG_ERR, "%s: %s not in namelist",
289			    dump_sys, dump_nl[dumpsyms[i]].n_name);
290			exit(1);
291		}
292	hdrsz = kvm_dump_mkheader(kd_dump, (off_t)dumplo);
293
294	/*
295	 * If 'hdrsz' == 0, kvm_dump_mkheader() failed on the magic-number
296	 * checks, ergo no dump is present...
297	 */
298	if (hdrsz == 0) {
299		syslog(LOG_WARNING, "no core dump");
300		exit(1);
301	}
302	if (hdrsz == -1) {
303		syslog(LOG_ERR, "%s: kvm_dump_mkheader: %s", dump_sys,
304			kvm_geterr(kd_dump));
305		exit(1);
306	}
307	dumplo += hdrsz;
308	kvm_close(kd_kern);
309}
310
311void
312check_kmem()
313{
314	char	*cp;
315	long	panicloc;
316	char core_vers[1024];
317
318	(void)kvm_read(kd_dump, dump_nl[X_VERSION].n_value, core_vers,
319		sizeof(core_vers));
320	core_vers[sizeof(core_vers) - 1] = '\0';
321
322	if (strcmp(vers, core_vers) && kernel == 0)
323		syslog(LOG_WARNING,
324		    "warning: %s version mismatch:\n\t%s\nand\t%s\n",
325		    _PATH_UNIX, vers, core_vers);
326
327	if (KREAD(kd_dump, dump_nl[X_PANICSTR].n_value, &panicstr) != 0) {
328		if (verbose)
329		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
330		return;
331	}
332	if (panicstr) {
333		cp       = panic_mesg;
334		panicloc = panicstr;
335		do {
336			if (KREAD(kd_dump, panicloc, cp) != 0) {
337				if (verbose)
338				    syslog(LOG_WARNING, "kvm_read: %s",
339					   kvm_geterr(kd_dump));
340				break;
341			}
342			panicloc++;
343		} while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)-1]);
344		panic_mesg[sizeof(panic_mesg) - 1] = '\0';
345	}
346}
347
348int
349dump_exists()
350{
351	int newdumpmag;
352
353	if (KREAD(kd_dump, dump_nl[X_DUMPMAG].n_value, &newdumpmag) != 0) {
354		if (verbose)
355		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
356		return (0);
357	}
358
359	/* Read the dump size. */
360	if (KREAD(kd_dump, dump_nl[X_DUMPSIZE].n_value, &dumpsize) != 0) {
361		if (verbose)
362		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
363		return (0);
364	}
365	dumpsize *= getpagesize();
366
367	/*
368	 * Return zero if core dump doesn't seem to be there, and note
369	 * it for syslog.  This check and return happens after the dump size
370	 * is read, so dumpsize is whether or not the core is valid (for -f).
371	 */
372	if (newdumpmag != dumpmag) {
373		if (verbose)
374			syslog(LOG_WARNING,
375			    "magic number mismatch (0x%x != 0x%x)",
376			    newdumpmag, dumpmag);
377		syslog(LOG_WARNING, "no core dump");
378		return (0);
379	}
380	return (1);
381}
382
383void
384clear_dump()
385{
386	if (kvm_dump_inval(kd_dump) == -1)
387		syslog(LOG_ERR, "%s: kvm_clear_dump: %s", ddname,
388			kvm_geterr(kd_dump));
389
390}
391
392char buf[1024 * 1024];
393
394void
395save_core()
396{
397	FILE *fp;
398	int bounds, ifd, nr, nw, ofd;
399	char *rawp, path[MAXPATHLEN];
400
401	ofd = -1;
402	/*
403	 * Get the current number and update the bounds file.  Do the update
404	 * now, because may fail later and don't want to overwrite anything.
405	 */
406	umask(002);
407	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
408	if ((fp = fopen(path, "r")) == NULL)
409		goto err1;
410	if (fgets(buf, sizeof(buf), fp) == NULL) {
411		if (ferror(fp))
412err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
413		bounds = 0;
414	} else
415		bounds = atoi(buf);
416	if (fp != NULL)
417		(void)fclose(fp);
418	if ((fp = fopen(path, "w")) == NULL)
419		syslog(LOG_ERR, "%s: %m", path);
420	else {
421		(void)fprintf(fp, "%d\n", bounds + 1);
422		(void)fclose(fp);
423	}
424	(void)fclose(fp);
425
426	/* Create the core file. */
427	(void)snprintf(path, sizeof(path), "%s/netbsd.%d.core%s",
428	    dirname, bounds, compress ? ".Z" : "");
429	if (compress) {
430		if ((fp = zopen(path, "w", 0)) == NULL) {
431			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
432			exit(1);
433		}
434	} else {
435		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
436		fp  = fdopen(ofd, "w");
437		if (fp == NULL) {
438			syslog(LOG_ERR, "%s: fdopen: %s", path,
439			    strerror(errno));
440			exit(1);
441		}
442	}
443
444	/* Open the raw device. */
445	rawp = rawname(ddname);
446	if ((ifd = open(rawp, O_RDONLY)) == -1) {
447		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
448		ifd = dumpfd;
449	}
450
451	/* Seek to the start of the core. */
452	Lseek(ifd, (off_t)dumplo, SEEK_SET);
453
454	if (kvm_dump_wrtheader(kd_dump, fp, dumpsize) == -1) {
455		syslog(LOG_ERR, "kvm_dump_wrtheader: %s : %s", path,
456			kvm_geterr(kd_dump));
457		exit(1);
458	}
459
460	/* Copy the core file. */
461	syslog(LOG_NOTICE, "writing %score to %s",
462	    compress ? "compressed " : "", path);
463	for (; dumpsize > 0; dumpsize -= nr) {
464		(void)printf("%6dK\r", dumpsize / 1024);
465		(void)fflush(stdout);
466		nr = read(ifd, buf, MIN(dumpsize, sizeof(buf)));
467		if (nr <= 0) {
468			if (nr == 0)
469				syslog(LOG_WARNING,
470				    "WARNING: EOF on dump device");
471			else
472				syslog(LOG_ERR, "%s: %m", rawp);
473			goto err2;
474		}
475		nw = fwrite(buf, 1, nr, fp);
476		if (nw != nr) {
477			syslog(LOG_ERR, "%s: %s",
478			    path, strerror(nw == 0 ? EIO : errno));
479err2:			syslog(LOG_WARNING,
480			    "WARNING: core may be incomplete");
481			(void)printf("\n");
482			exit(1);
483		}
484	}
485	(void)close(ifd);
486	(void)fclose(fp);
487
488	/* Copy the kernel. */
489	ifd = Open(kernel ? kernel : _PATH_UNIX, O_RDONLY);
490	(void)snprintf(path, sizeof(path), "%s/netbsd.%d%s",
491	    dirname, bounds, compress ? ".Z" : "");
492	if (compress) {
493		if ((fp = zopen(path, "w", 0)) == NULL) {
494			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
495			exit(1);
496		}
497	} else
498		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
499	syslog(LOG_NOTICE, "writing %skernel to %s",
500	    compress ? "compressed " : "", path);
501	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
502		if (compress)
503			nw = fwrite(buf, 1, nr, fp);
504		else
505			nw = write(ofd, buf, nr);
506		if (nw != nr) {
507			syslog(LOG_ERR, "%s: %s",
508			    path, strerror(nw == 0 ? EIO : errno));
509			syslog(LOG_WARNING,
510			    "WARNING: kernel may be incomplete");
511			exit(1);
512		}
513	}
514	if (nr < 0) {
515		syslog(LOG_ERR, "%s: %s",
516		    kernel ? kernel : _PATH_UNIX, strerror(errno));
517		syslog(LOG_WARNING,
518		    "WARNING: kernel may be incomplete");
519		exit(1);
520	}
521	if (compress)
522		(void)fclose(fp);
523	else
524		(void)close(ofd);
525}
526
527char *
528find_dev(dev, type)
529	dev_t dev;
530	int type;
531{
532	DIR *dfd;
533	struct dirent *dir;
534	struct stat sb;
535	char *dp, devname[MAXPATHLEN + 1];
536
537	if ((dfd = opendir(_PATH_DEV)) == NULL) {
538		syslog(LOG_ERR, "%s: %s", _PATH_DEV, strerror(errno));
539		exit(1);
540	}
541	(void)strcpy(devname, _PATH_DEV);
542	while ((dir = readdir(dfd))) {
543		(void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name);
544		if (lstat(devname, &sb)) {
545			syslog(LOG_ERR, "%s: %s", devname, strerror(errno));
546			continue;
547		}
548		if ((sb.st_mode & S_IFMT) != type)
549			continue;
550		if (dev == sb.st_rdev) {
551			closedir(dfd);
552			if ((dp = strdup(devname)) == NULL) {
553				syslog(LOG_ERR, "%s", strerror(errno));
554				exit(1);
555			}
556			return (dp);
557		}
558	}
559	closedir(dfd);
560	syslog(LOG_ERR, "can't find device %d/%d", major(dev), minor(dev));
561	exit(1);
562}
563
564char *
565rawname(s)
566	char *s;
567{
568	char *sl, name[MAXPATHLEN];
569
570	if ((sl = strrchr(s, '/')) == NULL || sl[1] == '0') {
571		syslog(LOG_ERR,
572		    "can't make raw dump device name from %s", s);
573		return (s);
574	}
575	(void)snprintf(name, sizeof(name), "%.*s/r%s", sl - s, s, sl + 1);
576	if ((sl = strdup(name)) == NULL) {
577		syslog(LOG_ERR, "%s", strerror(errno));
578		exit(1);
579	}
580	return (sl);
581}
582
583int
584get_crashtime()
585{
586	time_t dumptime;			/* Time the dump was taken. */
587
588	if (KREAD(kd_dump, dump_nl[X_TIME].n_value, &dumptime) != 0) {
589		if (verbose)
590		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
591		return (0);
592	}
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	char *tkernel;
612	off_t minfree, spacefree, kernelsize, needed;
613	struct stat st;
614	struct statfs fsbuf;
615	char buf[100], path[MAXPATHLEN];
616
617	tkernel = kernel ? kernel : _PATH_UNIX;
618	if (stat(tkernel, &st) < 0) {
619		syslog(LOG_ERR, "%s: %m", tkernel);
620		exit(1);
621	}
622	kernelsize = st.st_blocks * S_BLKSIZE;
623	if (statfs(dirname, &fsbuf) < 0) {
624		syslog(LOG_ERR, "%s: %m", dirname);
625		exit(1);
626	}
627 	spacefree = (fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
628
629	(void)snprintf(path, sizeof(path), "%s/minfree", dirname);
630	if ((fp = fopen(path, "r")) == NULL)
631		minfree = 0;
632	else {
633		if (fgets(buf, sizeof(buf), fp) == NULL)
634			minfree = 0;
635		else
636			minfree = atoi(buf);
637		(void)fclose(fp);
638	}
639
640	needed = (dumpsize + kernelsize) / 1024;
641 	if (minfree > 0 && spacefree - needed < minfree) {
642		syslog(LOG_WARNING,
643		    "no dump, not enough free space on device");
644		return (0);
645	}
646	if (spacefree - needed < minfree)
647		syslog(LOG_WARNING,
648		    "dump performed, but free space threshold crossed");
649	return (1);
650}
651
652int
653Open(name, rw)
654	char *name;
655	int rw;
656{
657	int fd;
658
659	if ((fd = open(name, rw, 0)) < 0) {
660		syslog(LOG_ERR, "%s: %m", name);
661		exit(1);
662	}
663	return (fd);
664}
665
666void
667Lseek(fd, off, flag)
668	int fd, flag;
669	off_t off;
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(file, mode)
682	char *file;
683	int mode;
684{
685	int fd;
686
687	fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode);
688	if (fd < 0) {
689		syslog(LOG_ERR, "%s: %m", file);
690		exit(1);
691	}
692	return (fd);
693}
694
695void
696Write(fd, bp, size)
697	int fd, size;
698	void *bp;
699{
700	int n;
701
702	if ((n = write(fd, bp, size)) < size) {
703		syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO));
704		exit(1);
705	}
706}
707
708void
709usage()
710{
711	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
712	exit(1);
713}
714