xref: /illumos-gate/usr/src/cmd/fm/fmdump/common/fmdump.c (revision 4e5b757f)
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 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <alloca.h>
30 #include <unistd.h>
31 #include <limits.h>
32 #include <strings.h>
33 #include <stdlib.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <errno.h>
37 #include <time.h>
38 #include <ctype.h>
39 
40 #include <fmdump.h>
41 
42 #define	FMDUMP_EXIT_SUCCESS	0
43 #define	FMDUMP_EXIT_FATAL	1
44 #define	FMDUMP_EXIT_USAGE	2
45 #define	FMDUMP_EXIT_ERROR	3
46 
47 const char *g_pname;
48 ulong_t g_errs;
49 ulong_t g_recs;
50 char *g_root;
51 struct topo_hdl *g_thp;
52 
53 /*PRINTFLIKE2*/
54 void
55 fmdump_printf(FILE *fp, const char *format, ...)
56 {
57 	va_list ap;
58 
59 	va_start(ap, format);
60 
61 	if (vfprintf(fp, format, ap) < 0) {
62 		(void) fprintf(stderr, "%s: failed to print record: %s\n",
63 		    g_pname, strerror(errno));
64 		g_errs++;
65 	}
66 
67 	va_end(ap);
68 }
69 
70 void
71 fmdump_vwarn(const char *format, va_list ap)
72 {
73 	int err = errno;
74 
75 	(void) fprintf(stderr, "%s: warning: ", g_pname);
76 	(void) vfprintf(stderr, format, ap);
77 
78 	if (strchr(format, '\n') == NULL)
79 		(void) fprintf(stderr, ": %s\n", strerror(err));
80 
81 	g_errs++;
82 }
83 
84 /*PRINTFLIKE1*/
85 void
86 fmdump_warn(const char *format, ...)
87 {
88 	va_list ap;
89 
90 	va_start(ap, format);
91 	fmdump_vwarn(format, ap);
92 	va_end(ap);
93 }
94 
95 char *
96 fmdump_date(char *buf, size_t len, const fmd_log_record_t *rp)
97 {
98 	if (rp->rec_sec > LONG_MAX) {
99 		fmdump_warn("record time is too large for 32-bit utility\n");
100 		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
101 	} else {
102 		time_t tod = (time_t)rp->rec_sec;
103 		(void) strftime(buf, len, "%b %d %T", localtime(&tod));
104 	}
105 
106 	return (buf);
107 }
108 
109 char *
110 fmdump_year(char *buf, size_t len, const fmd_log_record_t *rp)
111 {
112 #ifdef _ILP32
113 	if (rp->rec_sec > LONG_MAX) {
114 		fmdump_warn("record time is too large for 32-bit utility\n");
115 		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
116 	} else {
117 #endif
118 		time_t tod = (time_t)rp->rec_sec;
119 		(void) strftime(buf, len, "%b %d %Y %T", localtime(&tod));
120 #ifdef _ILP32
121 	}
122 #endif
123 	return (buf);
124 }
125 
126 static int
127 usage(FILE *fp)
128 {
129 	(void) fprintf(fp, "Usage: %s [-efvV] [-c class] [-R root] [-t time] "
130 	    "[-T time] [-u uuid] [file]\n", g_pname);
131 
132 	(void) fprintf(fp,
133 	    "\t-c  select events that match the specified class\n"
134 	    "\t-e  display error log content instead of fault log content\n"
135 	    "\t-f  follow growth of log file by waiting for additional data\n"
136 	    "\t-R  set root directory for pathname expansions\n"
137 	    "\t-t  select events that occurred after the specified time\n"
138 	    "\t-T  select events that occurred before the specified time\n"
139 	    "\t-u  select events that match the specified uuid\n"
140 	    "\t-v  set verbose mode: display additional event detail\n"
141 	    "\t-V  set very verbose mode: display complete event contents\n");
142 
143 	return (FMDUMP_EXIT_USAGE);
144 }
145 
146 /*ARGSUSED*/
147 static int
148 error(fmd_log_t *lp, void *private)
149 {
150 	fmdump_warn("skipping record: %s\n",
151 	    fmd_log_errmsg(lp, fmd_log_errno(lp)));
152 	return (0);
153 }
154 
155 /*
156  * Yet another disgusting argument parsing function (TM).  We attempt to parse
157  * a time argument in a variety of strptime(3C) formats, in which case it is
158  * interpreted as a local time and is converted to a timeval using mktime(3C).
159  * If those formats fail, we look to see if the time is a decimal integer
160  * followed by one of our magic suffixes, in which case the time is interpreted
161  * as a time delta *before* the current time-of-day (i.e. "1h" = "1 hour ago").
162  */
163 static struct timeval *
164 gettimeopt(const char *arg)
165 {
166 	const struct {
167 		const char *name;
168 		hrtime_t mul;
169 	} suffix[] = {
170 		{ "ns", 	NANOSEC / NANOSEC },
171 		{ "nsec",	NANOSEC / NANOSEC },
172 		{ "us",		NANOSEC / MICROSEC },
173 		{ "usec",	NANOSEC / MICROSEC },
174 		{ "ms",		NANOSEC / MILLISEC },
175 		{ "msec",	NANOSEC / MILLISEC },
176 		{ "s",		NANOSEC / SEC },
177 		{ "sec",	NANOSEC / SEC },
178 		{ "m",		NANOSEC * (hrtime_t)60 },
179 		{ "min",	NANOSEC * (hrtime_t)60 },
180 		{ "h",		NANOSEC * (hrtime_t)(60 * 60) },
181 		{ "hour",	NANOSEC * (hrtime_t)(60 * 60) },
182 		{ "d",		NANOSEC * (hrtime_t)(24 * 60 * 60) },
183 		{ "day",	NANOSEC * (hrtime_t)(24 * 60 * 60) },
184 		{ NULL }
185 	};
186 
187 	struct timeval *tvp = malloc(sizeof (struct timeval));
188 	struct timeval tod;
189 	struct tm tm;
190 	char *p;
191 
192 	if (tvp == NULL) {
193 		(void) fprintf(stderr, "%s: failed to allocate memory: %s\n",
194 		    g_pname, strerror(errno));
195 		exit(FMDUMP_EXIT_FATAL);
196 	}
197 
198 	if (gettimeofday(&tod, NULL) != 0) {
199 		(void) fprintf(stderr, "%s: failed to get tod: %s\n",
200 		    g_pname, strerror(errno));
201 		exit(FMDUMP_EXIT_FATAL);
202 	}
203 
204 	/*
205 	 * First try a variety of strptime() calls.  If these all fail, we'll
206 	 * try parsing an integer followed by one of our suffix[] strings.
207 	 * NOTE: any form using %y must appear *before* the equivalent %Y form;
208 	 * otherwise %Y will accept the two year digits but infer century zero.
209 	 * Any form ending in %y must additionally check isdigit(*p) to ensure
210 	 * that it does not inadvertently match 2 digits of a 4-digit year.
211 	 *
212 	 * Beware: Any strptime() sequence containing consecutive %x sequences
213 	 * may fall victim to SCCS expanding it as a keyword!  If this happens
214 	 * we use separate string constant that ANSI C will concatenate.
215 	 */
216 	if ((p = strptime(arg, "%m/%d/%y" "%t" "%H:%M:%S", &tm)) == NULL &&
217 	    (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M:%S", &tm)) == NULL &&
218 	    (p = strptime(arg, "%m/%d/%y" "%t" "%H:%M", &tm)) == NULL &&
219 	    (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M", &tm)) == NULL &&
220 	    ((p = strptime(arg, "%m/%d/%y", &tm)) == NULL || isdigit(*p)) &&
221 	    (p = strptime(arg, "%m/%d/%Y", &tm)) == NULL &&
222 	    (p = strptime(arg, "%y-%m-%dT%H:%M:%S", &tm)) == NULL &&
223 	    (p = strptime(arg, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL &&
224 	    (p = strptime(arg, "%y-%m-%dT%H:%M", &tm)) == NULL &&
225 	    (p = strptime(arg, "%Y-%m-%dT%H:%M", &tm)) == NULL &&
226 	    (p = strptime(arg, "%y-%m-%d", &tm)) == NULL &&
227 	    (p = strptime(arg, "%Y-%m-%d", &tm)) == NULL &&
228 	    (p = strptime(arg, "%d%b%y" "%t" "%H:%M:%S", &tm)) == NULL &&
229 	    (p = strptime(arg, "%d%b%Y" "%t" "%H:%M:%S", &tm)) == NULL &&
230 	    (p = strptime(arg, "%d%b%y" "%t" "%H:%M", &tm)) == NULL &&
231 	    (p = strptime(arg, "%d%b%Y" "%t" "%H:%M", &tm)) == NULL &&
232 	    ((p = strptime(arg, "%d%b%y", &tm)) == NULL || isdigit(*p)) &&
233 	    (p = strptime(arg, "%d%b%Y", &tm)) == NULL &&
234 	    (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL &&
235 	    (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL &&
236 	    (p = strptime(arg, "%H:%M:%S", &tm)) == NULL &&
237 	    (p = strptime(arg, "%H:%M", &tm)) == NULL) {
238 
239 		hrtime_t nsec;
240 		int i;
241 
242 		errno = 0;
243 		nsec = strtol(arg, (char **)&p, 10);
244 
245 		if (errno != 0 || nsec == 0 || p == arg || *p == '\0') {
246 			(void) fprintf(stderr, "%s: illegal time "
247 			    "format -- %s\n", g_pname, arg);
248 			exit(FMDUMP_EXIT_USAGE);
249 		}
250 
251 		for (i = 0; suffix[i].name != NULL; i++) {
252 			if (strcasecmp(suffix[i].name, p) == 0) {
253 				nsec *= suffix[i].mul;
254 				break;
255 			}
256 		}
257 
258 		if (suffix[i].name == NULL) {
259 			(void) fprintf(stderr, "%s: illegal time "
260 			    "format -- %s\n", g_pname, arg);
261 			exit(FMDUMP_EXIT_USAGE);
262 		}
263 
264 		tvp->tv_sec = nsec / NANOSEC;
265 		tvp->tv_usec = (nsec % NANOSEC) / (NANOSEC / MICROSEC);
266 
267 		if (tvp->tv_sec > tod.tv_sec) {
268 			(void) fprintf(stderr, "%s: time delta precedes "
269 			    "UTC time origin -- %s\n", g_pname, arg);
270 			exit(FMDUMP_EXIT_USAGE);
271 		}
272 
273 		tvp->tv_sec = tod.tv_sec - tvp->tv_sec;
274 
275 	} else if (*p == '\0' || *p == '.') {
276 		/*
277 		 * If tm_year is zero, we matched [%b %d] %H:%M[:%S]; use
278 		 * the result of localtime(&tod.tv_sec) to fill in the rest.
279 		 */
280 		if (tm.tm_year == 0) {
281 			int h = tm.tm_hour;
282 			int m = tm.tm_min;
283 			int s = tm.tm_sec;
284 			int b = tm.tm_mon;
285 			int d = tm.tm_mday;
286 
287 			bcopy(localtime(&tod.tv_sec), &tm, sizeof (tm));
288 			tm.tm_isdst = 0; /* see strptime(3C) and below */
289 
290 			if (d > 0) {
291 				tm.tm_mon = b;
292 				tm.tm_mday = d;
293 			}
294 
295 			tm.tm_hour = h;
296 			tm.tm_min = m;
297 			tm.tm_sec = s;
298 		}
299 
300 		errno = 0;
301 		tvp->tv_sec = mktime(&tm);
302 		tvp->tv_usec = 0;
303 
304 		if (tvp->tv_sec == -1L && errno != 0) {
305 			(void) fprintf(stderr, "%s: failed to compose "
306 			    "time %s: %s\n", g_pname, arg, strerror(errno));
307 			exit(FMDUMP_EXIT_ERROR);
308 		}
309 
310 		/*
311 		 * If our mktime() set tm_isdst, adjust the result for DST by
312 		 * subtracting the offset between the main and alternate zones.
313 		 */
314 		if (tm.tm_isdst)
315 			tvp->tv_sec -= timezone - altzone;
316 
317 		if (p[0] == '.') {
318 			arg = p;
319 			errno = 0;
320 			tvp->tv_usec =
321 			    (suseconds_t)(strtod(arg, &p) * (double)MICROSEC);
322 
323 			if (errno != 0 || p == arg || *p != '\0') {
324 				(void) fprintf(stderr, "%s: illegal time "
325 				    "suffix -- .%s\n", g_pname, arg);
326 				exit(FMDUMP_EXIT_USAGE);
327 			}
328 		}
329 
330 	} else {
331 		(void) fprintf(stderr, "%s: unexpected suffix after "
332 		    "time %s -- %s\n", g_pname, arg, p);
333 		exit(FMDUMP_EXIT_USAGE);
334 	}
335 
336 	return (tvp);
337 }
338 
339 /*
340  * If the -u option is specified in combination with the -e option, we iterate
341  * over each record in the fault log with a matching UUID finding xrefs to the
342  * error log, and then use this function to iterate over every xref'd record.
343  */
344 int
345 xref_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
346 {
347 	const fmd_log_record_t *xrp = rp->rec_xrefs;
348 	fmdump_arg_t *dap = arg;
349 	int i, rv = 0;
350 
351 	for (i = 0; rv == 0 && i < rp->rec_nrefs; i++, xrp++) {
352 		if (fmd_log_filter(lp, dap->da_fc, dap->da_fv, xrp))
353 			rv = dap->da_fmt->do_func(lp, xrp, dap->da_fp);
354 	}
355 
356 	return (rv);
357 }
358 
359 int
360 xoff_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
361 {
362 	fmdump_lyr_t *dyp = arg;
363 
364 	fmdump_printf(dyp->dy_fp, "%16llx ", (u_longlong_t)rp->rec_off);
365 	return (dyp->dy_func(lp, rp, dyp->dy_arg));
366 }
367 
368 /*
369  * If the -a option is not present, filter out fault records that correspond
370  * to events that the producer requested not be messaged for administrators.
371  */
372 /*ARGSUSED*/
373 int
374 log_filter_silent(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
375 {
376 	boolean_t msg;
377 
378 	return (nvlist_lookup_boolean_value(rp->rec_nvl,
379 	    FM_SUSPECT_MESSAGE, &msg) != 0 || msg != 0);
380 }
381 
382 int
383 main(int argc, char *argv[])
384 {
385 	int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0;
386 	int opt_u = 0, opt_v = 0, opt_V = 0;
387 
388 	char ifile[PATH_MAX] = "";
389 	int iflags = 0;
390 
391 	fmdump_arg_t arg;
392 	fmdump_lyr_t lyr;
393 	const fmdump_ops_t *ops;
394 	fmd_log_filter_t *filtv;
395 	uint_t filtc;
396 
397 	fmd_log_filter_t *errfv, *fltfv, *allfv;
398 	uint_t errfc = 0, fltfc = 0, allfc = 0;
399 
400 	fmd_log_header_t log;
401 	fmd_log_rec_f *func;
402 	void *farg;
403 	fmd_log_t *lp;
404 	int c, err;
405 	off64_t off = 0;
406 
407 	g_pname = argv[0];
408 
409 	errfv = alloca(sizeof (fmd_log_filter_t) * argc);
410 	fltfv = alloca(sizeof (fmd_log_filter_t) * argc);
411 	allfv = alloca(sizeof (fmd_log_filter_t) * argc);
412 
413 	while (optind < argc) {
414 		while ((c = getopt(argc, argv, "ac:efHO:R:t:T:u:vV")) != EOF) {
415 			switch (c) {
416 			case 'a':
417 				opt_a++;
418 				break;
419 			case 'c':
420 				errfv[errfc].filt_func = fmd_log_filter_class;
421 				errfv[errfc].filt_arg = optarg;
422 				allfv[allfc++] = errfv[errfc++];
423 				break;
424 			case 'e':
425 				opt_e++;
426 				break;
427 			case 'f':
428 				opt_f++;
429 				break;
430 			case 'H':
431 				opt_H++;
432 				break;
433 			case 'O':
434 				off = strtoull(optarg, NULL, 16);
435 				iflags |= FMD_LOG_XITER_OFFS;
436 				break;
437 			case 'R':
438 				g_root = optarg;
439 				break;
440 			case 't':
441 				errfv[errfc].filt_func = fmd_log_filter_after;
442 				errfv[errfc].filt_arg = gettimeopt(optarg);
443 				allfv[allfc++] = errfv[errfc++];
444 				break;
445 			case 'T':
446 				errfv[errfc].filt_func = fmd_log_filter_before;
447 				errfv[errfc].filt_arg = gettimeopt(optarg);
448 				allfv[allfc++] = errfv[errfc++];
449 				break;
450 			case 'u':
451 				fltfv[fltfc].filt_func = fmd_log_filter_uuid;
452 				fltfv[fltfc].filt_arg = optarg;
453 				allfv[allfc++] = fltfv[fltfc++];
454 				opt_u++;
455 				opt_a++; /* -u implies -a */
456 				break;
457 			case 'v':
458 				opt_v++;
459 				break;
460 			case 'V':
461 				opt_V++;
462 				break;
463 			default:
464 				return (usage(stderr));
465 			}
466 		}
467 
468 		if (optind < argc) {
469 			if (*ifile != '\0') {
470 				(void) fprintf(stderr, "%s: illegal "
471 				    "argument -- %s\n", g_pname, argv[optind]);
472 				return (FMDUMP_EXIT_USAGE);
473 			} else {
474 				(void) strlcpy(ifile,
475 				    argv[optind++], sizeof (ifile));
476 			}
477 		}
478 	}
479 
480 	if (*ifile == '\0') {
481 		(void) snprintf(ifile, sizeof (ifile), "%s/var/fm/fmd/%slog",
482 		    g_root ? g_root : "", opt_e && !opt_u ? "err" : "flt");
483 	} else if (g_root != NULL) {
484 		(void) fprintf(stderr, "%s: -R option is not appropriate "
485 		    "when file operand is present\n", g_pname);
486 		return (FMDUMP_EXIT_USAGE);
487 	}
488 
489 	if ((lp = fmd_log_open(FMD_LOG_VERSION, ifile, &err)) == NULL) {
490 		(void) fprintf(stderr, "%s: failed to open %s: %s\n",
491 		    g_pname, ifile, fmd_log_errmsg(NULL, err));
492 		return (FMDUMP_EXIT_FATAL);
493 	}
494 
495 	if (opt_H) {
496 		fmd_log_header(lp, &log);
497 
498 		(void) printf("EXD_CREATOR = %s\n", log.log_creator);
499 		(void) printf("EXD_HOSTNAME = %s\n", log.log_hostname);
500 		(void) printf("EXD_FMA_LABEL = %s\n", log.log_label);
501 		(void) printf("EXD_FMA_VERSION = %s\n", log.log_version);
502 		(void) printf("EXD_FMA_OSREL = %s\n", log.log_osrelease);
503 		(void) printf("EXD_FMA_OSVER = %s\n", log.log_osversion);
504 		(void) printf("EXD_FMA_PLAT = %s\n", log.log_platform);
505 		(void) printf("EXD_FMA_UUID = %s\n", log.log_uuid);
506 
507 		return (FMDUMP_EXIT_SUCCESS);
508 	}
509 
510 	if (off != 0 && fmd_log_seek(lp, off) != 0) {
511 		(void) fprintf(stderr, "%s: failed to seek %s: %s\n",
512 		    g_pname, ifile, fmd_log_errmsg(lp, fmd_log_errno(lp)));
513 		return (FMDUMP_EXIT_FATAL);
514 	}
515 
516 	if (opt_e && opt_u)
517 		ops = &fmdump_err_ops;
518 	else if (strcmp(fmd_log_label(lp), fmdump_flt_ops.do_label) == 0)
519 		ops = &fmdump_flt_ops;
520 	else if (strcmp(fmd_log_label(lp), fmdump_asru_ops.do_label) == 0)
521 		ops = &fmdump_asru_ops;
522 	else
523 		ops = &fmdump_err_ops;
524 
525 	if (!opt_a && ops == &fmdump_flt_ops) {
526 		fltfv[fltfc].filt_func = log_filter_silent;
527 		fltfv[fltfc].filt_arg = NULL;
528 		allfv[allfc++] = fltfv[fltfc++];
529 	}
530 
531 	if (opt_V) {
532 		arg.da_fmt = &ops->do_formats[FMDUMP_VERB2];
533 		iflags |= FMD_LOG_XITER_REFS;
534 	} else if (opt_v) {
535 		arg.da_fmt = &ops->do_formats[FMDUMP_VERB1];
536 	} else
537 		arg.da_fmt = &ops->do_formats[FMDUMP_SHORT];
538 
539 	arg.da_fv = errfv;
540 	arg.da_fc = errfc;
541 	arg.da_fp = stdout;
542 
543 	if (iflags & FMD_LOG_XITER_OFFS)
544 		fmdump_printf(arg.da_fp, "%16s ", "OFFSET");
545 
546 	if (arg.da_fmt->do_hdr)
547 		fmdump_printf(arg.da_fp, "%s\n", arg.da_fmt->do_hdr);
548 
549 	if (opt_e && opt_u) {
550 		iflags |= FMD_LOG_XITER_REFS;
551 		func = xref_iter;
552 		farg = &arg;
553 		filtc = fltfc;
554 		filtv = fltfv;
555 	} else {
556 		func = arg.da_fmt->do_func;
557 		farg = arg.da_fp;
558 		filtc = allfc;
559 		filtv = allfv;
560 	}
561 
562 	if (iflags & FMD_LOG_XITER_OFFS) {
563 		lyr.dy_func = func;
564 		lyr.dy_arg = farg;
565 		lyr.dy_fp = arg.da_fp;
566 		func = xoff_iter;
567 		farg = &lyr;
568 	}
569 
570 	do {
571 		if (fmd_log_xiter(lp, iflags, filtc, filtv,
572 		    func, error, farg, &g_recs) != 0) {
573 			(void) fprintf(stderr,
574 			    "%s: failed to dump %s: %s\n", g_pname, ifile,
575 			    fmd_log_errmsg(lp, fmd_log_errno(lp)));
576 			g_errs++;
577 		}
578 
579 		if (opt_f)
580 			(void) sleep(1);
581 
582 	} while (opt_f);
583 
584 	if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO))
585 		(void) fprintf(stderr, "%s: %s is empty\n", g_pname, ifile);
586 
587 	fmd_log_close(lp);
588 	return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS);
589 }
590