1/*	$OpenBSD: savecore.c,v 1.66 2024/05/09 08:35:40 florian Exp $	*/
2/*	$NetBSD: savecore.c,v 1.26 1996/03/18 21:16:05 leo Exp $	*/
3
4/*-
5 * Copyright (c) 1986, 1992, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/param.h>	/* NODEV DEV_BSIZE */
34#include <sys/stat.h>
35#include <sys/mount.h>
36#include <sys/syslog.h>
37#include <sys/time.h>
38#include <sys/resource.h>
39
40#include <dirent.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <nlist.h>
44#include <paths.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49#include <limits.h>
50#include <kvm.h>
51#include <vis.h>
52
53#define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
54
55extern FILE *zopen(const char *fname, const char *mode, int bits);
56
57#define KREAD(kd, addr, p)\
58	(kvm_read(kd, addr, (char *)(p), sizeof(*(p))) != sizeof(*(p)))
59
60struct nlist current_nl[] = {	/* Namelist for currently running system. */
61#define X_DUMPDEV	0
62	{ "_dumpdev" },
63#define X_DUMPLO	1
64	{ "_dumplo" },
65#define X_TIME		2
66	{ "_time_second" },
67#define	X_DUMPSIZE	3
68	{ "_dumpsize" },
69#define X_VERSION	4
70	{ "_version" },
71#define X_PANICSTR	5
72	{ "_panicstr" },
73#define	X_DUMPMAG	6
74	{ "_dumpmag" },
75	{ NULL },
76};
77int cursyms[] = { X_DUMPDEV, X_DUMPLO, X_VERSION, X_DUMPMAG, -1 };
78int dumpsyms[] = { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 };
79
80struct nlist dump_nl[] = {	/* Name list for dumped system. */
81	{ "_dumpdev" },		/* Entries MUST be the same as */
82	{ "_dumplo" },		/*	those in current_nl[].  */
83	{ "_time_second" },
84	{ "_dumpsize" },
85	{ "_version" },
86	{ "_panicstr" },
87	{ "_dumpmag" },
88	{ NULL },
89};
90
91#define VERSIONSIZE 512
92
93/* Types match kernel declarations. */
94long	dumplo;			/* where dump starts on dumpdev (in blocks) */
95off_t	dumpoff;		/* where dump starts on dumpdev (in bytes) */
96u_long	dumpmag;		/* magic number in dump */
97int	dumppages;		/* amount of memory dumped (in pages) */
98u_long	dumpsize;		/* amount of memory dumped */
99
100char	*kernel;
101char	*dirn;			/* directory to save dumps in */
102char	*ddname;		/* name of dump device */
103dev_t	dumpdev;		/* dump device */
104int	dumpfd;			/* read/write descriptor on block dev */
105kvm_t	*kd_dump;		/* kvm descriptor on block dev	*/
106time_t	now;			/* current date */
107char	panic_mesg[1024];
108int	panicstr;
109char	vers[VERSIONSIZE];
110
111int	clear, zcompress, force, verbose;	/* flags */
112
113void	 check_kmem(void);
114int	 check_space(void);
115void	 clear_dump(void);
116int	 dump_exists(void);
117char	*find_dev(dev_t, int);
118int	 get_crashtime(void);
119void	 kmem_setup(void);
120char	*rawname(char *s);
121void	 save_core(void);
122void	 usage(void);
123
124int
125main(int argc, char *argv[])
126{
127	struct rlimit rl;
128	int ch;
129
130	openlog("savecore", LOG_PERROR, LOG_DAEMON);
131
132	/* Increase our data size to the max if we can. */
133	if (getrlimit(RLIMIT_DATA, &rl) == 0) {
134		rl.rlim_cur = rl.rlim_max;
135		if (setrlimit(RLIMIT_DATA, &rl) == -1)
136			syslog(LOG_WARNING, "can't set rlimit data size: %m");
137	}
138
139	while ((ch = getopt(argc, argv, "cdfN:vz")) != -1)
140		switch(ch) {
141		case 'c':
142			clear = 1;
143			break;
144		case 'd':		/* Not documented. */
145		case 'v':
146			verbose = 1;
147			break;
148		case 'f':
149			force = 1;
150			break;
151		case 'N':
152			kernel = optarg;
153			break;
154		case 'z':
155			zcompress = 1;
156			break;
157		default:
158			usage();
159		}
160	argc -= optind;
161	argv += optind;
162
163	if (!clear) {
164		if (argc != 1)
165			usage();
166		dirn = argv[0];
167	}
168
169	(void)time(&now);
170	kmem_setup();
171
172	if (!clear) {
173		if (unveil(dirn, "rwc") == -1) {
174			syslog(LOG_ERR, "unveil: %m");
175			exit(1);
176		}
177		if (unveil(kernel ? kernel : _PATH_UNIX, "r") == -1) {
178			syslog(LOG_ERR, "unveil: %m");
179			exit(1);
180		}
181		if (unveil(rawname(ddname), "r") == -1) {
182			syslog(LOG_ERR, "unveil: %m");
183			exit(1);
184		}
185		if (pledge("stdio rpath wpath cpath", NULL) == -1) {
186			syslog(LOG_ERR, "pledge: %m");
187			exit(1);
188		}
189	} else {
190		clear_dump();
191		return (0);
192	}
193
194	if (!dump_exists() && !force)
195		return (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		return (1);
206
207	save_core();
208
209	clear_dump();
210	return (0);
211}
212
213char	*dump_sys;
214
215void
216kmem_setup(void)
217{
218	kvm_t	*kd_kern;
219	char	errbuf[_POSIX2_LINE_MAX];
220	int	i, hdrsz;
221
222	/*
223	 * Some names we need for the currently running system, others for
224	 * the system that was running when the dump was made.  The values
225	 * obtained from the current system are used to look for things in
226	 * /dev/kmem that cannot be found in the dump_sys namelist, but are
227	 * presumed to be the same (since the disk partitions are probably
228	 * the same!)
229	 */
230	kd_kern = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
231	if (kd_kern == NULL) {
232		syslog(LOG_ERR, "%s: kvm_openfiles: %s", _PATH_UNIX, errbuf);
233		exit(1);
234	}
235	if (kvm_nlist(kd_kern, current_nl) == -1)
236		syslog(LOG_ERR, "%s: kvm_nlist: %s", _PATH_UNIX,
237			kvm_geterr(kd_kern));
238
239	for (i = 0; cursyms[i] != -1; i++)
240		if (current_nl[cursyms[i]].n_value == 0) {
241			syslog(LOG_ERR, "%s: %s not in namelist",
242			    _PATH_UNIX, current_nl[cursyms[i]].n_name);
243			exit(1);
244		}
245
246	(void)KREAD(kd_kern, current_nl[X_DUMPDEV].n_value, &dumpdev);
247	if (dumpdev == NODEV) {
248		syslog(LOG_WARNING, "no core dump (no dumpdev)");
249		exit(1);
250	}
251	(void)KREAD(kd_kern, current_nl[X_DUMPLO].n_value, &dumplo);
252	dumpoff = (off_t)dumplo * DEV_BSIZE;
253	if (verbose)
254		(void)printf("dumpoff = %lld (%ld * %d)\n",
255		    (long long)dumpoff, dumplo, DEV_BSIZE);
256	(void) KREAD(kd_kern, current_nl[X_DUMPMAG].n_value, &dumpmag);
257
258	if (kernel == NULL) {
259		if (kvm_read(kd_kern, current_nl[X_VERSION].n_value,
260		    vers, sizeof(vers)) == -1) {
261			syslog(LOG_ERR, "%s: kvm_read: version misread", _PATH_UNIX);
262			exit(1);
263		}
264		vers[sizeof(vers) - 1] = '\0';
265	}
266
267	ddname = find_dev(dumpdev, S_IFBLK);
268	dumpfd = open(ddname, O_RDWR);
269	if (dumpfd == -1) {
270		syslog(LOG_ERR, "%s: %m", ddname);
271		exit(1);
272	}
273
274
275	dump_sys = kernel ? kernel : _PATH_UNIX;
276	kd_dump = kvm_openfiles(kernel, 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, dumpoff);
293	if (hdrsz == -1) {
294		if(verbose)
295			syslog(LOG_ERR, "%s: kvm_dump_mkheader: %s", dump_sys,
296				kvm_geterr(kd_dump));
297		syslog(LOG_WARNING, "no core dump");
298		exit(1);
299	}
300	dumpoff += hdrsz;
301	kvm_close(kd_kern);
302}
303
304void
305check_kmem(void)
306{
307	char	*cp;
308	int	panicloc;
309	char core_vers[VERSIONSIZE];
310
311	if (kvm_read(kd_dump, dump_nl[X_VERSION].n_value, core_vers,
312	    sizeof(core_vers)) != sizeof(core_vers)) {
313		syslog(LOG_ERR, "%s: kvm_read: version misread", dump_sys);
314		exit(1);
315	}
316	core_vers[sizeof(core_vers) - 1] = '\0';
317
318	if (strcmp(vers, core_vers) && kernel == 0) {
319		vers[strcspn(vers, "\n")] = '\0';
320		core_vers[strcspn(core_vers, "\n")] = '\0';
321
322		syslog(LOG_WARNING,
323		    "warning: %s version mismatch:\n\t%s\nand\t%s\n",
324		    _PATH_UNIX, vers, core_vers);
325	}
326
327	(void)KREAD(kd_dump, dump_nl[X_PANICSTR].n_value, &panicstr);
328	if (panicstr) {
329		char	c, visout[5];
330		size_t	vislen;
331
332		cp       = panic_mesg;
333		panicloc = panicstr;
334		for (;;) {
335			if (KREAD(kd_dump, panicloc, &c) != 0 || c == '\0')
336				break;
337			panicloc++;
338
339			vis(visout, c, VIS_SAFE|VIS_NOSLASH, 0);
340			vislen = strlen(visout);
341			if (cp - panic_mesg + vislen >= sizeof(panic_mesg))
342				break;
343			strlcat(cp, visout,
344			    panic_mesg + sizeof panic_mesg - cp);
345			cp += strlen(cp);
346		}
347	}
348}
349
350int
351dump_exists(void)
352{
353	u_long newdumpmag;
354
355	(void)KREAD(kd_dump, dump_nl[X_DUMPMAG].n_value, &newdumpmag);
356
357	/* Read the dump size. */
358	(void)KREAD(kd_dump, dump_nl[X_DUMPSIZE].n_value, &dumppages);
359	dumpsize = (u_long)dumppages * getpagesize();
360
361	/*
362	 * Return zero if core dump doesn't seem to be there and note
363	 * it for syslog.  This check and return happens after the dump size
364	 * is read, so dumpsize is whether or not the core is valid (for -f).
365	 */
366	if (newdumpmag != dumpmag) {
367		if (verbose)
368			syslog(LOG_WARNING,
369			    "magic number mismatch (%lx != %lx)",
370			    newdumpmag, dumpmag);
371		syslog(LOG_WARNING, "no core dump");
372		return (0);
373	}
374	return (1);
375}
376
377void
378clear_dump(void)
379{
380	if (pledge("stdio", NULL) == -1) {
381		syslog(LOG_ERR, "pledge: %m");
382		exit(1);
383	}
384
385	if (kvm_dump_inval(kd_dump) == -1)
386		syslog(LOG_ERR, "%s: kvm_clear_dump: %s", ddname,
387			kvm_geterr(kd_dump));
388
389}
390
391char buf[1024 * 1024];
392
393void
394save_core(void)
395{
396	FILE *fp;
397	int bounds, ifd, nr, nw, ofd = -1;
398	char *rawp, path[PATH_MAX];
399	mode_t um;
400
401	um = umask(S_IRWXG|S_IRWXO);
402
403	/*
404	 * Get the current number and update the bounds file.  Do the update
405	 * now, because we may fail later and don't want to overwrite anything.
406	 */
407	(void)snprintf(path, sizeof(path), "%s/bounds", dirn);
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		const char *errstr = NULL;
416		char *p;
417
418		if ((p = strchr(buf, '\n')) != NULL)
419			*p = '\0';
420		bounds = strtonum(buf, 0, INT_MAX, &errstr);
421		if (errstr)
422			syslog(LOG_WARNING, "bounds was corrupt: %s", errstr);
423	}
424	if (fp != NULL)
425		(void)fclose(fp);
426	if ((fp = fopen(path, "w")) == NULL)
427		syslog(LOG_ERR, "%s: %m", path);
428	else {
429		(void)fprintf(fp, "%d\n", bounds + 1);
430		(void)fclose(fp);
431	}
432
433	/* Create the core file. */
434	(void)snprintf(path, sizeof(path), "%s%s.%d.core%s",
435	    dirn, _PATH_UNIX, bounds, zcompress ? ".Z" : "");
436	if (zcompress) {
437		if ((fp = zopen(path, "w", 0)) == NULL) {
438			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
439			exit(1);
440		}
441	} else {
442		ofd = open(path, O_WRONLY | O_CREAT | O_TRUNC,
443		    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
444		if (ofd == -1) {
445			syslog(LOG_ERR, "%s: %m", path);
446			exit(1);
447		}
448
449		fp  = fdopen(ofd, "w");
450		if (fp == NULL) {
451			syslog(LOG_ERR, "%s: fdopen: %s", path, strerror(errno));
452			exit(1);
453		}
454	}
455
456	/* Open the raw device. */
457	rawp = rawname(ddname);
458	if ((ifd = open(rawp, O_RDONLY)) == -1) {
459		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
460		ifd = dumpfd;
461	}
462
463	/* Seek to the start of the core. */
464	if (lseek(ifd, dumpoff, SEEK_SET) == -1) {
465		syslog(LOG_ERR, "lseek: %m");
466		exit(1);
467	}
468
469	if (kvm_dump_wrtheader(kd_dump, fp, dumpsize) == -1) {
470		syslog(LOG_ERR, "kvm_dump_wrtheader: %s : %s", path,
471			kvm_geterr(kd_dump));
472		exit(1);
473	}
474
475	/* Copy the core file. */
476	syslog(LOG_NOTICE, "writing %score to %s",
477	    zcompress ? "compressed " : "", path);
478	for (; dumpsize != 0; dumpsize -= nr) {
479		(void)printf("%8luK\r", dumpsize / 1024);
480		(void)fflush(stdout);
481		nr = read(ifd, buf, MINIMUM(dumpsize, sizeof(buf)));
482		if (nr <= 0) {
483			if (nr == 0)
484				syslog(LOG_WARNING,
485				    "WARNING: EOF on dump device");
486			else
487				syslog(LOG_ERR, "%s: %m", rawp);
488			goto err2;
489		}
490		nw = fwrite(buf, 1, nr, fp);
491		if (nw != nr) {
492			syslog(LOG_ERR, "%s: %s",
493			    path, strerror(nw == 0 ? EIO : errno));
494err2:			syslog(LOG_WARNING,
495			    "WARNING: core may be incomplete");
496			(void)printf("\n");
497			exit(1);
498		}
499	}
500	(void)close(ifd);
501	(void)fclose(fp);
502
503	/* Copy the kernel. */
504	ifd = open(kernel ? kernel : _PATH_UNIX, O_RDONLY);
505	if (ifd == -1) {
506		syslog(LOG_ERR, "%s: %m", kernel ? kernel : _PATH_UNIX);
507		exit(1);
508	}
509	(void)snprintf(path, sizeof(path), "%s%s.%d%s",
510	    dirn, _PATH_UNIX, bounds, zcompress ? ".Z" : "");
511	if (zcompress) {
512		if ((fp = zopen(path, "w", 0)) == NULL) {
513			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
514			exit(1);
515		}
516	} else {
517		ofd = open(path, O_WRONLY | O_CREAT | O_TRUNC,
518		    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
519		if (ofd == -1) {
520			syslog(LOG_ERR, "%s: %m", path);
521			exit(1);
522		}
523	}
524	syslog(LOG_NOTICE, "writing %skernel to %s",
525	    zcompress ? "compressed " : "", path);
526	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
527		if (zcompress)
528			nw = fwrite(buf, 1, nr, fp);
529		else
530			nw = write(ofd, buf, nr);
531		if (nw != nr) {
532			syslog(LOG_ERR, "%s: %s",
533			    path, strerror(nw == 0 ? EIO : errno));
534			syslog(LOG_WARNING,
535			    "WARNING: kernel may be incomplete");
536			exit(1);
537		}
538	}
539	if (nr == -1) {
540		syslog(LOG_ERR, "%s: %s",
541		    kernel ? kernel : _PATH_UNIX, strerror(errno));
542		syslog(LOG_WARNING,
543		    "WARNING: kernel may be incomplete");
544		exit(1);
545	}
546	if (zcompress)
547		(void)fclose(fp);
548	else
549		(void)close(ofd);
550	(void)umask(um);
551}
552
553char *
554find_dev(dev_t dev, int type)
555{
556	DIR *dfd;
557	struct dirent *dir;
558	struct stat sb;
559	char *dp, devname[PATH_MAX];
560
561	if ((dfd = opendir(_PATH_DEV)) == NULL) {
562		syslog(LOG_ERR, "%s: %s", _PATH_DEV, strerror(errno));
563		exit(1);
564	}
565	(void)strlcpy(devname, _PATH_DEV, sizeof devname);
566	while ((dir = readdir(dfd))) {
567		(void)strlcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name,
568		    sizeof devname - (sizeof(_PATH_DEV) - 1));
569		if (lstat(devname, &sb)) {
570			syslog(LOG_ERR, "%s: %s", devname, strerror(errno));
571			continue;
572		}
573		if ((sb.st_mode & S_IFMT) != type)
574			continue;
575		if (dev == sb.st_rdev) {
576			closedir(dfd);
577			if ((dp = strdup(devname)) == NULL) {
578				syslog(LOG_ERR, "%s", strerror(errno));
579				exit(1);
580			}
581			return (dp);
582		}
583	}
584	closedir(dfd);
585	syslog(LOG_ERR, "can't find device %u/%u", major(dev), minor(dev));
586	exit(1);
587}
588
589char *
590rawname(char *s)
591{
592	char *sl, name[PATH_MAX];
593
594	if ((sl = strrchr(s, '/')) == NULL || sl[1] == '0') {
595		syslog(LOG_ERR,
596		    "can't make raw dump device name from %s", s);
597		return (s);
598	}
599	(void)snprintf(name, sizeof(name), "%.*s/r%s", (int)(sl - s), s, sl + 1);
600	if ((sl = strdup(name)) == NULL) {
601		syslog(LOG_ERR, "%s", strerror(errno));
602		exit(1);
603	}
604	return (sl);
605}
606
607int
608get_crashtime(void)
609{
610	time_t dumptime;			/* Time the dump was taken. */
611	char *ct;
612
613	(void)KREAD(kd_dump, dump_nl[X_TIME].n_value, &dumptime);
614	if (dumptime == 0) {
615		if (verbose)
616			syslog(LOG_ERR, "dump time is zero");
617		return (0);
618	}
619	ct = ctime(&dumptime);
620	if (ct)
621		printf("savecore: system went down at %s", ct);
622	else
623		printf("savecore: system went down %lld seconds after the"
624		    " epoch\n", dumptime);
625#define	SECSPERDAY	(24 * 60 * 60)
626#define	LEEWAY		(7 * SECSPERDAY)
627	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
628		(void)printf("dump time is unreasonable\n");
629		return (0);
630	}
631	return (1);
632}
633
634int
635check_space(void)
636{
637	FILE *fp;
638	char *tkernel;
639	off_t minfree, spacefree, kernelsize, needed;
640	struct stat st;
641	struct statfs fsbuf;
642	char buf[100], path[PATH_MAX];
643	int fd;
644
645	tkernel = kernel ? kernel : _PATH_UNIX;
646	if (stat(tkernel, &st) == -1) {
647		syslog(LOG_ERR, "%s: %m", tkernel);
648		exit(1);
649	}
650	kernelsize = st.st_blocks * S_BLKSIZE;
651	if ((fd = open(dirn, O_RDONLY)) == -1 || fstatfs(fd, &fsbuf) == -1) {
652		syslog(LOG_ERR, "%s: %m", dirn);
653		exit(1);
654	}
655	close(fd);
656	spacefree = ((off_t)fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
657
658	(void)snprintf(path, sizeof(path), "%s/minfree", dirn);
659	if ((fp = fopen(path, "r")) == NULL)
660		minfree = 0;
661	else {
662		if (fgets(buf, sizeof(buf), fp) == NULL)
663			minfree = 0;
664		else {
665			const char *errstr;
666			char *p;
667
668			if ((p = strchr(buf, '\n')) != NULL)
669				*p = '\0';
670			minfree = strtonum(buf, 0, LLONG_MAX, &errstr);
671			if (errstr)
672				syslog(LOG_WARNING,
673				    "minfree was corrupt: %s", errstr);
674		}
675		(void)fclose(fp);
676	}
677
678	needed = (dumpsize + kernelsize) / 1024;
679	if (minfree > 0 && spacefree - needed < minfree) {
680		syslog(LOG_WARNING,
681		    "no dump, not enough free space on device");
682		return (0);
683	}
684	if (spacefree - needed < minfree)
685		syslog(LOG_WARNING,
686		    "dump performed, but free space threshold crossed");
687	return (1);
688}
689
690void
691usage(void)
692{
693	extern char *__progname;
694	fprintf(stderr, "usage: %s [-cfvz] [-N system] directory\n",
695		__progname);
696	exit(1);
697}
698