]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/savecore/savecore.c
Add UPDATING entries and bump version.
[FreeBSD/FreeBSD.git] / sbin / savecore / savecore.c
1 /*-
2  * Copyright (c) 2002 Poul-Henning Kamp
3  * Copyright (c) 2002 Networks Associates Technology, Inc.
4  * All rights reserved.
5  *
6  * This software was developed for the FreeBSD Project by Poul-Henning Kamp
7  * and NAI Labs, the Security Research Division of Network Associates, Inc.
8  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
9  * DARPA CHATS research program.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. The names of the authors may not be used to endorse or promote
20  *    products derived from this software without specific prior written
21  *    permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  * Copyright (c) 1986, 1992, 1993
36  *      The Regents of the University of California.  All rights reserved.
37  *
38  * Redistribution and use in source and binary forms, with or without
39  * modification, are permitted provided that the following conditions
40  * are met:
41  * 1. Redistributions of source code must retain the above copyright
42  *    notice, this list of conditions and the following disclaimer.
43  * 2. Redistributions in binary form must reproduce the above copyright
44  *    notice, this list of conditions and the following disclaimer in the
45  *    documentation and/or other materials provided with the distribution.
46  * 4. Neither the name of the University nor the names of its contributors
47  *    may be used to endorse or promote products derived from this software
48  *    without specific prior written permission.
49  *
50  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
51  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
52  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
53  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
54  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
55  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
56  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
57  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
58  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
59  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
60  * SUCH DAMAGE.
61  */
62
63 #include <sys/cdefs.h>
64 __FBSDID("$FreeBSD$");
65
66 #include <sys/param.h>
67 #include <sys/disk.h>
68 #include <sys/kerneldump.h>
69 #include <sys/mount.h>
70 #include <sys/stat.h>
71 #include <ctype.h>
72 #include <errno.h>
73 #include <fcntl.h>
74 #include <fstab.h>
75 #include <paths.h>
76 #include <signal.h>
77 #include <stdarg.h>
78 #include <stdbool.h>
79 #include <stdio.h>
80 #include <stdlib.h>
81 #include <string.h>
82 #include <syslog.h>
83 #include <time.h>
84 #include <unistd.h>
85 #include <libxo/xo.h>
86
87 /* The size of the buffer used for I/O. */
88 #define BUFFERSIZE      (1024*1024)
89
90 #define STATUS_BAD      0
91 #define STATUS_GOOD     1
92 #define STATUS_UNKNOWN  2
93
94 static int checkfor, compress, clear, force, keep, verbose;     /* flags */
95 static int nfound, nsaved, nerr;                        /* statistics */
96 static int maxdumps;
97
98 extern FILE *zopen(const char *, const char *);
99
100 static sig_atomic_t got_siginfo;
101 static void infohandler(int);
102
103 static void
104 printheader(xo_handle_t *xo, const struct kerneldumpheader *h,
105     const char *device, int bounds, const int status)
106 {
107         uint64_t dumplen;
108         time_t t;
109         const char *stat_str;
110
111         xo_flush_h(xo);
112         xo_emit_h(xo, "{Lwc:Dump header from device}{:dump_device/%s}\n",
113             device);
114         xo_emit_h(xo, "{P:  }{Lwc:Architecture}{:architecture/%s}\n",
115             h->architecture);
116         xo_emit_h(xo,
117             "{P:  }{Lwc:Architecture Version}{:architecture_version/%u}\n",
118             dtoh32(h->architectureversion));
119         dumplen = dtoh64(h->dumplength);
120         xo_emit_h(xo, "{P:  }{Lwc:Dump Length}{:dump_length_bytes/%lld}\n",
121             (long long)dumplen);
122         xo_emit_h(xo, "{P:  }{Lwc:Blocksize}{:blocksize/%d}\n",
123             dtoh32(h->blocksize));
124
125         t = dtoh64(h->dumptime);
126         xo_emit_h(xo, "{P:  }{Lwc:Dumptime}{:dumptime/%s}", ctime(&t));
127         xo_emit_h(xo, "{P:  }{Lwc:Hostname}{:hostname/%s}\n", h->hostname);
128         xo_emit_h(xo, "{P:  }{Lwc:Magic}{:magic/%s}\n", h->magic);
129         xo_emit_h(xo, "{P:  }{Lwc:Version String}{:version_string/%s}",
130             h->versionstring);
131         xo_emit_h(xo, "{P:  }{Lwc:Panic String}{:panic_string/%s}\n",
132             h->panicstring);
133         xo_emit_h(xo, "{P:  }{Lwc:Dump Parity}{:dump_parity/%u}\n", h->parity);
134         xo_emit_h(xo, "{P:  }{Lwc:Bounds}{:bounds/%d}\n", bounds);
135
136         switch (status) {
137         case STATUS_BAD:
138                 stat_str = "bad";
139                 break;
140         case STATUS_GOOD:
141                 stat_str = "good";
142                 break;
143         default:
144                 stat_str = "unknown";
145                 break;
146         }
147         xo_emit_h(xo, "{P:  }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str);
148         xo_flush_h(xo);
149 }
150
151 static int
152 getbounds(void)
153 {
154         FILE *fp;
155         char buf[6];
156         int ret;
157
158         /*
159          * If we are just checking, then we haven't done a chdir to the dump
160          * directory and we should not try to read a bounds file.
161          */
162         if (checkfor)
163                 return (0);
164
165         ret = 0;
166
167         if ((fp = fopen("bounds", "r")) == NULL) {
168                 if (verbose)
169                         printf("unable to open bounds file, using 0\n");
170                 return (ret);
171         }
172
173         if (fgets(buf, sizeof buf, fp) == NULL) {
174                 if (feof(fp))
175                         syslog(LOG_WARNING, "bounds file is empty, using 0");
176                 else
177                         syslog(LOG_WARNING, "bounds file: %s", strerror(errno));
178                 fclose(fp);
179                 return (ret);
180         }
181
182         errno = 0;
183         ret = (int)strtol(buf, NULL, 10);
184         if (ret == 0 && (errno == EINVAL || errno == ERANGE))
185                 syslog(LOG_WARNING, "invalid value found in bounds, using 0");
186         fclose(fp);
187         return (ret);
188 }
189
190 static void
191 writebounds(int bounds)
192 {
193         FILE *fp;
194
195         if ((fp = fopen("bounds", "w")) == NULL) {
196                 syslog(LOG_WARNING, "unable to write to bounds file: %m");
197                 return;
198         }
199
200         if (verbose)
201                 printf("bounds number: %d\n", bounds);
202
203         fprintf(fp, "%d\n", bounds);
204         fclose(fp);
205 }
206
207 static off_t
208 file_size(const char *path)
209 {
210         struct stat sb;
211
212         /* Ignore all errors, those file may not exists. */
213         if (stat(path, &sb) == -1)
214                 return (0);
215         return (sb.st_size);
216 }
217
218 static off_t
219 saved_dump_size(int bounds)
220 {
221         static char path[PATH_MAX];
222         off_t dumpsize;
223
224         dumpsize = 0;
225
226         (void)snprintf(path, sizeof(path), "info.%d", bounds);
227         dumpsize += file_size(path);
228         (void)snprintf(path, sizeof(path), "vmcore.%d", bounds);
229         dumpsize += file_size(path);
230         (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds);
231         dumpsize += file_size(path);
232         (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds);
233         dumpsize += file_size(path);
234         (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds);
235         dumpsize += file_size(path);
236
237         return (dumpsize);
238 }
239
240 static void
241 saved_dump_remove(int bounds)
242 {
243         static char path[PATH_MAX];
244
245         (void)snprintf(path, sizeof(path), "info.%d", bounds);
246         (void)unlink(path);
247         (void)snprintf(path, sizeof(path), "vmcore.%d", bounds);
248         (void)unlink(path);
249         (void)snprintf(path, sizeof(path), "vmcore.%d.gz", bounds);
250         (void)unlink(path);
251         (void)snprintf(path, sizeof(path), "textdump.tar.%d", bounds);
252         (void)unlink(path);
253         (void)snprintf(path, sizeof(path), "textdump.tar.%d.gz", bounds);
254         (void)unlink(path);
255 }
256
257 static void
258 symlinks_remove(void)
259 {
260
261         (void)unlink("info.last");
262         (void)unlink("vmcore.last");
263         (void)unlink("vmcore.last.gz");
264         (void)unlink("textdump.tar.last");
265         (void)unlink("textdump.tar.last.gz");
266 }
267
268 /*
269  * Check that sufficient space is available on the disk that holds the
270  * save directory.
271  */
272 static int
273 check_space(const char *savedir, off_t dumpsize, int bounds)
274 {
275         FILE *fp;
276         off_t available, minfree, spacefree, totfree, needed;
277         struct statfs fsbuf;
278         char buf[100];
279
280         if (statfs(".", &fsbuf) < 0) {
281                 syslog(LOG_ERR, "%s: %m", savedir);
282                 exit(1);
283         }
284         spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
285         totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024;
286
287         if ((fp = fopen("minfree", "r")) == NULL)
288                 minfree = 0;
289         else {
290                 if (fgets(buf, sizeof(buf), fp) == NULL)
291                         minfree = 0;
292                 else {
293                         char *endp;
294
295                         errno = 0;
296                         minfree = strtoll(buf, &endp, 10);
297                         if (minfree == 0 && errno != 0)
298                                 minfree = -1;
299                         else {
300                                 while (*endp != '\0' && isspace(*endp))
301                                         endp++;
302                                 if (*endp != '\0' || minfree < 0)
303                                         minfree = -1;
304                         }
305                         if (minfree < 0)
306                                 syslog(LOG_WARNING,
307                                     "`minfree` didn't contain a valid size "
308                                     "(`%s`). Defaulting to 0", buf);
309                 }
310                 (void)fclose(fp);
311         }
312
313         available = minfree > 0 ? spacefree - minfree : totfree;
314         needed = dumpsize / 1024 + 2;   /* 2 for info file */
315         needed -= saved_dump_size(bounds);
316         if (available < needed) {
317                 syslog(LOG_WARNING,
318                     "no dump: not enough free space on device (need at least "
319                     "%jdkB for dump; %jdkB available; %jdkB reserved)",
320                     (intmax_t)needed,
321                     (intmax_t)available + minfree,
322                     (intmax_t)minfree);
323                 return (0);
324         }
325         if (spacefree - needed < 0)
326                 syslog(LOG_WARNING,
327                     "dump performed, but free space threshold crossed");
328         return (1);
329 }
330
331 static bool
332 compare_magic(const struct kerneldumpheader *kdh, const char *magic)
333 {
334
335         return (strncmp(kdh->magic, magic, sizeof(kdh->magic)) == 0);
336 }
337
338 #define BLOCKSIZE (1<<12)
339 #define BLOCKMASK (~(BLOCKSIZE-1))
340
341 static int
342 DoRegularFile(int fd, off_t dumpsize, char *buf, const char *device,
343     const char *filename, FILE *fp)
344 {
345         int he, hs, nr, nw, wl;
346         off_t dmpcnt, origsize;
347
348         dmpcnt = 0;
349         origsize = dumpsize;
350         he = 0;
351         while (dumpsize > 0) {
352                 wl = BUFFERSIZE;
353                 if (wl > dumpsize)
354                         wl = dumpsize;
355                 nr = read(fd, buf, wl);
356                 if (nr != wl) {
357                         if (nr == 0)
358                                 syslog(LOG_WARNING,
359                                     "WARNING: EOF on dump device");
360                         else
361                                 syslog(LOG_ERR, "read error on %s: %m", device);
362                         nerr++;
363                         return (-1);
364                 }
365                 if (compress) {
366                         nw = fwrite(buf, 1, wl, fp);
367                 } else {
368                         for (nw = 0; nw < nr; nw = he) {
369                                 /* find a contiguous block of zeroes */
370                                 for (hs = nw; hs < nr; hs += BLOCKSIZE) {
371                                         for (he = hs; he < nr && buf[he] == 0;
372                                             ++he)
373                                                 /* nothing */ ;
374                                         /* is the hole long enough to matter? */
375                                         if (he >= hs + BLOCKSIZE)
376                                                 break;
377                                 }
378
379                                 /* back down to a block boundary */
380                                 he &= BLOCKMASK;
381
382                                 /*
383                                  * 1) Don't go beyond the end of the buffer.
384                                  * 2) If the end of the buffer is less than
385                                  *    BLOCKSIZE bytes away, we're at the end
386                                  *    of the file, so just grab what's left.
387                                  */
388                                 if (hs + BLOCKSIZE > nr)
389                                         hs = he = nr;
390
391                                 /*
392                                  * At this point, we have a partial ordering:
393                                  *     nw <= hs <= he <= nr
394                                  * If hs > nw, buf[nw..hs] contains non-zero
395                                  * data. If he > hs, buf[hs..he] is all zeroes.
396                                  */
397                                 if (hs > nw)
398                                         if (fwrite(buf + nw, hs - nw, 1, fp)
399                                             != 1)
400                                         break;
401                                 if (he > hs)
402                                         if (fseeko(fp, he - hs, SEEK_CUR) == -1)
403                                                 break;
404                         }
405                 }
406                 if (nw != wl) {
407                         syslog(LOG_ERR,
408                             "write error on %s file: %m", filename);
409                         syslog(LOG_WARNING,
410                             "WARNING: vmcore may be incomplete");
411                         nerr++;
412                         return (-1);
413                 }
414                 if (verbose) {
415                         dmpcnt += wl;
416                         printf("%llu\r", (unsigned long long)dmpcnt);
417                         fflush(stdout);
418                 }
419                 dumpsize -= wl;
420                 if (got_siginfo) {
421                         printf("%s %.1lf%%\n", filename, (100.0 - (100.0 *
422                             (double)dumpsize / (double)origsize)));
423                         got_siginfo = 0;
424                 }
425         }
426         return (0);
427 }
428
429 /*
430  * Specialized version of dump-reading logic for use with textdumps, which
431  * are written backwards from the end of the partition, and must be reversed
432  * before being written to the file.  Textdumps are small, so do a bit less
433  * work to optimize/sparsify.
434  */
435 static int
436 DoTextdumpFile(int fd, off_t dumpsize, off_t lasthd, char *buf,
437     const char *device, const char *filename, FILE *fp)
438 {
439         int nr, nw, wl;
440         off_t dmpcnt, totsize;
441
442         totsize = dumpsize;
443         dmpcnt = 0;
444         wl = 512;
445         if ((dumpsize % wl) != 0) {
446                 syslog(LOG_ERR, "textdump uneven multiple of 512 on %s",
447                     device);
448                 nerr++;
449                 return (-1);
450         }
451         while (dumpsize > 0) {
452                 nr = pread(fd, buf, wl, lasthd - (totsize - dumpsize) - wl);
453                 if (nr != wl) {
454                         if (nr == 0)
455                                 syslog(LOG_WARNING,
456                                     "WARNING: EOF on dump device");
457                         else
458                                 syslog(LOG_ERR, "read error on %s: %m", device);
459                         nerr++;
460                         return (-1);
461                 }
462                 nw = fwrite(buf, 1, wl, fp);
463                 if (nw != wl) {
464                         syslog(LOG_ERR,
465                             "write error on %s file: %m", filename);
466                         syslog(LOG_WARNING,
467                             "WARNING: textdump may be incomplete");
468                         nerr++;
469                         return (-1);
470                 }
471                 if (verbose) {
472                         dmpcnt += wl;
473                         printf("%llu\r", (unsigned long long)dmpcnt);
474                         fflush(stdout);
475                 }
476                 dumpsize -= wl;
477         }
478         return (0);
479 }
480
481 static void
482 DoFile(const char *savedir, const char *device)
483 {
484         xo_handle_t *xostdout, *xoinfo;
485         static char infoname[PATH_MAX], corename[PATH_MAX], linkname[PATH_MAX];
486         static char *buf = NULL, *temp = NULL;
487         struct kerneldumpheader kdhf, kdhl;
488         off_t mediasize, dumpsize, firsthd, lasthd;
489         FILE *info, *fp;
490         mode_t oumask;
491         int fd, fdinfo, error;
492         int bounds, status;
493         u_int sectorsize, xostyle;
494         int istextdump;
495
496         bounds = getbounds();
497         mediasize = 0;
498         status = STATUS_UNKNOWN;
499
500         xostdout = xo_create_to_file(stdout, XO_STYLE_TEXT, 0);
501         if (xostdout == NULL) {
502                 syslog(LOG_ERR, "%s: %m", infoname);
503                 return;
504         }
505
506         if (maxdumps > 0 && bounds == maxdumps)
507                 bounds = 0;
508
509         if (buf == NULL) {
510                 buf = malloc(BUFFERSIZE);
511                 if (buf == NULL) {
512                         syslog(LOG_ERR, "%m");
513                         return;
514                 }
515         }
516
517         if (verbose)
518                 printf("checking for kernel dump on device %s\n", device);
519
520         fd = open(device, (checkfor || keep) ? O_RDONLY : O_RDWR);
521         if (fd < 0) {
522                 syslog(LOG_ERR, "%s: %m", device);
523                 return;
524         }
525
526         error = ioctl(fd, DIOCGMEDIASIZE, &mediasize);
527         if (!error)
528                 error = ioctl(fd, DIOCGSECTORSIZE, &sectorsize);
529         if (error) {
530                 syslog(LOG_ERR,
531                     "couldn't find media and/or sector size of %s: %m", device);
532                 goto closefd;
533         }
534
535         if (verbose) {
536                 printf("mediasize = %lld bytes\n", (long long)mediasize);
537                 printf("sectorsize = %u bytes\n", sectorsize);
538         }
539
540         if (sectorsize < sizeof(kdhl)) {
541                 syslog(LOG_ERR,
542                     "Sector size is less the kernel dump header %zu",
543                     sizeof(kdhl));
544                 goto closefd;
545         }
546
547         lasthd = mediasize - sectorsize;
548         if (temp == NULL) {
549                 temp = malloc(sectorsize);
550                 if (temp == NULL) {
551                         syslog(LOG_ERR, "%m");
552                         goto closefd;
553                 }
554         }
555         if (lseek(fd, lasthd, SEEK_SET) != lasthd ||
556             read(fd, temp, sectorsize) != (ssize_t)sectorsize) {
557                 syslog(LOG_ERR,
558                     "error reading last dump header at offset %lld in %s: %m",
559                     (long long)lasthd, device);
560                 goto closefd;
561         }
562         memcpy(&kdhl, temp, sizeof(kdhl));
563         istextdump = 0;
564         if (compare_magic(&kdhl, TEXTDUMPMAGIC)) {
565                 if (verbose)
566                         printf("textdump magic on last dump header on %s\n",
567                             device);
568                 istextdump = 1;
569                 if (dtoh32(kdhl.version) != KERNELDUMP_TEXT_VERSION) {
570                         syslog(LOG_ERR,
571                             "unknown version (%d) in last dump header on %s",
572                             dtoh32(kdhl.version), device);
573
574                         status = STATUS_BAD;
575                         if (force == 0)
576                                 goto closefd;
577                 }
578         } else if (compare_magic(&kdhl, KERNELDUMPMAGIC)) {
579                 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
580                         syslog(LOG_ERR,
581                             "unknown version (%d) in last dump header on %s",
582                             dtoh32(kdhl.version), device);
583
584                         status = STATUS_BAD;
585                         if (force == 0)
586                                 goto closefd;
587                 }
588         } else {
589                 if (verbose)
590                         printf("magic mismatch on last dump header on %s\n",
591                             device);
592
593                 status = STATUS_BAD;
594                 if (force == 0)
595                         goto closefd;
596
597                 if (compare_magic(&kdhl, KERNELDUMPMAGIC_CLEARED)) {
598                         if (verbose)
599                                 printf("forcing magic on %s\n", device);
600                         memcpy(kdhl.magic, KERNELDUMPMAGIC,
601                             sizeof kdhl.magic);
602                 } else {
603                         syslog(LOG_ERR, "unable to force dump - bad magic");
604                         goto closefd;
605                 }
606                 if (dtoh32(kdhl.version) != KERNELDUMPVERSION) {
607                         syslog(LOG_ERR,
608                             "unknown version (%d) in last dump header on %s",
609                             dtoh32(kdhl.version), device);
610
611                         status = STATUS_BAD;
612                         if (force == 0)
613                                 goto closefd;
614                 }
615         }
616
617         nfound++;
618         if (clear)
619                 goto nuke;
620
621         if (kerneldump_parity(&kdhl)) {
622                 syslog(LOG_ERR,
623                     "parity error on last dump header on %s", device);
624                 nerr++;
625                 status = STATUS_BAD;
626                 if (force == 0)
627                         goto closefd;
628         }
629         dumpsize = dtoh64(kdhl.dumplength);
630         firsthd = lasthd - dumpsize - sectorsize;
631         if (lseek(fd, firsthd, SEEK_SET) != firsthd ||
632             read(fd, temp, sectorsize) != (ssize_t)sectorsize) {
633                 syslog(LOG_ERR,
634                     "error reading first dump header at offset %lld in %s: %m",
635                     (long long)firsthd, device);
636                 nerr++;
637                 goto closefd;
638         }
639         memcpy(&kdhf, temp, sizeof(kdhf));
640
641         if (verbose >= 2) {
642                 printf("First dump headers:\n");
643                 printheader(xostdout, &kdhf, device, bounds, -1);
644
645                 printf("\nLast dump headers:\n");
646                 printheader(xostdout, &kdhl, device, bounds, -1);
647                 printf("\n");
648         }
649
650         if (memcmp(&kdhl, &kdhf, sizeof(kdhl))) {
651                 syslog(LOG_ERR,
652                     "first and last dump headers disagree on %s", device);
653                 nerr++;
654                 status = STATUS_BAD;
655                 if (force == 0)
656                         goto closefd;
657         } else {
658                 status = STATUS_GOOD;
659         }
660
661         if (checkfor) {
662                 printf("A dump exists on %s\n", device);
663                 close(fd);
664                 exit(0);
665         }
666
667         if (kdhl.panicstring[0] != '\0')
668                 syslog(LOG_ALERT, "reboot after panic: %.*s",
669                     (int)sizeof(kdhl.panicstring), kdhl.panicstring);
670         else
671                 syslog(LOG_ALERT, "reboot");
672
673         if (verbose)
674                 printf("Checking for available free space\n");
675
676         if (!check_space(savedir, dumpsize, bounds)) {
677                 nerr++;
678                 goto closefd;
679         }
680
681         writebounds(bounds + 1);
682
683         saved_dump_remove(bounds);
684
685         snprintf(infoname, sizeof(infoname), "info.%d", bounds);
686
687         /*
688          * Create or overwrite any existing dump header files.
689          */
690         fdinfo = open(infoname, O_WRONLY | O_CREAT | O_TRUNC, 0600);
691         if (fdinfo < 0) {
692                 syslog(LOG_ERR, "%s: %m", infoname);
693                 nerr++;
694                 goto closefd;
695         }
696
697         oumask = umask(S_IRWXG|S_IRWXO); /* Restrict access to the core file. */
698         if (compress) {
699                 snprintf(corename, sizeof(corename), "%s.%d.gz",
700                     istextdump ? "textdump.tar" : "vmcore", bounds);
701                 fp = zopen(corename, "w");
702         } else {
703                 snprintf(corename, sizeof(corename), "%s.%d",
704                     istextdump ? "textdump.tar" : "vmcore", bounds);
705                 fp = fopen(corename, "w");
706         }
707         if (fp == NULL) {
708                 syslog(LOG_ERR, "%s: %m", corename);
709                 close(fdinfo);
710                 nerr++;
711                 goto closefd;
712         }
713         (void)umask(oumask);
714
715         info = fdopen(fdinfo, "w");
716
717         if (info == NULL) {
718                 syslog(LOG_ERR, "fdopen failed: %m");
719                 nerr++;
720                 goto closeall;
721         }
722
723         xostyle = xo_get_style(NULL);
724         xoinfo = xo_create_to_file(info, xostyle, 0);
725         if (xoinfo == NULL) {
726                 syslog(LOG_ERR, "%s: %m", infoname);
727                 nerr++;
728                 goto closeall;
729         }
730         xo_open_container_h(xoinfo, "crashdump");
731
732         if (verbose)
733                 printheader(xostdout, &kdhl, device, bounds, status);
734
735         printheader(xoinfo, &kdhl, device, bounds, status);
736         xo_close_container_h(xoinfo, "crashdump");
737         xo_flush_h(xoinfo);
738         xo_finish_h(xoinfo);
739         fclose(info);
740
741         syslog(LOG_NOTICE, "writing %score to %s/%s",
742             compress ? "compressed " : "", savedir, corename);
743
744         if (istextdump) {
745                 if (DoTextdumpFile(fd, dumpsize, lasthd, buf, device,
746                     corename, fp) < 0)
747                         goto closeall;
748         } else {
749                 if (DoRegularFile(fd, dumpsize, buf, device, corename, fp)
750                     < 0)
751                         goto closeall;
752         }
753         if (verbose)
754                 printf("\n");
755
756         if (fclose(fp) < 0) {
757                 syslog(LOG_ERR, "error on %s: %m", corename);
758                 nerr++;
759                 goto closefd;
760         }
761
762         symlinks_remove();
763         if (symlink(infoname, "info.last") == -1) {
764                 syslog(LOG_WARNING, "unable to create symlink %s/%s: %m",
765                     savedir, "info.last");
766         }
767         if (compress) {
768                 snprintf(linkname, sizeof(linkname), "%s.last.gz",
769                     istextdump ? "textdump.tar" : "vmcore");
770         } else {
771                 snprintf(linkname, sizeof(linkname), "%s.last",
772                     istextdump ? "textdump.tar" : "vmcore");
773         }
774         if (symlink(corename, linkname) == -1) {
775                 syslog(LOG_WARNING, "unable to create symlink %s/%s: %m",
776                     savedir, linkname);
777         }
778
779         nsaved++;
780
781         if (verbose)
782                 printf("dump saved\n");
783
784 nuke:
785         if (!keep) {
786                 if (verbose)
787                         printf("clearing dump header\n");
788                 memcpy(kdhl.magic, KERNELDUMPMAGIC_CLEARED, sizeof(kdhl.magic));
789                 memcpy(temp, &kdhl, sizeof(kdhl));
790                 if (lseek(fd, lasthd, SEEK_SET) != lasthd ||
791                     write(fd, temp, sectorsize) != (ssize_t)sectorsize)
792                         syslog(LOG_ERR,
793                             "error while clearing the dump header: %m");
794         }
795         xo_close_container_h(xostdout, "crashdump");
796         xo_finish_h(xostdout);
797         close(fd);
798         return;
799
800 closeall:
801         fclose(fp);
802
803 closefd:
804         close(fd);
805 }
806
807 static void
808 usage(void)
809 {
810         xo_error("%s\n%s\n%s\n",
811             "usage: savecore -c [-v] [device ...]",
812             "       savecore -C [-v] [device ...]",
813             "       savecore [-fkvz] [-m maxdumps] [directory [device ...]]");
814         exit(1);
815 }
816
817 int
818 main(int argc, char **argv)
819 {
820         const char *savedir = ".";
821         struct fstab *fsp;
822         int i, ch, error;
823
824         checkfor = compress = clear = force = keep = verbose = 0;
825         nfound = nsaved = nerr = 0;
826
827         openlog("savecore", LOG_PERROR, LOG_DAEMON);
828         signal(SIGINFO, infohandler);
829
830         argc = xo_parse_args(argc, argv);
831         if (argc < 0)
832                 exit(1);
833
834         while ((ch = getopt(argc, argv, "Ccfkm:vz")) != -1)
835                 switch(ch) {
836                 case 'C':
837                         checkfor = 1;
838                         break;
839                 case 'c':
840                         clear = 1;
841                         break;
842                 case 'f':
843                         force = 1;
844                         break;
845                 case 'k':
846                         keep = 1;
847                         break;
848                 case 'm':
849                         maxdumps = atoi(optarg);
850                         if (maxdumps <= 0) {
851                                 syslog(LOG_ERR, "Invalid maxdump value");
852                                 exit(1);
853                         }
854                         break;
855                 case 'v':
856                         verbose++;
857                         break;
858                 case 'z':
859                         compress = 1;
860                         break;
861                 case '?':
862                 default:
863                         usage();
864                 }
865         if (checkfor && (clear || force || keep))
866                 usage();
867         if (clear && (compress || keep))
868                 usage();
869         if (maxdumps > 0 && (checkfor || clear))
870                 usage();
871         argc -= optind;
872         argv += optind;
873         if (argc >= 1 && !checkfor && !clear) {
874                 error = chdir(argv[0]);
875                 if (error) {
876                         syslog(LOG_ERR, "chdir(%s): %m", argv[0]);
877                         exit(1);
878                 }
879                 savedir = argv[0];
880                 argc--;
881                 argv++;
882         }
883         if (argc == 0) {
884                 for (;;) {
885                         fsp = getfsent();
886                         if (fsp == NULL)
887                                 break;
888                         if (strcmp(fsp->fs_vfstype, "swap") &&
889                             strcmp(fsp->fs_vfstype, "dump"))
890                                 continue;
891                         DoFile(savedir, fsp->fs_spec);
892                 }
893                 endfsent();
894         } else {
895                 for (i = 0; i < argc; i++)
896                         DoFile(savedir, argv[i]);
897         }
898
899         /* Emit minimal output. */
900         if (nfound == 0) {
901                 if (checkfor) {
902                         if (verbose)
903                                 printf("No dump exists\n");
904                         exit(1);
905                 }
906                 if (verbose)
907                         syslog(LOG_WARNING, "no dumps found");
908         } else if (nsaved == 0) {
909                 if (nerr != 0) {
910                         if (verbose)
911                                 syslog(LOG_WARNING,
912                                     "unsaved dumps found but not saved");
913                         exit(1);
914                 } else if (verbose)
915                         syslog(LOG_WARNING, "no unsaved dumps found");
916         }
917
918         return (0);
919 }
920
921 static void
922 infohandler(int sig __unused)
923 {
924         got_siginfo = 1;
925 }