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