xref: /freebsd/sbin/savecore/savecore.c (revision cd8537910406e68d4719136a5b0cf6d23bb1b23b)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2002 Poul-Henning Kamp
5  * Copyright (c) 2002 Networks Associates Technology, Inc.
6  * All rights reserved.
7  *
8  * This software was developed for the FreeBSD Project by Poul-Henning Kamp
9  * and NAI Labs, the Security Research Division of Network Associates, Inc.
10  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
11  * DARPA CHATS research program.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. The names of the authors may not be used to endorse or promote
22  *    products derived from this software without specific prior written
23  *    permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  *
37  * Copyright (c) 1986, 1992, 1993
38  *	The Regents of the University of California.  All rights reserved.
39  *
40  * Redistribution and use in source and binary forms, with or without
41  * modification, are permitted provided that the following conditions
42  * are met:
43  * 1. Redistributions of source code must retain the above copyright
44  *    notice, this list of conditions and the following disclaimer.
45  * 2. Redistributions in binary form must reproduce the above copyright
46  *    notice, this list of conditions and the following disclaimer in the
47  *    documentation and/or other materials provided with the distribution.
48  * 3. Neither the name of the University nor the names of its contributors
49  *    may be used to endorse or promote products derived from this software
50  *    without specific prior written permission.
51  *
52  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
53  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
54  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
55  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
56  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
57  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
58  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
59  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
60  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
61  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
62  * SUCH DAMAGE.
63  */
64 
65 #include <sys/cdefs.h>
66 __FBSDID("$FreeBSD$");
67 
68 #include <sys/param.h>
69 #include <sys/disk.h>
70 #include <sys/kerneldump.h>
71 #include <sys/mount.h>
72 #include <sys/stat.h>
73 
74 #include <capsicum_helpers.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <fcntl.h>
78 #include <fstab.h>
79 #include <paths.h>
80 #include <signal.h>
81 #include <stdarg.h>
82 #include <stdbool.h>
83 #include <stdio.h>
84 #include <stdlib.h>
85 #include <string.h>
86 #include <syslog.h>
87 #include <time.h>
88 #include <unistd.h>
89 #define	Z_SOLO
90 #include <zlib.h>
91 #include <zstd.h>
92 
93 #include <libcasper.h>
94 #include <casper/cap_fileargs.h>
95 #include <casper/cap_syslog.h>
96 
97 #include <libxo/xo.h>
98 
99 /* The size of the buffer used for I/O. */
100 #define	BUFFERSIZE	(1024*1024)
101 
102 #define	STATUS_BAD	0
103 #define	STATUS_GOOD	1
104 #define	STATUS_UNKNOWN	2
105 
106 static cap_channel_t *capsyslog;
107 static fileargs_t *capfa;
108 static bool checkfor, compress, uncompress, clear, force, keep;	/* flags */
109 static int verbose;
110 static int nfound, nsaved, nerr;			/* statistics */
111 static int maxdumps;
112 
113 extern FILE *zdopen(int, const char *);
114 
115 static sig_atomic_t got_siginfo;
116 static void infohandler(int);
117 
118 static void
119 logmsg(int pri, const char *fmt, ...)
120 {
121 	va_list ap;
122 
123 	va_start(ap, fmt);
124 	if (capsyslog != NULL)
125 		cap_vsyslog(capsyslog, pri, fmt, ap);
126 	else
127 		vsyslog(pri, fmt, ap);
128 	va_end(ap);
129 }
130 
131 static FILE *
132 xfopenat(int dirfd, const char *path, int flags, const char *modestr, ...)
133 {
134 	va_list ap;
135 	FILE *fp;
136 	mode_t mode;
137 	int error, fd;
138 
139 	if ((flags & O_CREAT) == O_CREAT) {
140 		va_start(ap, modestr);
141 		mode = (mode_t)va_arg(ap, int);
142 		va_end(ap);
143 	} else
144 		mode = 0;
145 
146 	fd = openat(dirfd, path, flags, mode);
147 	if (fd < 0)
148 		return (NULL);
149 	fp = fdopen(fd, modestr);
150 	if (fp == NULL) {
151 		error = errno;
152 		(void)close(fd);
153 		errno = error;
154 	}
155 	return (fp);
156 }
157 
158 static void
159 printheader(xo_handle_t *xo, const struct kerneldumpheader *h,
160     const char *device, int bounds, const int status)
161 {
162 	uint64_t dumplen;
163 	time_t t;
164 	struct tm tm;
165 	char time_str[64];
166 	const char *stat_str;
167 	const char *comp_str;
168 
169 	xo_flush_h(xo);
170 	xo_emit_h(xo, "{Lwc:Dump header from device}{:dump_device/%s}\n",
171 	    device);
172 	xo_emit_h(xo, "{P:  }{Lwc:Architecture}{:architecture/%s}\n",
173 	    h->architecture);
174 	xo_emit_h(xo,
175 	    "{P:  }{Lwc:Architecture Version}{:architecture_version/%u}\n",
176 	    dtoh32(h->architectureversion));
177 	dumplen = dtoh64(h->dumplength);
178 	xo_emit_h(xo, "{P:  }{Lwc:Dump Length}{:dump_length_bytes/%lld}\n",
179 	    (long long)dumplen);
180 	xo_emit_h(xo, "{P:  }{Lwc:Blocksize}{:blocksize/%d}\n",
181 	    dtoh32(h->blocksize));
182 	switch (h->compression) {
183 	case KERNELDUMP_COMP_NONE:
184 		comp_str = "none";
185 		break;
186 	case KERNELDUMP_COMP_GZIP:
187 		comp_str = "gzip";
188 		break;
189 	case KERNELDUMP_COMP_ZSTD:
190 		comp_str = "zstd";
191 		break;
192 	default:
193 		comp_str = "???";
194 		break;
195 	}
196 	xo_emit_h(xo, "{P:  }{Lwc:Compression}{:compression/%s}\n", comp_str);
197 	t = dtoh64(h->dumptime);
198 	localtime_r(&t, &tm);
199 	if (strftime(time_str, sizeof(time_str), "%F %T %z", &tm) == 0)
200 		time_str[0] = '\0';
201 	xo_emit_h(xo, "{P:  }{Lwc:Dumptime}{:dumptime/%s}\n", time_str);
202 	xo_emit_h(xo, "{P:  }{Lwc:Hostname}{:hostname/%s}\n", h->hostname);
203 	xo_emit_h(xo, "{P:  }{Lwc:Magic}{:magic/%s}\n", h->magic);
204 	xo_emit_h(xo, "{P:  }{Lwc:Version String}{:version_string/%s}",
205 	    h->versionstring);
206 	xo_emit_h(xo, "{P:  }{Lwc:Panic String}{:panic_string/%s}\n",
207 	    h->panicstring);
208 	xo_emit_h(xo, "{P:  }{Lwc:Dump Parity}{:dump_parity/%u}\n", h->parity);
209 	xo_emit_h(xo, "{P:  }{Lwc:Bounds}{:bounds/%d}\n", bounds);
210 
211 	switch (status) {
212 	case STATUS_BAD:
213 		stat_str = "bad";
214 		break;
215 	case STATUS_GOOD:
216 		stat_str = "good";
217 		break;
218 	default:
219 		stat_str = "unknown";
220 		break;
221 	}
222 	xo_emit_h(xo, "{P:  }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str);
223 	xo_flush_h(xo);
224 }
225 
226 static int
227 getbounds(int savedirfd)
228 {
229 	FILE *fp;
230 	char buf[6];
231 	int ret;
232 
233 	/*
234 	 * If we are just checking, then we haven't done a chdir to the dump
235 	 * directory and we should not try to read a bounds file.
236 	 */
237 	if (checkfor)
238 		return (0);
239 
240 	ret = 0;
241 
242 	if ((fp = xfopenat(savedirfd, "bounds", O_RDONLY, "r")) == NULL) {
243 		if (verbose)
244 			printf("unable to open bounds file, using 0\n");
245 		return (ret);
246 	}
247 	if (fgets(buf, sizeof(buf), fp) == NULL) {
248 		if (feof(fp))
249 			logmsg(LOG_WARNING, "bounds file is empty, using 0");
250 		else
251 			logmsg(LOG_WARNING, "bounds file: %s", strerror(errno));
252 		fclose(fp);
253 		return (ret);
254 	}
255 
256 	errno = 0;
257 	ret = (int)strtol(buf, NULL, 10);
258 	if (ret == 0 && (errno == EINVAL || errno == ERANGE))
259 		logmsg(LOG_WARNING, "invalid value found in bounds, using 0");
260 	fclose(fp);
261 	return (ret);
262 }
263 
264 static void
265 writebounds(int savedirfd, int bounds)
266 {
267 	FILE *fp;
268 
269 	if ((fp = xfopenat(savedirfd, "bounds", O_WRONLY | O_CREAT | O_TRUNC,
270 	    "w", 0644)) == NULL) {
271 		logmsg(LOG_WARNING, "unable to write to bounds file: %m");
272 		return;
273 	}
274 
275 	if (verbose)
276 		printf("bounds number: %d\n", bounds);
277 
278 	fprintf(fp, "%d\n", bounds);
279 	fclose(fp);
280 }
281 
282 static bool
283 writekey(int savedirfd, const char *keyname, uint8_t *dumpkey,
284     uint32_t dumpkeysize)
285 {
286 	int fd;
287 
288 	fd = openat(savedirfd, keyname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
289 	if (fd == -1) {
290 		logmsg(LOG_ERR, "Unable to open %s to write the key: %m.",
291 		    keyname);
292 		return (false);
293 	}
294 
295 	if (write(fd, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) {
296 		logmsg(LOG_ERR, "Unable to write the key to %s: %m.", keyname);
297 		close(fd);
298 		return (false);
299 	}
300 
301 	close(fd);
302 	return (true);
303 }
304 
305 static off_t
306 file_size(int savedirfd, const char *path)
307 {
308 	struct stat sb;
309 
310 	/* Ignore all errors, this file may not exist. */
311 	if (fstatat(savedirfd, path, &sb, 0) == -1)
312 		return (0);
313 	return (sb.st_size);
314 }
315 
316 static off_t
317 saved_dump_size(int savedirfd, int bounds)
318 {
319 	static char path[PATH_MAX];
320 	off_t dumpsize;
321 
322 	dumpsize = 0;
323 
324 	(void)snprintf(path, sizeof(path), "info.%d", bounds);
325 	dumpsize += file_size(savedirfd, path);
326 	(void)snprintf(path, sizeof(path), "vmcore.%d", bounds);
327 	dumpsize += file_size(savedirfd, path);
328 	(void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds);
329 	dumpsize += file_size(savedirfd, path);
330 	(void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds);
331 	dumpsize += file_size(savedirfd, path);
332 	(void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds);
333 	dumpsize += file_size(savedirfd, path);
334 	(void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds);
335 	dumpsize += file_size(savedirfd, path);
336 
337 	return (dumpsize);
338 }
339 
340 static void
341 saved_dump_remove(int savedirfd, int bounds)
342 {
343 	static char path[PATH_MAX];
344 
345 	(void)snprintf(path, sizeof(path), "info.%d", bounds);
346 	(void)unlinkat(savedirfd, path, 0);
347 	(void)snprintf(path, sizeof(path), "vmcore.%d", bounds);
348 	(void)unlinkat(savedirfd, path, 0);
349 	(void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds);
350 	(void)unlinkat(savedirfd, path, 0);
351 	(void)snprintf(path, sizeof(path), "vmcore.%d.zst", bounds);
352 	(void)unlinkat(savedirfd, path, 0);
353 	(void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds);
354 	(void)unlinkat(savedirfd, path, 0);
355 	(void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds);
356 	(void)unlinkat(savedirfd, path, 0);
357 }
358 
359 static void
360 symlinks_remove(int savedirfd)
361 {
362 
363 	(void)unlinkat(savedirfd, "info.last", 0);
364 	(void)unlinkat(savedirfd, "key.last", 0);
365 	(void)unlinkat(savedirfd, "vmcore.last", 0);
366 	(void)unlinkat(savedirfd, "vmcore.last.gz", 0);
367 	(void)unlinkat(savedirfd, "vmcore.last.zst", 0);
368 	(void)unlinkat(savedirfd, "vmcore_encrypted.last", 0);
369 	(void)unlinkat(savedirfd, "vmcore_encrypted.last.gz", 0);
370 	(void)unlinkat(savedirfd, "textdump.tar.last", 0);
371 	(void)unlinkat(savedirfd, "textdump.tar.last.gz", 0);
372 }
373 
374 /*
375  * Check that sufficient space is available on the disk that holds the
376  * save directory.
377  */
378 static int
379 check_space(const char *savedir, int savedirfd, off_t dumpsize, int bounds)
380 {
381 	char buf[100];
382 	struct statfs fsbuf;
383 	FILE *fp;
384 	off_t available, minfree, spacefree, totfree, needed;
385 
386 	if (fstatfs(savedirfd, &fsbuf) < 0) {
387 		logmsg(LOG_ERR, "%s: %m", savedir);
388 		exit(1);
389 	}
390 	spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
391 	totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024;
392 
393 	if ((fp = xfopenat(savedirfd, "minfree", O_RDONLY, "r")) == NULL)
394 		minfree = 0;
395 	else {
396 		if (fgets(buf, sizeof(buf), fp) == NULL)
397 			minfree = 0;
398 		else {
399 			char *endp;
400 
401 			errno = 0;
402 			minfree = strtoll(buf, &endp, 10);
403 			if (minfree == 0 && errno != 0)
404 				minfree = -1;
405 			else {
406 				while (*endp != '\0' && isspace(*endp))
407 					endp++;
408 				if (*endp != '\0' || minfree < 0)
409 					minfree = -1;
410 			}
411 			if (minfree < 0)
412 				logmsg(LOG_WARNING,
413 				    "`minfree` didn't contain a valid size "
414 				    "(`%s`). Defaulting to 0", buf);
415 		}
416 		(void)fclose(fp);
417 	}
418 
419 	available = minfree > 0 ? spacefree - minfree : totfree;
420 	needed = dumpsize / 1024 + 2;	/* 2 for info file */
421 	needed -= saved_dump_size(savedirfd, bounds);
422 	if (available < needed) {
423 		logmsg(LOG_WARNING,
424 		    "no dump: not enough free space on device (need at least "
425 		    "%jdkB for dump; %jdkB available; %jdkB reserved)",
426 		    (intmax_t)needed,
427 		    (intmax_t)available + minfree,
428 		    (intmax_t)minfree);
429 		return (0);
430 	}
431 	if (spacefree - needed < 0)
432 		logmsg(LOG_WARNING,
433 		    "dump performed, but free space threshold crossed");
434 	return (1);
435 }
436 
437 static bool
438 compare_magic(const struct kerneldumpheader *kdh, const char *magic)
439 {
440 
441 	return (strncmp(kdh->magic, magic, sizeof(kdh->magic)) == 0);
442 }
443 
444 #define BLOCKSIZE (1<<12)
445 #define BLOCKMASK (~(BLOCKSIZE-1))
446 
447 static size_t
448 sparsefwrite(const char *buf, size_t nr, FILE *fp)
449 {
450 	size_t nw, he, hs;
451 
452 	for (nw = 0; nw < nr; nw = he) {
453 		/* find a contiguous block of zeroes */
454 		for (hs = nw; hs < nr; hs += BLOCKSIZE) {
455 			for (he = hs; he < nr && buf[he] == 0; ++he)
456 				/* nothing */ ;
457 			/* is the hole long enough to matter? */
458 			if (he >= hs + BLOCKSIZE)
459 				break;
460 		}
461 
462 		/* back down to a block boundary */
463 		he &= BLOCKMASK;
464 
465 		/*
466 		 * 1) Don't go beyond the end of the buffer.
467 		 * 2) If the end of the buffer is less than
468 		 *    BLOCKSIZE bytes away, we're at the end
469 		 *    of the file, so just grab what's left.
470 		 */
471 		if (hs + BLOCKSIZE > nr)
472 			hs = he = nr;
473 
474 		/*
475 		 * At this point, we have a partial ordering:
476 		 *     nw <= hs <= he <= nr
477 		 * If hs > nw, buf[nw..hs] contains non-zero
478 		 * data. If he > hs, buf[hs..he] is all zeroes.
479 		 */
480 		if (hs > nw)
481 			if (fwrite(buf + nw, hs - nw, 1, fp) != 1)
482 				break;
483 		if (he > hs)
484 			if (fseeko(fp, he - hs, SEEK_CUR) == -1)
485 				break;
486 	}
487 
488 	return (nw);
489 }
490 
491 static char *zbuf;
492 static size_t zbufsize;
493 
494 static size_t
495 GunzipWrite(z_stream *z, char *in, size_t insize, FILE *fp)
496 {
497 	static bool firstblock = true;		/* XXX not re-entrable/usable */
498 	const size_t hdrlen = 10;
499 	size_t nw = 0;
500 	int rv;
501 
502 	z->next_in = in;
503 	z->avail_in = insize;
504 	/*
505 	 * Since contrib/zlib for some reason is compiled
506 	 * without GUNZIP define, we need to skip the gzip
507 	 * header manually.  Kernel puts minimal 10 byte
508 	 * header, see sys/kern/subr_compressor.c:gz_reset().
509 	 */
510 	if (firstblock) {
511 		z->next_in += hdrlen;
512 		z->avail_in -= hdrlen;
513 		firstblock = false;
514 	}
515 	do {
516 		z->next_out = zbuf;
517 		z->avail_out = zbufsize;
518 		rv = inflate(z, Z_NO_FLUSH);
519 		if (rv != Z_OK && rv != Z_STREAM_END) {
520 			logmsg(LOG_ERR, "decompression failed: %s", z->msg);
521 			return (-1);
522 		}
523 		nw += sparsefwrite(zbuf, zbufsize - z->avail_out, fp);
524 	} while (z->avail_in > 0 && rv != Z_STREAM_END);
525 
526 	return (nw);
527 }
528 
529 static size_t
530 ZstdWrite(ZSTD_DCtx *Zctx, char *in, size_t insize, FILE *fp)
531 {
532 	ZSTD_inBuffer Zin;
533 	ZSTD_outBuffer Zout;
534 	size_t nw = 0;
535 	int rv;
536 
537 	Zin.src = in;
538 	Zin.size = insize;
539 	Zin.pos = 0;
540 	do {
541 		Zout.dst = zbuf;
542 		Zout.size = zbufsize;
543 		Zout.pos = 0;
544 		rv = ZSTD_decompressStream(Zctx, &Zout, &Zin);
545 		if (ZSTD_isError(rv)) {
546 			logmsg(LOG_ERR, "decompression failed: %s",
547 			    ZSTD_getErrorName(rv));
548 			return (-1);
549 		}
550 		nw += sparsefwrite(zbuf, Zout.pos, fp);
551 	} while (Zin.pos < Zin.size && rv != 0);
552 
553 	return (nw);
554 }
555 
556 static int
557 DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse,
558     uint8_t compression, char *buf, const char *device,
559     const char *filename, FILE *fp)
560 {
561 	size_t nr, nw, wl;
562 	off_t dmpcnt, origsize;
563 	z_stream z;		/* gzip */
564 	ZSTD_DCtx *Zctx;	/* zstd */
565 
566 	dmpcnt = 0;
567 	origsize = dumpsize;
568 	if (compression == KERNELDUMP_COMP_GZIP) {
569 		memset(&z, 0, sizeof(z));
570 		z.zalloc = Z_NULL;
571 		z.zfree = Z_NULL;
572 		if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
573 			logmsg(LOG_ERR, "failed to initialize zlib: %s", z.msg);
574 			return (-1);
575 		}
576 		zbufsize = BUFFERSIZE;
577 	} else if (compression == KERNELDUMP_COMP_ZSTD) {
578 		if ((Zctx = ZSTD_createDCtx()) == NULL) {
579 			logmsg(LOG_ERR, "failed to initialize zstd");
580 			return (-1);
581 		}
582 		zbufsize = ZSTD_DStreamOutSize();
583 	}
584 	if (zbufsize > 0)
585 		if ((zbuf = malloc(zbufsize)) == NULL) {
586 			logmsg(LOG_ERR, "failed to alloc decompression buffer");
587 			return (-1);
588 		}
589 
590 	while (dumpsize > 0) {
591 		wl = BUFFERSIZE;
592 		if (wl > (size_t)dumpsize)
593 			wl = dumpsize;
594 		nr = read(fd, buf, roundup(wl, sectorsize));
595 		if (nr != roundup(wl, sectorsize)) {
596 			if (nr == 0)
597 				logmsg(LOG_WARNING,
598 				    "WARNING: EOF on dump device");
599 			else
600 				logmsg(LOG_ERR, "read error on %s: %m", device);
601 			nerr++;
602 			return (-1);
603 		}
604 		if (compression == KERNELDUMP_COMP_GZIP)
605 			nw = GunzipWrite(&z, buf, nr, fp);
606 		else if (compression == KERNELDUMP_COMP_ZSTD)
607 			nw = ZstdWrite(Zctx, buf, nr, fp);
608 		else if (!sparse)
609 			nw = fwrite(buf, 1, wl, fp);
610 		else
611 			nw = sparsefwrite(buf, wl, fp);
612 		if ((compression == KERNELDUMP_COMP_NONE && nw != wl) ||
613 		    (compression != KERNELDUMP_COMP_NONE && nw < 0)) {
614 			logmsg(LOG_ERR,
615 			    "write error on %s file: %m", filename);
616 			logmsg(LOG_WARNING,
617 			    "WARNING: vmcore may be incomplete");
618 			nerr++;
619 			return (-1);
620 		}
621 		if (verbose) {
622 			dmpcnt += wl;
623 			printf("%llu\r", (unsigned long long)dmpcnt);
624 			fflush(stdout);
625 		}
626 		dumpsize -= wl;
627 		if (got_siginfo) {
628 			printf("%s %.1lf%%\n", filename, (100.0 - (100.0 *
629 			    (double)dumpsize / (double)origsize)));
630 			got_siginfo = 0;
631 		}
632 	}
633 	return (0);
634 }
635 
636 /*
637  * Specialized version of dump-reading logic for use with textdumps, which
638  * are written backwards from the end of the partition, and must be reversed
639  * before being written to the file.  Textdumps are small, so do a bit less
640  * work to optimize/sparsify.
641  */
642 static int
643 DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf,
644     const char *device, const char *filename, FILE *fp)
645 {
646 	int nr, nw, wl;
647 	off_t dmpcnt, totsize;
648 
649 	totsize = dumpsize;
650 	dmpcnt = 0;
651 	wl = 512;
652 	if ((dumpsize % wl) != 0) {
653 		logmsg(LOG_ERR, "textdump uneven multiple of 512 on %s",
654 		    device);
655 		nerr++;
656 		return (-1);
657 	}
658 	while (dumpsize > 0) {
659 		nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl);
660 		if (nr != wl) {
661 			if (nr == 0)
662 				logmsg(LOG_WARNING,
663 				    "WARNING: EOF on dump device");
664 			else
665 				logmsg(LOG_ERR, "read error on %s: %m", device);
666 			nerr++;
667 			return (-1);
668 		}
669 		nw = fwrite(buf, 1, wl, fp);
670 		if (nw != wl) {
671 			logmsg(LOG_ERR,
672 			    "write error on %s file: %m", filename);
673 			logmsg(LOG_WARNING,
674 			    "WARNING: textdump may be incomplete");
675 			nerr++;
676 			return (-1);
677 		}
678 		if (verbose) {
679 			dmpcnt += wl;
680 			printf("%llu\r", (unsigned long long)dmpcnt);
681 			fflush(stdout);
682 		}
683 		dumpsize -= wl;
684 	}
685 	return (0);
686 }
687 
688 static void
689 DoFile(const char *savedir, int savedirfd, const char *device)
690 {
691 	xo_handle_t *xostdout, *xoinfo;
692 	static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX];
693 	static char keyname[PATH_MAX];
694 	static char *buf = NULL;
695 	char *temp = NULL;
696 	struct kerneldumpheader kdhf, kdhl;
697 	uint8_t *dumpkey;
698 	off_t mediasize, dumpextent, dumplength, firsthd, lasthd;
699 	FILE *core, *info;
700 	int fdcore, fddev, error;
701 	int bounds, status;
702 	u_int sectorsize, xostyle;
703 	uint32_t dumpkeysize;
704 	bool iscompressed, isencrypted, istextdump, ret;
705 
706 	bounds = getbounds(savedirfd);
707 	dumpkey = NULL;
708 	mediasize = 0;
709 	status = STATUS_UNKNOWN;
710 
711 	xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0);
712 	if (xostdout == NULL) {
713 		logmsg(LOG_ERR, "%s: %m", infoname);
714 		return;
715 	}
716 
717 	if (maxdumps > 0 && bounds == maxdumps)
718 		bounds = 0;
719 
720 	if (buf == NULL) {
721 		buf = malloc(BUFFERSIZE);
722 		if (buf == NULL) {
723 			logmsg(LOG_ERR, "%m");
724 			return;
725 		}
726 	}
727 
728 	if (verbose)
729 		printf("checking for kernel dump on device %s\n", device);
730 
731 	fddev = fileargs_open(capfa, device);
732 	if (fddev < 0) {
733 		logmsg(LOG_ERR, "%s: %m", device);
734 		return;
735 	}
736 
737 	error = ioctl(fddev, DIOCGMEDIASIZE, &mediasize);
738 	if (!error)
739 		error = ioctl(fddev, DIOCGSECTORSIZE, &sectorsize);
740 	if (error) {
741 		logmsg(LOG_ERR,
742 		    "couldn't find media and/or sector size of %s: %m", device);
743 		goto closefd;
744 	}
745 
746 	if (verbose) {
747 		printf("mediasize = %lld bytes\n", (long long)mediasize);
748 		printf("sectorsize = %u bytes\n", sectorsize);
749 	}
750 
751 	if (sectorsize < sizeof(kdhl)) {
752 		logmsg(LOG_ERR,
753 		    "Sector size is less the kernel dump header %zu",
754 		    sizeof(kdhl));
755 		goto closefd;
756 	}
757 
758 	lasthd = mediasize - sectorsize;
759 	temp = malloc(sectorsize);
760 	if (temp == NULL) {
761 		logmsg(LOG_ERR, "%m");
762 		goto closefd;
763 	}
764 	if (lseek(fddev, lasthd, SEEK_SET) != lasthd ||
765 	    read(fddev, temp, sectorsize) != (ssize_t)sectorsize) {
766 		logmsg(LOG_ERR,
767 		    "error reading last dump header at offset %lld in %s: %m",
768 		    (long long)lasthd, device);
769 		goto closefd;
770 	}
771 	memcpy(&kdhl, temp, sizeof(kdhl));
772 	iscompressed = istextdump = false;
773 	if (compare_magic(&kdhl, TEXTDUMPMAGIC)) {
774 		if (verbose)
775 			printf("textdump magic on last dump header on %s\n",
776 			    device);
777 		istextdump = true;
778 		if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) {
779 			logmsg(LOG_ERR,
780 			    "unknown version (%d) in last dump header on %s",
781 			    dtoh32(kdhl.version), device);
782 
783 			status = STATUS_BAD;
784 			if (!force)
785 				goto closefd;
786 		}
787 	} else if (compare_magic(&kdhl, KERNELDUMPMAGIC)) {
788 		if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
789 			logmsg(LOG_ERR,
790 			    "unknown version (%d) in last dump header on %s",
791 			    dtoh32(kdhl.version), device);
792 
793 			status = STATUS_BAD;
794 			if (!force)
795 				goto closefd;
796 		}
797 		switch (kdhl.compression) {
798 		case KERNELDUMP_COMP_NONE:
799 			uncompress = false;
800 			break;
801 		case KERNELDUMP_COMP_GZIP:
802 		case KERNELDUMP_COMP_ZSTD:
803 			if (compress && verbose)
804 				printf("dump is already compressed\n");
805 			if (uncompress && verbose)
806 				printf("dump to be uncompressed\n");
807 			compress = false;
808 			iscompressed = true;
809 			break;
810 		default:
811 			logmsg(LOG_ERR, "unknown compression type %d on %s",
812 			    kdhl.compression, device);
813 			break;
814 		}
815 	} else {
816 		if (verbose)
817 			printf("magic mismatch on last dump header on %s\n",
818 			    device);
819 
820 		status = STATUS_BAD;
821 		if (!force)
822 			goto closefd;
823 
824 		if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) {
825 			if (verbose)
826 				printf("forcing magic on %s\n", device);
827 			memcpy(kdhl.magic, KERNELDUMPMAGIC, sizeof(kdhl.magic));
828 		} else {
829 			logmsg(LOG_ERR, "unable to force dump - bad magic");
830 			goto closefd;
831 		}
832 		if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
833 			logmsg(LOG_ERR,
834 			    "unknown version (%d) in last dump header on %s",
835 			    dtoh32(kdhl.version), device);
836 
837 			status = STATUS_BAD;
838 			if (!force)
839 				goto closefd;
840 		}
841 	}
842 
843 	nfound++;
844 	if (clear)
845 		goto nuke;
846 
847 	if (kerneldump_parity(&kdhl)) {
848 		logmsg(LOG_ERR,
849 		    "parity error on last dump header on %s", device);
850 		nerr++;
851 		status = STATUS_BAD;
852 		if (!force)
853 			goto closefd;
854 	}
855 	dumpextent = dtoh64(kdhl.dumpextent);
856 	dumplength = dtoh64(kdhl.dumplength);
857 	dumpkeysize = dtoh32(kdhl.dumpkeysize);
858 	firsthd = lasthd - dumpextent - sectorsize - dumpkeysize;
859 	if (lseek(fddev, firsthd, SEEK_SET) != firsthd ||
860 	    read(fddev, temp, sectorsize) != (ssize_t)sectorsize) {
861 		logmsg(LOG_ERR,
862 		    "error reading first dump header at offset %lld in %s: %m",
863 		    (long long)firsthd, device);
864 		nerr++;
865 		goto closefd;
866 	}
867 	memcpy(&kdhf, temp, sizeof(kdhf));
868 
869 	if (verbose >= 2) {
870 		printf("First dump headers:\n");
871 		printheader(xostdout, &kdhf, device, bounds, -1);
872 
873 		printf("\nLast dump headers:\n");
874 		printheader(xostdout, &kdhl, device, bounds, -1);
875 		printf("\n");
876 	}
877 
878 	if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) {
879 		logmsg(LOG_ERR,
880 		    "first and last dump headers disagree on %s", device);
881 		nerr++;
882 		status = STATUS_BAD;
883 		if (!force)
884 			goto closefd;
885 	} else {
886 		status = STATUS_GOOD;
887 	}
888 
889 	if (checkfor) {
890 		printf("A dump exists on %s\n", device);
891 		close(fddev);
892 		exit(0);
893 	}
894 
895 	if (kdhl.panicstring[0] != '\0')
896 		logmsg(LOG_ALERT, "reboot after panic: %.*s",
897 		    (int)sizeof(kdhl.panicstring), kdhl.panicstring);
898 	else
899 		logmsg(LOG_ALERT, "reboot");
900 
901 	if (verbose)
902 		printf("Checking for available free space\n");
903 
904 	if (!check_space(savedir, savedirfd, dumplength, bounds)) {
905 		nerr++;
906 		goto closefd;
907 	}
908 
909 	writebounds(savedirfd, bounds + 1);
910 
911 	saved_dump_remove(savedirfd, bounds);
912 
913 	snprintf(infoname, sizeof(infoname), "info.%d", bounds);
914 
915 	/*
916 	 * Create or overwrite any existing dump header files.
917 	 */
918 	if ((info = xfopenat(savedirfd, infoname,
919 	    O_WRONLY | O_CREAT | O_TRUNC, "w", 0600)) == NULL) {
920 		logmsg(LOG_ERR, "open(%s): %m", infoname);
921 		nerr++;
922 		goto closefd;
923 	}
924 
925 	isencrypted = (dumpkeysize > 0);
926 	if (compress)
927 		snprintf(corename, sizeof(corename), "%s.%d.gz",
928 		    istextdump ? "textdump.tar" :
929 		    (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds);
930 	else if (iscompressed && !isencrypted && !uncompress)
931 		snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds,
932 		    (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst");
933 	else
934 		snprintf(corename, sizeof(corename), "%s.%d",
935 		    istextdump ? "textdump.tar" :
936 		    (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds);
937 	fdcore = openat(savedirfd, corename, O_WRONLY | O_CREAT | O_TRUNC,
938 	    0600);
939 	if (fdcore < 0) {
940 		logmsg(LOG_ERR, "open(%s): %m", corename);
941 		fclose(info);
942 		nerr++;
943 		goto closefd;
944 	}
945 
946 	if (compress)
947 		core = zdopen(fdcore, "w");
948 	else
949 		core = fdopen(fdcore, "w");
950 	if (core == NULL) {
951 		logmsg(LOG_ERR, "%s: %m", corename);
952 		(void)close(fdcore);
953 		(void)fclose(info);
954 		nerr++;
955 		goto closefd;
956 	}
957 	fdcore = -1;
958 
959 	xostyle = xo_get_style(NULL);
960 	xoinfo = xo_create_to_file(info, xostyle, 0);
961 	if (xoinfo == NULL) {
962 		logmsg(LOG_ERR, "%s: %m", infoname);
963 		fclose(info);
964 		nerr++;
965 		goto closeall;
966 	}
967 	xo_open_container_h(xoinfo, "crashdump");
968 
969 	if (verbose)
970 		printheader(xostdout, &kdhl, device, bounds, status);
971 
972 	printheader(xoinfo, &kdhl, device, bounds, status);
973 	xo_close_container_h(xoinfo, "crashdump");
974 	xo_flush_h(xoinfo);
975 	xo_finish_h(xoinfo);
976 	fclose(info);
977 
978 	if (isencrypted) {
979 		dumpkey = calloc(1, dumpkeysize);
980 		if (dumpkey == NULL) {
981 			logmsg(LOG_ERR, "Unable to allocate kernel dump key.");
982 			nerr++;
983 			goto closeall;
984 		}
985 
986 		if (read(fddev, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) {
987 			logmsg(LOG_ERR, "Unable to read kernel dump key: %m.");
988 			nerr++;
989 			goto closeall;
990 		}
991 
992 		snprintf(keyname, sizeof(keyname), "key.%d", bounds);
993 		ret = writekey(savedirfd, keyname, dumpkey, dumpkeysize);
994 		explicit_bzero(dumpkey, dumpkeysize);
995 		if (!ret) {
996 			nerr++;
997 			goto closeall;
998 		}
999 	}
1000 
1001 	logmsg(LOG_NOTICE, "writing %s%score to %s/%s",
1002 	    isencrypted ? "encrypted " : "", compress ? "compressed " : "",
1003 	    savedir, corename);
1004 
1005 	if (istextdump) {
1006 		if (DoTextdumpFile(fddev, dumplength, lasthd, buf, device,
1007 		    corename, core) < 0)
1008 			goto closeall;
1009 	} else {
1010 		if (DoRegularFile(fddev, dumplength, sectorsize,
1011 		    !(compress || iscompressed || isencrypted),
1012 		    uncompress ? kdhl.compression : KERNELDUMP_COMP_NONE,
1013 		    buf, device, corename, core) < 0) {
1014 			goto closeall;
1015 		}
1016 	}
1017 	if (verbose)
1018 		printf("\n");
1019 
1020 	if (fclose(core) < 0) {
1021 		logmsg(LOG_ERR, "error on %s: %m", corename);
1022 		nerr++;
1023 		goto closefd;
1024 	}
1025 
1026 	symlinks_remove(savedirfd);
1027 	if (symlinkat(infoname, savedirfd, "info.last") == -1) {
1028 		logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m",
1029 		    savedir, "info.last");
1030 	}
1031 	if (isencrypted) {
1032 		if (symlinkat(keyname, savedirfd, "key.last") == -1) {
1033 			logmsg(LOG_WARNING,
1034 			    "unable to create symlink %s/%s: %m", savedir,
1035 			    "key.last");
1036 		}
1037 	}
1038 	if ((iscompressed && !uncompress) || compress) {
1039 		snprintf(linkname, sizeof(linkname), "%s.last.%s",
1040 		    istextdump ? "textdump.tar" :
1041 		    (isencrypted ? "vmcore_encrypted" : "vmcore"),
1042 		    (kdhl.compression == KERNELDUMP_COMP_ZSTD) ? "zst" : "gz");
1043 	} else {
1044 		snprintf(linkname, sizeof(linkname), "%s.last",
1045 		    istextdump ? "textdump.tar" :
1046 		    (isencrypted ? "vmcore_encrypted" : "vmcore"));
1047 	}
1048 	if (symlinkat(corename, savedirfd, linkname) == -1) {
1049 		logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m",
1050 		    savedir, linkname);
1051 	}
1052 
1053 	nsaved++;
1054 
1055 	if (verbose)
1056 		printf("dump saved\n");
1057 
1058 nuke:
1059 	if (!keep) {
1060 		if (verbose)
1061 			printf("clearing dump header\n");
1062 		memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic));
1063 		memcpy(temp, &kdhl, sizeof(kdhl));
1064 		if (lseek(fddev, lasthd, SEEK_SET) != lasthd ||
1065 		    write(fddev, temp, sectorsize) != (ssize_t)sectorsize)
1066 			logmsg(LOG_ERR,
1067 			    "error while clearing the dump header: %m");
1068 	}
1069 	xo_close_container_h(xostdout, "crashdump");
1070 	xo_finish_h(xostdout);
1071 	free(dumpkey);
1072 	free(temp);
1073 	close(fddev);
1074 	return;
1075 
1076 closeall:
1077 	fclose(core);
1078 
1079 closefd:
1080 	free(dumpkey);
1081 	free(temp);
1082 	close(fddev);
1083 }
1084 
1085 /* Prepend "/dev/" to any arguments that don't already have it */
1086 static char **
1087 devify(int argc, char **argv)
1088 {
1089 	char **devs;
1090 	int i, l;
1091 
1092 	devs = malloc(argc * sizeof(*argv));
1093 	if (devs == NULL) {
1094 		logmsg(LOG_ERR, "malloc(): %m");
1095 		exit(1);
1096 	}
1097 	for (i = 0; i < argc; i++) {
1098 		if (strncmp(argv[i], _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
1099 			devs[i] = strdup(argv[i]);
1100 		else {
1101 			char *fullpath;
1102 
1103 			fullpath = malloc(PATH_MAX);
1104 			if (fullpath == NULL) {
1105 				logmsg(LOG_ERR, "malloc(): %m");
1106 				exit(1);
1107 			}
1108 			l = snprintf(fullpath, PATH_MAX, "%s%s", _PATH_DEV,
1109 			    argv[i]);
1110 			if (l < 0) {
1111 				logmsg(LOG_ERR, "snprintf(): %m");
1112 				exit(1);
1113 			} else if (l >= PATH_MAX) {
1114 				logmsg(LOG_ERR, "device name too long");
1115 				exit(1);
1116 			}
1117 			devs[i] = fullpath;
1118 		}
1119 	}
1120 	return (devs);
1121 }
1122 
1123 static char **
1124 enum_dumpdevs(int *argcp)
1125 {
1126 	struct fstab *fsp;
1127 	char **argv;
1128 	int argc, n;
1129 
1130 	/*
1131 	 * We cannot use getfsent(3) in capability mode, so we must
1132 	 * scan /etc/fstab and build up a list of candidate devices
1133 	 * before proceeding.
1134 	 */
1135 	argc = 0;
1136 	n = 8;
1137 	argv = malloc(n * sizeof(*argv));
1138 	if (argv == NULL) {
1139 		logmsg(LOG_ERR, "malloc(): %m");
1140 		exit(1);
1141 	}
1142 	for (;;) {
1143 		fsp = getfsent();
1144 		if (fsp == NULL)
1145 			break;
1146 		if (strcmp(fsp->fs_vfstype, "swap") != 0 &&
1147 		    strcmp(fsp->fs_vfstype, "dump") != 0)
1148 			continue;
1149 		if (argc >= n) {
1150 			n *= 2;
1151 			argv = realloc(argv, n * sizeof(*argv));
1152 			if (argv == NULL) {
1153 				logmsg(LOG_ERR, "realloc(): %m");
1154 				exit(1);
1155 			}
1156 		}
1157 		argv[argc] = strdup(fsp->fs_spec);
1158 		if (argv[argc] == NULL) {
1159 			logmsg(LOG_ERR, "strdup(): %m");
1160 			exit(1);
1161 		}
1162 		argc++;
1163 	}
1164 	*argcp = argc;
1165 	return (argv);
1166 }
1167 
1168 static void
1169 init_caps(int argc, char **argv)
1170 {
1171 	cap_rights_t rights;
1172 	cap_channel_t *capcas;
1173 
1174 	capcas = cap_init();
1175 	if (capcas == NULL) {
1176 		logmsg(LOG_ERR, "cap_init(): %m");
1177 		exit(1);
1178 	}
1179 	/*
1180 	 * The fileargs capability does not currently provide a way to limit
1181 	 * ioctls.
1182 	 */
1183 	(void)cap_rights_init(&rights, CAP_PREAD, CAP_WRITE, CAP_IOCTL);
1184 	capfa = fileargs_init(argc, argv, checkfor || keep ? O_RDONLY : O_RDWR,
1185 	    0, &rights, FA_OPEN);
1186 	if (capfa == NULL) {
1187 		logmsg(LOG_ERR, "fileargs_init(): %m");
1188 		exit(1);
1189 	}
1190 	caph_cache_catpages();
1191 	caph_cache_tzdata();
1192 	if (caph_enter_casper() != 0) {
1193 		logmsg(LOG_ERR, "caph_enter_casper(): %m");
1194 		exit(1);
1195 	}
1196 	capsyslog = cap_service_open(capcas, "system.syslog");
1197 	if (capsyslog == NULL) {
1198 		logmsg(LOG_ERR, "cap_service_open(system.syslog): %m");
1199 		exit(1);
1200 	}
1201 	cap_close(capcas);
1202 }
1203 
1204 static void
1205 usage(void)
1206 {
1207 	xo_error("%s\n%s\n%s\n",
1208 	    "usage: savecore -c [-v] [device ...]",
1209 	    "       savecore -C [-v] [device ...]",
1210 	    "       savecore [-fkvz] [-m maxdumps] [directory [device ...]]");
1211 	exit(1);
1212 }
1213 
1214 int
1215 main(int argc, char **argv)
1216 {
1217 	cap_rights_t rights;
1218 	const char *savedir;
1219 	char **devs;
1220 	int i, ch, error, savedirfd;
1221 
1222 	checkfor = compress = clear = force = keep = false;
1223 	verbose = 0;
1224 	nfound = nsaved = nerr = 0;
1225 	savedir = ".";
1226 
1227 	openlog("savecore", LOG_PERROR, LOG_DAEMON);
1228 	signal(SIGINFO, infohandler);
1229 
1230 	argc = xo_parse_args(argc, argv);
1231 	if (argc < 0)
1232 		exit(1);
1233 
1234 	while ((ch = getopt(argc, argv, "Ccfkm:uvz")) != -1)
1235 		switch(ch) {
1236 		case 'C':
1237 			checkfor = true;
1238 			break;
1239 		case 'c':
1240 			clear = true;
1241 			break;
1242 		case 'f':
1243 			force = true;
1244 			break;
1245 		case 'k':
1246 			keep = true;
1247 			break;
1248 		case 'm':
1249 			maxdumps = atoi(optarg);
1250 			if (maxdumps <= 0) {
1251 				logmsg(LOG_ERR, "Invalid maxdump value");
1252 				exit(1);
1253 			}
1254 			break;
1255 		case 'u':
1256 			uncompress = true;
1257 			break;
1258 		case 'v':
1259 			verbose++;
1260 			break;
1261 		case 'z':
1262 			compress = true;
1263 			break;
1264 		case '?':
1265 		default:
1266 			usage();
1267 		}
1268 	if (checkfor && (clear || force || keep))
1269 		usage();
1270 	if (clear && (compress || keep))
1271 		usage();
1272 	if (maxdumps > 0 && (checkfor || clear))
1273 		usage();
1274 	if (compress && uncompress)
1275 		usage();
1276 	argc -= optind;
1277 	argv += optind;
1278 	if (argc >= 1 && !checkfor && !clear) {
1279 		error = chdir(argv[0]);
1280 		if (error) {
1281 			logmsg(LOG_ERR, "chdir(%s): %m", argv[0]);
1282 			exit(1);
1283 		}
1284 		savedir = argv[0];
1285 		argc--;
1286 		argv++;
1287 	}
1288 	if (argc == 0)
1289 		devs = enum_dumpdevs(&argc);
1290 	else
1291 		devs = devify(argc, argv);
1292 
1293 	savedirfd = open(savedir, O_RDONLY | O_DIRECTORY);
1294 	if (savedirfd < 0) {
1295 		logmsg(LOG_ERR, "open(%s): %m", savedir);
1296 		exit(1);
1297 	}
1298 	(void)cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL, CAP_FSTATAT,
1299 	    CAP_FSTATFS, CAP_PREAD, CAP_SYMLINKAT, CAP_FTRUNCATE, CAP_UNLINKAT,
1300 	    CAP_WRITE);
1301 	if (caph_rights_limit(savedirfd, &rights) < 0) {
1302 		logmsg(LOG_ERR, "cap_rights_limit(): %m");
1303 		exit(1);
1304 	}
1305 
1306 	/* Enter capability mode. */
1307 	init_caps(argc, devs);
1308 
1309 	for (i = 0; i < argc; i++)
1310 		DoFile(savedir, savedirfd, devs[i]);
1311 
1312 	/* Emit minimal output. */
1313 	if (nfound == 0) {
1314 		if (checkfor) {
1315 			if (verbose)
1316 				printf("No dump exists\n");
1317 			exit(1);
1318 		}
1319 		if (verbose)
1320 			logmsg(LOG_WARNING, "no dumps found");
1321 	} else if (nsaved == 0) {
1322 		if (nerr != 0) {
1323 			if (verbose)
1324 				logmsg(LOG_WARNING,
1325 				    "unsaved dumps found but not saved");
1326 			exit(1);
1327 		} else if (verbose)
1328 			logmsg(LOG_WARNING, "no unsaved dumps found");
1329 	}
1330 
1331 	return (0);
1332 }
1333 
1334 static void
1335 infohandler(int sig __unused)
1336 {
1337 	got_siginfo = 1;
1338 }
1339