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