savecore.c revision 67264
1/*-
2 * Copyright (c) 1986, 1992, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1986, 1992, 1993\n\
37	The Regents of the University of California.  All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41#if 0
42static char sccsid[] = "@(#)savecore.c	8.3 (Berkeley) 1/2/94";
43#endif
44static const char rcsid[] =
45  "$FreeBSD: head/sbin/savecore/savecore.c 67264 2000-10-17 22:43:41Z des $";
46#endif /* not lint */
47
48#include <sys/param.h>
49#include <sys/stat.h>
50#include <sys/mount.h>
51#include <sys/syslog.h>
52#include <sys/sysctl.h>
53
54#include <vm/vm.h>
55#include <vm/vm_param.h>
56#include <vm/pmap.h>
57
58#include <dirent.h>
59#include <fcntl.h>
60#include <nlist.h>
61#include <paths.h>
62#include <stdio.h>
63#include <stdlib.h>
64#include <string.h>
65#include <unistd.h>
66#include "zopen.h"
67
68#ifdef __alpha__
69#define ok(number) ALPHA_K0SEG_TO_PHYS(number)
70#endif
71
72#ifdef __i386__
73#define ok(number) ((number) - KERNBASE)
74#endif
75
76struct nlist current_nl[] = {	/* Namelist for currently running system. */
77#define X_DUMPLO	0
78	{ "_dumplo" },
79#define X_TIME		1
80	{ "_time_second" },
81#define	X_DUMPSIZE	2
82	{ "_dumpsize" },
83#define X_VERSION	3
84	{ "_version" },
85#define X_PANICSTR	4
86	{ "_panicstr" },
87#define	X_DUMPMAG	5
88	{ "_dumpmag" },
89	{ "" },
90};
91int cursyms[] = { X_DUMPLO, X_VERSION, X_DUMPMAG, -1 };
92int dumpsyms[] = { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 };
93
94struct nlist dump_nl[] = {	/* Name list for dumped system. */
95	{ "_dumplo" },		/* Entries MUST be the same as */
96	{ "_time_second" },	/*	those in current_nl[].  */
97	{ "_dumpsize" },
98	{ "_version" },
99	{ "_panicstr" },
100	{ "_dumpmag" },
101	{ "" },
102};
103
104/* Types match kernel declarations. */
105off_t	dumplo;				/* where dump starts on dumpdev */
106int	dumpmag;			/* magic number in dump */
107int	dumpsize;			/* amount of memory dumped */
108
109char	*kernel;			/* user-specified kernel */
110char	*savedir;			/* directory to save dumps in */
111char	ddname[MAXPATHLEN];		/* name of dump device */
112dev_t	dumpdev;			/* dump device */
113int	dumpfd;				/* read/write descriptor on char dev */
114time_t	now;				/* current date */
115char	panic_mesg[1024];		/* panic message */
116int	panicstr;		        /* flag: dump was caused by panic */
117char	vers[1024];			/* version of kernel that crashed */
118
119int	clear, compress, force, verbose;	/* flags */
120
121void	 check_kmem __P((void));
122int	 check_space __P((void));
123void	 clear_dump __P((void));
124void	 DumpRead __P((int fd, void *bp, int size, off_t off, int flag));
125void	 DumpWrite __P((int fd, void *bp, int size, off_t off, int flag));
126int	 dump_exists __P((void));
127void     find_dev __P((dev_t));
128int	 get_crashtime __P((void));
129void	 get_dumpsize __P((void));
130void	 kmem_setup __P((void));
131void	 log __P((int, char *, ...));
132void	 Lseek __P((int, off_t, int));
133int	 Open __P((const char *, int rw));
134int	 Read __P((int, void *, int));
135void	 save_core __P((void));
136void	 usage __P((void));
137void	 Write __P((int, void *, int));
138
139int
140main(argc, argv)
141	int argc;
142	char *argv[];
143{
144	int ch;
145
146	openlog("savecore", LOG_PERROR, LOG_DAEMON);
147
148	while ((ch = getopt(argc, argv, "cdfN:vz")) != -1)
149		switch(ch) {
150		case 'c':
151			clear = 1;
152			break;
153		case 'd':		/* Not documented. */
154		case 'v':
155			verbose = 1;
156			break;
157		case 'f':
158			force = 1;
159			break;
160		case 'N':
161			kernel = optarg;
162			break;
163		case 'z':
164			compress = 1;
165			break;
166		case '?':
167		default:
168			usage();
169		}
170	argc -= optind;
171	argv += optind;
172
173	if (!clear) {
174		if (argc != 1 && argc != 2)
175			usage();
176		savedir = argv[0];
177	}
178	if (argc == 2)
179		kernel = argv[1];
180
181	(void)time(&now);
182	kmem_setup();
183
184	if (clear) {
185		clear_dump();
186		exit(0);
187	}
188
189	if (!dump_exists() && !force)
190		exit(1);
191
192	check_kmem();
193
194	if (panicstr)
195		syslog(LOG_ALERT, "reboot after panic: %s", panic_mesg);
196	else
197		syslog(LOG_ALERT, "reboot");
198
199	get_dumpsize();
200
201	if ((!get_crashtime() || !check_space()) && !force)
202		exit(1);
203
204	save_core();
205
206	clear_dump();
207	exit(0);
208}
209
210void
211kmem_setup()
212{
213	FILE *fp;
214	int kmem, i;
215	const char *dump_sys;
216	int mib[2];
217	size_t len;
218	long kdumplo;		/* block number where dump starts on dumpdev */
219
220	/*
221	 * Some names we need for the currently running system, others for
222	 * the system that was running when the dump was made.  The values
223	 * obtained from the current system are used to look for things in
224	 * /dev/kmem that cannot be found in the dump_sys namelist, but are
225	 * presumed to be the same (since the disk partitions are probably
226	 * the same!)
227	 */
228	if ((nlist(getbootfile(), current_nl)) == -1)
229		syslog(LOG_ERR, "%s: nlist: %m", getbootfile());
230	for (i = 0; cursyms[i] != -1; i++)
231		if (current_nl[cursyms[i]].n_value == 0) {
232			syslog(LOG_ERR, "%s: %s not in namelist",
233			    getbootfile(), current_nl[cursyms[i]].n_name);
234			exit(1);
235		}
236
237	dump_sys = kernel ? kernel : getbootfile();
238	if ((nlist(dump_sys, dump_nl)) == -1)
239		syslog(LOG_ERR, "%s: nlist: %m", dump_sys);
240	for (i = 0; dumpsyms[i] != -1; i++)
241		if (dump_nl[dumpsyms[i]].n_value == 0) {
242			syslog(LOG_ERR, "%s: %s not in namelist",
243			    dump_sys, dump_nl[dumpsyms[i]].n_name);
244			exit(1);
245		}
246
247	mib[0] = CTL_KERN;
248	mib[1] = KERN_DUMPDEV;
249	len = sizeof dumpdev;
250	if (sysctl(mib, 2, &dumpdev, &len, NULL, 0) == -1) {
251		syslog(LOG_ERR, "sysctl: kern.dumpdev: %m");
252		exit(1);
253	}
254	if (dumpdev == NODEV) {
255		syslog(LOG_WARNING, "no core dump (no dumpdev)");
256		exit(1);
257	}
258
259	kmem = Open(_PATH_KMEM, O_RDONLY);
260	Lseek(kmem, (off_t)current_nl[X_DUMPLO].n_value, L_SET);
261	(void)Read(kmem, &kdumplo, sizeof(kdumplo));
262	dumplo = (off_t)kdumplo * DEV_BSIZE;
263	if (verbose)
264		(void)printf("dumplo = %lld (%ld * %d)\n",
265		    (long long)dumplo, kdumplo, DEV_BSIZE);
266	Lseek(kmem, (off_t)current_nl[X_DUMPMAG].n_value, L_SET);
267	(void)Read(kmem, &dumpmag, sizeof(dumpmag));
268	find_dev(dumpdev);
269	dumpfd = Open(ddname, O_RDWR);
270	fp = fdopen(kmem, "r");
271	if (fp == NULL) {
272		syslog(LOG_ERR, "%s: fdopen: %m", _PATH_KMEM);
273		exit(1);
274	}
275	if (kernel)
276		return;
277	(void)fseek(fp, (off_t)current_nl[X_VERSION].n_value, L_SET);
278	(void)fgets(vers, sizeof(vers), fp);
279
280	/* Don't fclose(fp), we use dumpfd later. */
281}
282
283void
284check_kmem()
285{
286	char core_vers[1024], *p;
287
288	DumpRead(dumpfd, core_vers, sizeof(core_vers),
289	    (off_t)(dumplo + ok(dump_nl[X_VERSION].n_value)), L_SET);
290	core_vers[sizeof(core_vers) - 1] = '\0';
291	p = strchr(core_vers, '\n');
292	if (p)
293		p[1] = '\0';
294	if (strcmp(vers, core_vers) && kernel == 0)
295		syslog(LOG_WARNING,
296		    "warning: %s version mismatch:\n\t\"%s\"\nand\t\"%s\"\n",
297		    getbootfile(), vers, core_vers);
298	DumpRead(dumpfd, &panicstr, sizeof(panicstr),
299	    (off_t)(dumplo + ok(dump_nl[X_PANICSTR].n_value)), L_SET);
300	if (panicstr) {
301		DumpRead(dumpfd, panic_mesg, sizeof(panic_mesg),
302		    (off_t)(dumplo + ok(panicstr)), L_SET);
303	}
304}
305
306/*
307 * Clear the magic number in the dump header.
308 */
309void
310clear_dump()
311{
312	int newdumpmag;
313
314	newdumpmag = 0;
315	DumpWrite(dumpfd, &newdumpmag, sizeof(newdumpmag),
316	    (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET);
317	close(dumpfd);
318}
319
320/*
321 * Check if a dump exists by looking for a magic number in the dump
322 * header.
323 */
324int
325dump_exists()
326{
327	int newdumpmag;
328
329	DumpRead(dumpfd, &newdumpmag, sizeof(newdumpmag),
330	    (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET);
331	if (newdumpmag != dumpmag) {
332		if (verbose)
333			syslog(LOG_WARNING, "magic number mismatch (%x != %x)",
334			    newdumpmag, dumpmag);
335		syslog(LOG_WARNING, "no core dump");
336		return (0);
337	}
338	return (1);
339}
340
341char buf[1024 * 1024];
342
343/*
344 * Save the core dump.
345 */
346void
347save_core()
348{
349	register FILE *fp;
350	register int bounds, ifd, nr, nw;
351	char path[MAXPATHLEN];
352	mode_t oumask;
353
354	/*
355	 * Get the current number and update the bounds file.  Do the update
356	 * now, because may fail later and don't want to overwrite anything.
357	 */
358	(void)snprintf(path, sizeof(path), "%s/bounds", savedir);
359	if ((fp = fopen(path, "r")) == NULL)
360		goto err1;
361	if (fgets(buf, sizeof(buf), fp) == NULL) {
362		if (ferror(fp))
363err1:			syslog(LOG_WARNING, "%s: %m", path);
364		bounds = 0;
365	} else
366		bounds = atoi(buf);
367	if (fp != NULL)
368		(void)fclose(fp);
369	if ((fp = fopen(path, "w")) == NULL)
370		syslog(LOG_ERR, "%s: %m", path);
371	else {
372		(void)fprintf(fp, "%d\n", bounds + 1);
373		(void)fclose(fp);
374	}
375
376	/* Create the core file. */
377	oumask = umask(S_IRWXG|S_IRWXO); /* Restrict access to the core file.*/
378	(void)snprintf(path, sizeof(path), "%s/vmcore.%d%s",
379	    savedir, bounds, compress ? ".Z" : "");
380	if (compress)
381		fp = zopen(path, "w", 0);
382	else
383		fp = fopen(path, "w");
384	if (fp == NULL) {
385		syslog(LOG_ERR, "%s: %m", path);
386		exit(1);
387	}
388	(void)umask(oumask);
389
390	/* Seek to the start of the core. */
391	Lseek(dumpfd, (off_t)dumplo, L_SET);
392
393	/* Copy the core file. */
394	syslog(LOG_NOTICE, "writing %score to %s",
395	    compress ? "compressed " : "", path);
396	for (; dumpsize > 0; dumpsize -= nr) {
397		(void)printf("%6dK\r", dumpsize / 1024);
398		(void)fflush(stdout);
399		nr = read(dumpfd, buf, MIN(dumpsize, sizeof(buf)));
400		if (nr <= 0) {
401			if (nr == 0)
402				syslog(LOG_WARNING,
403				    "WARNING: EOF on dump device");
404			else
405				syslog(LOG_ERR, "%s: %m", ddname);
406			goto err2;
407		}
408		nw = fwrite(buf, 1, nr, fp);
409		if (nw != nr) {
410			syslog(LOG_ERR, "%s: %m", path);
411err2:			syslog(LOG_WARNING,
412			    "WARNING: vmcore may be incomplete");
413			(void)printf("\n");
414			exit(1);
415		}
416	}
417
418	(void)fclose(fp);
419
420	/* Copy the kernel. */
421	ifd = Open(kernel ? kernel : getbootfile(), O_RDONLY);
422	(void)snprintf(path, sizeof(path), "%s/kernel.%d%s",
423	    savedir, bounds, compress ? ".Z" : "");
424	if (compress)
425		fp = zopen(path, "w", 0);
426	else
427		fp = fopen(path, "w");
428	if (fp == NULL) {
429		syslog(LOG_ERR, "%s: %m", path);
430		exit(1);
431	}
432	syslog(LOG_NOTICE, "writing %skernel to %s",
433	    compress ? "compressed " : "", path);
434	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
435		nw = fwrite(buf, 1, nr, fp);
436		if (nw != nr) {
437			syslog(LOG_ERR, "%s: %m", path);
438			syslog(LOG_WARNING,
439			    "WARNING: kernel may be incomplete");
440			exit(1);
441		}
442	}
443	if (nr < 0) {
444		syslog(LOG_ERR, "%s: %m", kernel ? kernel : getbootfile());
445		syslog(LOG_WARNING,
446		    "WARNING: kernel may be incomplete");
447		exit(1);
448	}
449	(void)fclose(fp);
450	close(ifd);
451}
452
453/*
454 * Verify that the specified device node exists and matches the
455 * specified device.
456 */
457int
458verify_dev(name, dev)
459	char *name;
460	register dev_t dev;
461{
462	struct stat sb;
463
464	if (lstat(name, &sb) == -1)
465		return (-1);
466	if (!S_ISCHR(sb.st_mode) || sb.st_rdev != dev)
467		return (-1);
468	return (0);
469}
470
471/*
472 * Find the dump device.
473 *
474 *  1) try devname(3); see if it returns something sensible
475 *  2) scan /dev for the desired node
476 *  3) as a last resort, try to create the node we need
477 */
478void
479find_dev(dev)
480	register dev_t dev;
481{
482	struct dirent *ent;
483	char *dn, *dnp;
484	DIR *d;
485
486	strcpy(ddname, _PATH_DEV);
487	dnp = ddname + sizeof _PATH_DEV - 1;
488	if ((dn = devname(dev, S_IFCHR)) != NULL) {
489		strcpy(dnp, dn);
490		if (verify_dev(ddname, dev) == 0)
491			return;
492	}
493	if ((d = opendir(_PATH_DEV)) != NULL) {
494		while ((ent = readdir(d))) {
495			strcpy(dnp, ent->d_name);
496			if (verify_dev(ddname, dev) == 0) {
497				closedir(d);
498				return;
499			}
500		}
501		closedir(d);
502	}
503	strcpy(dnp, "dump");
504	if (mknod(ddname, S_IFCHR|S_IRUSR|S_IWUSR, dev) == 0)
505		return;
506	syslog(LOG_ERR, "can't find device %d/%#x", major(dev), minor(dev));
507	exit(1);
508}
509
510/*
511 * Extract the date and time of the crash from the dump header, and
512 * make sure it looks sane (within one week of current date and time).
513 */
514int
515get_crashtime()
516{
517	time_t dumptime;			/* Time the dump was taken. */
518
519	DumpRead(dumpfd, &dumptime, sizeof(dumptime),
520	    (off_t)(dumplo + ok(dump_nl[X_TIME].n_value)), L_SET);
521	if (dumptime == 0) {
522		if (verbose)
523			syslog(LOG_ERR, "dump time is zero");
524		return (0);
525	}
526	(void)printf("savecore: system went down at %s", ctime(&dumptime));
527#define	LEEWAY	(7 * 86400)
528	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
529		(void)printf("dump time is unreasonable\n");
530		return (0);
531	}
532	return (1);
533}
534
535/*
536 * Extract the size of the dump from the dump header.
537 */
538void
539get_dumpsize()
540{
541	/* Read the dump size. */
542	DumpRead(dumpfd, &dumpsize, sizeof(dumpsize),
543	    (off_t)(dumplo + ok(dump_nl[X_DUMPSIZE].n_value)), L_SET);
544	dumpsize *= getpagesize();
545}
546
547/*
548 * Check that sufficient space is available on the disk that holds the
549 * save directory.
550 */
551int
552check_space()
553{
554	register FILE *fp;
555	const char *tkernel;
556	off_t minfree, spacefree, totfree, kernelsize, needed;
557	struct stat st;
558	struct statfs fsbuf;
559	char buf[100], path[MAXPATHLEN];
560
561	tkernel = kernel ? kernel : getbootfile();
562	if (stat(tkernel, &st) < 0) {
563		syslog(LOG_ERR, "%s: %m", tkernel);
564		exit(1);
565	}
566	kernelsize = st.st_blocks * S_BLKSIZE;
567
568	if (statfs(savedir, &fsbuf) < 0) {
569		syslog(LOG_ERR, "%s: %m", savedir);
570		exit(1);
571	}
572 	spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
573	totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024;
574
575	(void)snprintf(path, sizeof(path), "%s/minfree", savedir);
576	if ((fp = fopen(path, "r")) == NULL)
577		minfree = 0;
578	else {
579		if (fgets(buf, sizeof(buf), fp) == NULL)
580			minfree = 0;
581		else
582			minfree = atoi(buf);
583		(void)fclose(fp);
584	}
585
586	needed = (dumpsize + kernelsize) / 1024;
587 	if (((minfree > 0) ? spacefree : totfree) - needed < minfree) {
588		syslog(LOG_WARNING,
589	"no dump, not enough free space on device (%lld available, need %lld)",
590		    (long long)(minfree > 0 ? spacefree : totfree),
591		    (long long)needed);
592		return (0);
593	}
594	if (spacefree - needed < 0)
595		syslog(LOG_WARNING,
596		    "dump performed, but free space threshold crossed");
597	return (1);
598}
599
600int
601Open(name, rw)
602	const char *name;
603	int rw;
604{
605	int fd;
606
607	if ((fd = open(name, rw, 0)) < 0) {
608		syslog(LOG_ERR, "%s: %m", name);
609		exit(1);
610	}
611	return (fd);
612}
613
614int
615Read(fd, bp, size)
616	int fd, size;
617	void *bp;
618{
619	int nr;
620
621	nr = read(fd, bp, size);
622	if (nr != size) {
623		syslog(LOG_ERR, "read: %m");
624		exit(1);
625	}
626	return (nr);
627}
628
629void
630Lseek(fd, off, flag)
631	int fd, flag;
632	off_t off;
633{
634	off_t ret;
635
636	ret = lseek(fd, off, flag);
637	if (ret == -1) {
638		syslog(LOG_ERR, "lseek: %m");
639		exit(1);
640	}
641}
642
643/*
644 * DumpWrite and DumpRead block io requests to the * dump device.
645 */
646#define DUMPBUFSIZE	8192
647void
648DumpWrite(fd, bp, size, off, flag)
649	int fd, size, flag;
650	void *bp;
651	off_t off;
652{
653	unsigned char buf[DUMPBUFSIZE], *p, *q;
654	off_t pos;
655	int i, j;
656
657	if (flag != L_SET) {
658		syslog(LOG_ERR, "lseek: not LSET");
659		exit(2);
660	}
661	q = bp;
662	while (size) {
663		pos = off & ~(DUMPBUFSIZE - 1);
664		Lseek(fd, pos, flag);
665		(void)Read(fd, buf, sizeof(buf));
666		j = off & (DUMPBUFSIZE - 1);
667		p = buf + j;
668		i = size;
669		if (i > DUMPBUFSIZE - j)
670			i = DUMPBUFSIZE - j;
671		memcpy(p, q, i);
672		Lseek(fd, pos, flag);
673		(void)Write(fd, buf, sizeof(buf));
674		size -= i;
675		q += i;
676		off += i;
677	}
678}
679
680void
681DumpRead(fd, bp, size, off, flag)
682	int fd, size, flag;
683	void *bp;
684	off_t off;
685{
686	unsigned char buf[DUMPBUFSIZE], *p, *q;
687	off_t pos;
688	int i, j;
689
690	if (flag != L_SET) {
691		syslog(LOG_ERR, "lseek: not LSET");
692		exit(2);
693	}
694	q = bp;
695	while (size) {
696		pos = off & ~(DUMPBUFSIZE - 1);
697		Lseek(fd, pos, flag);
698		(void)Read(fd, buf, sizeof(buf));
699		j = off & (DUMPBUFSIZE - 1);
700		p = buf + j;
701		i = size;
702		if (i > DUMPBUFSIZE - j)
703			i = DUMPBUFSIZE - j;
704		memcpy(q, p, i);
705		size -= i;
706		q += i;
707		off += i;
708	}
709}
710
711void
712Write(fd, bp, size)
713	int fd, size;
714	void *bp;
715{
716	int n;
717
718	if ((n = write(fd, bp, size)) < size) {
719		syslog(LOG_ERR, "write: %m");
720		exit(1);
721	}
722}
723
724void
725usage()
726{
727	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
728	exit(1);
729}
730