2 * Copyright (c) 2002 Poul-Henning Kamp
3 * Copyright (c) 2002 Networks Associates Technology, Inc.
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.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
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
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
35 * Copyright (c) 1986, 1992, 1993
36 * The Regents of the University of California. All rights reserved.
38 * Redistribution and use in source and binary forms, with or without
39 * modification, are permitted provided that the following conditions
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.
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
63 #include <sys/cdefs.h>
64 __FBSDID("$FreeBSD$");
66 #include <sys/param.h>
68 #include <sys/kerneldump.h>
69 #include <sys/mount.h>
85 /* The size of the buffer used for I/O. */
86 #define BUFFERSIZE (1024*1024)
90 #define STATUS_UNKNOWN 2
92 static int checkfor
, compress
, clear
, force
, keep
, verbose
; /* flags */
93 static int nfound
, nsaved
, nerr
; /* statistics */
96 extern FILE *zopen(const char *, const char *);
98 static sig_atomic_t got_siginfo
;
99 static void infohandler(int);
102 printheader(xo_handle_t
*xo
, const struct kerneldumpheader
*h
, const char *device
,
103 int bounds
, const int status
)
107 const char *stat_str
;
110 xo_emit_h(xo
, "{Lwc:Dump header from device}{:dump_device/%s}\n", device
);
111 xo_emit_h(xo
, "{P: }{Lwc:Architecture}{:architecture/%s}\n", h
->architecture
);
112 xo_emit_h(xo
, "{P: }{Lwc:Architecture Version}{:architecture_version/%u}\n", dtoh32(h
->architectureversion
));
113 dumplen
= dtoh64(h
->dumplength
);
114 xo_emit_h(xo
, "{P: }{Lwc:Dump Length}{:dump_length_bytes/%lld}\n", (long long)dumplen
);
115 xo_emit_h(xo
, "{P: }{Lwc:Blocksize}{:blocksize/%d}\n", dtoh32(h
->blocksize
));
116 t
= dtoh64(h
->dumptime
);
117 xo_emit_h(xo
, "{P: }{Lwc:Dumptime}{:dumptime/%s}", ctime(&t
));
118 xo_emit_h(xo
, "{P: }{Lwc:Hostname}{:hostname/%s}\n", h
->hostname
);
119 xo_emit_h(xo
, "{P: }{Lwc:Magic}{:magic/%s}\n", h
->magic
);
120 xo_emit_h(xo
, "{P: }{Lwc:Version String}{:version_string/%s}", h
->versionstring
);
121 xo_emit_h(xo
, "{P: }{Lwc:Panic String}{:panic_string/%s}\n", h
->panicstring
);
122 xo_emit_h(xo
, "{P: }{Lwc:Dump Parity}{:dump_parity/%u}\n", h
->parity
);
123 xo_emit_h(xo
, "{P: }{Lwc:Bounds}{:bounds/%d}\n", bounds
);
133 stat_str
= "unknown";
135 xo_emit_h(xo
, "{P: }{Lwc:Dump Status}{:dump_status/%s}\n", stat_str
);
147 if ((fp
= fopen("bounds", "r")) == NULL
) {
149 printf("unable to open bounds file, using 0\n");
153 if (fgets(buf
, sizeof buf
, fp
) == NULL
) {
155 syslog(LOG_WARNING
, "bounds file is empty, using 0");
157 syslog(LOG_WARNING
, "bounds file: %s", strerror(errno
));
163 ret
= (int)strtol(buf
, NULL
, 10);
164 if (ret
== 0 && (errno
== EINVAL
|| errno
== ERANGE
))
165 syslog(LOG_WARNING
, "invalid value found in bounds, using 0");
171 writebounds(int bounds
) {
174 if ((fp
= fopen("bounds", "w")) == NULL
) {
175 syslog(LOG_WARNING
, "unable to write to bounds file: %m");
180 printf("bounds number: %d\n", bounds
);
182 fprintf(fp
, "%d\n", bounds
);
187 file_size(const char *path
)
191 /* Ignore all errors, those file may not exists. */
192 if (stat(path
, &sb
) == -1)
198 saved_dump_size(int bounds
)
200 static char path
[PATH_MAX
];
205 (void)snprintf(path
, sizeof(path
), "info.%d", bounds
);
206 dumpsize
+= file_size(path
);
207 (void)snprintf(path
, sizeof(path
), "vmcore.%d", bounds
);
208 dumpsize
+= file_size(path
);
209 (void)snprintf(path
, sizeof(path
), "vmcore.%d.gz", bounds
);
210 dumpsize
+= file_size(path
);
211 (void)snprintf(path
, sizeof(path
), "textdump.tar.%d", bounds
);
212 dumpsize
+= file_size(path
);
213 (void)snprintf(path
, sizeof(path
), "textdump.tar.%d.gz", bounds
);
214 dumpsize
+= file_size(path
);
220 saved_dump_remove(int bounds
)
222 static char path
[PATH_MAX
];
224 (void)snprintf(path
, sizeof(path
), "info.%d", bounds
);
226 (void)snprintf(path
, sizeof(path
), "vmcore.%d", bounds
);
228 (void)snprintf(path
, sizeof(path
), "vmcore.%d.gz", bounds
);
230 (void)snprintf(path
, sizeof(path
), "textdump.tar.%d", bounds
);
232 (void)snprintf(path
, sizeof(path
), "textdump.tar.%d.gz", bounds
);
237 symlinks_remove(void)
240 (void)unlink("info.last");
241 (void)unlink("vmcore.last");
242 (void)unlink("vmcore.last.gz");
243 (void)unlink("textdump.tar.last");
244 (void)unlink("textdump.tar.last.gz");
248 * Check that sufficient space is available on the disk that holds the
252 check_space(const char *savedir
, off_t dumpsize
, int bounds
)
255 off_t minfree
, spacefree
, totfree
, needed
;
259 if (statfs(".", &fsbuf
) < 0) {
260 syslog(LOG_ERR
, "%s: %m", savedir
);
263 spacefree
= ((off_t
) fsbuf
.f_bavail
* fsbuf
.f_bsize
) / 1024;
264 totfree
= ((off_t
) fsbuf
.f_bfree
* fsbuf
.f_bsize
) / 1024;
266 if ((fp
= fopen("minfree", "r")) == NULL
)
269 if (fgets(buf
, sizeof(buf
), fp
) == NULL
)
276 needed
= dumpsize
/ 1024 + 2; /* 2 for info file */
277 needed
-= saved_dump_size(bounds
);
278 if ((minfree
> 0 ? spacefree
: totfree
) - needed
< minfree
) {
280 "no dump, not enough free space on device (%lld available, need %lld)",
281 (long long)(minfree
> 0 ? spacefree
: totfree
),
285 if (spacefree
- needed
< 0)
287 "dump performed, but free space threshold crossed");
291 #define BLOCKSIZE (1<<12)
292 #define BLOCKMASK (~(BLOCKSIZE-1))
295 DoRegularFile(int fd
, off_t dumpsize
, char *buf
, const char *device
,
296 const char *filename
, FILE *fp
)
298 int he
, hs
, nr
, nw
, wl
;
299 off_t dmpcnt
, origsize
;
304 while (dumpsize
> 0) {
308 nr
= read(fd
, buf
, wl
);
312 "WARNING: EOF on dump device");
314 syslog(LOG_ERR
, "read error on %s: %m", device
);
319 nw
= fwrite(buf
, 1, wl
, fp
);
321 for (nw
= 0; nw
< nr
; nw
= he
) {
322 /* find a contiguous block of zeroes */
323 for (hs
= nw
; hs
< nr
; hs
+= BLOCKSIZE
) {
324 for (he
= hs
; he
< nr
&& buf
[he
] == 0;
327 /* is the hole long enough to matter? */
328 if (he
>= hs
+ BLOCKSIZE
)
332 /* back down to a block boundary */
336 * 1) Don't go beyond the end of the buffer.
337 * 2) If the end of the buffer is less than
338 * BLOCKSIZE bytes away, we're at the end
339 * of the file, so just grab what's left.
341 if (hs
+ BLOCKSIZE
> nr
)
345 * At this point, we have a partial ordering:
346 * nw <= hs <= he <= nr
347 * If hs > nw, buf[nw..hs] contains non-zero data.
348 * If he > hs, buf[hs..he] is all zeroes.
351 if (fwrite(buf
+ nw
, hs
- nw
, 1, fp
)
355 if (fseeko(fp
, he
- hs
, SEEK_CUR
) == -1)
361 "write error on %s file: %m", filename
);
363 "WARNING: vmcore may be incomplete");
369 printf("%llu\r", (unsigned long long)dmpcnt
);
374 printf("%s %.1lf%%\n", filename
, (100.0 - (100.0 *
375 (double)dumpsize
/ (double)origsize
)));
383 * Specialized version of dump-reading logic for use with textdumps, which
384 * are written backwards from the end of the partition, and must be reversed
385 * before being written to the file. Textdumps are small, so do a bit less
386 * work to optimize/sparsify.
389 DoTextdumpFile(int fd
, off_t dumpsize
, off_t lasthd
, char *buf
,
390 const char *device
, const char *filename
, FILE *fp
)
393 off_t dmpcnt
, totsize
;
398 if ((dumpsize
% wl
) != 0) {
399 syslog(LOG_ERR
, "textdump uneven multiple of 512 on %s",
404 while (dumpsize
> 0) {
405 nr
= pread(fd
, buf
, wl
, lasthd
- (totsize
- dumpsize
) - wl
);
409 "WARNING: EOF on dump device");
411 syslog(LOG_ERR
, "read error on %s: %m", device
);
415 nw
= fwrite(buf
, 1, wl
, fp
);
418 "write error on %s file: %m", filename
);
420 "WARNING: textdump may be incomplete");
426 printf("%llu\r", (unsigned long long)dmpcnt
);
435 DoFile(const char *savedir
, const char *device
)
437 xo_handle_t
*xostdout
, *xoinfo
;
438 static char infoname
[PATH_MAX
], corename
[PATH_MAX
], linkname
[PATH_MAX
];
439 static char *buf
= NULL
, *temp
= NULL
;
440 struct kerneldumpheader kdhf
, kdhl
;
441 off_t mediasize
, dumpsize
, firsthd
, lasthd
;
444 int fd
, fdinfo
, error
;
446 u_int sectorsize
, xostyle
;
449 bounds
= getbounds();
451 status
= STATUS_UNKNOWN
;
453 xostdout
= xo_create_to_file(stdout
, XO_STYLE_TEXT
, 0);
454 if (xostdout
== NULL
) {
455 syslog(LOG_ERR
, "%s: %m", infoname
);
459 if (maxdumps
> 0 && bounds
== maxdumps
)
463 buf
= malloc(BUFFERSIZE
);
465 syslog(LOG_ERR
, "%m");
471 printf("checking for kernel dump on device %s\n", device
);
473 fd
= open(device
, (checkfor
|| keep
) ? O_RDONLY
: O_RDWR
);
475 syslog(LOG_ERR
, "%s: %m", device
);
479 error
= ioctl(fd
, DIOCGMEDIASIZE
, &mediasize
);
481 error
= ioctl(fd
, DIOCGSECTORSIZE
, §orsize
);
484 "couldn't find media and/or sector size of %s: %m", device
);
489 printf("mediasize = %lld\n", (long long)mediasize
);
490 printf("sectorsize = %u\n", sectorsize
);
493 if (sectorsize
< sizeof(kdhl
)) {
495 "Sector size is less the kernel dump header %zu",
500 lasthd
= mediasize
- sectorsize
;
502 temp
= malloc(sectorsize
);
504 syslog(LOG_ERR
, "%m");
508 if (lseek(fd
, lasthd
, SEEK_SET
) != lasthd
||
509 read(fd
, temp
, sectorsize
) != (ssize_t
)sectorsize
) {
511 "error reading last dump header at offset %lld in %s: %m",
512 (long long)lasthd
, device
);
515 memcpy(&kdhl
, temp
, sizeof(kdhl
));
517 if (strncmp(kdhl
.magic
, TEXTDUMPMAGIC
, sizeof kdhl
) == 0) {
519 printf("textdump magic on last dump header on %s\n",
522 if (dtoh32(kdhl
.version
) != KERNELDUMP_TEXT_VERSION
) {
524 "unknown version (%d) in last dump header on %s",
525 dtoh32(kdhl
.version
), device
);
531 } else if (memcmp(kdhl
.magic
, KERNELDUMPMAGIC
, sizeof kdhl
.magic
) ==
533 if (dtoh32(kdhl
.version
) != KERNELDUMPVERSION
) {
535 "unknown version (%d) in last dump header on %s",
536 dtoh32(kdhl
.version
), device
);
544 printf("magic mismatch on last dump header on %s\n",
551 if (memcmp(kdhl
.magic
, KERNELDUMPMAGIC_CLEARED
,
552 sizeof kdhl
.magic
) == 0) {
554 printf("forcing magic on %s\n", device
);
555 memcpy(kdhl
.magic
, KERNELDUMPMAGIC
,
558 syslog(LOG_ERR
, "unable to force dump - bad magic");
561 if (dtoh32(kdhl
.version
) != KERNELDUMPVERSION
) {
563 "unknown version (%d) in last dump header on %s",
564 dtoh32(kdhl
.version
), device
);
576 if (kerneldump_parity(&kdhl
)) {
578 "parity error on last dump header on %s", device
);
584 dumpsize
= dtoh64(kdhl
.dumplength
);
585 firsthd
= lasthd
- dumpsize
- sectorsize
;
586 if (lseek(fd
, firsthd
, SEEK_SET
) != firsthd
||
587 read(fd
, temp
, sectorsize
) != (ssize_t
)sectorsize
) {
589 "error reading first dump header at offset %lld in %s: %m",
590 (long long)firsthd
, device
);
594 memcpy(&kdhf
, temp
, sizeof(kdhf
));
597 printf("First dump headers:\n");
598 printheader(xostdout
, &kdhf
, device
, bounds
, -1);
600 printf("\nLast dump headers:\n");
601 printheader(xostdout
, &kdhl
, device
, bounds
, -1);
605 if (memcmp(&kdhl
, &kdhf
, sizeof(kdhl
))) {
607 "first and last dump headers disagree on %s", device
);
613 status
= STATUS_GOOD
;
617 printf("A dump exists on %s\n", device
);
622 if (kdhl
.panicstring
[0] != '\0')
623 syslog(LOG_ALERT
, "reboot after panic: %*s",
624 (int)sizeof(kdhl
.panicstring
), kdhl
.panicstring
);
626 syslog(LOG_ALERT
, "reboot");
629 printf("Checking for available free space\n");
631 if (!check_space(savedir
, dumpsize
, bounds
)) {
636 writebounds(bounds
+ 1);
638 saved_dump_remove(bounds
);
640 snprintf(infoname
, sizeof(infoname
), "info.%d", bounds
);
643 * Create or overwrite any existing dump header files.
645 fdinfo
= open(infoname
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600);
647 syslog(LOG_ERR
, "%s: %m", infoname
);
652 oumask
= umask(S_IRWXG
|S_IRWXO
); /* Restrict access to the core file.*/
654 snprintf(corename
, sizeof(corename
), "%s.%d.gz",
655 istextdump
? "textdump.tar" : "vmcore", bounds
);
656 fp
= zopen(corename
, "w");
658 snprintf(corename
, sizeof(corename
), "%s.%d",
659 istextdump
? "textdump.tar" : "vmcore", bounds
);
660 fp
= fopen(corename
, "w");
663 syslog(LOG_ERR
, "%s: %m", corename
);
670 info
= fdopen(fdinfo
, "w");
673 syslog(LOG_ERR
, "fdopen failed: %m");
678 xostyle
= xo_get_style(NULL
);
679 xoinfo
= xo_create_to_file(info
, xostyle
, 0);
680 if (xoinfo
== NULL
) {
681 syslog(LOG_ERR
, "%s: %m", infoname
);
685 xo_open_container_h(xoinfo
, "crashdump");
688 printheader(xostdout
, &kdhl
, device
, bounds
, status
);
690 printheader(xoinfo
, &kdhl
, device
, bounds
, status
);
691 xo_close_container_h(xoinfo
, "crashdump");
696 syslog(LOG_NOTICE
, "writing %score to %s/%s",
697 compress
? "compressed " : "", savedir
, corename
);
700 if (DoTextdumpFile(fd
, dumpsize
, lasthd
, buf
, device
,
704 if (DoRegularFile(fd
, dumpsize
, buf
, device
, corename
, fp
)
711 if (fclose(fp
) < 0) {
712 syslog(LOG_ERR
, "error on %s: %m", corename
);
718 if (symlink(infoname
, "info.last") == -1) {
719 syslog(LOG_WARNING
, "unable to create symlink %s/%s: %m",
720 savedir
, "info.last");
723 snprintf(linkname
, sizeof(linkname
), "%s.last.gz",
724 istextdump
? "textdump.tar" : "vmcore");
726 snprintf(linkname
, sizeof(linkname
), "%s.last",
727 istextdump
? "textdump.tar" : "vmcore");
729 if (symlink(corename
, linkname
) == -1) {
730 syslog(LOG_WARNING
, "unable to create symlink %s/%s: %m",
737 printf("dump saved\n");
742 printf("clearing dump header\n");
743 memcpy(kdhl
.magic
, KERNELDUMPMAGIC_CLEARED
, sizeof(kdhl
.magic
));
744 memcpy(temp
, &kdhl
, sizeof(kdhl
));
745 if (lseek(fd
, lasthd
, SEEK_SET
) != lasthd
||
746 write(fd
, temp
, sectorsize
) != (ssize_t
)sectorsize
)
748 "error while clearing the dump header: %m");
750 xo_close_container_h(xostdout
, "crashdump");
751 xo_finish_h(xostdout
);
765 xo_error("%s\n%s\n%s\n",
766 "usage: savecore -c [-v] [device ...]",
767 " savecore -C [-v] [device ...]",
768 " savecore [-fkvz] [-m maxdumps] [directory [device ...]]");
773 main(int argc
, char **argv
)
775 const char *savedir
= ".";
779 checkfor
= compress
= clear
= force
= keep
= verbose
= 0;
780 nfound
= nsaved
= nerr
= 0;
782 openlog("savecore", LOG_PERROR
, LOG_DAEMON
);
783 signal(SIGINFO
, infohandler
);
785 argc
= xo_parse_args(argc
, argv
);
789 while ((ch
= getopt(argc
, argv
, "Ccfkm:vz")) != -1)
804 maxdumps
= atoi(optarg
);
806 syslog(LOG_ERR
, "Invalid maxdump value");
820 if (checkfor
&& (clear
|| force
|| keep
))
822 if (clear
&& (compress
|| keep
))
824 if (maxdumps
> 0 && (checkfor
|| clear
))
828 if (argc
>= 1 && !checkfor
&& !clear
) {
829 error
= chdir(argv
[0]);
831 syslog(LOG_ERR
, "chdir(%s): %m", argv
[0]);
843 if (strcmp(fsp
->fs_vfstype
, "swap") &&
844 strcmp(fsp
->fs_vfstype
, "dump"))
846 DoFile(savedir
, fsp
->fs_spec
);
850 for (i
= 0; i
< argc
; i
++)
851 DoFile(savedir
, argv
[i
]);
854 /* Emit minimal output. */
858 printf("No dump exists\n");
862 syslog(LOG_WARNING
, "no dumps found");
863 } else if (nsaved
== 0) {
866 syslog(LOG_WARNING
, "unsaved dumps found but not saved");
869 syslog(LOG_WARNING
, "no unsaved dumps found");
876 infohandler(int sig __unused
)