xref: /illumos-gate/usr/src/cmd/savecore/savecore.c (revision 4de2612967d06c4fdbf524a62556a1e8118a006f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1998-2002 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <deflt.h>
37 #include <time.h>
38 #include <syslog.h>
39 #include <stropts.h>
40 #include <sys/mem.h>
41 #include <sys/statvfs.h>
42 #include <sys/dumphdr.h>
43 #include <sys/dumpadm.h>
44 #include <sys/compress.h>
45 #include <sys/sysmacros.h>
46 
47 static char 	progname[9] = "savecore";
48 static char	*savedir;		/* savecore directory */
49 static char	*dumpfile;		/* source of raw crash dump */
50 static long	pagesize;		/* dump pagesize */
51 static int	dumpfd = -1;		/* dumpfile descriptor */
52 static dumphdr_t corehdr, dumphdr;	/* initial and terminal dumphdrs */
53 static offset_t	endoff;			/* offset of end-of-dump header */
54 static int	verbose;		/* chatty mode */
55 static int	disregard_valid_flag;	/* disregard valid flag */
56 static int	livedump;		/* dump the current running system */
57 
58 static void
59 usage(void)
60 {
61 	(void) fprintf(stderr,
62 	    "usage: %s [-Lvd] [-f dumpfile] [dirname]\n", progname);
63 	exit(1);
64 }
65 
66 static void
67 logprint(int logpri, int showmsg, int exitcode, char *message, ...)
68 {
69 	va_list args;
70 	char buf[1024];
71 
72 	if (showmsg) {
73 		va_start(args, message);
74 		(void) vsnprintf(buf, 1024, message, args);
75 		(void) fprintf(stderr, "%s: %s\n", progname, buf);
76 		if (logpri >= 0)
77 			syslog(logpri, buf);
78 		va_end(args);
79 	}
80 	if (exitcode >= 0)
81 		exit(exitcode);
82 }
83 
84 /*
85  * System call / libc wrappers that exit on error.
86  */
87 static int
88 Open(const char *name, int oflags, mode_t mode)
89 {
90 	int fd;
91 
92 	if ((fd = open64(name, oflags, mode)) == -1)
93 		logprint(LOG_ERR, 1, 1, "open(\"%s\"): %s",
94 		    name, strerror(errno));
95 	return (fd);
96 }
97 
98 static void
99 Pread(int fd, void *buf, size_t size, offset_t off)
100 {
101 	if (pread64(fd, buf, size, off) != size)
102 		logprint(LOG_ERR, 1, 1, "pread: %s", strerror(errno));
103 }
104 
105 static void
106 Pwrite(int fd, void *buf, size_t size, offset_t off)
107 {
108 	if (pwrite64(fd, buf, size, off) != size)
109 		logprint(LOG_ERR, 1, 1, "pwrite: %s", strerror(errno));
110 }
111 
112 static void *
113 Zalloc(size_t size)
114 {
115 	void *buf;
116 
117 	if ((buf = calloc(size, 1)) == NULL)
118 		logprint(LOG_ERR, 1, 1, "calloc: %s", strerror(errno));
119 	return (buf);
120 }
121 
122 static long
123 read_number_from_file(const char *filename, long default_value)
124 {
125 	long file_value = -1;
126 	FILE *fp;
127 
128 	if ((fp = fopen(filename, "r")) != NULL) {
129 		(void) fscanf(fp, "%ld", &file_value);
130 		(void) fclose(fp);
131 	}
132 	return (file_value < 0 ? default_value : file_value);
133 }
134 
135 static void
136 read_dumphdr(void)
137 {
138 	dumpfd = Open(dumpfile, O_RDWR | O_DSYNC, 0644);
139 	endoff = llseek(dumpfd, -DUMP_OFFSET, SEEK_END) & -DUMP_OFFSET;
140 	Pread(dumpfd, &dumphdr, sizeof (dumphdr), endoff);
141 
142 	pagesize = dumphdr.dump_pagesize;
143 
144 	if ((dumphdr.dump_flags & DF_VALID) == 0 && !disregard_valid_flag)
145 		logprint(-1, verbose, 0, "dump already processed");
146 
147 	if (dumphdr.dump_magic != DUMP_MAGIC)
148 		logprint(-1, verbose, 0, "bad magic number %x",
149 		    dumphdr.dump_magic);
150 
151 	if (dumphdr.dump_version != DUMP_VERSION)
152 		logprint(-1, verbose, 0,
153 		    "dump version (%d) != %s version (%d)",
154 		    dumphdr.dump_version, progname, DUMP_VERSION);
155 
156 	if (dumphdr.dump_wordsize != DUMP_WORDSIZE)
157 		logprint(LOG_WARNING, 1, 0,
158 		    "dump is from %u-bit kernel - cannot save on %u-bit kernel",
159 		    dumphdr.dump_wordsize, DUMP_WORDSIZE);
160 	/*
161 	 * Read the initial header, clear the valid bits, and compare headers.
162 	 * The main header may have been overwritten by swapping if we're
163 	 * using a swap partition as the dump device, in which case we bail.
164 	 */
165 	Pread(dumpfd, &corehdr, sizeof (dumphdr_t), dumphdr.dump_start);
166 
167 	corehdr.dump_flags &= ~DF_VALID;
168 	dumphdr.dump_flags &= ~DF_VALID;
169 
170 	if (memcmp(&corehdr, &dumphdr, sizeof (dumphdr_t)) != 0) {
171 		/*
172 		 * Clear valid bit so we don't complain on every invocation.
173 		 */
174 		Pwrite(dumpfd, &dumphdr, sizeof (dumphdr), endoff);
175 		logprint(LOG_ERR, 1, 1, "initial dump header corrupt");
176 	}
177 }
178 
179 static void
180 check_space(void)
181 {
182 	struct statvfs fsb;
183 	int64_t spacefree, dumpsize, minfree;
184 
185 	if (statvfs(".", &fsb) < 0)
186 		logprint(LOG_ERR, 1, 1, "statvfs: %s", strerror(errno));
187 
188 	dumpsize = (dumphdr.dump_data - dumphdr.dump_start) +
189 	    (int64_t)dumphdr.dump_npages * pagesize;
190 	spacefree = (int64_t)fsb.f_bavail * fsb.f_frsize;
191 	minfree = 1024LL * read_number_from_file("minfree", 1024);
192 	if (spacefree < minfree + dumpsize)
193 		logprint(LOG_ERR, 1, 1,
194 		    "not enough space in %s (%lld MB avail, %lld MB needed)",
195 		    savedir, spacefree >> 20, (minfree + dumpsize) >> 20);
196 }
197 
198 static void
199 build_dump_map(int corefd, const pfn_t *pfn_table)
200 {
201 	long i;
202 	static long misses = 0;
203 	size_t dump_mapsize = (corehdr.dump_hashmask + 1) * sizeof (dump_map_t);
204 	mem_vtop_t vtop;
205 	dump_map_t *dmp = Zalloc(dump_mapsize);
206 
207 	corehdr.dump_data = corehdr.dump_map + roundup(dump_mapsize, pagesize);
208 
209 	for (i = 0; i < corehdr.dump_nvtop; i++) {
210 		long first = 0;
211 		long last = corehdr.dump_npages - 1;
212 		long middle;
213 		pfn_t pfn;
214 		uintptr_t h;
215 
216 		Pread(dumpfd, &vtop, sizeof (mem_vtop_t),
217 		    dumphdr.dump_map + i * sizeof (mem_vtop_t));
218 
219 		while (last >= first) {
220 			middle = (first + last) / 2;
221 			pfn = pfn_table[middle];
222 			if (pfn == vtop.m_pfn)
223 				break;
224 			if (pfn < vtop.m_pfn)
225 				first = middle + 1;
226 			else
227 				last = middle - 1;
228 		}
229 		if (pfn != vtop.m_pfn) {
230 			if (++misses <= 10)
231 				(void) fprintf(stderr,
232 				    "pfn %ld not found for as=%p, va=%p\n",
233 				    vtop.m_pfn, (void *)vtop.m_as, vtop.m_va);
234 			continue;
235 		}
236 
237 		dmp[i].dm_as = vtop.m_as;
238 		dmp[i].dm_va = (uintptr_t)vtop.m_va;
239 		dmp[i].dm_data = corehdr.dump_data +
240 		    ((uint64_t)middle << corehdr.dump_pageshift);
241 
242 		h = DUMP_HASH(&corehdr, dmp[i].dm_as, dmp[i].dm_va);
243 		dmp[i].dm_next = dmp[h].dm_first;
244 		dmp[h].dm_first = corehdr.dump_map + i * sizeof (dump_map_t);
245 	}
246 
247 	Pwrite(corefd, dmp, dump_mapsize, corehdr.dump_map);
248 	free(dmp);
249 }
250 
251 static void
252 build_corefile(const char *namelist, const char *corefile)
253 {
254 	char *inbuf = Zalloc(pagesize);
255 	char *outbuf = Zalloc(pagesize);
256 	size_t pfn_table_size = dumphdr.dump_npages * sizeof (pfn_t);
257 	size_t ksyms_size = dumphdr.dump_ksyms_size;
258 	size_t ksyms_csize = dumphdr.dump_ksyms_csize;
259 	pfn_t *pfn_table = Zalloc(pfn_table_size);
260 	char *ksyms_base = Zalloc(ksyms_size);
261 	char *ksyms_cbase = Zalloc(ksyms_csize);
262 	int corefd = Open(corefile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
263 	int namefd = Open(namelist, O_WRONLY | O_CREAT | O_TRUNC, 0644);
264 	offset_t dumpoff;
265 	int percent_done = 0;
266 	pgcnt_t saved = 0;
267 	uint32_t csize;
268 	size_t dsize;
269 
270 	(void) printf("Constructing namelist %s/%s\n", savedir, namelist);
271 
272 	/*
273 	 * Read in the compressed symbol table, copy it to corefile,
274 	 * decompress it, and write the result to namelist.
275 	 */
276 	corehdr.dump_ksyms = pagesize;
277 	Pread(dumpfd, ksyms_cbase, ksyms_csize, dumphdr.dump_ksyms);
278 	Pwrite(corefd, ksyms_cbase, ksyms_csize, corehdr.dump_ksyms);
279 
280 	if ((dsize = decompress(ksyms_cbase, ksyms_base, ksyms_csize,
281 	    ksyms_size)) != ksyms_size)
282 	    logprint(LOG_WARNING, 1, -1, "bad data in symbol table, %lu of %lu"
283 		" bytes saved", dsize, ksyms_size);
284 
285 	Pwrite(namefd, ksyms_base, ksyms_size, 0);
286 	(void) close(namefd);
287 	free(ksyms_cbase);
288 	free(ksyms_base);
289 
290 	(void) printf("Constructing corefile %s/%s\n", savedir, corefile);
291 
292 	/*
293 	 * Read in and write out the pfn table.
294 	 */
295 	corehdr.dump_pfn = corehdr.dump_ksyms + roundup(ksyms_size, pagesize);
296 	Pread(dumpfd, pfn_table, pfn_table_size, dumphdr.dump_pfn);
297 	Pwrite(corefd, pfn_table, pfn_table_size, corehdr.dump_pfn);
298 
299 	/*
300 	 * Convert the raw translation data into a hashed dump map.
301 	 */
302 	corehdr.dump_map = corehdr.dump_pfn + roundup(pfn_table_size, pagesize);
303 	build_dump_map(corefd, pfn_table);
304 
305 	/*
306 	 * Decompress and save the pages.
307 	 */
308 	dumpoff = dumphdr.dump_data;
309 	while (saved < dumphdr.dump_npages) {
310 		Pread(dumpfd, &csize, sizeof (uint32_t), dumpoff);
311 		dumpoff += sizeof (uint32_t);
312 		if (csize > pagesize)
313 			break;
314 		Pread(dumpfd, inbuf, csize, dumpoff);
315 		dumpoff += csize;
316 		if (decompress(inbuf, outbuf, csize, pagesize) != pagesize)
317 			break;
318 		Pwrite(corefd, outbuf, pagesize,
319 		    corehdr.dump_data + saved * pagesize);
320 		if (++saved * 100LL / dumphdr.dump_npages > percent_done) {
321 			(void) printf("\r%3d%% done", ++percent_done);
322 			(void) fflush(stdout);
323 		}
324 	}
325 
326 	(void) printf(": %ld of %ld pages saved\n", saved, dumphdr.dump_npages);
327 
328 	if (saved != dumphdr.dump_npages)
329 		logprint(LOG_WARNING, 1, -1, "bad data after page %ld", saved);
330 
331 	/*
332 	 * Write out the modified dump headers.
333 	 */
334 	Pwrite(corefd, &corehdr, sizeof (corehdr), 0);
335 	Pwrite(dumpfd, &dumphdr, sizeof (dumphdr), endoff);
336 
337 	(void) close(corefd);
338 	(void) close(dumpfd);
339 }
340 
341 /*
342  * When the system panics, the kernel saves all undelivered messages (messages
343  * that never made it out to syslogd(1M)) in the dump.  At a mimimum, the
344  * panic message itself will always fall into this category.  Upon reboot,
345  * the syslog startup script runs savecore -m to recover these messages.
346  *
347  * To do this, we read the unsent messages from the dump and send them to
348  * /dev/conslog on priority band 1.  This has the effect of prepending them
349  * to any already-accumulated messages in the console backlog, thus preserving
350  * temporal ordering across the reboot.
351  *
352  * Note: since savecore -m is used *only* for this purpose, it does *not*
353  * attempt to save the crash dump.  The dump will be saved later, after
354  * syslogd(1M) starts, by the savecore startup script.
355  */
356 static int
357 message_save(void)
358 {
359 	offset_t dumpoff = -(DUMP_OFFSET + DUMP_LOGSIZE);
360 	offset_t ldoff;
361 	log_dump_t ld;
362 	log_ctl_t lc;
363 	struct strbuf ctl, dat;
364 	int logfd;
365 
366 	logfd = Open("/dev/conslog", O_WRONLY, 0644);
367 	dumpfd = Open(dumpfile, O_RDWR | O_DSYNC, 0644);
368 	dumpoff = llseek(dumpfd, dumpoff, SEEK_END) & -DUMP_OFFSET;
369 
370 	ctl.buf = (void *)&lc;
371 	ctl.len = sizeof (log_ctl_t);
372 
373 	dat.buf = Zalloc(DUMP_LOGSIZE);
374 
375 	for (;;) {
376 		ldoff = dumpoff;
377 
378 		Pread(dumpfd, &ld, sizeof (log_dump_t), dumpoff);
379 		dumpoff += sizeof (log_dump_t);
380 		dat.len = ld.ld_msgsize;
381 
382 		if (ld.ld_magic == 0)
383 			break;
384 
385 		if (ld.ld_magic != LOG_MAGIC)
386 			logprint(-1, verbose, 0, "bad magic %x", ld.ld_magic);
387 
388 		if (dat.len >= DUMP_LOGSIZE)
389 			logprint(-1, verbose, 0, "bad size %d", ld.ld_msgsize);
390 
391 		Pread(dumpfd, ctl.buf, ctl.len, dumpoff);
392 		dumpoff += ctl.len;
393 
394 		if (ld.ld_csum != checksum32(ctl.buf, ctl.len))
395 			logprint(-1, verbose, 0, "bad log_ctl checksum");
396 
397 		lc.flags |= SL_LOGONLY;
398 
399 		Pread(dumpfd, dat.buf, dat.len, dumpoff);
400 		dumpoff += dat.len;
401 
402 		if (ld.ld_msum != checksum32(dat.buf, dat.len))
403 			logprint(-1, verbose, 0, "bad message checksum");
404 
405 		if (putpmsg(logfd, &ctl, &dat, 1, MSG_BAND) == -1)
406 			logprint(LOG_ERR, 1, 1, "putpmsg: %s", strerror(errno));
407 
408 		ld.ld_magic = 0;	/* clear magic so we never save twice */
409 		Pwrite(dumpfd, &ld, sizeof (log_dump_t), ldoff);
410 	}
411 	return (0);
412 }
413 
414 int
415 main(int argc, char *argv[])
416 {
417 	int c, bfd;
418 	int mflag = 0;
419 	long bounds;
420 	char namelist[30], corefile[30], boundstr[30];
421 
422 	openlog(progname, LOG_ODELAY, LOG_AUTH);
423 	(void) defopen("/etc/dumpadm.conf");
424 	savedir = defread("DUMPADM_SAVDIR=");
425 
426 	while ((c = getopt(argc, argv, "Lvdmf:")) != EOF) {
427 		switch (c) {
428 		case 'L':
429 			livedump++;
430 			break;
431 		case 'v':
432 			verbose++;
433 			break;
434 		case 'd':
435 			disregard_valid_flag++;
436 			break;
437 		case 'm':
438 			mflag++;
439 			break;
440 		case 'f':
441 			dumpfile = optarg;
442 			break;
443 		case '?':
444 			usage();
445 		}
446 	}
447 
448 	if (dumpfile == NULL || livedump)
449 		dumpfd = Open("/dev/dump", O_RDONLY, 0444);
450 
451 	if (dumpfile == NULL) {
452 		dumpfile = Zalloc(MAXPATHLEN);
453 		if (ioctl(dumpfd, DIOCGETDEV, dumpfile) == -1)
454 			logprint(LOG_ERR, 1, 1, "no dump device configured");
455 	}
456 
457 	if (mflag)
458 		return (message_save());
459 
460 	if (optind == argc - 1)
461 		savedir = argv[optind];
462 	if (savedir == NULL || optind < argc - 1)
463 		usage();
464 
465 	if (livedump && ioctl(dumpfd, DIOCDUMP, NULL) == -1)
466 		logprint(-1, 1, 1, "dedicated dump device required");
467 
468 	(void) close(dumpfd);
469 
470 	read_dumphdr();
471 
472 	/*
473 	 * We want this message to go to the log file, but not the console.
474 	 * There's no good way to do that with the existing syslog facility.
475 	 * We could extend it to handle this, but there doesn't seem to be
476 	 * a general need for it, so we isolate the complexity here instead.
477 	 */
478 	if (dumphdr.dump_panicstring[0] != '\0') {
479 		int logfd = Open("/dev/conslog", O_WRONLY, 0644);
480 		log_ctl_t lc;
481 		struct strbuf ctl, dat;
482 		char msg[DUMP_PANICSIZE + 100];
483 		char fmt[] = "reboot after panic: %s";
484 		uint32_t msgid;
485 
486 		STRLOG_MAKE_MSGID(fmt, msgid);
487 
488 		(void) sprintf(msg, "%s: [ID %u FACILITY_AND_PRIORITY] ",
489 		    progname, msgid);
490 		(void) sprintf(msg + strlen(msg), fmt,
491 		    dumphdr.dump_panicstring);
492 
493 		lc.pri = LOG_AUTH | LOG_ERR;
494 		lc.flags = SL_CONSOLE | SL_LOGONLY;
495 		lc.level = 0;
496 
497 		ctl.buf = (void *)&lc;
498 		ctl.len = sizeof (log_ctl_t);
499 
500 		dat.buf = (void *)msg;
501 		dat.len = strlen(msg) + 1;
502 
503 		(void) putmsg(logfd, &ctl, &dat, 0);
504 		(void) close(logfd);
505 	}
506 
507 	if (chdir(savedir) == -1)
508 		logprint(LOG_ERR, 1, 1, "chdir(\"%s\"): %s",
509 		    savedir, strerror(errno));
510 
511 	if ((dumphdr.dump_flags & DF_COMPLETE) == 0)
512 		logprint(LOG_WARNING, 1, -1, "incomplete dump on dump device");
513 
514 	(void) printf("System dump time: %s", ctime(&dumphdr.dump_crashtime));
515 
516 	check_space();
517 
518 	bounds = read_number_from_file("bounds", 0);
519 
520 	(void) sprintf(namelist, "unix.%ld", bounds);
521 	(void) sprintf(corefile, "vmcore.%ld", bounds);
522 
523 	syslog(LOG_ERR, "saving system crash dump in %s/*.%ld",
524 	    savedir, bounds);
525 
526 	build_corefile(namelist, corefile);
527 
528 	(void) sprintf(boundstr, "%ld\n", bounds + 1);
529 	bfd = Open("bounds", O_WRONLY | O_CREAT | O_TRUNC, 0644);
530 	Pwrite(bfd, boundstr, strlen(boundstr), 0);
531 	(void) close(bfd);
532 
533 	return (0);
534 }
535