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