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