xref: /original-bsd/sbin/savecore/savecore.c (revision 753853ba)
1 /*
2  * Copyright (c) 1980, 1986, 1989 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 char copyright[] =
10 "@(#) Copyright (c) 1980, 1986, 1989 The Regents of the University of California.\n\
11  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)savecore.c	5.28 (Berkeley) 02/18/92";
16 #endif /* not lint */
17 
18 #include <sys/param.h>
19 #include <sys/mount.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22 #include <sys/file.h>
23 #include <sys/syslog.h>
24 #include <dirent.h>
25 #include <stdio.h>
26 #include <nlist.h>
27 #include <paths.h>
28 
29 #define	DAY	(60L*60L*24L)
30 #define	LEEWAY	(3*DAY)
31 
32 #define eq(a,b) (!strcmp(a,b))
33 #define ok(number) ((number) - KERNBASE)
34 
35 struct nlist current_nl[] = {	/* namelist for currently running system */
36 #define X_DUMPDEV	0
37 	{ "_dumpdev" },
38 #define X_DUMPLO	1
39 	{ "_dumplo" },
40 #define X_TIME		2
41 	{ "_time" },
42 #define	X_DUMPSIZE	3
43 	{ "_dumpsize" },
44 #define X_VERSION	4
45 	{ "_version" },
46 #define X_PANICSTR	5
47 	{ "_panicstr" },
48 #define	X_DUMPMAG	6
49 	{ "_dumpmag" },
50 	{ "" },
51 };
52 
53 struct nlist dump_nl[] = {	/* name list for dumped system */
54 	{ "_dumpdev" },		/* entries MUST be the same as */
55 	{ "_dumplo" },		/*	those in current_nl[]  */
56 	{ "_time" },
57 	{ "_dumpsize" },
58 	{ "_version" },
59 	{ "_panicstr" },
60 	{ "_dumpmag" },
61 	{ "" },
62 };
63 
64 char	*system;
65 char	*dirname;			/* directory to save dumps in */
66 char	*ddname;			/* name of dump device */
67 int	dumpfd;				/* read/write descriptor on block dev */
68 char	*find_dev();
69 dev_t	dumpdev;			/* dump device */
70 time_t	dumptime;			/* time the dump was taken */
71 int	dumplo;				/* where dump starts on dumpdev */
72 int	dumpsize;			/* amount of memory dumped */
73 int	dumpmag;			/* magic number in dump */
74 time_t	now;				/* current date */
75 char	*path();
76 char	*malloc();
77 char	*ctime();
78 char	vers[80];
79 char	core_vers[80];
80 char	panic_mesg[80];
81 int	panicstr;
82 off_t	lseek();
83 off_t	Lseek();
84 int	verbose;
85 int	force;
86 int	clear;
87 extern	int errno;
88 
89 main(argc, argv)
90 	char **argv;
91 	int argc;
92 {
93 	extern char *optarg;
94 	extern int optind;
95 	int ch;
96 	char *cp;
97 
98 	while ((ch = getopt(argc, argv, "cdfv")) != EOF)
99 		switch(ch) {
100 		case 'c':
101 			clear = 1;
102 			break;
103 		case 'd':		/* not documented */
104 		case 'v':
105 			verbose = 1;
106 			break;
107 		case 'f':
108 			force = 1;
109 			break;
110 		case '?':
111 		default:
112 			usage();
113 		}
114 	argc -= optind;
115 	argv += optind;
116 
117 	/* This is wrong, but I want "savecore -c" to work. */
118 	if (!clear) {
119 		if (argc != 1 && argc != 2)
120 			usage();
121 		dirname = argv[0];
122 	}
123 	if (argc == 2)
124 		system = argv[1];
125 
126 	openlog("savecore", LOG_ODELAY, LOG_AUTH);
127 
128 	read_kmem();
129 	if (!dump_exists()) {
130 		(void)fprintf(stderr, "savecore: no core dump\n");
131 		if (!force)
132 			exit(0);
133 	}
134 	if (clear) {
135 		clear_dump();
136 		exit(0);
137 	}
138 	(void) time(&now);
139 	check_kmem();
140 	if (panicstr)
141 		log(LOG_CRIT, "reboot after panic: %s\n", panic_mesg);
142 	else
143 		syslog(LOG_CRIT, "reboot\n");
144 
145 	if (access(dirname, W_OK) < 0) {
146 		Perror(LOG_ERR, "%s: %m\n", dirname);
147 		exit(1);
148 	}
149 	if ((!get_crashtime() || !check_space()) && !force)
150 		exit(1);
151 	save_core();
152 	clear_dump();
153 	exit(0);
154 }
155 
156 dump_exists()
157 {
158 	int word;
159 
160 	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET);
161 	Read(dumpfd, (char *)&word, sizeof (word));
162 	if (verbose && word != dumpmag)
163 		printf("magic number mismatch: %x != %x\n", word, dumpmag);
164 	return (word == dumpmag);
165 }
166 
167 clear_dump()
168 {
169 	int zero = 0;
170 
171 	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET);
172 	Write(dumpfd, (char *)&zero, sizeof (zero));
173 }
174 
175 char *
176 find_dev(dev, type)
177 	register dev_t dev;
178 	register int type;
179 {
180 	register DIR *dfd = opendir(_PATH_DEV);
181 	struct dirent *dir;
182 	struct stat statb;
183 	static char devname[MAXPATHLEN + 1];
184 	char *dp;
185 
186 	strcpy(devname, _PATH_DEV);
187 	while ((dir = readdir(dfd))) {
188 		strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name);
189 		if (stat(devname, &statb)) {
190 			perror(devname);
191 			continue;
192 		}
193 		if ((statb.st_mode&S_IFMT) != type)
194 			continue;
195 		if (dev == statb.st_rdev) {
196 			closedir(dfd);
197 			dp = malloc(strlen(devname)+1);
198 			strcpy(dp, devname);
199 			return (dp);
200 		}
201 	}
202 	closedir(dfd);
203 	log(LOG_ERR, "Can't find device %d/%d\n", major(dev), minor(dev));
204 	exit(1);
205 	/*NOTREACHED*/
206 }
207 
208 char *
209 rawname(s)
210 	char *s;
211 {
212 	static char name[MAXPATHLEN];
213 	char *sl, *rindex();
214 
215 	if ((sl = rindex(s, '/')) == NULL || sl[1] == '0') {
216 		log(LOG_ERR, "can't make raw dump device name from %s?\n", s);
217 		return (s);
218 	}
219 	sprintf(name, "%.*s/r%s", sl - s, s, sl + 1);
220 	return (name);
221 }
222 
223 int	cursyms[] =
224     { X_DUMPDEV, X_DUMPLO, X_VERSION, X_DUMPMAG, -1 };
225 int	dumpsyms[] =
226     { X_TIME, X_DUMPSIZE, X_VERSION, X_PANICSTR, X_DUMPMAG, -1 };
227 read_kmem()
228 {
229 	register char *cp;
230 	FILE *fp;
231 	char *dump_sys;
232 	int kmem, i;
233 
234 	dump_sys = system ? system : _PATH_UNIX;
235 	nlist(_PATH_UNIX, current_nl);
236 	nlist(dump_sys, dump_nl);
237 	/*
238 	 * Some names we need for the currently running system,
239 	 * others for the system that was running when the dump was made.
240 	 * The values obtained from the current system are used
241 	 * to look for things in /dev/kmem that cannot be found
242 	 * in the dump_sys namelist, but are presumed to be the same
243 	 * (since the disk partitions are probably the same!)
244 	 */
245 	for (i = 0; cursyms[i] != -1; i++)
246 		if (current_nl[cursyms[i]].n_value == 0) {
247 			log(LOG_ERR, "%s: %s not in namelist\n", _PATH_UNIX,
248 			    current_nl[cursyms[i]].n_name);
249 			exit(1);
250 		}
251 	for (i = 0; dumpsyms[i] != -1; i++)
252 		if (dump_nl[dumpsyms[i]].n_value == 0) {
253 			log(LOG_ERR, "%s: %s not in namelist\n", dump_sys,
254 			    dump_nl[dumpsyms[i]].n_name);
255 			exit(1);
256 		}
257 	kmem = Open(_PATH_KMEM, O_RDONLY);
258 	Lseek(kmem, (long)current_nl[X_DUMPDEV].n_value, L_SET);
259 	Read(kmem, (char *)&dumpdev, sizeof (dumpdev));
260 	Lseek(kmem, (long)current_nl[X_DUMPLO].n_value, L_SET);
261 	Read(kmem, (char *)&dumplo, sizeof (dumplo));
262 	if (verbose)
263 		printf("dumplo = %d (%d * %d)\n", dumplo, dumplo/DEV_BSIZE,
264 		    DEV_BSIZE);
265 	Lseek(kmem, (long)current_nl[X_DUMPMAG].n_value, L_SET);
266 	Read(kmem, (char *)&dumpmag, sizeof (dumpmag));
267 	dumplo *= DEV_BSIZE;
268 	ddname = find_dev(dumpdev, S_IFBLK);
269 	dumpfd = Open(ddname, O_RDWR);
270 	fp = fdopen(kmem, "r");
271 	if (fp == NULL) {
272 		log(LOG_ERR, "Couldn't fdopen kmem\n");
273 		exit(1);
274 	}
275 	if (system)
276 		return;
277 	fseek(fp, (long)current_nl[X_VERSION].n_value, L_SET);
278 	fgets(vers, sizeof (vers), fp);
279 	(void)fclose(fp);
280 }
281 
282 check_kmem()
283 {
284 	FILE *fp;
285 	register char *cp;
286 
287 	fp = fdopen(dumpfd, "r");
288 	if (fp == NULL) {
289 		log(LOG_ERR, "Can't fdopen dumpfd\n");
290 		exit(1);
291 	}
292 	fseek(fp, (off_t)(dumplo+ok(dump_nl[X_VERSION].n_value)), L_SET);
293 	fgets(core_vers, sizeof (core_vers), fp);
294 	if (!eq(vers, core_vers) && system == 0) {
295 		log(LOG_WARNING, "Warning: %s version mismatch:\n", _PATH_UNIX);
296 		log(LOG_WARNING, "\t%s\n", vers);
297 		log(LOG_WARNING, "and\t%s\n", core_vers);
298 	}
299 	fseek(fp, (off_t)(dumplo + ok(dump_nl[X_PANICSTR].n_value)), L_SET);
300 	fread((char *)&panicstr, sizeof (panicstr), 1, fp);
301 	if (panicstr) {
302 		fseek(fp, dumplo + ok(panicstr), L_SET);
303 		cp = panic_mesg;
304 		do
305 			*cp = getc(fp);
306 		while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)]);
307 	}
308 	/* don't fclose(fp); we want the file descriptor */
309 }
310 
311 get_crashtime()
312 {
313 	time_t clobber = (time_t)0;
314 
315 	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_TIME].n_value)), L_SET);
316 	Read(dumpfd, (char *)&dumptime, sizeof dumptime);
317 	if (dumptime == 0) {
318 		if (verbose)
319 			printf("Dump time is zero.\n");
320 		return (0);
321 	}
322 	printf("System went down at %s", ctime(&dumptime));
323 	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
324 		printf("dump time is unreasonable\n");
325 		return (0);
326 	}
327 	return (1);
328 }
329 
330 char *
331 path(file)
332 	char *file;
333 {
334 	register char *cp = malloc(strlen(file) + strlen(dirname) + 2);
335 
336 	(void) strcpy(cp, dirname);
337 	(void) strcat(cp, "/");
338 	(void) strcat(cp, file);
339 	return (cp);
340 }
341 
342 check_space()
343 {
344 	long minfree, spacefree;
345 	struct statfs fsbuf;
346 
347 	if (statfs(dirname, &fsbuf) < 0) {
348 		Perror(LOG_ERR, "%s: %m\n", dirname);
349 		exit(1);
350 	}
351  	spacefree = fsbuf.f_bavail * fsbuf.f_bsize / 1024;
352 	minfree = read_number("minfree");
353  	if (minfree > 0 && spacefree - dumpsize < minfree) {
354 		log(LOG_WARNING, "Dump omitted, not enough space on device\n");
355 		return (0);
356 	}
357 	if (spacefree - dumpsize < minfree)
358 		log(LOG_WARNING,
359 		    "Dump performed, but free space threshold crossed\n");
360 	return (1);
361 }
362 
363 read_number(fn)
364 	char *fn;
365 {
366 	char lin[80];
367 	register FILE *fp;
368 
369 	fp = fopen(path(fn), "r");
370 	if (fp == NULL)
371 		return (0);
372 	if (fgets(lin, 80, fp) == NULL) {
373 		(void)fclose(fp);
374 		return (0);
375 	}
376 	(void)fclose(fp);
377 	return (atoi(lin));
378 }
379 
380 #define	BUFSIZE		(256*1024)		/* 1/4 Mb */
381 
382 save_core()
383 {
384 	register int n;
385 	register char *cp;
386 	register int ifd, ofd, bounds;
387 	int ret;
388 	char *bfile;
389 	register FILE *fp;
390 
391 	cp = malloc(BUFSIZE);
392 	if (cp == 0) {
393 		log(LOG_ERR, "savecore: Can't allocate i/o buffer.\n");
394 		return;
395 	}
396 	bounds = read_number("bounds");
397 	ifd = Open(system ? system : _PATH_UNIX, O_RDONLY);
398 	(void)sprintf(cp, "vmunix.%d", bounds);
399 	ofd = Create(path(cp), 0644);
400 	while((n = Read(ifd, cp, BUFSIZE)) > 0)
401 		Write(ofd, cp, n);
402 	close(ifd);
403 	close(ofd);
404 	if ((ifd = open(rawname(ddname), O_RDONLY)) == -1) {
405 		log(LOG_WARNING, "Can't open %s (%m); using block device",
406 			rawname(ddname));
407 		ifd = dumpfd;
408 	}
409 	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPSIZE].n_value)), L_SET);
410 	Read(dumpfd, (char *)&dumpsize, sizeof (dumpsize));
411 	(void)sprintf(cp, "vmcore.%d", bounds);
412 	ofd = Create(path(cp), 0644);
413 	Lseek(ifd, (off_t)dumplo, L_SET);
414 	dumpsize *= NBPG;
415 	log(LOG_NOTICE, "Saving %d bytes of image in vmcore.%d\n",
416 	    dumpsize, bounds);
417 	while (dumpsize > 0) {
418 		n = read(ifd, cp,
419 		    dumpsize > BUFSIZE ? BUFSIZE : dumpsize);
420 		if (n <= 0) {
421 			if (n == 0)
422 				log(LOG_WARNING,
423 				    "WARNING: EOF on dump device; %s\n",
424 				    "vmcore may be incomplete");
425 			else
426 				Perror(LOG_ERR, "read from dumpdev: %m",
427 				    "read");
428 			break;
429 		}
430 		if ((ret = write(ofd, cp, n)) < n) {
431 			if (ret < 0)
432 				Perror(LOG_ERR, "write: %m", "write");
433 			else
434 				log(LOG_ERR, "short write: wrote %d of %d\n",
435 				    ret, n);
436 			log(LOG_WARNING, "WARNING: vmcore may be incomplete\n");
437 			break;
438 		}
439 		dumpsize -= n;
440 	}
441 	close(ifd);
442 	close(ofd);
443 	bfile = path("bounds");
444 	fp = fopen(bfile, "w");
445 	if (fp) {
446 		(void)fprintf(fp, "%d\n", bounds+1);
447 		(void)fclose(fp);
448 	} else
449 		Perror(LOG_ERR, "Can't create bounds file %s: %m", bfile);
450 	free(cp);
451 }
452 
453 /*
454  * Versions of std routines that exit on error.
455  */
456 Open(name, rw)
457 	char *name;
458 	int rw;
459 {
460 	int fd;
461 
462 	fd = open(name, rw);
463 	if (fd < 0) {
464 		Perror(LOG_ERR, "%s: %m", name);
465 		exit(1);
466 	}
467 	return (fd);
468 }
469 
470 Read(fd, buff, size)
471 	int fd, size;
472 	char *buff;
473 {
474 	int ret;
475 
476 	ret = read(fd, buff, size);
477 	if (ret < 0) {
478 		Perror(LOG_ERR, "read: %m", "read");
479 		exit(1);
480 	}
481 	return (ret);
482 }
483 
484 off_t
485 Lseek(fd, off, flag)
486 	int fd, flag;
487 	long off;
488 {
489 	long ret;
490 
491 	ret = lseek(fd, off, flag);
492 	if (ret == -1) {
493 		Perror(LOG_ERR, "lseek: %m", "lseek");
494 		exit(1);
495 	}
496 	return (ret);
497 }
498 
499 Create(file, mode)
500 	char *file;
501 	int mode;
502 {
503 	register int fd;
504 
505 	fd = creat(file, mode);
506 	if (fd < 0) {
507 		Perror(LOG_ERR, "%s: %m", file);
508 		exit(1);
509 	}
510 	return (fd);
511 }
512 
513 Write(fd, buf, size)
514 	int fd, size;
515 	char *buf;
516 {
517 	int n;
518 
519 	if ((n = write(fd, buf, size)) < size) {
520 		if (n < 0)
521 			Perror(LOG_ERR, "write: %m", "write");
522 		else
523 			log(LOG_ERR, "short write: wrote %d of %d\n", n, size);
524 		exit(1);
525 	}
526 }
527 
528 /* VARARGS2 */
529 log(level, msg, a1, a2)
530 	int level;
531 	char *msg;
532 {
533 
534 	(void)fprintf(stderr, msg, a1, a2);
535 	syslog(level, msg, a1, a2);
536 }
537 
538 Perror(level, msg, s)
539 	int level;
540 	char *msg, *s;
541 {
542 	int oerrno = errno;
543 
544 	perror(s);
545 	errno = oerrno;
546 	syslog(level, msg, s);
547 }
548 
549 usage()
550 {
551 	(void)fprintf(stderr, "usage: savecore [-cfv] dirname [system]\n");
552 	exit(1);
553 }
554