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