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