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