savecore.c revision 18914
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 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
41static char sccsid[] = "@(#)savecore.c	8.3 (Berkeley) 1/2/94";
42#endif /* not lint */
43
44#include <sys/param.h>
45#include <sys/stat.h>
46#include <sys/mount.h>
47#include <sys/syslog.h>
48#include <sys/time.h>
49
50#include <vm/vm.h>
51#include <vm/vm_param.h>
52#include <vm/pmap.h>
53
54#include <dirent.h>
55#include <errno.h>
56#include <fcntl.h>
57#include <nlist.h>
58#include <paths.h>
59#include <stdio.h>
60#include <stdlib.h>
61#include <string.h>
62#include <unistd.h>
63#include "zopen.h"
64
65#define ok(number) ((number) - KERNBASE)
66
67struct nlist current_nl[] = {	/* Namelist for currently running system. */
68#define X_DUMPDEV	0
69	{ "_dumpdev" },
70#define X_DUMPLO	1
71	{ "_dumplo" },
72#define X_TIME		2
73	{ "_time" },
74#define	X_DUMPSIZE	3
75	{ "_dumpsize" },
76#define X_VERSION	4
77	{ "_version" },
78#define X_PANICSTR	5
79	{ "_panicstr" },
80#define	X_DUMPMAG	6
81	{ "_dumpmag" },
82	{ "" },
83};
84int cursyms[] = { X_DUMPDEV, X_DUMPLO, X_VERSION, X_DUMPMAG, -1 };
85int dumpsyms[] = { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 };
86
87struct nlist dump_nl[] = {	/* Name list for dumped system. */
88	{ "_dumpdev" },		/* Entries MUST be the same as */
89	{ "_dumplo" },		/*	those in current_nl[].  */
90	{ "_time" },
91	{ "_dumpsize" },
92	{ "_version" },
93	{ "_panicstr" },
94	{ "_dumpmag" },
95	{ "" },
96};
97
98/* Types match kernel declarations. */
99long	dumplo;				/* where dump starts on dumpdev */
100int	dumpmag;			/* magic number in dump */
101int	dumpsize;			/* amount of memory dumped */
102
103char	*kernel;
104char	*dirname;			/* directory to save dumps in */
105char	*ddname;			/* name of dump device */
106dev_t	dumpdev;			/* dump device */
107int	dumpfd;				/* read/write descriptor on block dev */
108time_t	now;				/* current date */
109char	panic_mesg[1024];
110int	panicstr;
111char	vers[1024];
112
113int	clear, compress, force, verbose;	/* flags */
114
115void	 check_kmem __P((void));
116int	 check_space __P((void));
117void	 clear_dump __P((void));
118int	 Create __P((char *, int));
119int	 dump_exists __P((void));
120char	*find_dev __P((dev_t, int));
121int	 get_crashtime __P((void));
122int	 get_dumpsize __P((void));
123void	 kmem_setup __P((void));
124void	 log __P((int, char *, ...));
125void	 Lseek __P((int, off_t, int));
126int	 Open __P((const char *, int rw));
127int	 Read __P((int, void *, int));
128char	*rawname __P((char *s));
129void	 save_core __P((void));
130void	 usage __P((void));
131void	 Write __P((int, void *, int));
132
133int
134main(argc, argv)
135	int argc;
136	char *argv[];
137{
138	int ch;
139
140	openlog("savecore", LOG_PERROR, LOG_DAEMON);
141
142	while ((ch = getopt(argc, argv, "cdfN:vz")) != EOF)
143		switch(ch) {
144		case 'c':
145			clear = 1;
146			break;
147		case 'd':		/* Not documented. */
148		case 'v':
149			verbose = 1;
150			break;
151		case 'f':
152			force = 1;
153			break;
154		case 'N':
155			kernel = optarg;
156			break;
157		case 'z':
158			compress = 1;
159			break;
160		case '?':
161		default:
162			usage();
163		}
164	argc -= optind;
165	argv += optind;
166
167	if (!clear) {
168		if (argc != 1 && argc != 2)
169			usage();
170		dirname = argv[0];
171	}
172	if (argc == 2)
173		kernel = argv[1];
174
175	(void)time(&now);
176	kmem_setup();
177
178	if (clear) {
179		clear_dump();
180		exit(0);
181	}
182
183	if (!dump_exists() && !force)
184		exit(1);
185
186	check_kmem();
187
188	if (panicstr)
189		syslog(LOG_ALERT, "reboot after panic: %s", panic_mesg);
190	else
191		syslog(LOG_ALERT, "reboot");
192
193	get_dumpsize();
194
195	if ((!get_crashtime() || !check_space()) && !force)
196		exit(1);
197
198	save_core();
199
200	clear_dump();
201	exit(0);
202}
203
204void
205kmem_setup()
206{
207	FILE *fp;
208	int kmem, i;
209	const char *dump_sys;
210
211	/*
212	 * Some names we need for the currently running system, others for
213	 * the system that was running when the dump was made.  The values
214	 * obtained from the current system are used to look for things in
215	 * /dev/kmem that cannot be found in the dump_sys namelist, but are
216	 * presumed to be the same (since the disk partitions are probably
217	 * the same!)
218	 */
219	if ((nlist(getbootfile(), current_nl)) == -1)
220		syslog(LOG_ERR, "%s: nlist: %s", getbootfile(),
221		       strerror(errno));
222	for (i = 0; cursyms[i] != -1; i++)
223		if (current_nl[cursyms[i]].n_value == 0) {
224			syslog(LOG_ERR, "%s: %s not in namelist",
225			    getbootfile(), current_nl[cursyms[i]].n_name);
226			exit(1);
227		}
228
229	dump_sys = kernel ? kernel : getbootfile();
230	if ((nlist(dump_sys, dump_nl)) == -1)
231		syslog(LOG_ERR, "%s: nlist: %s", dump_sys, strerror(errno));
232	for (i = 0; dumpsyms[i] != -1; i++)
233		if (dump_nl[dumpsyms[i]].n_value == 0) {
234			syslog(LOG_ERR, "%s: %s not in namelist",
235			    dump_sys, dump_nl[dumpsyms[i]].n_name);
236			exit(1);
237		}
238
239	kmem = Open(_PATH_KMEM, O_RDONLY);
240	Lseek(kmem, (off_t)current_nl[X_DUMPDEV].n_value, L_SET);
241	(void)Read(kmem, &dumpdev, sizeof(dumpdev));
242	if (dumpdev == NODEV) {
243		syslog(LOG_WARNING, "no core dump (no dumpdev)");
244		exit(1);
245	}
246	Lseek(kmem, (off_t)current_nl[X_DUMPLO].n_value, L_SET);
247	(void)Read(kmem, &dumplo, sizeof(dumplo));
248	if (verbose)
249		(void)printf("dumplo = %d (%d * %d)\n",
250		    dumplo, dumplo/DEV_BSIZE, DEV_BSIZE);
251	Lseek(kmem, (off_t)current_nl[X_DUMPMAG].n_value, L_SET);
252	(void)Read(kmem, &dumpmag, sizeof(dumpmag));
253	dumplo *= DEV_BSIZE;
254	ddname = find_dev(dumpdev, S_IFBLK);
255	dumpfd = Open(ddname, O_RDWR);
256	fp = fdopen(kmem, "r");
257	if (fp == NULL) {
258		syslog(LOG_ERR, "%s: fdopen: %m", _PATH_KMEM);
259		exit(1);
260	}
261	if (kernel)
262		return;
263	(void)fseek(fp, (off_t)current_nl[X_VERSION].n_value, L_SET);
264	(void)fgets(vers, sizeof(vers), fp);
265
266	/* Don't fclose(fp), we use dumpfd later. */
267}
268
269void
270check_kmem()
271{
272	register char *cp;
273	FILE *fp;
274	char core_vers[1024];
275
276	fp = fdopen(dumpfd, "r");
277	if (fp == NULL) {
278		syslog(LOG_ERR, "%s: fdopen: %m", ddname);
279		exit(1);
280	}
281	fseek(fp, (off_t)(dumplo + ok(dump_nl[X_VERSION].n_value)), L_SET);
282	fgets(core_vers, sizeof(core_vers), fp);
283	if (strcmp(vers, core_vers) && kernel == 0)
284		syslog(LOG_WARNING,
285		    "warning: %s version mismatch:\n\t%s\nand\t%s\n",
286		    getbootfile(), vers, core_vers);
287	(void)fseek(fp,
288	    (off_t)(dumplo + ok(dump_nl[X_PANICSTR].n_value)), L_SET);
289	(void)fread(&panicstr, sizeof(panicstr), 1, fp);
290	if (panicstr) {
291		(void)fseek(fp, dumplo + ok(panicstr), L_SET);
292		cp = panic_mesg;
293		do
294			*cp = getc(fp);
295		while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)]);
296	}
297	/* Don't fclose(fp), we use dumpfd later. */
298}
299
300void
301clear_dump()
302{
303	long newdumplo;
304
305	newdumplo = 0;
306	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET);
307	Write(dumpfd, &newdumplo, sizeof(newdumplo));
308}
309
310int
311dump_exists()
312{
313	int newdumpmag;
314
315	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET);
316	(void)Read(dumpfd, &newdumpmag, sizeof(newdumpmag));
317	if (newdumpmag != dumpmag) {
318		if (verbose)
319			syslog(LOG_WARNING, "magic number mismatch (%x != %x)",
320			    newdumpmag, dumpmag);
321		syslog(LOG_WARNING, "no core dump");
322		return (0);
323	}
324	return (1);
325}
326
327char buf[1024 * 1024];
328
329void
330save_core()
331{
332	register FILE *fp;
333	register int bounds, ifd, nr, nw, ofd;
334	char *rawp, path[MAXPATHLEN];
335
336	/*
337	 * Get the current number and update the bounds file.  Do the update
338	 * now, because may fail later and don't want to overwrite anything.
339	 */
340	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
341	if ((fp = fopen(path, "r")) == NULL)
342		goto err1;
343	if (fgets(buf, sizeof(buf), fp) == NULL) {
344		if (ferror(fp))
345err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
346		bounds = 0;
347	} else
348		bounds = atoi(buf);
349	if (fp != NULL)
350		(void)fclose(fp);
351	if ((fp = fopen(path, "w")) == NULL)
352		syslog(LOG_ERR, "%s: %m", path);
353	else {
354		(void)fprintf(fp, "%d\n", bounds + 1);
355		(void)fclose(fp);
356	}
357
358	/* Create the core file. */
359	(void)snprintf(path, sizeof(path), "%s/vmcore.%d%s",
360	    dirname, bounds, compress ? ".Z" : "");
361	if (compress) {
362		if ((fp = zopen(path, "w", 0)) == NULL) {
363			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
364			exit(1);
365		}
366	} else
367		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
368
369	/* Open the raw device. */
370	rawp = rawname(ddname);
371	if ((ifd = open(rawp, O_RDONLY)) == -1) {
372		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
373		ifd = dumpfd;
374	}
375
376	/* Seek to the start of the core. */
377	Lseek(ifd, (off_t)dumplo, L_SET);
378
379	/* Copy the core file. */
380	syslog(LOG_NOTICE, "writing %score to %s",
381	    compress ? "compressed " : "", path);
382	for (; dumpsize > 0; dumpsize -= nr) {
383		(void)printf("%6dK\r", dumpsize / 1024);
384		(void)fflush(stdout);
385		nr = read(ifd, buf, MIN(dumpsize, sizeof(buf)));
386		if (nr <= 0) {
387			if (nr == 0)
388				syslog(LOG_WARNING,
389				    "WARNING: EOF on dump device");
390			else
391				syslog(LOG_ERR, "%s: %m", rawp);
392			goto err2;
393		}
394		if (compress)
395			nw = fwrite(buf, 1, nr, fp);
396		else
397			nw = write(ofd, buf, nr);
398		if (nw != nr) {
399			syslog(LOG_ERR, "%s: %s",
400			    path, strerror(nw == 0 ? EIO : errno));
401err2:			syslog(LOG_WARNING,
402			    "WARNING: vmcore may be incomplete");
403			(void)printf("\n");
404			exit(1);
405		}
406	}
407	(void)close(ifd);
408	if (compress)
409		(void)fclose(fp);
410	else
411		(void)close(ofd);
412
413	/* Copy the kernel. */
414	ifd = Open(kernel ? kernel : getbootfile(), O_RDONLY);
415	(void)snprintf(path, sizeof(path), "%s/kernel.%d%s",
416	    dirname, bounds, compress ? ".Z" : "");
417	if (compress) {
418		if ((fp = zopen(path, "w", 0)) == NULL) {
419			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
420			exit(1);
421		}
422	} else
423		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
424	syslog(LOG_NOTICE, "writing %skernel to %s",
425	    compress ? "compressed " : "", path);
426	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
427		if (compress)
428			nw = fwrite(buf, 1, nr, fp);
429		else
430			nw = write(ofd, buf, nr);
431		if (nw != nr) {
432			syslog(LOG_ERR, "%s: %s",
433			    path, strerror(nw == 0 ? EIO : errno));
434			syslog(LOG_WARNING,
435			    "WARNING: kernel may be incomplete");
436			exit(1);
437		}
438	}
439	if (nr < 0) {
440		syslog(LOG_ERR, "%s: %s",
441		    kernel ? kernel : getbootfile(), strerror(errno));
442		syslog(LOG_WARNING,
443		    "WARNING: kernel may be incomplete");
444		exit(1);
445	}
446	if (compress)
447		(void)fclose(fp);
448	else
449		(void)close(ofd);
450}
451
452char *
453find_dev(dev, type)
454	register dev_t dev;
455	register int type;
456{
457	register DIR *dfd;
458	struct dirent *dir;
459	struct stat sb;
460	char *dp, devname[MAXPATHLEN + 1];
461
462	if ((dfd = opendir(_PATH_DEV)) == NULL) {
463		syslog(LOG_ERR, "%s: %s", _PATH_DEV, strerror(errno));
464		exit(1);
465	}
466	(void)strcpy(devname, _PATH_DEV);
467	while ((dir = readdir(dfd))) {
468		(void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name);
469		if (lstat(devname, &sb)) {
470			syslog(LOG_ERR, "%s: %s", devname, strerror(errno));
471			continue;
472		}
473		if ((sb.st_mode & S_IFMT) != type)
474			continue;
475		if (dev == sb.st_rdev) {
476			closedir(dfd);
477			if ((dp = strdup(devname)) == NULL) {
478				syslog(LOG_ERR, "%s", strerror(errno));
479				exit(1);
480			}
481			return (dp);
482		}
483	}
484	closedir(dfd);
485	syslog(LOG_ERR, "can't find device %d/%d", major(dev), minor(dev));
486	exit(1);
487}
488
489char *
490rawname(s)
491	char *s;
492{
493	char *sl, name[MAXPATHLEN];
494
495	if ((sl = rindex(s, '/')) == NULL || sl[1] == '0') {
496		syslog(LOG_ERR,
497		    "can't make raw dump device name from %s", s);
498		return (s);
499	}
500	(void)snprintf(name, sizeof(name), "%.*s/r%s", sl - s, s, sl + 1);
501	if ((sl = strdup(name)) == NULL) {
502		syslog(LOG_ERR, "%s", strerror(errno));
503		exit(1);
504	}
505	return (sl);
506}
507
508int
509get_crashtime()
510{
511	time_t dumptime;			/* Time the dump was taken. */
512
513	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_TIME].n_value)), L_SET);
514	(void)Read(dumpfd, &dumptime, sizeof(dumptime));
515	if (dumptime == 0) {
516		if (verbose)
517			syslog(LOG_ERR, "dump time is zero");
518		return (0);
519	}
520	(void)printf("savecore: system went down at %s", ctime(&dumptime));
521#define	LEEWAY	(7 * 86400)
522	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
523		(void)printf("dump time is unreasonable\n");
524		return (0);
525	}
526	return (1);
527}
528
529int
530get_dumpsize()
531{
532	/* Read the dump size. */
533	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPSIZE].n_value)), L_SET);
534	(void)Read(dumpfd, &dumpsize, sizeof(dumpsize));
535	dumpsize *= getpagesize();
536
537	return(1);
538}
539
540int
541check_space()
542{
543	register FILE *fp;
544	const char *tkernel;
545	off_t minfree, spacefree, totfree, kernelsize, needed;
546	struct stat st;
547	struct statfs fsbuf;
548	char buf[100], path[MAXPATHLEN];
549
550	tkernel = kernel ? kernel : getbootfile();
551	if (stat(tkernel, &st) < 0) {
552		syslog(LOG_ERR, "%s: %m", tkernel);
553		exit(1);
554	}
555	kernelsize = st.st_blocks * S_BLKSIZE;
556
557	if (statfs(dirname, &fsbuf) < 0) {
558		syslog(LOG_ERR, "%s: %m", dirname);
559		exit(1);
560	}
561 	spacefree = ((off_t) fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
562	totfree = ((off_t) fsbuf.f_bfree * fsbuf.f_bsize) / 1024;
563
564	(void)snprintf(path, sizeof(path), "%s/minfree", dirname);
565	if ((fp = fopen(path, "r")) == NULL)
566		minfree = 0;
567	else {
568		if (fgets(buf, sizeof(buf), fp) == NULL)
569			minfree = 0;
570		else
571			minfree = atoi(buf);
572		(void)fclose(fp);
573	}
574
575	needed = (dumpsize + kernelsize) / 1024;
576 	if (((minfree > 0) ? spacefree : totfree) - needed < minfree) {
577		syslog(LOG_WARNING,
578		    "no dump, not enough free space on device");
579		return (0);
580	}
581	if (spacefree - needed < 0)
582		syslog(LOG_WARNING,
583		    "dump performed, but free space threshold crossed");
584	return (1);
585}
586
587int
588Open(name, rw)
589	const char *name;
590	int rw;
591{
592	int fd;
593
594	if ((fd = open(name, rw, 0)) < 0) {
595		syslog(LOG_ERR, "%s: %m", name);
596		exit(1);
597	}
598	return (fd);
599}
600
601int
602Read(fd, bp, size)
603	int fd, size;
604	void *bp;
605{
606	int nr;
607
608	nr = read(fd, bp, size);
609	if (nr != size) {
610		syslog(LOG_ERR, "read: %m");
611		exit(1);
612	}
613	return (nr);
614}
615
616void
617Lseek(fd, off, flag)
618	int fd, flag;
619	off_t off;
620{
621	off_t ret;
622
623	ret = lseek(fd, off, flag);
624	if (ret == -1) {
625		syslog(LOG_ERR, "lseek: %m");
626		exit(1);
627	}
628}
629
630int
631Create(file, mode)
632	char *file;
633	int mode;
634{
635	register int fd;
636
637	fd = creat(file, mode);
638	if (fd < 0) {
639		syslog(LOG_ERR, "%s: %m", file);
640		exit(1);
641	}
642	return (fd);
643}
644
645void
646Write(fd, bp, size)
647	int fd, size;
648	void *bp;
649{
650	int n;
651
652	if ((n = write(fd, bp, size)) < size) {
653		syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO));
654		exit(1);
655	}
656}
657
658void
659usage()
660{
661	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
662	exit(1);
663}
664