]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/savecore/savecore.c
cdn-patch: offer option to mount /etc/keys before attaching geli devices
[FreeBSD/FreeBSD.git] / sbin / savecore / savecore.c
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         /*
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
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
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
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
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
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
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
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
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
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
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
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, &sectorsize);
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
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
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
1035 infohandler(int sig __unused)
1036 {
1037         got_siginfo = 1;
1038 }