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