savecore.c revision 1.25
1/*	$NetBSD: savecore.c,v 1.25 1996/03/16 10:29:46 leo Exp $	*/
2
3/*-
4 * Copyright (c) 1986, 1992, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *	This product includes software developed by the University of
18 *	California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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
36#ifndef lint
37static char copyright[] =
38"@(#) Copyright (c) 1986, 1992, 1993\n\
39	The Regents of the University of California.  All rights reserved.\n";
40#endif /* not lint */
41
42#ifndef lint
43#if 0
44static char sccsid[] = "@(#)savecore.c	8.3 (Berkeley) 1/2/94";
45#else
46static char rcsid[] = "$NetBSD: savecore.c,v 1.25 1996/03/16 10:29:46 leo Exp $";
47#endif
48#endif /* not lint */
49
50#include <sys/param.h>
51#include <sys/stat.h>
52#include <sys/mount.h>
53#include <sys/syslog.h>
54#include <sys/time.h>
55
56#include <dirent.h>
57#include <errno.h>
58#include <fcntl.h>
59#include <nlist.h>
60#include <paths.h>
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64#include <tzfile.h>
65#include <unistd.h>
66#include <limits.h>
67#include <kvm.h>
68
69extern FILE *zopen __P((const char *fname, const char *mode, int bits));
70
71#define KREAD(kd, addr, p)\
72	(kvm_read(kd, addr, (char *)(p), sizeof(*(p))) != sizeof(*(p)))
73
74struct nlist current_nl[] = {	/* Namelist for currently running system. */
75#define X_DUMPDEV	0
76	{ "_dumpdev" },
77#define X_DUMPLO	1
78	{ "_dumplo" },
79#define X_TIME		2
80	{ "_time" },
81#define	X_DUMPSIZE	3
82	{ "_dumpsize" },
83#define X_VERSION	4
84	{ "_version" },
85#define X_PANICSTR	5
86	{ "_panicstr" },
87#define	X_DUMPMAG	6
88	{ "_dumpmag" },
89	{ NULL },
90};
91int cursyms[] = { X_DUMPDEV, 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	{ "_dumpdev" },		/* Entries MUST be the same as */
96	{ "_dumplo" },		/*	those in current_nl[].  */
97	{ "_time" },
98	{ "_dumpsize" },
99	{ "_version" },
100	{ "_panicstr" },
101	{ "_dumpmag" },
102	{ NULL },
103};
104
105/* Types match kernel declarations. */
106long	dumplo;				/* where dump starts on dumpdev */
107int	dumpmag;			/* magic number in dump */
108int	dumpsize;			/* amount of memory dumped */
109
110char	*kernel;
111char	*dirname;			/* directory to save dumps in */
112char	*ddname;			/* name of dump device */
113dev_t	dumpdev;			/* dump device */
114int	dumpfd;				/* read/write descriptor on block dev */
115kvm_t	*kd_dump;			/* kvm descriptor on block dev	*/
116time_t	now;				/* current date */
117char	panic_mesg[1024];
118int	panicstr;
119char	vers[1024];
120
121int	clear, compress, force, verbose;	/* flags */
122
123void	 check_kmem __P((void));
124int	 check_space __P((void));
125void	 clear_dump __P((void));
126int	 Create __P((char *, int));
127int	 dump_exists __P((void));
128char	*find_dev __P((dev_t, int));
129int	 get_crashtime __P((void));
130void	 kmem_setup __P((void));
131void	 log __P((int, char *, ...));
132void	 Lseek __P((int, off_t, int));
133int	 Open __P((char *, int rw));
134char	*rawname __P((char *s));
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		dirname = 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	if ((!get_crashtime() || !check_space()) && !force)
200		exit(1);
201
202	save_core();
203
204	clear_dump();
205	exit(0);
206}
207
208void
209kmem_setup()
210{
211	kvm_t	*kd_kern;
212	char	errbuf[_POSIX2_LINE_MAX];
213	int	i, hdrsz;
214	char	*dump_sys;
215
216	/*
217	 * Some names we need for the currently running system, others for
218	 * the system that was running when the dump was made.  The values
219	 * obtained from the current system are used to look for things in
220	 * /dev/kmem that cannot be found in the dump_sys namelist, but are
221	 * presumed to be the same (since the disk partitions are probably
222	 * the same!)
223	 */
224	kd_kern = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf);
225	if (kd_kern == NULL) {
226		syslog(LOG_ERR, "%s: kvm_openfiles: %s", _PATH_UNIX, errbuf);
227		exit(1);
228	}
229	if (kvm_nlist(kd_kern, current_nl) == -1)
230		syslog(LOG_ERR, "%s: kvm_nlist: %s", _PATH_UNIX,
231			kvm_geterr(kd_kern));
232
233	for (i = 0; cursyms[i] != -1; i++)
234		if (current_nl[cursyms[i]].n_value == 0) {
235			syslog(LOG_ERR, "%s: %s not in namelist",
236			    _PATH_UNIX, current_nl[cursyms[i]].n_name);
237			exit(1);
238		}
239
240	KREAD(kd_kern, current_nl[X_DUMPDEV].n_value, &dumpdev);
241	if (dumpdev == NODEV) {
242		syslog(LOG_WARNING, "no core dump (no dumpdev)");
243		exit(1);
244	}
245	KREAD(kd_kern, current_nl[X_DUMPLO].n_value, &dumplo);
246	dumplo *= DEV_BSIZE;
247	if (verbose)
248		(void)printf("dumplo = %d (%d * %d)\n",
249		    dumplo, dumplo / DEV_BSIZE, DEV_BSIZE);
250	KREAD(kd_kern, current_nl[X_DUMPMAG].n_value, &dumpmag);
251
252	if (kernel == NULL) {
253		(void)kvm_read(kd_kern, current_nl[X_VERSION].n_value,
254			vers, sizeof(vers));
255		vers[sizeof(vers) - 1] = '\0';
256	}
257
258	ddname = find_dev(dumpdev, S_IFBLK);
259	dumpfd = Open(ddname, O_RDWR);
260
261	dump_sys = kernel ? kernel : _PATH_UNIX;
262
263	kd_dump = kvm_openfiles(dump_sys, ddname, NULL, O_RDWR, errbuf);
264	if (kd_dump == NULL) {
265		syslog(LOG_ERR, "%s: kvm_openfiles: %s", dump_sys, errbuf);
266		exit(1);
267	}
268
269	if (kvm_nlist(kd_dump, dump_nl) == -1)
270		syslog(LOG_ERR, "%s: kvm_nlist: %s", dump_sys,
271			kvm_geterr(kd_dump));
272
273	for (i = 0; dumpsyms[i] != -1; i++)
274		if (dump_nl[dumpsyms[i]].n_value == 0) {
275			syslog(LOG_ERR, "%s: %s not in namelist",
276			    dump_sys, dump_nl[dumpsyms[i]].n_name);
277			exit(1);
278		}
279	hdrsz = kvm_dump_mkheader(kd_kern, kd_dump, (off_t)dumplo);
280	if (hdrsz == -1) {
281		syslog(LOG_ERR, "%s: kvm_dump_mkheader: %s", dump_sys,
282			kvm_geterr(kd_kern));
283		exit(1);
284	}
285	dumplo += hdrsz;
286	kvm_close(kd_kern);
287}
288
289void
290check_kmem()
291{
292	register char	*cp;
293	register int	panicloc;
294	char core_vers[1024];
295
296	(void)kvm_read(kd_dump, dump_nl[X_VERSION].n_value, core_vers,
297		sizeof(core_vers));
298	core_vers[sizeof(core_vers) - 1] = '\0';
299
300	if (strcmp(vers, core_vers) && kernel == 0)
301		syslog(LOG_WARNING,
302		    "warning: %s version mismatch:\n\t%s\nand\t%s\n",
303		    _PATH_UNIX, vers, core_vers);
304
305	KREAD(kd_dump, dump_nl[X_PANICSTR].n_value, &panicstr);
306	if (panicstr) {
307		cp       = panic_mesg;
308		panicloc = panicstr;
309		do {
310			KREAD(kd_dump, panicloc, cp);
311			panicloc++;
312		} while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)]);
313	}
314}
315
316int
317dump_exists()
318{
319	int newdumpmag;
320
321	KREAD(kd_dump, dump_nl[X_DUMPMAG].n_value, &newdumpmag);
322
323	/* Read the dump size. */
324	KREAD(kd_dump, dump_nl[X_DUMPSIZE].n_value, &dumpsize);
325	dumpsize *= getpagesize();
326
327	/*
328	 * Return zero if core dump doesn't seem to be there, and note
329	 * it for syslog.  This check and return happens after the dump size
330	 * is read, so dumpsize is whether or not the core is valid (for -f).
331	 */
332	if (newdumpmag != dumpmag) {
333		if (verbose)
334			syslog(LOG_WARNING, "magic number mismatch (%x != %x)",
335			    newdumpmag, dumpmag);
336		syslog(LOG_WARNING, "no core dump");
337		return (0);
338	}
339	return (1);
340}
341
342void
343clear_dump()
344{
345	if (kvm_dump_inval(kd_dump) == -1)
346		syslog(LOG_ERR, "%s: kvm_clear_dump: %s", ddname,
347			kvm_geterr(kd_dump));
348
349}
350
351char buf[1024 * 1024];
352
353void
354save_core()
355{
356	register FILE *fp;
357	register int bounds, ifd, nr, nw, ofd;
358	char *rawp, path[MAXPATHLEN];
359
360	/*
361	 * Get the current number and update the bounds file.  Do the update
362	 * now, because may fail later and don't want to overwrite anything.
363	 */
364	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
365	if ((fp = fopen(path, "r")) == NULL)
366		goto err1;
367	if (fgets(buf, sizeof(buf), fp) == NULL) {
368		if (ferror(fp))
369err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
370		bounds = 0;
371	} else
372		bounds = atoi(buf);
373	if (fp != NULL)
374		(void)fclose(fp);
375	if ((fp = fopen(path, "w")) == NULL)
376		syslog(LOG_ERR, "%s: %m", path);
377	else {
378		(void)fprintf(fp, "%d\n", bounds + 1);
379		(void)fclose(fp);
380	}
381	(void)fclose(fp);
382
383	/* Create the core file. */
384	(void)snprintf(path, sizeof(path), "%s/netbsd.%d.core%s",
385	    dirname, bounds, compress ? ".Z" : "");
386	if (compress) {
387		if ((fp = zopen(path, "w", 0)) == NULL) {
388			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
389			exit(1);
390		}
391	} else {
392		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
393		fp  = fdopen(ofd, "w");
394		if (fp == NULL) {
395			syslog(LOG_ERR, "%s: fdopen: %s", path);
396			exit(1);
397		}
398	}
399
400	/* Open the raw device. */
401	rawp = rawname(ddname);
402	if ((ifd = open(rawp, O_RDONLY)) == -1) {
403		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
404		ifd = dumpfd;
405	}
406
407	/* Seek to the start of the core. */
408	Lseek(ifd, (off_t)dumplo, L_SET);
409
410	if (kvm_dump_wrtheader(kd_dump, fp, dumpsize) == -1) {
411		syslog(LOG_ERR, "kvm_dump_wrtheader: %s : %s", path,
412			kvm_geterr(kd_dump));
413		exit(1);
414	}
415
416	/* Copy the core file. */
417	syslog(LOG_NOTICE, "writing %score to %s",
418	    compress ? "compressed " : "", path);
419	for (; dumpsize > 0; dumpsize -= nr) {
420		(void)printf("%6dK\r", dumpsize / 1024);
421		(void)fflush(stdout);
422		nr = read(ifd, buf, MIN(dumpsize, sizeof(buf)));
423		if (nr <= 0) {
424			if (nr == 0)
425				syslog(LOG_WARNING,
426				    "WARNING: EOF on dump device");
427			else
428				syslog(LOG_ERR, "%s: %m", rawp);
429			goto err2;
430		}
431		nw = fwrite(buf, 1, nr, fp);
432		if (nw != nr) {
433			syslog(LOG_ERR, "%s: %s",
434			    path, strerror(nw == 0 ? EIO : errno));
435err2:			syslog(LOG_WARNING,
436			    "WARNING: core may be incomplete");
437			(void)printf("\n");
438			exit(1);
439		}
440	}
441	(void)close(ifd);
442	(void)fclose(fp);
443
444	/* Copy the kernel. */
445	ifd = Open(kernel ? kernel : _PATH_UNIX, O_RDONLY);
446	(void)snprintf(path, sizeof(path), "%s/netbsd.%d%s",
447	    dirname, bounds, compress ? ".Z" : "");
448	if (compress) {
449		if ((fp = zopen(path, "w", 0)) == NULL) {
450			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
451			exit(1);
452		}
453	} else
454		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
455	syslog(LOG_NOTICE, "writing %skernel to %s",
456	    compress ? "compressed " : "", path);
457	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
458		if (compress)
459			nw = fwrite(buf, 1, nr, fp);
460		else
461			nw = write(ofd, buf, nr);
462		if (nw != nr) {
463			syslog(LOG_ERR, "%s: %s",
464			    path, strerror(nw == 0 ? EIO : errno));
465			syslog(LOG_WARNING,
466			    "WARNING: kernel may be incomplete");
467			exit(1);
468		}
469	}
470	if (nr < 0) {
471		syslog(LOG_ERR, "%s: %s",
472		    kernel ? kernel : _PATH_UNIX, strerror(errno));
473		syslog(LOG_WARNING,
474		    "WARNING: kernel may be incomplete");
475		exit(1);
476	}
477	if (compress)
478		(void)fclose(fp);
479	else
480		(void)close(ofd);
481}
482
483char *
484find_dev(dev, type)
485	register dev_t dev;
486	register int type;
487{
488	register DIR *dfd;
489	struct dirent *dir;
490	struct stat sb;
491	char *dp, devname[MAXPATHLEN + 1];
492
493	if ((dfd = opendir(_PATH_DEV)) == NULL) {
494		syslog(LOG_ERR, "%s: %s", _PATH_DEV, strerror(errno));
495		exit(1);
496	}
497	(void)strcpy(devname, _PATH_DEV);
498	while ((dir = readdir(dfd))) {
499		(void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name);
500		if (lstat(devname, &sb)) {
501			syslog(LOG_ERR, "%s: %s", devname, strerror(errno));
502			continue;
503		}
504		if ((sb.st_mode & S_IFMT) != type)
505			continue;
506		if (dev == sb.st_rdev) {
507			closedir(dfd);
508			if ((dp = strdup(devname)) == NULL) {
509				syslog(LOG_ERR, "%s", strerror(errno));
510				exit(1);
511			}
512			return (dp);
513		}
514	}
515	closedir(dfd);
516	syslog(LOG_ERR, "can't find device %d/%d", major(dev), minor(dev));
517	exit(1);
518}
519
520char *
521rawname(s)
522	char *s;
523{
524	char *sl, name[MAXPATHLEN];
525
526	if ((sl = strrchr(s, '/')) == NULL || sl[1] == '0') {
527		syslog(LOG_ERR,
528		    "can't make raw dump device name from %s", s);
529		return (s);
530	}
531	(void)snprintf(name, sizeof(name), "%.*s/r%s", sl - s, s, sl + 1);
532	if ((sl = strdup(name)) == NULL) {
533		syslog(LOG_ERR, "%s", strerror(errno));
534		exit(1);
535	}
536	return (sl);
537}
538
539int
540get_crashtime()
541{
542	time_t dumptime;			/* Time the dump was taken. */
543
544	KREAD(kd_dump, dump_nl[X_TIME].n_value, &dumptime);
545	if (dumptime == 0) {
546		if (verbose)
547			syslog(LOG_ERR, "dump time is zero");
548		return (0);
549	}
550	(void)printf("savecore: system went down at %s", ctime(&dumptime));
551#define	LEEWAY	(7 * SECSPERDAY)
552	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
553		(void)printf("dump time is unreasonable\n");
554		return (0);
555	}
556	return (1);
557}
558
559int
560check_space()
561{
562	register FILE *fp;
563	char *tkernel;
564	off_t minfree, spacefree, kernelsize, needed;
565	struct stat st;
566	struct statfs fsbuf;
567	char buf[100], path[MAXPATHLEN];
568
569	tkernel = kernel ? kernel : _PATH_UNIX;
570	if (stat(tkernel, &st) < 0) {
571		syslog(LOG_ERR, "%s: %m", tkernel);
572		exit(1);
573	}
574	kernelsize = st.st_blocks * S_BLKSIZE;
575	if (statfs(dirname, &fsbuf) < 0) {
576		syslog(LOG_ERR, "%s: %m", dirname);
577		exit(1);
578	}
579 	spacefree = (fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
580
581	(void)snprintf(path, sizeof(path), "%s/minfree", dirname);
582	if ((fp = fopen(path, "r")) == NULL)
583		minfree = 0;
584	else {
585		if (fgets(buf, sizeof(buf), fp) == NULL)
586			minfree = 0;
587		else
588			minfree = atoi(buf);
589		(void)fclose(fp);
590	}
591
592	needed = (dumpsize + kernelsize) / 1024;
593 	if (minfree > 0 && spacefree - needed < minfree) {
594		syslog(LOG_WARNING,
595		    "no dump, not enough free space on device");
596		return (0);
597	}
598	if (spacefree - needed < minfree)
599		syslog(LOG_WARNING,
600		    "dump performed, but free space threshold crossed");
601	return (1);
602}
603
604int
605Open(name, rw)
606	char *name;
607	int rw;
608{
609	int fd;
610
611	if ((fd = open(name, rw, 0)) < 0) {
612		syslog(LOG_ERR, "%s: %m", name);
613		exit(1);
614	}
615	return (fd);
616}
617
618void
619Lseek(fd, off, flag)
620	int fd, flag;
621	off_t off;
622{
623	off_t ret;
624
625	ret = lseek(fd, off, flag);
626	if (ret == -1) {
627		syslog(LOG_ERR, "lseek: %m");
628		exit(1);
629	}
630}
631
632int
633Create(file, mode)
634	char *file;
635	int mode;
636{
637	register int fd;
638
639	fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode);
640	if (fd < 0) {
641		syslog(LOG_ERR, "%s: %m", file);
642		exit(1);
643	}
644	return (fd);
645}
646
647void
648Write(fd, bp, size)
649	int fd, size;
650	void *bp;
651{
652	int n;
653
654	if ((n = write(fd, bp, size)) < size) {
655		syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO));
656		exit(1);
657	}
658}
659
660void
661usage()
662{
663	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
664	exit(1);
665}
666