xref: /original-bsd/sbin/savecore/savecore.c (revision 95ecee29)
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.2 (Berkeley) 11/24/93";
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, "cdfNvz")) != 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 	Lseek(kmem, (off_t)current_nl[X_DUMPLO].n_value, L_SET);
209 	(void)Read(kmem, &dumplo, sizeof(dumplo));
210 	if (verbose)
211 		(void)printf("dumplo = %d (%d * %d)\n",
212 		    dumplo, dumplo/DEV_BSIZE, DEV_BSIZE);
213 	Lseek(kmem, (off_t)current_nl[X_DUMPMAG].n_value, L_SET);
214 	(void)Read(kmem, &dumpmag, sizeof(dumpmag));
215 	dumplo *= DEV_BSIZE;
216 	ddname = find_dev(dumpdev, S_IFBLK);
217 	dumpfd = Open(ddname, O_RDWR);
218 	fp = fdopen(kmem, "r");
219 	if (fp == NULL) {
220 		syslog(LOG_ERR, "%s: fdopen: %m", _PATH_KMEM);
221 		exit(1);
222 	}
223 	if (vmunix)
224 		return;
225 	(void)fseek(fp, (off_t)current_nl[X_VERSION].n_value, L_SET);
226 	(void)fgets(vers, sizeof(vers), fp);
227 
228 	/* Don't fclose(fp), we use dumpfd later. */
229 }
230 
231 void
232 check_kmem()
233 {
234 	register char *cp;
235 	FILE *fp;
236 	char core_vers[1024];
237 
238 	fp = fdopen(dumpfd, "r");
239 	if (fp == NULL) {
240 		syslog(LOG_ERR, "%s: fdopen: %m", ddname);
241 		exit(1);
242 	}
243 	fseek(fp, (off_t)(dumplo + ok(dump_nl[X_VERSION].n_value)), L_SET);
244 	fgets(core_vers, sizeof(core_vers), fp);
245 	if (strcmp(vers, core_vers) && vmunix == 0)
246 		syslog(LOG_WARNING,
247 		    "warning: %s version mismatch:\n\t%s\nand\t%s\n",
248 		    _PATH_UNIX, vers, core_vers);
249 	(void)fseek(fp,
250 	    (off_t)(dumplo + ok(dump_nl[X_PANICSTR].n_value)), L_SET);
251 	(void)fread(&panicstr, sizeof(panicstr), 1, fp);
252 	if (panicstr) {
253 		(void)fseek(fp, dumplo + ok(panicstr), L_SET);
254 		cp = panic_mesg;
255 		do
256 			*cp = getc(fp);
257 		while (*cp++ && cp < &panic_mesg[sizeof(panic_mesg)]);
258 	}
259 	/* Don't fclose(fp), we use dumpfd later. */
260 }
261 
262 void
263 clear_dump()
264 {
265 	long newdumplo;
266 
267 	newdumplo = 0;
268 	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET);
269 	Write(dumpfd, &newdumplo, sizeof(newdumplo));
270 }
271 
272 int
273 dump_exists()
274 {
275 	int newdumpmag;
276 
277 	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPMAG].n_value)), L_SET);
278 	(void)Read(dumpfd, &newdumpmag, sizeof(newdumpmag));
279 	if (newdumpmag != dumpmag) {
280 		if (verbose)
281 			syslog(LOG_WARNING, "magic number mismatch (%x != %x)",
282 			    newdumpmag, dumpmag);
283 		syslog(LOG_WARNING, "no core dump");
284 		return (0);
285 	}
286 	return (1);
287 }
288 
289 char buf[1024 * 1024];
290 
291 void
292 save_core()
293 {
294 	register FILE *fp;
295 	register int bounds, ifd, nr, nw, ofd;
296 	char *rawp, path[MAXPATHLEN];
297 
298 	/*
299 	 * Get the current number and update the bounds file.  Do the update
300 	 * now, because may fail later and don't want to overwrite anything.
301 	 */
302 	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
303 	if ((fp = fopen(path, "r")) == NULL)
304 		goto err1;
305 	if (fgets(buf, sizeof(buf), fp) == NULL) {
306 		if (ferror(fp))
307 err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
308 		bounds = 0;
309 	} else
310 		bounds = atoi(buf);
311 	if (fp != NULL)
312 		(void)fclose(fp);
313 	if ((fp = fopen(path, "w")) == NULL)
314 		syslog(LOG_ERR, "%s: %m", path);
315 	else {
316 		(void)fprintf(fp, "%d\n", bounds + 1);
317 		(void)fclose(fp);
318 	}
319 	(void)fclose(fp);
320 
321 	/* Create the core file. */
322 	(void)snprintf(path, sizeof(path), "%s/vmcore.%d%s",
323 	    dirname, bounds, compress ? ".Z" : "");
324 	if (compress) {
325 		if ((fp = zopen(path, "w", 0)) == NULL) {
326 			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
327 			exit(1);
328 		}
329 	} else
330 		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
331 
332 	/* Open the raw device. */
333 	rawp = rawname(ddname);
334 	if ((ifd = open(rawp, O_RDONLY)) == -1) {
335 		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
336 		ifd = dumpfd;
337 	}
338 
339 	/* Read the dump size. */
340 	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPSIZE].n_value)), L_SET);
341 	(void)Read(dumpfd, &dumpsize, sizeof(dumpsize));
342 
343 	/* Seek to the start of the core. */
344 	Lseek(ifd, (off_t)dumplo, L_SET);
345 
346 	/* Copy the core file. */
347 	dumpsize *= NBPG;
348 	syslog(LOG_NOTICE, "writing %score to %s",
349 	    compress ? "compressed " : "", path);
350 	for (; dumpsize > 0; dumpsize -= nr) {
351 		(void)printf("%6dK\r", dumpsize / 1024);
352 		(void)fflush(stdout);
353 		nr = read(ifd, buf, MIN(dumpsize, sizeof(buf)));
354 		if (nr <= 0) {
355 			if (nr == 0)
356 				syslog(LOG_WARNING,
357 				    "WARNING: EOF on dump device");
358 			else
359 				syslog(LOG_ERR, "%s: %m", rawp);
360 			goto err2;
361 		}
362 		if (compress)
363 			nw = fwrite(buf, 1, nr, fp);
364 		else
365 			nw = write(ofd, buf, nr);
366 		if (nw != nr) {
367 			syslog(LOG_ERR, "%s: %s",
368 			    path, strerror(nw == 0 ? EIO : errno));
369 err2:			syslog(LOG_WARNING,
370 			    "WARNING: vmcore may be incomplete");
371 			(void)printf("\n");
372 			exit(1);
373 		}
374 	}
375 	(void)printf("\n");
376 	(void)close(ifd);
377 	if (compress)
378 		(void)fclose(fp);
379 	else
380 		(void)close(ofd);
381 
382 	/* Copy the kernel. */
383 	ifd = Open(vmunix ? vmunix : _PATH_UNIX, O_RDONLY);
384 	(void)snprintf(path, sizeof(path), "%s/vmunix.%d%s",
385 	    dirname, bounds, compress ? ".Z" : "");
386 	if (compress) {
387 		if ((fp = zopen(path, "w", 0)) == NULL) {
388 			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
389 			exit(1);
390 		}
391 	} else
392 		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
393 	syslog(LOG_NOTICE, "writing %skernel to %s",
394 	    compress ? "compressed " : "", path);
395 	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
396 		if (compress)
397 			nw = fwrite(buf, 1, nr, fp);
398 		else
399 			nw = write(ofd, buf, nr);
400 		if (nw != nr) {
401 			syslog(LOG_ERR, "%s: %s",
402 			    path, strerror(nw == 0 ? EIO : errno));
403 			syslog(LOG_WARNING,
404 			    "WARNING: vmunix may be incomplete");
405 			exit(1);
406 		}
407 	}
408 	if (nr < 0) {
409 		syslog(LOG_ERR, "%s: %s",
410 		    vmunix ? vmunix : _PATH_UNIX, strerror(errno));
411 		syslog(LOG_WARNING,
412 		    "WARNING: vmunix may be incomplete");
413 		exit(1);
414 	}
415 	if (compress)
416 		(void)fclose(fp);
417 	else
418 		(void)close(ofd);
419 }
420 
421 char *
422 find_dev(dev, type)
423 	register dev_t dev;
424 	register int type;
425 {
426 	register DIR *dfd;
427 	struct dirent *dir;
428 	struct stat sb;
429 	char *dp, devname[MAXPATHLEN + 1];
430 
431 	if ((dfd = opendir(_PATH_DEV)) == NULL) {
432 		syslog(LOG_ERR, "%s: %s", _PATH_DEV, strerror(errno));
433 		exit(1);
434 	}
435 	(void)strcpy(devname, _PATH_DEV);
436 	while ((dir = readdir(dfd))) {
437 		(void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name);
438 		if (stat(devname, &sb)) {
439 			syslog(LOG_ERR, "%s: %s", devname, strerror(errno));
440 			continue;
441 		}
442 		if ((sb.st_mode & S_IFMT) != type)
443 			continue;
444 		if (dev == sb.st_rdev) {
445 			closedir(dfd);
446 			if ((dp = strdup(devname)) == NULL) {
447 				syslog(LOG_ERR, "%s", strerror(errno));
448 				exit(1);
449 			}
450 			return (dp);
451 		}
452 	}
453 	closedir(dfd);
454 	syslog(LOG_ERR, "can't find device %d/%d", major(dev), minor(dev));
455 	exit(1);
456 }
457 
458 char *
459 rawname(s)
460 	char *s;
461 {
462 	char *sl, name[MAXPATHLEN];
463 
464 	if ((sl = rindex(s, '/')) == NULL || sl[1] == '0') {
465 		syslog(LOG_ERR,
466 		    "can't make raw dump device name from %s", s);
467 		return (s);
468 	}
469 	(void)snprintf(name, sizeof(name), "%.*s/r%s", sl - s, s, sl + 1);
470 	if ((sl = strdup(name)) == NULL) {
471 		syslog(LOG_ERR, "%s", strerror(errno));
472 		exit(1);
473 	}
474 	return (sl);
475 }
476 
477 int
478 get_crashtime()
479 {
480 	time_t dumptime;			/* Time the dump was taken. */
481 
482 	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_TIME].n_value)), L_SET);
483 	(void)Read(dumpfd, &dumptime, sizeof(dumptime));
484 	if (dumptime == 0) {
485 		if (verbose)
486 			syslog(LOG_ERR, "dump time is zero");
487 		return (0);
488 	}
489 	(void)printf("savecore: system went down at %s", ctime(&dumptime));
490 #define	LEEWAY	(7 * SECSPERDAY)
491 	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
492 		(void)printf("dump time is unreasonable\n");
493 		return (0);
494 	}
495 	return (1);
496 }
497 
498 int
499 check_space()
500 {
501 	register FILE *fp;
502 	char *tvmunix;
503 	off_t minfree, spacefree, vmunixsize, needed;
504 	struct stat st;
505 	struct statfs fsbuf;
506 	char buf[100], path[MAXPATHLEN];
507 
508 	tvmunix = vmunix ? vmunix : _PATH_UNIX;
509 	if (stat(tvmunix, &st) < 0) {
510 		syslog(LOG_ERR, "%s: %m", tvmunix);
511 		exit(1);
512 	}
513 	vmunixsize = st.st_blocks * S_BLKSIZE;
514 	if (statfs(dirname, &fsbuf) < 0) {
515 		syslog(LOG_ERR, "%s: %m", dirname);
516 		exit(1);
517 	}
518  	spacefree = (fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
519 
520 	(void)snprintf(path, sizeof(path), "%s/minfree", dirname);
521 	if ((fp = fopen(path, "r")) == NULL)
522 		minfree = 0;
523 	else {
524 		if (fgets(buf, sizeof(buf), fp) == NULL)
525 			minfree = 0;
526 		else
527 			minfree = atoi(buf);
528 		(void)fclose(fp);
529 	}
530 
531 	needed = (dumpsize + vmunixsize) / 1024;
532  	if (minfree > 0 && spacefree - needed < minfree) {
533 		syslog(LOG_WARNING,
534 		    "no dump, not enough free space on device");
535 		return (0);
536 	}
537 	if (spacefree - needed < minfree)
538 		syslog(LOG_WARNING,
539 		    "dump performed, but free space threshold crossed");
540 	return (1);
541 }
542 
543 int
544 Open(name, rw)
545 	char *name;
546 	int rw;
547 {
548 	int fd;
549 
550 	if ((fd = open(name, rw, 0)) < 0) {
551 		syslog(LOG_ERR, "%s: %m", name);
552 		exit(1);
553 	}
554 	return (fd);
555 }
556 
557 int
558 Read(fd, bp, size)
559 	int fd, size;
560 	void *bp;
561 {
562 	int nr;
563 
564 	nr = read(fd, bp, size);
565 	if (nr != size) {
566 		syslog(LOG_ERR, "read: %m");
567 		exit(1);
568 	}
569 	return (nr);
570 }
571 
572 void
573 Lseek(fd, off, flag)
574 	int fd, flag;
575 	off_t off;
576 {
577 	off_t ret;
578 
579 	ret = lseek(fd, off, flag);
580 	if (ret == -1) {
581 		syslog(LOG_ERR, "lseek: %m");
582 		exit(1);
583 	}
584 }
585 
586 int
587 Create(file, mode)
588 	char *file;
589 	int mode;
590 {
591 	register int fd;
592 
593 	fd = creat(file, mode);
594 	if (fd < 0) {
595 		syslog(LOG_ERR, "%s: %m", file);
596 		exit(1);
597 	}
598 	return (fd);
599 }
600 
601 void
602 Write(fd, bp, size)
603 	int fd, size;
604 	void *bp;
605 {
606 	int n;
607 
608 	if ((n = write(fd, bp, size)) < size) {
609 		syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO));
610 		exit(1);
611 	}
612 }
613 
614 void
615 usage()
616 {
617 	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
618 	exit(1);
619 }
620