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