savecore.c revision 1.20
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.20 1995/01/04 05:30:07 mycroft 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, "cdfN:vz")) != -1)
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
324void
325save_core()
326{
327	register FILE *fp;
328	register int bounds, ifd, nr, nw, ofd;
329	char *rawp, path[MAXPATHLEN];
330
331	/*
332	 * Get the current number and update the bounds file.  Do the update
333	 * now, because may fail later and don't want to overwrite anything.
334	 */
335	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
336	if ((fp = fopen(path, "r")) == NULL)
337		goto err1;
338	if (fgets(buf, sizeof(buf), fp) == NULL) {
339		if (ferror(fp))
340err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
341		bounds = 0;
342	} else
343		bounds = atoi(buf);
344	if (fp != NULL)
345		(void)fclose(fp);
346	if ((fp = fopen(path, "w")) == NULL)
347		syslog(LOG_ERR, "%s: %m", path);
348	else {
349		(void)fprintf(fp, "%d\n", bounds + 1);
350		(void)fclose(fp);
351	}
352	(void)fclose(fp);
353
354	/* Create the core file. */
355	(void)snprintf(path, sizeof(path), "%s/netbsd.%d%s.core",
356	    dirname, bounds, compress ? ".Z" : "");
357	if (compress) {
358		if ((fp = zopen(path, "w", 0)) == NULL) {
359			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
360			exit(1);
361		}
362	} else
363		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
364
365	/* Open the raw device. */
366	rawp = rawname(ddname);
367	if ((ifd = open(rawp, O_RDONLY)) == -1) {
368		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
369		ifd = dumpfd;
370	}
371
372	/* Read the dump size. */
373	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPSIZE].n_value)), L_SET);
374	(void)Read(dumpfd, &dumpsize, sizeof(dumpsize));
375
376	/* Seek to the start of the core. */
377	Lseek(ifd, (off_t)dumplo, L_SET);
378
379	/* Copy the core file. */
380	dumpsize *= getpagesize();
381	syslog(LOG_NOTICE, "writing %score to %s",
382	    compress ? "compressed " : "", path);
383	for (; dumpsize > 0; dumpsize -= nr) {
384		(void)printf("%6dK\r", dumpsize / 1024);
385		(void)fflush(stdout);
386		nr = read(ifd, buf, MIN(dumpsize, sizeof(buf)));
387		if (nr <= 0) {
388			if (nr == 0)
389				syslog(LOG_WARNING,
390				    "WARNING: EOF on dump device");
391			else
392				syslog(LOG_ERR, "%s: %m", rawp);
393			goto err2;
394		}
395		if (compress)
396			nw = fwrite(buf, 1, nr, fp);
397		else
398			nw = write(ofd, buf, nr);
399		if (nw != nr) {
400			syslog(LOG_ERR, "%s: %s",
401			    path, strerror(nw == 0 ? EIO : errno));
402err2:			syslog(LOG_WARNING,
403			    "WARNING: core may be incomplete");
404			(void)printf("\n");
405			exit(1);
406		}
407	}
408	(void)printf("\n");
409	(void)close(ifd);
410	if (compress)
411		(void)fclose(fp);
412	else
413		(void)close(ofd);
414
415	/* Copy the kernel. */
416	ifd = Open(kernel ? kernel : _PATH_UNIX, O_RDONLY);
417	(void)snprintf(path, sizeof(path), "%s/netbsd.%d%s",
418	    dirname, bounds, compress ? ".Z" : "");
419	if (compress) {
420		if ((fp = zopen(path, "w", 0)) == NULL) {
421			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
422			exit(1);
423		}
424	} else
425		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
426	syslog(LOG_NOTICE, "writing %skernel to %s",
427	    compress ? "compressed " : "", path);
428	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
429		if (compress)
430			nw = fwrite(buf, 1, nr, fp);
431		else
432			nw = write(ofd, buf, nr);
433		if (nw != nr) {
434			syslog(LOG_ERR, "%s: %s",
435			    path, strerror(nw == 0 ? EIO : errno));
436			syslog(LOG_WARNING,
437			    "WARNING: kernel may be incomplete");
438			exit(1);
439		}
440	}
441	if (nr < 0) {
442		syslog(LOG_ERR, "%s: %s",
443		    kernel ? kernel : _PATH_UNIX, strerror(errno));
444		syslog(LOG_WARNING,
445		    "WARNING: kernel may be incomplete");
446		exit(1);
447	}
448	if (compress)
449		(void)fclose(fp);
450	else
451		(void)close(ofd);
452}
453
454char *
455find_dev(dev, type)
456	register dev_t dev;
457	register int type;
458{
459	register DIR *dfd;
460	struct dirent *dir;
461	struct stat sb;
462	char *dp, devname[MAXPATHLEN + 1];
463
464	if ((dfd = opendir(_PATH_DEV)) == NULL) {
465		syslog(LOG_ERR, "%s: %s", _PATH_DEV, strerror(errno));
466		exit(1);
467	}
468	(void)strcpy(devname, _PATH_DEV);
469	while ((dir = readdir(dfd))) {
470		(void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name);
471		if (lstat(devname, &sb)) {
472			syslog(LOG_ERR, "%s: %s", devname, strerror(errno));
473			continue;
474		}
475		if ((sb.st_mode & S_IFMT) != type)
476			continue;
477		if (dev == sb.st_rdev) {
478			closedir(dfd);
479			if ((dp = strdup(devname)) == NULL) {
480				syslog(LOG_ERR, "%s", strerror(errno));
481				exit(1);
482			}
483			return (dp);
484		}
485	}
486	closedir(dfd);
487	syslog(LOG_ERR, "can't find device %d/%d", major(dev), minor(dev));
488	exit(1);
489}
490
491char *
492rawname(s)
493	char *s;
494{
495	char *sl, name[MAXPATHLEN];
496
497	if ((sl = strrchr(s, '/')) == NULL || sl[1] == '0') {
498		syslog(LOG_ERR,
499		    "can't make raw dump device name from %s", s);
500		return (s);
501	}
502	(void)snprintf(name, sizeof(name), "%.*s/r%s", sl - s, s, sl + 1);
503	if ((sl = strdup(name)) == NULL) {
504		syslog(LOG_ERR, "%s", strerror(errno));
505		exit(1);
506	}
507	return (sl);
508}
509
510int
511get_crashtime()
512{
513	time_t dumptime;			/* Time the dump was taken. */
514
515	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_TIME].n_value)), L_SET);
516	(void)Read(dumpfd, &dumptime, sizeof(dumptime));
517	if (dumptime == 0) {
518		if (verbose)
519			syslog(LOG_ERR, "dump time is zero");
520		return (0);
521	}
522	(void)printf("savecore: system went down at %s", ctime(&dumptime));
523#define	LEEWAY	(7 * SECSPERDAY)
524	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
525		(void)printf("dump time is unreasonable\n");
526		return (0);
527	}
528	return (1);
529}
530
531int
532check_space()
533{
534	register FILE *fp;
535	char *tkernel;
536	off_t minfree, spacefree, kernelsize, needed;
537	struct stat st;
538	struct statfs fsbuf;
539	char buf[100], path[MAXPATHLEN];
540
541	tkernel = kernel ? kernel : _PATH_UNIX;
542	if (stat(tkernel, &st) < 0) {
543		syslog(LOG_ERR, "%s: %m", tkernel);
544		exit(1);
545	}
546	kernelsize = st.st_blocks * S_BLKSIZE;
547	if (statfs(dirname, &fsbuf) < 0) {
548		syslog(LOG_ERR, "%s: %m", dirname);
549		exit(1);
550	}
551 	spacefree = (fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
552
553	(void)snprintf(path, sizeof(path), "%s/minfree", dirname);
554	if ((fp = fopen(path, "r")) == NULL)
555		minfree = 0;
556	else {
557		if (fgets(buf, sizeof(buf), fp) == NULL)
558			minfree = 0;
559		else
560			minfree = atoi(buf);
561		(void)fclose(fp);
562	}
563
564	needed = (dumpsize + kernelsize) / 1024;
565 	if (minfree > 0 && spacefree - needed < minfree) {
566		syslog(LOG_WARNING,
567		    "no dump, not enough free space on device");
568		return (0);
569	}
570	if (spacefree - needed < minfree)
571		syslog(LOG_WARNING,
572		    "dump performed, but free space threshold crossed");
573	return (1);
574}
575
576int
577Open(name, rw)
578	char *name;
579	int rw;
580{
581	int fd;
582
583	if ((fd = open(name, rw, 0)) < 0) {
584		syslog(LOG_ERR, "%s: %m", name);
585		exit(1);
586	}
587	return (fd);
588}
589
590int
591Read(fd, bp, size)
592	int fd, size;
593	void *bp;
594{
595	int nr;
596
597	nr = read(fd, bp, size);
598	if (nr != size) {
599		syslog(LOG_ERR, "read: %m");
600		exit(1);
601	}
602	return (nr);
603}
604
605void
606Lseek(fd, off, flag)
607	int fd, flag;
608	off_t off;
609{
610	off_t ret;
611
612	ret = lseek(fd, off, flag);
613	if (ret == -1) {
614		syslog(LOG_ERR, "lseek: %m");
615		exit(1);
616	}
617}
618
619int
620Create(file, mode)
621	char *file;
622	int mode;
623{
624	register int fd;
625
626	fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode);
627	if (fd < 0) {
628		syslog(LOG_ERR, "%s: %m", file);
629		exit(1);
630	}
631	return (fd);
632}
633
634void
635Write(fd, bp, size)
636	int fd, size;
637	void *bp;
638{
639	int n;
640
641	if ((n = write(fd, bp, size)) < size) {
642		syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO));
643		exit(1);
644	}
645}
646
647void
648usage()
649{
650	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
651	exit(1);
652}
653