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