]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/savecore/savecore.c
Add '-u' switch that would uncompress cores that were compressed by
[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 size_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;
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                 nw += sparsefwrite(zbuf, zbufsize - z->avail_out, fp);
524         } while (z->avail_in > 0 && rv != Z_STREAM_END);
525
526         return (nw);
527 }
528
529 static size_t
530 ZstdWrite(ZSTD_DCtx *Zctx, char *in, size_t insize, FILE *fp)
531 {
532         ZSTD_inBuffer Zin;
533         ZSTD_outBuffer Zout;
534         size_t nw = 0;
535         int rv;
536
537         Zin.src = in;
538         Zin.size = insize;
539         Zin.pos = 0;
540         do {
541                 Zout.dst = zbuf;
542                 Zout.size = zbufsize;
543                 Zout.pos = 0;
544                 rv = ZSTD_decompressStream(Zctx, &Zout, &Zin);
545                 if (ZSTD_isError(rv)) {
546                         logmsg(LOG_ERR, "decompression failed: %s",
547                             ZSTD_getErrorName(rv));
548                         return (-1);
549                 }
550                 nw += sparsefwrite(zbuf, Zout.pos, fp);
551         } while (Zin.pos < Zin.size && rv != 0);
552
553         return (nw);
554 }
555
556 static int
557 DoRegularFile(int fd, off_t dumpsize, u_int sectorsize, bool sparse,
558     uint8_t compression, char *buf, const char *device,
559     const char *filename, FILE *fp)
560 {
561         size_t nr, nw, wl;
562         off_t dmpcnt, origsize;
563         z_stream z;             /* gzip */
564         ZSTD_DCtx *Zctx;        /* zstd */
565
566         dmpcnt = 0;
567         origsize = dumpsize;
568         if (compression == KERNELDUMP_COMP_GZIP) {
569                 memset(&z, 0, sizeof(z));
570                 z.zalloc = Z_NULL;
571                 z.zfree = Z_NULL;
572                 if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
573                         logmsg(LOG_ERR, "failed to initialize zlib: %s", z.msg);
574                         return (-1);
575                 }
576                 zbufsize = BUFFERSIZE;
577         } else if (compression == KERNELDUMP_COMP_ZSTD) {
578                 if ((Zctx = ZSTD_createDCtx()) == NULL) {
579                         logmsg(LOG_ERR, "failed to initialize zstd");
580                         return (-1);
581                 }
582                 zbufsize = ZSTD_DStreamOutSize();
583         }
584         if (zbufsize > 0)
585                 if ((zbuf = malloc(zbufsize)) == NULL) {
586                         logmsg(LOG_ERR, "failed to alloc decompression buffer");
587                         return (-1);
588                 }
589
590         while (dumpsize > 0) {
591                 wl = BUFFERSIZE;
592                 if (wl > (size_t)dumpsize)
593                         wl = dumpsize;
594                 nr = read(fd, buf, roundup(wl, sectorsize));
595                 if (nr != roundup(wl, sectorsize)) {
596                         if (nr == 0)
597                                 logmsg(LOG_WARNING,
598                                     "WARNING: EOF on dump device");
599                         else
600                                 logmsg(LOG_ERR, "read error on %s: %m", device);
601                         nerr++;
602                         return (-1);
603                 }
604                 if (compression == KERNELDUMP_COMP_GZIP)
605                         nw = GunzipWrite(&z, buf, nr, fp);
606                 else if (compression == KERNELDUMP_COMP_ZSTD)
607                         nw = ZstdWrite(Zctx, buf, nr, fp);
608                 else if (!sparse)
609                         nw = fwrite(buf, 1, wl, fp);
610                 else
611                         nw = sparsefwrite(buf, wl, fp);
612                 if ((compression == KERNELDUMP_COMP_NONE && nw != wl) ||
613                     (compression != KERNELDUMP_COMP_NONE && nw < 0)) {
614                         logmsg(LOG_ERR,
615                             "write error on %s file: %m", filename);
616                         logmsg(LOG_WARNING,
617                             "WARNING: vmcore may be incomplete");
618                         nerr++;
619                         return (-1);
620                 }
621                 if (verbose) {
622                         dmpcnt += wl;
623                         printf("%llu\r", (unsigned long long)dmpcnt);
624                         fflush(stdout);
625                 }
626                 dumpsize -= wl;
627                 if (got_siginfo) {
628                         printf("%s %.1lf%%\n", filename, (100.0 - (100.0 *
629                             (double)dumpsize / (double)origsize)));
630                         got_siginfo = 0;
631                 }
632         }
633         return (0);
634 }
635
636 /*
637  * Specialized version of dump-reading logic for use with textdumps, which
638  * are written backwards from the end of the partition, and must be reversed
639  * before being written to the file.  Textdumps are small, so do a bit less
640  * work to optimize/sparsify.
641  */
642 static int
643 DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf,
644     const char *device, const char *filename, FILE *fp)
645 {
646         int nr, nw, wl;
647         off_t dmpcnt, totsize;
648
649         totsize = dumpsize;
650         dmpcnt = 0;
651         wl = 512;
652         if ((dumpsize % wl) != 0) {
653                 logmsg(LOG_ERR, "textdump uneven multiple of 512 on %s",
654                     device);
655                 nerr++;
656                 return (-1);
657         }
658         while (dumpsize > 0) {
659                 nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl);
660                 if (nr != wl) {
661                         if (nr == 0)
662                                 logmsg(LOG_WARNING,
663                                     "WARNING: EOF on dump device");
664                         else
665                                 logmsg(LOG_ERR, "read error on %s: %m", device);
666                         nerr++;
667                         return (-1);
668                 }
669                 nw = fwrite(buf, 1, wl, fp);
670                 if (nw != wl) {
671                         logmsg(LOG_ERR,
672                             "write error on %s file: %m", filename);
673                         logmsg(LOG_WARNING,
674                             "WARNING: textdump may be incomplete");
675                         nerr++;
676                         return (-1);
677                 }
678                 if (verbose) {
679                         dmpcnt += wl;
680                         printf("%llu\r", (unsigned long long)dmpcnt);
681                         fflush(stdout);
682                 }
683                 dumpsize -= wl;
684         }
685         return (0);
686 }
687
688 static void
689 DoFile(const char *savedir, int savedirfd, const char *device)
690 {
691         xo_handle_t *xostdout, *xoinfo;
692         static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX];
693         static char keyname[PATH_MAX];
694         static char *buf = NULL;
695         char *temp = NULL;
696         struct kerneldumpheader kdhf, kdhl;
697         uint8_t *dumpkey;
698         off_t mediasize, dumpextent, dumplength, firsthd, lasthd;
699         FILE *core, *info;
700         int fdcore, fddev, error;
701         int bounds, status;
702         u_int sectorsize, xostyle;
703         uint32_t dumpkeysize;
704         bool iscompressed, isencrypted, istextdump, ret;
705
706         bounds = getbounds(savedirfd);
707         dumpkey = NULL;
708         mediasize = 0;
709         status = STATUS_UNKNOWN;
710
711         xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0);
712         if (xostdout == NULL) {
713                 logmsg(LOG_ERR, "%s: %m", infoname);
714                 return;
715         }
716
717         if (maxdumps > 0 && bounds == maxdumps)
718                 bounds = 0;
719
720         if (buf == NULL) {
721                 buf = malloc(BUFFERSIZE);
722                 if (buf == NULL) {
723                         logmsg(LOG_ERR, "%m");
724                         return;
725                 }
726         }
727
728         if (verbose)
729                 printf("checking for kernel dump on device %s\n", device);
730
731         fddev = fileargs_open(capfa, device);
732         if (fddev < 0) {
733                 logmsg(LOG_ERR, "%s: %m", device);
734                 return;
735         }
736
737         error = ioctl(fddev, DIOCGMEDIASIZE, &mediasize);
738         if (!error)
739                 error = ioctl(fddev, DIOCGSECTORSIZE, &sectorsize);
740         if (error) {
741                 logmsg(LOG_ERR,
742                     "couldn't find media and/or sector size of %s: %m", device);
743                 goto closefd;
744         }
745
746         if (verbose) {
747                 printf("mediasize = %lld bytes\n", (long long)mediasize);
748                 printf("sectorsize = %u bytes\n", sectorsize);
749         }
750
751         if (sectorsize < sizeof(kdhl)) {
752                 logmsg(LOG_ERR,
753                     "Sector size is less the kernel dump header %zu",
754                     sizeof(kdhl));
755                 goto closefd;
756         }
757
758         lasthd = mediasize - sectorsize;
759         temp = malloc(sectorsize);
760         if (temp == NULL) {
761                 logmsg(LOG_ERR, "%m");
762                 goto closefd;
763         }
764         if (lseek(fddev, lasthd, SEEK_SET) != lasthd ||
765             read(fddev, temp, sectorsize) != (ssize_t)sectorsize) {
766                 logmsg(LOG_ERR,
767                     "error reading last dump header at offset %lld in %s: %m",
768                     (long long)lasthd, device);
769                 goto closefd;
770         }
771         memcpy(&kdhl, temp, sizeof(kdhl));
772         iscompressed = istextdump = false;
773         if (compare_magic(&kdhl, TEXTDUMPMAGIC)) {
774                 if (verbose)
775                         printf("textdump magic on last dump header on %s\n",
776                             device);
777                 istextdump = true;
778                 if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) {
779                         logmsg(LOG_ERR,
780                             "unknown version (%d) in last dump header on %s",
781                             dtoh32(kdhl.version), device);
782
783                         status = STATUS_BAD;
784                         if (!force)
785                                 goto closefd;
786                 }
787         } else if (compare_magic(&kdhl, KERNELDUMPMAGIC)) {
788                 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
789                         logmsg(LOG_ERR,
790                             "unknown version (%d) in last dump header on %s",
791                             dtoh32(kdhl.version), device);
792
793                         status = STATUS_BAD;
794                         if (!force)
795                                 goto closefd;
796                 }
797                 switch (kdhl.compression) {
798                 case KERNELDUMP_COMP_NONE:
799                         uncompress = false;
800                         break;
801                 case KERNELDUMP_COMP_GZIP:
802                 case KERNELDUMP_COMP_ZSTD:
803                         if (compress && verbose)
804                                 printf("dump is already compressed\n");
805                         if (uncompress && verbose)
806                                 printf("dump to be uncompressed\n");
807                         compress = false;
808                         iscompressed = true;
809                         break;
810                 default:
811                         logmsg(LOG_ERR, "unknown compression type %d on %s",
812                             kdhl.compression, device);
813                         break;
814                 }
815         } else {
816                 if (verbose)
817                         printf("magic mismatch on last dump header on %s\n",
818                             device);
819
820                 status = STATUS_BAD;
821                 if (!force)
822                         goto closefd;
823
824                 if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) {
825                         if (verbose)
826                                 printf("forcing magic on %s\n", device);
827                         memcpy(kdhl.magic, KERNELDUMPMAGIC, sizeof(kdhl.magic));
828                 } else {
829                         logmsg(LOG_ERR, "unable to force dump - bad magic");
830                         goto closefd;
831                 }
832                 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
833                         logmsg(LOG_ERR,
834                             "unknown version (%d) in last dump header on %s",
835                             dtoh32(kdhl.version), device);
836
837                         status = STATUS_BAD;
838                         if (!force)
839                                 goto closefd;
840                 }
841         }
842
843         nfound++;
844         if (clear)
845                 goto nuke;
846
847         if (kerneldump_parity(&kdhl)) {
848                 logmsg(LOG_ERR,
849                     "parity error on last dump header on %s", device);
850                 nerr++;
851                 status = STATUS_BAD;
852                 if (!force)
853                         goto closefd;
854         }
855         dumpextent = dtoh64(kdhl.dumpextent);
856         dumplength = dtoh64(kdhl.dumplength);
857         dumpkeysize = dtoh32(kdhl.dumpkeysize);
858         firsthd = lasthd - dumpextent - sectorsize - dumpkeysize;
859         if (lseek(fddev, firsthd, SEEK_SET) != firsthd ||
860             read(fddev, temp, sectorsize) != (ssize_t)sectorsize) {
861                 logmsg(LOG_ERR,
862                     "error reading first dump header at offset %lld in %s: %m",
863                     (long long)firsthd, device);
864                 nerr++;
865                 goto closefd;
866         }
867         memcpy(&kdhf, temp, sizeof(kdhf));
868
869         if (verbose >= 2) {
870                 printf("First dump headers:\n");
871                 printheader(xostdout, &kdhf, device, bounds, -1);
872
873                 printf("\nLast dump headers:\n");
874                 printheader(xostdout, &kdhl, device, bounds, -1);
875                 printf("\n");
876         }
877
878         if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) {
879                 logmsg(LOG_ERR,
880                     "first and last dump headers disagree on %s", device);
881                 nerr++;
882                 status = STATUS_BAD;
883                 if (!force)
884                         goto closefd;
885         } else {
886                 status = STATUS_GOOD;
887         }
888
889         if (checkfor) {
890                 printf("A dump exists on %s\n", device);
891                 close(fddev);
892                 exit(0);
893         }
894
895         if (kdhl.panicstring[0] != '\0')
896                 logmsg(LOG_ALERT, "reboot after panic: %.*s",
897                     (int)sizeof(kdhl.panicstring), kdhl.panicstring);
898         else
899                 logmsg(LOG_ALERT, "reboot");
900
901         if (verbose)
902                 printf("Checking for available free space\n");
903
904         if (!check_space(savedir, savedirfd, dumplength, bounds)) {
905                 nerr++;
906                 goto closefd;
907         }
908
909         writebounds(savedirfd, bounds + 1);
910
911         saved_dump_remove(savedirfd, bounds);
912
913         snprintf(infoname, sizeof(infoname), "info.%d", bounds);
914
915         /*
916          * Create or overwrite any existing dump header files.
917          */
918         if ((info = xfopenat(savedirfd, infoname,
919             O_WRONLY | O_CREAT | O_TRUNC, "w", 0600)) == NULL) {
920                 logmsg(LOG_ERR, "open(%s): %m", infoname);
921                 nerr++;
922                 goto closefd;
923         }
924
925         isencrypted = (dumpkeysize > 0);
926         if (compress)
927                 snprintf(corename, sizeof(corename), "%s.%d.gz",
928                     istextdump ? "textdump.tar" :
929                     (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds);
930         else if (iscompressed && !isencrypted && !uncompress)
931                 snprintf(corename, sizeof(corename), "vmcore.%d.%s", bounds,
932                     (kdhl.compression == KERNELDUMP_COMP_GZIP) ? "gz" : "zst");
933         else
934                 snprintf(corename, sizeof(corename), "%s.%d",
935                     istextdump ? "textdump.tar" :
936                     (isencrypted ? "vmcore_encrypted" : "vmcore"), bounds);
937         fdcore = openat(savedirfd, corename, O_WRONLY | O_CREAT | O_TRUNC,
938             0600);
939         if (fdcore < 0) {
940                 logmsg(LOG_ERR, "open(%s): %m", corename);
941                 fclose(info);
942                 nerr++;
943                 goto closefd;
944         }
945
946         if (compress)
947                 core = zdopen(fdcore, "w");
948         else
949                 core = fdopen(fdcore, "w");
950         if (core == NULL) {
951                 logmsg(LOG_ERR, "%s: %m", corename);
952                 (void)close(fdcore);
953                 (void)fclose(info);
954                 nerr++;
955                 goto closefd;
956         }
957         fdcore = -1;
958
959         xostyle = xo_get_style(NULL);
960         xoinfo = xo_create_to_file(info, xostyle, 0);
961         if (xoinfo == NULL) {
962                 logmsg(LOG_ERR, "%s: %m", infoname);
963                 fclose(info);
964                 nerr++;
965                 goto closeall;
966         }
967         xo_open_container_h(xoinfo, "crashdump");
968
969         if (verbose)
970                 printheader(xostdout, &kdhl, device, bounds, status);
971
972         printheader(xoinfo, &kdhl, device, bounds, status);
973         xo_close_container_h(xoinfo, "crashdump");
974         xo_flush_h(xoinfo);
975         xo_finish_h(xoinfo);
976         fclose(info);
977
978         if (isencrypted) {
979                 dumpkey = calloc(1, dumpkeysize);
980                 if (dumpkey == NULL) {
981                         logmsg(LOG_ERR, "Unable to allocate kernel dump key.");
982                         nerr++;
983                         goto closeall;
984                 }
985
986                 if (read(fddev, dumpkey, dumpkeysize) != (ssize_t)dumpkeysize) {
987                         logmsg(LOG_ERR, "Unable to read kernel dump key: %m.");
988                         nerr++;
989                         goto closeall;
990                 }
991
992                 snprintf(keyname, sizeof(keyname), "key.%d", bounds);
993                 ret = writekey(savedirfd, keyname, dumpkey, dumpkeysize);
994                 explicit_bzero(dumpkey, dumpkeysize);
995                 if (!ret) {
996                         nerr++;
997                         goto closeall;
998                 }
999         }
1000
1001         logmsg(LOG_NOTICE, "writing %s%score to %s/%s",
1002             isencrypted ? "encrypted " : "", compress ? "compressed " : "",
1003             savedir, corename);
1004
1005         if (istextdump) {
1006                 if (DoTextdumpFile(fddev, dumplength, lasthd, buf, device,
1007                     corename, core) < 0)
1008                         goto closeall;
1009         } else {
1010                 if (DoRegularFile(fddev, dumplength, sectorsize,
1011                     !(compress || iscompressed || isencrypted),
1012                     uncompress ? kdhl.compression : KERNELDUMP_COMP_NONE,
1013                     buf, device, corename, core) < 0) {
1014                         goto closeall;
1015                 }
1016         }
1017         if (verbose)
1018                 printf("\n");
1019
1020         if (fclose(core) < 0) {
1021                 logmsg(LOG_ERR, "error on %s: %m", corename);
1022                 nerr++;
1023                 goto closefd;
1024         }
1025
1026         symlinks_remove(savedirfd);
1027         if (symlinkat(infoname, savedirfd, "info.last") == -1) {
1028                 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m",
1029                     savedir, "info.last");
1030         }
1031         if (isencrypted) {
1032                 if (symlinkat(keyname, savedirfd, "key.last") == -1) {
1033                         logmsg(LOG_WARNING,
1034                             "unable to create symlink %s/%s: %m", savedir,
1035                             "key.last");
1036                 }
1037         }
1038         if ((iscompressed && !uncompress) || compress) {
1039                 snprintf(linkname, sizeof(linkname), "%s.last.%s",
1040                     istextdump ? "textdump.tar" :
1041                     (isencrypted ? "vmcore_encrypted" : "vmcore"),
1042                     (kdhl.compression == KERNELDUMP_COMP_ZSTD) ? "zst" : "gz");
1043         } else {
1044                 snprintf(linkname, sizeof(linkname), "%s.last",
1045                     istextdump ? "textdump.tar" :
1046                     (isencrypted ? "vmcore_encrypted" : "vmcore"));
1047         }
1048         if (symlinkat(corename, savedirfd, linkname) == -1) {
1049                 logmsg(LOG_WARNING, "unable to create symlink %s/%s: %m",
1050                     savedir, linkname);
1051         }
1052
1053         nsaved++;
1054
1055         if (verbose)
1056                 printf("dump saved\n");
1057
1058 nuke:
1059         if (!keep) {
1060                 if (verbose)
1061                         printf("clearing dump header\n");
1062                 memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic));
1063                 memcpy(temp, &kdhl, sizeof(kdhl));
1064                 if (lseek(fddev, lasthd, SEEK_SET) != lasthd ||
1065                     write(fddev, temp, sectorsize) != (ssize_t)sectorsize)
1066                         logmsg(LOG_ERR,
1067                             "error while clearing the dump header: %m");
1068         }
1069         xo_close_container_h(xostdout, "crashdump");
1070         xo_finish_h(xostdout);
1071         free(dumpkey);
1072         free(temp);
1073         close(fddev);
1074         return;
1075
1076 closeall:
1077         fclose(core);
1078
1079 closefd:
1080         free(dumpkey);
1081         free(temp);
1082         close(fddev);
1083 }
1084
1085 /* Prepend "/dev/" to any arguments that don't already have it */
1086 static char **
1087 devify(int argc, char **argv)
1088 {
1089         char **devs;
1090         int i, l;
1091
1092         devs = malloc(argc * sizeof(*argv));
1093         if (devs == NULL) {
1094                 logmsg(LOG_ERR, "malloc(): %m");
1095                 exit(1);
1096         }
1097         for (i = 0; i < argc; i++) {
1098                 if (strncmp(argv[i], _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
1099                         devs[i] = strdup(argv[i]);
1100                 else {
1101                         char *fullpath;
1102
1103                         fullpath = malloc(PATH_MAX);
1104                         if (fullpath == NULL) {
1105                                 logmsg(LOG_ERR, "malloc(): %m");
1106                                 exit(1);
1107                         }
1108                         l = snprintf(fullpath, PATH_MAX, "%s%s", _PATH_DEV,
1109                             argv[i]);
1110                         if (l < 0) {
1111                                 logmsg(LOG_ERR, "snprintf(): %m");
1112                                 exit(1);
1113                         } else if (l >= PATH_MAX) {
1114                                 logmsg(LOG_ERR, "device name too long");
1115                                 exit(1);
1116                         }
1117                         devs[i] = fullpath;
1118                 }
1119         }
1120         return (devs);
1121 }
1122
1123 static char **
1124 enum_dumpdevs(int *argcp)
1125 {
1126         struct fstab *fsp;
1127         char **argv;
1128         int argc, n;
1129
1130         /*
1131          * We cannot use getfsent(3) in capability mode, so we must
1132          * scan /etc/fstab and build up a list of candidate devices
1133          * before proceeding.
1134          */
1135         argc = 0;
1136         n = 8;
1137         argv = malloc(n * sizeof(*argv));
1138         if (argv == NULL) {
1139                 logmsg(LOG_ERR, "malloc(): %m");
1140                 exit(1);
1141         }
1142         for (;;) {
1143                 fsp = getfsent();
1144                 if (fsp == NULL)
1145                         break;
1146                 if (strcmp(fsp->fs_vfstype, "swap") != 0 &&
1147                     strcmp(fsp->fs_vfstype, "dump") != 0)
1148                         continue;
1149                 if (argc >= n) {
1150                         n *= 2;
1151                         argv = realloc(argv, n * sizeof(*argv));
1152                         if (argv == NULL) {
1153                                 logmsg(LOG_ERR, "realloc(): %m");
1154                                 exit(1);
1155                         }
1156                 }
1157                 argv[argc] = strdup(fsp->fs_spec);
1158                 if (argv[argc] == NULL) {
1159                         logmsg(LOG_ERR, "strdup(): %m");
1160                         exit(1);
1161                 }
1162                 argc++;
1163         }
1164         *argcp = argc;
1165         return (argv);
1166 }
1167
1168 static void
1169 init_caps(int argc, char **argv)
1170 {
1171         cap_rights_t rights;
1172         cap_channel_t *capcas;
1173
1174         capcas = cap_init();
1175         if (capcas == NULL) {
1176                 logmsg(LOG_ERR, "cap_init(): %m");
1177                 exit(1);
1178         }
1179         /*
1180          * The fileargs capability does not currently provide a way to limit
1181          * ioctls.
1182          */
1183         (void)cap_rights_init(&rights, CAP_PREAD, CAP_WRITE, CAP_IOCTL);
1184         capfa = fileargs_init(argc, argv, checkfor || keep ? O_RDONLY : O_RDWR,
1185             0, &rights, FA_OPEN);
1186         if (capfa == NULL) {
1187                 logmsg(LOG_ERR, "fileargs_init(): %m");
1188                 exit(1);
1189         }
1190         caph_cache_catpages();
1191         caph_cache_tzdata();
1192         if (caph_enter_casper() != 0) {
1193                 logmsg(LOG_ERR, "caph_enter_casper(): %m");
1194                 exit(1);
1195         }
1196         capsyslog = cap_service_open(capcas, "system.syslog");
1197         if (capsyslog == NULL) {
1198                 logmsg(LOG_ERR, "cap_service_open(system.syslog): %m");
1199                 exit(1);
1200         }
1201         cap_close(capcas);
1202 }
1203
1204 static void
1205 usage(void)
1206 {
1207         xo_error("%s\n%s\n%s\n",
1208             "usage: savecore -c [-v] [device ...]",
1209             "       savecore -C [-v] [device ...]",
1210             "       savecore [-fkvz] [-m maxdumps] [directory [device ...]]");
1211         exit(1);
1212 }
1213
1214 int
1215 main(int argc, char **argv)
1216 {
1217         cap_rights_t rights;
1218         const char *savedir;
1219         char **devs;
1220         int i, ch, error, savedirfd;
1221
1222         checkfor = compress = clear = force = keep = false;
1223         verbose = 0;
1224         nfound = nsaved = nerr = 0;
1225         savedir = ".";
1226
1227         openlog("savecore", LOG_PERROR, LOG_DAEMON);
1228         signal(SIGINFO, infohandler);
1229
1230         argc = xo_parse_args(argc, argv);
1231         if (argc < 0)
1232                 exit(1);
1233
1234         while ((ch = getopt(argc, argv, "Ccfkm:uvz")) != -1)
1235                 switch(ch) {
1236                 case 'C':
1237                         checkfor = true;
1238                         break;
1239                 case 'c':
1240                         clear = true;
1241                         break;
1242                 case 'f':
1243                         force = true;
1244                         break;
1245                 case 'k':
1246                         keep = true;
1247                         break;
1248                 case 'm':
1249                         maxdumps = atoi(optarg);
1250                         if (maxdumps <= 0) {
1251                                 logmsg(LOG_ERR, "Invalid maxdump value");
1252                                 exit(1);
1253                         }
1254                         break;
1255                 case 'u':
1256                         uncompress = true;
1257                         break;
1258                 case 'v':
1259                         verbose++;
1260                         break;
1261                 case 'z':
1262                         compress = true;
1263                         break;
1264                 case '?':
1265                 default:
1266                         usage();
1267                 }
1268         if (checkfor && (clear || force || keep))
1269                 usage();
1270         if (clear && (compress || keep))
1271                 usage();
1272         if (maxdumps > 0 && (checkfor || clear))
1273                 usage();
1274         if (compress && uncompress)
1275                 usage();
1276         argc -= optind;
1277         argv += optind;
1278         if (argc >= 1 && !checkfor && !clear) {
1279                 error = chdir(argv[0]);
1280                 if (error) {
1281                         logmsg(LOG_ERR, "chdir(%s): %m", argv[0]);
1282                         exit(1);
1283                 }
1284                 savedir = argv[0];
1285                 argc--;
1286                 argv++;
1287         }
1288         if (argc == 0)
1289                 devs = enum_dumpdevs(&argc);
1290         else
1291                 devs = devify(argc, argv);
1292
1293         savedirfd = open(savedir, O_RDONLY | O_DIRECTORY);
1294         if (savedirfd < 0) {
1295                 logmsg(LOG_ERR, "open(%s): %m", savedir);
1296                 exit(1);
1297         }
1298         (void)cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL, CAP_FSTATAT,
1299             CAP_FSTATFS, CAP_PREAD, CAP_SYMLINKAT, CAP_FTRUNCATE, CAP_UNLINKAT,
1300             CAP_WRITE);
1301         if (caph_rights_limit(savedirfd, &rights) < 0) {
1302                 logmsg(LOG_ERR, "cap_rights_limit(): %m");
1303                 exit(1);
1304         }
1305
1306         /* Enter capability mode. */
1307         init_caps(argc, devs);
1308
1309         for (i = 0; i < argc; i++)
1310                 DoFile(savedir, savedirfd, devs[i]);
1311
1312         /* Emit minimal output. */
1313         if (nfound == 0) {
1314                 if (checkfor) {
1315                         if (verbose)
1316                                 printf("No dump exists\n");
1317                         exit(1);
1318                 }
1319                 if (verbose)
1320                         logmsg(LOG_WARNING, "no dumps found");
1321         } else if (nsaved == 0) {
1322                 if (nerr != 0) {
1323                         if (verbose)
1324                                 logmsg(LOG_WARNING,
1325                                     "unsaved dumps found but not saved");
1326                         exit(1);
1327                 } else if (verbose)
1328                         logmsg(LOG_WARNING, "no unsaved dumps found");
1329         }
1330
1331         return (0);
1332 }
1333
1334 static void
1335 infohandler(int sig __unused)
1336 {
1337         got_siginfo = 1;
1338 }