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