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