savecore.c revision 1.14
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.14 1994/06/11 08:01:17 mycroft 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	*vmunix;
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			vmunix = 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		vmunix = 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 = vmunix ? vmunix : _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 (vmunix)
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) && vmunix == 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
322void
323save_core()
324{
325	register FILE *fp;
326	register int bounds, ifd, nr, nw, ofd;
327	char *rawp, path[MAXPATHLEN];
328
329	/*
330	 * Get the current number and update the bounds file.  Do the update
331	 * now, because may fail later and don't want to overwrite anything.
332	 */
333	(void)snprintf(path, sizeof(path), "%s/bounds", dirname);
334	if ((fp = fopen(path, "r")) == NULL)
335		goto err1;
336	if (fgets(buf, sizeof(buf), fp) == NULL) {
337		if (ferror(fp))
338err1:			syslog(LOG_WARNING, "%s: %s", path, strerror(errno));
339		bounds = 0;
340	} else
341		bounds = atoi(buf);
342	if (fp != NULL)
343		(void)fclose(fp);
344	if ((fp = fopen(path, "w")) == NULL)
345		syslog(LOG_ERR, "%s: %m", path);
346	else {
347		(void)fprintf(fp, "%d\n", bounds + 1);
348		(void)fclose(fp);
349	}
350	(void)fclose(fp);
351
352	/* Create the core file. */
353	(void)snprintf(path, sizeof(path), "%s/vmcore.%d%s",
354	    dirname, bounds, compress ? ".Z" : "");
355	if (compress) {
356		if ((fp = zopen(path, "w", 0)) == NULL) {
357			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
358			exit(1);
359		}
360	} else
361		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
362
363	/* Open the raw device. */
364	rawp = rawname(ddname);
365	if ((ifd = open(rawp, O_RDONLY)) == -1) {
366		syslog(LOG_WARNING, "%s: %m; using block device", rawp);
367		ifd = dumpfd;
368	}
369
370	/* Read the dump size. */
371	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_DUMPSIZE].n_value)), L_SET);
372	(void)Read(dumpfd, &dumpsize, sizeof(dumpsize));
373
374	/* Seek to the start of the core. */
375	Lseek(ifd, (off_t)dumplo, L_SET);
376
377	/* Copy the core file. */
378	dumpsize *= NBPG;
379	syslog(LOG_NOTICE, "writing %score to %s",
380	    compress ? "compressed " : "", path);
381	for (; dumpsize > 0; dumpsize -= nr) {
382		(void)printf("%6dK\r", dumpsize / 1024);
383		(void)fflush(stdout);
384		nr = read(ifd, buf, MIN(dumpsize, sizeof(buf)));
385		if (nr <= 0) {
386			if (nr == 0)
387				syslog(LOG_WARNING,
388				    "WARNING: EOF on dump device");
389			else
390				syslog(LOG_ERR, "%s: %m", rawp);
391			goto err2;
392		}
393		if (compress)
394			nw = fwrite(buf, 1, nr, fp);
395		else
396			nw = write(ofd, buf, nr);
397		if (nw != nr) {
398			syslog(LOG_ERR, "%s: %s",
399			    path, strerror(nw == 0 ? EIO : errno));
400err2:			syslog(LOG_WARNING,
401			    "WARNING: vmcore may be incomplete");
402			(void)printf("\n");
403			exit(1);
404		}
405	}
406	(void)printf("\n");
407	(void)close(ifd);
408	if (compress)
409		(void)fclose(fp);
410	else
411		(void)close(ofd);
412
413	/* Copy the kernel. */
414	ifd = Open(vmunix ? vmunix : _PATH_UNIX, O_RDONLY);
415	(void)snprintf(path, sizeof(path), "%s/netbsd.%d%s",
416	    dirname, bounds, compress ? ".Z" : "");
417	if (compress) {
418		if ((fp = zopen(path, "w", 0)) == NULL) {
419			syslog(LOG_ERR, "%s: %s", path, strerror(errno));
420			exit(1);
421		}
422	} else
423		ofd = Create(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
424	syslog(LOG_NOTICE, "writing %skernel to %s",
425	    compress ? "compressed " : "", path);
426	while ((nr = read(ifd, buf, sizeof(buf))) > 0) {
427		if (compress)
428			nw = fwrite(buf, 1, nr, fp);
429		else
430			nw = write(ofd, buf, nr);
431		if (nw != nr) {
432			syslog(LOG_ERR, "%s: %s",
433			    path, strerror(nw == 0 ? EIO : errno));
434			syslog(LOG_WARNING,
435			    "WARNING: vmunix may be incomplete");
436			exit(1);
437		}
438	}
439	if (nr < 0) {
440		syslog(LOG_ERR, "%s: %s",
441		    vmunix ? vmunix : _PATH_UNIX, strerror(errno));
442		syslog(LOG_WARNING,
443		    "WARNING: vmunix may be incomplete");
444		exit(1);
445	}
446	if (compress)
447		(void)fclose(fp);
448	else
449		(void)close(ofd);
450}
451
452char *
453find_dev(dev, type)
454	register dev_t dev;
455	register int type;
456{
457	register DIR *dfd;
458	struct dirent *dir;
459	struct stat sb;
460	char *dp, devname[MAXPATHLEN + 1];
461
462	if ((dfd = opendir(_PATH_DEV)) == NULL) {
463		syslog(LOG_ERR, "%s: %s", _PATH_DEV, strerror(errno));
464		exit(1);
465	}
466	(void)strcpy(devname, _PATH_DEV);
467	while ((dir = readdir(dfd))) {
468		(void)strcpy(devname + sizeof(_PATH_DEV) - 1, dir->d_name);
469		if (lstat(devname, &sb)) {
470			syslog(LOG_ERR, "%s: %s", devname, strerror(errno));
471			continue;
472		}
473		if ((sb.st_mode & S_IFMT) != type)
474			continue;
475		if (dev == sb.st_rdev) {
476			closedir(dfd);
477			if ((dp = strdup(devname)) == NULL) {
478				syslog(LOG_ERR, "%s", strerror(errno));
479				exit(1);
480			}
481			return (dp);
482		}
483	}
484	closedir(dfd);
485	syslog(LOG_ERR, "can't find device %d/%d", major(dev), minor(dev));
486	exit(1);
487}
488
489char *
490rawname(s)
491	char *s;
492{
493	char *sl, name[MAXPATHLEN];
494
495	if ((sl = rindex(s, '/')) == NULL || sl[1] == '0') {
496		syslog(LOG_ERR,
497		    "can't make raw dump device name from %s", s);
498		return (s);
499	}
500	(void)snprintf(name, sizeof(name), "%.*s/r%s", sl - s, s, sl + 1);
501	if ((sl = strdup(name)) == NULL) {
502		syslog(LOG_ERR, "%s", strerror(errno));
503		exit(1);
504	}
505	return (sl);
506}
507
508int
509get_crashtime()
510{
511	time_t dumptime;			/* Time the dump was taken. */
512
513	Lseek(dumpfd, (off_t)(dumplo + ok(dump_nl[X_TIME].n_value)), L_SET);
514	(void)Read(dumpfd, &dumptime, sizeof(dumptime));
515	if (dumptime == 0) {
516		if (verbose)
517			syslog(LOG_ERR, "dump time is zero");
518		return (0);
519	}
520	(void)printf("savecore: system went down at %s", ctime(&dumptime));
521#define	LEEWAY	(7 * SECSPERDAY)
522	if (dumptime < now - LEEWAY || dumptime > now + LEEWAY) {
523		(void)printf("dump time is unreasonable\n");
524		return (0);
525	}
526	return (1);
527}
528
529int
530check_space()
531{
532	register FILE *fp;
533	char *tvmunix;
534	off_t minfree, spacefree, vmunixsize, needed;
535	struct stat st;
536	struct statfs fsbuf;
537	char buf[100], path[MAXPATHLEN];
538
539	tvmunix = vmunix ? vmunix : _PATH_UNIX;
540	if (stat(tvmunix, &st) < 0) {
541		syslog(LOG_ERR, "%s: %m", tvmunix);
542		exit(1);
543	}
544	vmunixsize = st.st_blocks * S_BLKSIZE;
545	if (statfs(dirname, &fsbuf) < 0) {
546		syslog(LOG_ERR, "%s: %m", dirname);
547		exit(1);
548	}
549 	spacefree = (fsbuf.f_bavail * fsbuf.f_bsize) / 1024;
550
551	(void)snprintf(path, sizeof(path), "%s/minfree", dirname);
552	if ((fp = fopen(path, "r")) == NULL)
553		minfree = 0;
554	else {
555		if (fgets(buf, sizeof(buf), fp) == NULL)
556			minfree = 0;
557		else
558			minfree = atoi(buf);
559		(void)fclose(fp);
560	}
561
562	needed = (dumpsize + vmunixsize) / 1024;
563 	if (minfree > 0 && spacefree - needed < minfree) {
564		syslog(LOG_WARNING,
565		    "no dump, not enough free space on device");
566		return (0);
567	}
568	if (spacefree - needed < minfree)
569		syslog(LOG_WARNING,
570		    "dump performed, but free space threshold crossed");
571	return (1);
572}
573
574int
575Open(name, rw)
576	char *name;
577	int rw;
578{
579	int fd;
580
581	if ((fd = open(name, rw, 0)) < 0) {
582		syslog(LOG_ERR, "%s: %m", name);
583		exit(1);
584	}
585	return (fd);
586}
587
588int
589Read(fd, bp, size)
590	int fd, size;
591	void *bp;
592{
593	int nr;
594
595	nr = read(fd, bp, size);
596	if (nr != size) {
597		syslog(LOG_ERR, "read: %m");
598		exit(1);
599	}
600	return (nr);
601}
602
603void
604Lseek(fd, off, flag)
605	int fd, flag;
606	off_t off;
607{
608	off_t ret;
609
610	ret = lseek(fd, off, flag);
611	if (ret == -1) {
612		syslog(LOG_ERR, "lseek: %m");
613		exit(1);
614	}
615}
616
617int
618Create(file, mode)
619	char *file;
620	int mode;
621{
622	register int fd;
623
624	fd = creat(file, mode);
625	if (fd < 0) {
626		syslog(LOG_ERR, "%s: %m", file);
627		exit(1);
628	}
629	return (fd);
630}
631
632void
633Write(fd, bp, size)
634	int fd, size;
635	void *bp;
636{
637	int n;
638
639	if ((n = write(fd, bp, size)) < size) {
640		syslog(LOG_ERR, "write: %s", strerror(n == -1 ? errno : EIO));
641		exit(1);
642	}
643}
644
645void
646usage()
647{
648	(void)syslog(LOG_ERR, "usage: savecore [-cfvz] [-N system] directory");
649	exit(1);
650}
651