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