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