savecore.c revision 1.42
1/*	$NetBSD: savecore.c,v 1.42 2000/10/01 02:27:06 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.42 2000/10/01 02:27:06 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;
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	char	*dump_sys;
217
218	/*
219	 * Some names we need for the currently running system, others for
220	 * the system that was running when the dump was made.  The values
221	 * obtained from the current system are used to look for things in
222	 * /dev/kmem that cannot be found in the dump_sys namelist, but are
223	 * presumed to be the same (since the disk partitions are probably
224	 * the same!)
225	 */
226	kd_kern = kvm_openfiles(kernel, NULL, NULL, O_RDONLY, errbuf);
227	if (kd_kern == NULL) {
228		syslog(LOG_ERR, "%s: kvm_openfiles: %s", _PATH_UNIX, errbuf);
229		exit(1);
230	}
231	if (kvm_nlist(kd_kern, current_nl) == -1)
232		syslog(LOG_ERR, "%s: kvm_nlist: %s", _PATH_UNIX,
233			kvm_geterr(kd_kern));
234
235	for (i = 0; cursyms[i] != -1; i++)
236		if (current_nl[cursyms[i]].n_value == 0) {
237			syslog(LOG_ERR, "%s: %s not in namelist",
238			    _PATH_UNIX, current_nl[cursyms[i]].n_name);
239			exit(1);
240		}
241
242	if (KREAD(kd_kern, current_nl[X_DUMPDEV].n_value, &dumpdev) != 0) {
243		if (verbose)
244		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
245		exit(1);
246	}
247	if (dumpdev == NODEV) {
248		syslog(LOG_WARNING, "no core dump (no dumpdev)");
249		exit(1);
250	}
251	if (KREAD(kd_kern, current_nl[X_DUMPLO].n_value, &dumplo) != 0) {
252		if (verbose)
253		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
254		exit(1);
255	}
256	dumplo *= DEV_BSIZE;
257	if (verbose)
258		(void)printf("dumplo = %ld (%ld * %ld)\n",
259		    (long)dumplo, (long)(dumplo / DEV_BSIZE), (long)DEV_BSIZE);
260	if (KREAD(kd_kern, current_nl[X_DUMPMAG].n_value, &dumpmag) != 0) {
261		if (verbose)
262		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_kern));
263		exit(1);
264	}
265
266	if (kernel == NULL) {
267		(void)kvm_read(kd_kern, current_nl[X_VERSION].n_value,
268			vers, sizeof(vers));
269		vers[sizeof(vers) - 1] = '\0';
270	}
271
272	ddname = find_dev(dumpdev, S_IFBLK);
273	dumpfd = Open(ddname, O_RDWR);
274
275	dump_sys = kernel ? kernel : _PATH_UNIX;
276
277	kd_dump = kvm_openfiles(dump_sys, ddname, NULL, O_RDWR, errbuf);
278	if (kd_dump == NULL) {
279		syslog(LOG_ERR, "%s: kvm_openfiles: %s", dump_sys, errbuf);
280		exit(1);
281	}
282
283	if (kvm_nlist(kd_dump, dump_nl) == -1)
284		syslog(LOG_ERR, "%s: kvm_nlist: %s", dump_sys,
285			kvm_geterr(kd_dump));
286
287	for (i = 0; dumpsyms[i] != -1; i++)
288		if (dump_nl[dumpsyms[i]].n_value == 0) {
289			syslog(LOG_ERR, "%s: %s not in namelist",
290			    dump_sys, dump_nl[dumpsyms[i]].n_name);
291			exit(1);
292		}
293	hdrsz = kvm_dump_mkheader(kd_dump, (off_t)dumplo);
294
295	/*
296	 * If 'hdrsz' == 0, kvm_dump_mkheader() failed on the magic-number
297	 * checks, ergo no dump is present...
298	 */
299	if (hdrsz == 0) {
300		syslog(LOG_WARNING, "no core dump");
301		exit(1);
302	}
303	if (hdrsz == -1) {
304		syslog(LOG_ERR, "%s: kvm_dump_mkheader: %s", dump_sys,
305			kvm_geterr(kd_dump));
306		exit(1);
307	}
308	dumplo += hdrsz;
309	kvm_close(kd_kern);
310}
311
312void
313check_kmem()
314{
315	char	*cp;
316	long	panicloc;
317	char core_vers[1024];
318
319	(void)kvm_read(kd_dump, dump_nl[X_VERSION].n_value, core_vers,
320		sizeof(core_vers));
321	core_vers[sizeof(core_vers) - 1] = '\0';
322
323	if (strcmp(vers, core_vers) && kernel == 0)
324		syslog(LOG_WARNING,
325		    "warning: %s version mismatch:\n\t%s\nand\t%s\n",
326		    _PATH_UNIX, vers, core_vers);
327
328	if (KREAD(kd_dump, dump_nl[X_PANICSTR].n_value, &panicstr) != 0) {
329		if (verbose)
330		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
331		return;
332	}
333	if (panicstr) {
334		cp       = panic_mesg;
335		panicloc = panicstr;
336		do {
337			if (KREAD(kd_dump, panicloc, cp) != 0) {
338				if (verbose)
339				    syslog(LOG_WARNING, "kvm_read: %s",
340					   kvm_geterr(kd_dump));
341				break;
342			}
343			panicloc++;
344		} while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)-1]);
345		panic_mesg[sizeof(panic_mesg) - 1] = '\0';
346	}
347}
348
349int
350dump_exists()
351{
352	int newdumpmag;
353
354	if (KREAD(kd_dump, dump_nl[X_DUMPMAG].n_value, &newdumpmag) != 0) {
355		if (verbose)
356		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
357		return (0);
358	}
359
360	/* Read the dump size. */
361	if (KREAD(kd_dump, dump_nl[X_DUMPSIZE].n_value, &dumpsize) != 0) {
362		if (verbose)
363		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
364		return (0);
365	}
366	dumpsize *= getpagesize();
367
368	/*
369	 * Return zero if core dump doesn't seem to be there, and note
370	 * it for syslog.  This check and return happens after the dump size
371	 * is read, so dumpsize is whether or not the core is valid (for -f).
372	 */
373	if (newdumpmag != dumpmag) {
374		if (verbose)
375			syslog(LOG_WARNING,
376			    "magic number mismatch (0x%x != 0x%x)",
377			    newdumpmag, dumpmag);
378		syslog(LOG_WARNING, "no core dump");
379		return (0);
380	}
381	return (1);
382}
383
384void
385clear_dump()
386{
387	if (kvm_dump_inval(kd_dump) == -1)
388		syslog(LOG_ERR, "%s: kvm_clear_dump: %s", ddname,
389			kvm_geterr(kd_dump));
390
391}
392
393char buf[1024 * 1024];
394
395void
396save_core()
397{
398	FILE *fp;
399	int bounds, ifd, nr, nw, ofd;
400	char *rawp, path[MAXPATHLEN];
401
402	ofd = -1;
403	/*
404	 * Get the current number and update the bounds file.  Do the update
405	 * now, because may fail later and don't want to overwrite anything.
406	 */
407	umask(066);
408	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
409	if ((fp = fopen(path, "r")) == NULL)
410		goto err1;
411	if (fgets(buf, sizeof(buf), fp) == NULL) {
412		if (ferror(fp))
413err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
414		bounds = 0;
415	} else
416		bounds = atoi(buf);
417	if (fp != NULL)
418		(void)fclose(fp);
419	if ((fp = fopen(path, "w")) == NULL)
420		syslog(LOG_ERR, "%s: %m", path);
421	else {
422		(void)fprintf(fp, "%d\n", bounds + 1);
423		(void)fclose(fp);
424	}
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);
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", (int)(sl - s), s,
576	    sl + 1);
577	if ((sl = strdup(name)) == NULL) {
578		syslog(LOG_ERR, "%s", strerror(errno));
579		exit(1);
580	}
581	return (sl);
582}
583
584int
585get_crashtime()
586{
587	struct timeval time;
588	time_t dumptime;			/* Time the dump was taken. */
589
590	if (KREAD(kd_dump, dump_nl[X_TIME].n_value, &time) != 0) {
591		if (verbose)
592		    syslog(LOG_WARNING, "kvm_read: %s", kvm_geterr(kd_dump));
593		return (0);
594	}
595	dumptime = time.tv_sec;
596	if (dumptime == 0) {
597		if (verbose)
598			syslog(LOG_ERR, "dump time is zero");
599		return (0);
600	}
601	(void)printf("savecore: system went down at %s", ctime(&dumptime));
602#define	LEEWAY	(7 * SECSPERDAY)
603	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
604		(void)printf("dump time is unreasonable\n");
605		return (0);
606	}
607	return (1);
608}
609
610int
611check_space()
612{
613	FILE *fp;
614	char *tkernel;
615	off_t minfree, spacefree, kernelsize, needed;
616	struct stat st;
617	struct statfs fsbuf;
618	char buf[100], path[MAXPATHLEN];
619
620#ifdef __GNUC__
621	(void) &minfree;
622#endif
623
624	tkernel = kernel ? kernel : _PATH_UNIX;
625	if (stat(tkernel, &st) < 0) {
626		syslog(LOG_ERR, "%s: %m", tkernel);
627		exit(1);
628	}
629	kernelsize = st.st_blocks * S_BLKSIZE;
630	if (statfs(dirname, &fsbuf) < 0) {
631		syslog(LOG_ERR, "%s: %m", dirname);
632		exit(1);
633	}
634	spacefree = fsbuf.f_bavail;
635	spacefree *= fsbuf.f_bsize;
636	spacefree /= 1024;
637
638	(void)snprintf(path, sizeof(path), "%s/minfree", dirname);
639	if ((fp = fopen(path, "r")) == NULL)
640		minfree = 0;
641	else {
642		if (fgets(buf, sizeof(buf), fp) == NULL)
643			minfree = 0;
644		else
645			minfree = atoi(buf);
646		(void)fclose(fp);
647	}
648
649	needed = (dumpsize + kernelsize) / 1024;
650 	if (minfree > 0 && spacefree - needed < minfree) {
651		syslog(LOG_WARNING,
652		    "no dump, not enough free space in %s", dirname);
653		return (0);
654	}
655	if (spacefree - needed < minfree)
656		syslog(LOG_WARNING,
657		    "dump performed, but free space threshold crossed");
658	return (1);
659}
660
661int
662Open(name, rw)
663	char *name;
664	int rw;
665{
666	int fd;
667
668	if ((fd = open(name, rw, 0)) < 0) {
669		syslog(LOG_ERR, "%s: %m", name);
670		exit(1);
671	}
672	return (fd);
673}
674
675void
676Lseek(fd, off, flag)
677	int fd, flag;
678	off_t off;
679{
680	off_t ret;
681
682	ret = lseek(fd, off, flag);
683	if (ret == -1) {
684		syslog(LOG_ERR, "lseek: %m");
685		exit(1);
686	}
687}
688
689int
690Create(file, mode)
691	char *file;
692	int mode;
693{
694	int fd;
695
696	fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode);
697	if (fd < 0) {
698		syslog(LOG_ERR, "%s: %m", file);
699		exit(1);
700	}
701	return (fd);
702}
703
704void
705Write(fd, bp, size)
706	int fd, size;
707	void *bp;
708{
709	int n;
710
711	if ((n = write(fd, bp, size)) < size) {
712		syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO));
713		exit(1);
714	}
715}
716
717void
718usage()
719{
720	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
721	exit(1);
722}
723