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