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