xref: /illumos-gate/usr/src/cmd/fm/fmdump/common/fmdump.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <alloca.h>
27 #include <unistd.h>
28 #include <limits.h>
29 #include <strings.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <errno.h>
34 #include <time.h>
35 #include <ctype.h>
36 #include <regex.h>
37 #include <dirent.h>
38 
39 #include <fmdump.h>
40 
41 #define	FMDUMP_EXIT_SUCCESS	0
42 #define	FMDUMP_EXIT_FATAL	1
43 #define	FMDUMP_EXIT_USAGE	2
44 #define	FMDUMP_EXIT_ERROR	3
45 
46 const char *g_pname;
47 ulong_t g_errs;
48 ulong_t g_recs;
49 char *g_root;
50 
51 struct topo_hdl *g_thp;
52 fmd_msg_hdl_t *g_msg;
53 
54 /*PRINTFLIKE2*/
55 void
56 fmdump_printf(FILE *fp, const char *format, ...)
57 {
58 	va_list ap;
59 
60 	va_start(ap, format);
61 
62 	if (vfprintf(fp, format, ap) < 0) {
63 		(void) fprintf(stderr, "%s: failed to print record: %s\n",
64 		    g_pname, strerror(errno));
65 		g_errs++;
66 	}
67 
68 	va_end(ap);
69 }
70 
71 void
72 fmdump_vwarn(const char *format, va_list ap)
73 {
74 	int err = errno;
75 
76 	(void) fprintf(stderr, "%s: warning: ", g_pname);
77 	(void) vfprintf(stderr, format, ap);
78 
79 	if (strchr(format, '\n') == NULL)
80 		(void) fprintf(stderr, ": %s\n", strerror(err));
81 
82 	g_errs++;
83 }
84 
85 /*PRINTFLIKE1*/
86 void
87 fmdump_warn(const char *format, ...)
88 {
89 	va_list ap;
90 
91 	va_start(ap, format);
92 	fmdump_vwarn(format, ap);
93 	va_end(ap);
94 }
95 
96 char *
97 fmdump_date(char *buf, size_t len, const fmd_log_record_t *rp)
98 {
99 	if (rp->rec_sec > LONG_MAX) {
100 		fmdump_warn("record time is too large for 32-bit utility\n");
101 		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
102 	} else {
103 		time_t tod = (time_t)rp->rec_sec;
104 		time_t now = time(NULL);
105 		if (tod > now+60 ||
106 		    tod < now - 6L*30L*24L*60L*60L) { /* 6 months ago */
107 			(void) strftime(buf, len, "%b %d %Y %T",
108 			    localtime(&tod));
109 		} else {
110 			size_t sz;
111 			sz = strftime(buf, len, "%b %d %T", localtime(&tod));
112 			(void) snprintf(buf + sz, len - sz, ".%4.4llu",
113 			    rp->rec_nsec / (NANOSEC / 10000));
114 		}
115 	}
116 
117 	return (buf);
118 }
119 
120 char *
121 fmdump_year(char *buf, size_t len, const fmd_log_record_t *rp)
122 {
123 #ifdef _ILP32
124 	if (rp->rec_sec > LONG_MAX) {
125 		fmdump_warn("record time is too large for 32-bit utility\n");
126 		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
127 	} else {
128 #endif
129 		time_t tod = (time_t)rp->rec_sec;
130 		(void) strftime(buf, len, "%b %d %Y %T", localtime(&tod));
131 #ifdef _ILP32
132 	}
133 #endif
134 	return (buf);
135 }
136 
137 static int
138 usage(FILE *fp)
139 {
140 	(void) fprintf(fp, "Usage: %s [-efmvV] [-c class] [-R root] [-t time] "
141 	    "[-T time] [-u uuid]\n\t\t[-n name[.name]*[=value]] [file]\n",
142 	    g_pname);
143 
144 	(void) fprintf(fp,
145 	    "\t-c  select events that match the specified class\n"
146 	    "\t-e  display error log content instead of fault log content\n"
147 	    "\t-f  follow growth of log file by waiting for additional data\n"
148 	    "\t-m  display human-readable messages for the fault log\n"
149 	    "\t-R  set root directory for pathname expansions\n"
150 	    "\t-t  select events that occurred after the specified time\n"
151 	    "\t-T  select events that occurred before the specified time\n"
152 	    "\t-u  select events that match the specified uuid\n"
153 	    "\t-n  select events containing named nvpair "
154 	    "(with matching value)\n"
155 	    "\t-v  set verbose mode: display additional event detail\n"
156 	    "\t-V  set very verbose mode: display complete event contents\n");
157 
158 	return (FMDUMP_EXIT_USAGE);
159 }
160 
161 /*ARGSUSED*/
162 static int
163 error(fmd_log_t *lp, void *private)
164 {
165 	fmdump_warn("skipping record: %s\n",
166 	    fmd_log_errmsg(lp, fmd_log_errno(lp)));
167 	return (0);
168 }
169 
170 /*
171  * Yet another disgusting argument parsing function (TM).  We attempt to parse
172  * a time argument in a variety of strptime(3C) formats, in which case it is
173  * interpreted as a local time and is converted to a timeval using mktime(3C).
174  * If those formats fail, we look to see if the time is a decimal integer
175  * followed by one of our magic suffixes, in which case the time is interpreted
176  * as a time delta *before* the current time-of-day (i.e. "1h" = "1 hour ago").
177  */
178 static struct timeval *
179 gettimeopt(const char *arg)
180 {
181 	const struct {
182 		const char *name;
183 		hrtime_t mul;
184 	} suffix[] = {
185 		{ "ns",		NANOSEC / NANOSEC },
186 		{ "nsec",	NANOSEC / NANOSEC },
187 		{ "us",		NANOSEC / MICROSEC },
188 		{ "usec",	NANOSEC / MICROSEC },
189 		{ "ms",		NANOSEC / MILLISEC },
190 		{ "msec",	NANOSEC / MILLISEC },
191 		{ "s",		NANOSEC / SEC },
192 		{ "sec",	NANOSEC / SEC },
193 		{ "m",		NANOSEC * (hrtime_t)60 },
194 		{ "min",	NANOSEC * (hrtime_t)60 },
195 		{ "h",		NANOSEC * (hrtime_t)(60 * 60) },
196 		{ "hour",	NANOSEC * (hrtime_t)(60 * 60) },
197 		{ "d",		NANOSEC * (hrtime_t)(24 * 60 * 60) },
198 		{ "day",	NANOSEC * (hrtime_t)(24 * 60 * 60) },
199 		{ NULL }
200 	};
201 
202 	struct timeval *tvp = malloc(sizeof (struct timeval));
203 	struct timeval tod;
204 	struct tm tm;
205 	char *p;
206 
207 	if (tvp == NULL) {
208 		(void) fprintf(stderr, "%s: failed to allocate memory: %s\n",
209 		    g_pname, strerror(errno));
210 		exit(FMDUMP_EXIT_FATAL);
211 	}
212 
213 	if (gettimeofday(&tod, NULL) != 0) {
214 		(void) fprintf(stderr, "%s: failed to get tod: %s\n",
215 		    g_pname, strerror(errno));
216 		exit(FMDUMP_EXIT_FATAL);
217 	}
218 
219 	/*
220 	 * First try a variety of strptime() calls.  If these all fail, we'll
221 	 * try parsing an integer followed by one of our suffix[] strings.
222 	 * NOTE: any form using %y must appear *before* the equivalent %Y form;
223 	 * otherwise %Y will accept the two year digits but infer century zero.
224 	 * Any form ending in %y must additionally check isdigit(*p) to ensure
225 	 * that it does not inadvertently match 2 digits of a 4-digit year.
226 	 *
227 	 * Beware: Any strptime() sequence containing consecutive %x sequences
228 	 * may fall victim to SCCS expanding it as a keyword!  If this happens
229 	 * we use separate string constant that ANSI C will concatenate.
230 	 */
231 	if ((p = strptime(arg, "%m/%d/%y" "%t" "%H:%M:%S", &tm)) == NULL &&
232 	    (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M:%S", &tm)) == NULL &&
233 	    (p = strptime(arg, "%m/%d/%y" "%t" "%H:%M", &tm)) == NULL &&
234 	    (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M", &tm)) == NULL &&
235 	    ((p = strptime(arg, "%m/%d/%y", &tm)) == NULL || isdigit(*p)) &&
236 	    (p = strptime(arg, "%m/%d/%Y", &tm)) == NULL &&
237 	    (p = strptime(arg, "%y-%m-%dT%H:%M:%S", &tm)) == NULL &&
238 	    (p = strptime(arg, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL &&
239 	    (p = strptime(arg, "%y-%m-%dT%H:%M", &tm)) == NULL &&
240 	    (p = strptime(arg, "%Y-%m-%dT%H:%M", &tm)) == NULL &&
241 	    (p = strptime(arg, "%y-%m-%d", &tm)) == NULL &&
242 	    (p = strptime(arg, "%Y-%m-%d", &tm)) == NULL &&
243 	    (p = strptime(arg, "%d%b%y" "%t" "%H:%M:%S", &tm)) == NULL &&
244 	    (p = strptime(arg, "%d%b%Y" "%t" "%H:%M:%S", &tm)) == NULL &&
245 	    (p = strptime(arg, "%d%b%y" "%t" "%H:%M", &tm)) == NULL &&
246 	    (p = strptime(arg, "%d%b%Y" "%t" "%H:%M", &tm)) == NULL &&
247 	    ((p = strptime(arg, "%d%b%y", &tm)) == NULL || isdigit(*p)) &&
248 	    (p = strptime(arg, "%d%b%Y", &tm)) == NULL &&
249 	    (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL &&
250 	    (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL &&
251 	    (p = strptime(arg, "%H:%M:%S", &tm)) == NULL &&
252 	    (p = strptime(arg, "%H:%M", &tm)) == NULL) {
253 
254 		hrtime_t nsec;
255 		int i;
256 
257 		errno = 0;
258 		nsec = strtol(arg, (char **)&p, 10);
259 
260 		if (errno != 0 || nsec == 0 || p == arg || *p == '\0') {
261 			(void) fprintf(stderr, "%s: illegal time "
262 			    "format -- %s\n", g_pname, arg);
263 			exit(FMDUMP_EXIT_USAGE);
264 		}
265 
266 		for (i = 0; suffix[i].name != NULL; i++) {
267 			if (strcasecmp(suffix[i].name, p) == 0) {
268 				nsec *= suffix[i].mul;
269 				break;
270 			}
271 		}
272 
273 		if (suffix[i].name == NULL) {
274 			(void) fprintf(stderr, "%s: illegal time "
275 			    "format -- %s\n", g_pname, arg);
276 			exit(FMDUMP_EXIT_USAGE);
277 		}
278 
279 		tvp->tv_sec = nsec / NANOSEC;
280 		tvp->tv_usec = (nsec % NANOSEC) / (NANOSEC / MICROSEC);
281 
282 		if (tvp->tv_sec > tod.tv_sec) {
283 			(void) fprintf(stderr, "%s: time delta precedes "
284 			    "UTC time origin -- %s\n", g_pname, arg);
285 			exit(FMDUMP_EXIT_USAGE);
286 		}
287 
288 		tvp->tv_sec = tod.tv_sec - tvp->tv_sec;
289 
290 	} else if (*p == '\0' || *p == '.') {
291 		/*
292 		 * If tm_year is zero, we matched [%b %d] %H:%M[:%S]; use
293 		 * the result of localtime(&tod.tv_sec) to fill in the rest.
294 		 */
295 		if (tm.tm_year == 0) {
296 			int h = tm.tm_hour;
297 			int m = tm.tm_min;
298 			int s = tm.tm_sec;
299 			int b = tm.tm_mon;
300 			int d = tm.tm_mday;
301 
302 			bcopy(localtime(&tod.tv_sec), &tm, sizeof (tm));
303 			tm.tm_isdst = 0; /* see strptime(3C) and below */
304 
305 			if (d > 0) {
306 				tm.tm_mon = b;
307 				tm.tm_mday = d;
308 			}
309 
310 			tm.tm_hour = h;
311 			tm.tm_min = m;
312 			tm.tm_sec = s;
313 		}
314 
315 		errno = 0;
316 		tvp->tv_sec = mktime(&tm);
317 		tvp->tv_usec = 0;
318 
319 		if (tvp->tv_sec == -1L && errno != 0) {
320 			(void) fprintf(stderr, "%s: failed to compose "
321 			    "time %s: %s\n", g_pname, arg, strerror(errno));
322 			exit(FMDUMP_EXIT_ERROR);
323 		}
324 
325 		/*
326 		 * If our mktime() set tm_isdst, adjust the result for DST by
327 		 * subtracting the offset between the main and alternate zones.
328 		 */
329 		if (tm.tm_isdst)
330 			tvp->tv_sec -= timezone - altzone;
331 
332 		if (p[0] == '.') {
333 			arg = p;
334 			errno = 0;
335 			tvp->tv_usec =
336 			    (suseconds_t)(strtod(arg, &p) * (double)MICROSEC);
337 
338 			if (errno != 0 || p == arg || *p != '\0') {
339 				(void) fprintf(stderr, "%s: illegal time "
340 				    "suffix -- .%s\n", g_pname, arg);
341 				exit(FMDUMP_EXIT_USAGE);
342 			}
343 		}
344 
345 	} else {
346 		(void) fprintf(stderr, "%s: unexpected suffix after "
347 		    "time %s -- %s\n", g_pname, arg, p);
348 		exit(FMDUMP_EXIT_USAGE);
349 	}
350 
351 	return (tvp);
352 }
353 
354 /*
355  * If the -u option is specified in combination with the -e option, we iterate
356  * over each record in the fault log with a matching UUID finding xrefs to the
357  * error log, and then use this function to iterate over every xref'd record.
358  */
359 int
360 xref_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
361 {
362 	const fmd_log_record_t *xrp = rp->rec_xrefs;
363 	fmdump_arg_t *dap = arg;
364 	int i, rv = 0;
365 
366 	for (i = 0; rv == 0 && i < rp->rec_nrefs; i++, xrp++) {
367 		if (fmd_log_filter(lp, dap->da_fc, dap->da_fv, xrp))
368 			rv = dap->da_fmt->do_func(lp, xrp, dap->da_fp);
369 	}
370 
371 	return (rv);
372 }
373 
374 int
375 xoff_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
376 {
377 	fmdump_lyr_t *dyp = arg;
378 
379 	fmdump_printf(dyp->dy_fp, "%16llx ", (u_longlong_t)rp->rec_off);
380 	return (dyp->dy_func(lp, rp, dyp->dy_arg));
381 }
382 
383 /*
384  * Initialize fmd_log_filter_nvarg_t from -n name=value argument string.
385  */
386 static fmd_log_filter_nvarg_t *
387 setupnamevalue(char *namevalue)
388 {
389 	fmd_log_filter_nvarg_t	*argt;
390 	char			*value;
391 	regex_t			*value_regex = NULL;
392 	char			errstr[128];
393 	int			rv;
394 
395 	if ((value = strchr(namevalue, '=')) == NULL) {
396 		value_regex = NULL;
397 	} else {
398 		*value++ = '\0';	/* separate name and value string */
399 
400 		/*
401 		 * Skip white space before value to facilitate direct
402 		 * cut/paste from previous fmdump output.
403 		 */
404 		while (isspace(*value))
405 			value++;
406 
407 		if ((value_regex = malloc(sizeof (regex_t))) == NULL) {
408 			(void) fprintf(stderr, "%s: failed to allocate memory: "
409 			    "%s\n", g_pname, strerror(errno));
410 			exit(FMDUMP_EXIT_FATAL);
411 		}
412 
413 		/* compile regular expression for possible string match */
414 		if ((rv = regcomp(value_regex, value,
415 		    REG_NOSUB|REG_NEWLINE)) != 0) {
416 			(void) regerror(rv, value_regex, errstr,
417 			    sizeof (errstr));
418 			(void) fprintf(stderr, "unexpected regular expression "
419 			    "in %s: %s\n", value, errstr);
420 			free(value_regex);
421 			exit(FMDUMP_EXIT_USAGE);
422 		}
423 	}
424 
425 	if ((argt = malloc(sizeof (fmd_log_filter_nvarg_t))) == NULL) {
426 		(void) fprintf(stderr, "%s: failed to allocate memory: %s\n",
427 		    g_pname, strerror(errno));
428 		exit(FMDUMP_EXIT_FATAL);
429 	}
430 	argt->nvarg_name = namevalue;		/* now just name */
431 	argt->nvarg_value = value;
432 	argt->nvarg_value_regex = value_regex;
433 	return (argt);
434 }
435 
436 /*
437  * If the -a option is not present, filter out fault records that correspond
438  * to events that the producer requested not be messaged for administrators.
439  */
440 /*ARGSUSED*/
441 int
442 log_filter_silent(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
443 {
444 	boolean_t msg;
445 
446 	return (nvlist_lookup_boolean_value(rp->rec_nvl,
447 	    FM_SUSPECT_MESSAGE, &msg) != 0 || msg != 0);
448 }
449 
450 struct loglink {
451 	char 		*path;
452 	long		suffix;
453 	struct loglink	*next;
454 };
455 
456 static void
457 addlink(struct loglink **llp, char *dirname, char *logname, long suffix)
458 {
459 	struct loglink *newp;
460 	size_t len;
461 	char *str;
462 
463 	newp = malloc(sizeof (struct loglink));
464 	len = strlen(dirname) + strlen(logname) + 2;
465 	str = malloc(len);
466 	if (newp == NULL || str == NULL) {
467 		(void) fprintf(stderr, "%s: failed to allocate memory: %s\n",
468 		    g_pname, strerror(errno));
469 		exit(FMDUMP_EXIT_FATAL);
470 	}
471 
472 	(void) snprintf(str, len, "%s/%s", dirname, logname);
473 	newp->path = str;
474 	newp->suffix = suffix;
475 
476 	while (*llp != NULL && suffix < (*llp)->suffix)
477 		llp = &(*llp)->next;
478 
479 	newp->next = *llp;
480 	*llp = newp;
481 }
482 
483 /*
484  * Find and return all the rotated logs.
485  */
486 static struct loglink *
487 get_rotated_logs(char *logpath)
488 {
489 	char dirname[PATH_MAX], *logname, *endptr;
490 	DIR *dirp;
491 	struct dirent *dp;
492 	long len, suffix;
493 	struct loglink *head = NULL;
494 
495 	(void) strlcpy(dirname, logpath, sizeof (dirname));
496 	logname = strrchr(dirname, '/');
497 	*logname++ = '\0';
498 	len = strlen(logname);
499 
500 	if ((dirp = opendir(dirname)) == NULL) {
501 		(void) fprintf(stderr, "%s: failed to opendir `%s': %s\n",
502 		    g_pname, dirname, strerror(errno));
503 		return (NULL);
504 	}
505 
506 	while ((dp = readdir(dirp)) != NULL) {
507 		/*
508 		 * Search the log directory for logs named "<logname>.0",
509 		 * "<logname>.1", etc and add to the link in the
510 		 * reverse numeric order.
511 		 */
512 		if (strlen(dp->d_name) < len + 2 ||
513 		    strncmp(dp->d_name, logname, len) != 0 ||
514 		    dp->d_name[len] != '.')
515 			continue;
516 
517 		/*
518 		 * "*.0-" file normally should not be seen.  It may
519 		 * exist when user manually run 'fmadm rotate'.
520 		 * In such case, we put it at the end of the list so
521 		 * it'll be dumped after all the rotated logs, before
522 		 * the current one.
523 		 */
524 		if (strcmp(dp->d_name + len + 1, "0-") == 0)
525 			addlink(&head, dirname, dp->d_name, -1);
526 		else if ((suffix = strtol(dp->d_name + len + 1,
527 		    &endptr, 10)) >= 0 && *endptr == '\0')
528 			addlink(&head, dirname, dp->d_name, suffix);
529 	}
530 
531 	(void) closedir(dirp);
532 
533 	return (head);
534 }
535 
536 int
537 main(int argc, char *argv[])
538 {
539 	int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0, opt_m = 0;
540 	int opt_u = 0, opt_v = 0, opt_V = 0;
541 
542 	char ifile[PATH_MAX] = "";
543 	int iflags = 0;
544 
545 	fmdump_arg_t arg;
546 	fmdump_lyr_t lyr;
547 	const fmdump_ops_t *ops;
548 	fmd_log_filter_t *filtv;
549 	uint_t filtc;
550 
551 	fmd_log_filter_t *errfv, *fltfv, *allfv;
552 	uint_t errfc = 0, fltfc = 0, allfc = 0;
553 
554 	fmd_log_header_t log;
555 	fmd_log_rec_f *func;
556 	void *farg;
557 	fmd_log_t *lp;
558 	int c, err;
559 	off64_t off = 0;
560 	ulong_t recs;
561 	struct loglink *rotated_logs = NULL, *llp;
562 
563 	g_pname = argv[0];
564 
565 	errfv = alloca(sizeof (fmd_log_filter_t) * argc);
566 	fltfv = alloca(sizeof (fmd_log_filter_t) * argc);
567 	allfv = alloca(sizeof (fmd_log_filter_t) * argc);
568 
569 	while (optind < argc) {
570 		while ((c =
571 		    getopt(argc, argv, "ac:efHmn:O:R:t:T:u:vV")) != EOF) {
572 			switch (c) {
573 			case 'a':
574 				opt_a++;
575 				break;
576 			case 'c':
577 				errfv[errfc].filt_func = fmd_log_filter_class;
578 				errfv[errfc].filt_arg = optarg;
579 				allfv[allfc++] = errfv[errfc++];
580 				break;
581 			case 'e':
582 				opt_e++;
583 				break;
584 			case 'f':
585 				opt_f++;
586 				break;
587 			case 'H':
588 				opt_H++;
589 				break;
590 			case 'm':
591 				opt_m++;
592 				break;
593 			case 'O':
594 				off = strtoull(optarg, NULL, 16);
595 				iflags |= FMD_LOG_XITER_OFFS;
596 				break;
597 			case 'R':
598 				g_root = optarg;
599 				break;
600 			case 't':
601 				errfv[errfc].filt_func = fmd_log_filter_after;
602 				errfv[errfc].filt_arg = gettimeopt(optarg);
603 				allfv[allfc++] = errfv[errfc++];
604 				break;
605 			case 'T':
606 				errfv[errfc].filt_func = fmd_log_filter_before;
607 				errfv[errfc].filt_arg = gettimeopt(optarg);
608 				allfv[allfc++] = errfv[errfc++];
609 				break;
610 			case 'u':
611 				fltfv[fltfc].filt_func = fmd_log_filter_uuid;
612 				fltfv[fltfc].filt_arg = optarg;
613 				allfv[allfc++] = fltfv[fltfc++];
614 				opt_u++;
615 				opt_a++; /* -u implies -a */
616 				break;
617 			case 'n': {
618 				fltfv[fltfc].filt_func = fmd_log_filter_nv;
619 				fltfv[fltfc].filt_arg = setupnamevalue(optarg);
620 				allfv[allfc++] = fltfv[fltfc++];
621 				break;
622 			}
623 			case 'v':
624 				opt_v++;
625 				break;
626 			case 'V':
627 				opt_V++;
628 				break;
629 			default:
630 				return (usage(stderr));
631 			}
632 		}
633 
634 		if (optind < argc) {
635 			if (*ifile != '\0') {
636 				(void) fprintf(stderr, "%s: illegal "
637 				    "argument -- %s\n", g_pname, argv[optind]);
638 				return (FMDUMP_EXIT_USAGE);
639 			} else {
640 				(void) strlcpy(ifile,
641 				    argv[optind++], sizeof (ifile));
642 			}
643 		}
644 	}
645 
646 	if (*ifile == '\0') {
647 		(void) snprintf(ifile, sizeof (ifile), "%s/var/fm/fmd/%slog",
648 		    g_root ? g_root : "", opt_e && !opt_u ? "err" : "flt");
649 		/*
650 		 * logadm may rotate the logs.  When no input file is specified,
651 		 * we try to dump all the rotated logs as well in the right
652 		 * order.
653 		 */
654 		if (!opt_H && off == 0)
655 			rotated_logs = get_rotated_logs(ifile);
656 	} else if (g_root != NULL) {
657 		(void) fprintf(stderr, "%s: -R option is not appropriate "
658 		    "when file operand is present\n", g_pname);
659 		return (FMDUMP_EXIT_USAGE);
660 	}
661 
662 	if ((g_msg = fmd_msg_init(g_root, FMD_MSG_VERSION)) == NULL) {
663 		(void) fprintf(stderr, "%s: failed to initialize "
664 		    "libfmd_msg: %s\n", g_pname, strerror(errno));
665 		return (FMDUMP_EXIT_FATAL);
666 	}
667 
668 	if ((lp = fmd_log_open(FMD_LOG_VERSION, ifile, &err)) == NULL) {
669 		(void) fprintf(stderr, "%s: failed to open %s: %s\n",
670 		    g_pname, ifile, fmd_log_errmsg(NULL, err));
671 		return (FMDUMP_EXIT_FATAL);
672 	}
673 
674 	if (opt_H) {
675 		fmd_log_header(lp, &log);
676 
677 		(void) printf("EXD_CREATOR = %s\n", log.log_creator);
678 		(void) printf("EXD_HOSTNAME = %s\n", log.log_hostname);
679 		(void) printf("EXD_FMA_LABEL = %s\n", log.log_label);
680 		(void) printf("EXD_FMA_VERSION = %s\n", log.log_version);
681 		(void) printf("EXD_FMA_OSREL = %s\n", log.log_osrelease);
682 		(void) printf("EXD_FMA_OSVER = %s\n", log.log_osversion);
683 		(void) printf("EXD_FMA_PLAT = %s\n", log.log_platform);
684 		(void) printf("EXD_FMA_UUID = %s\n", log.log_uuid);
685 
686 		return (FMDUMP_EXIT_SUCCESS);
687 	}
688 
689 	if (off != 0 && fmd_log_seek(lp, off) != 0) {
690 		(void) fprintf(stderr, "%s: failed to seek %s: %s\n",
691 		    g_pname, ifile, fmd_log_errmsg(lp, fmd_log_errno(lp)));
692 		return (FMDUMP_EXIT_FATAL);
693 	}
694 
695 	if (opt_e && opt_u)
696 		ops = &fmdump_err_ops;
697 	else if (strcmp(fmd_log_label(lp), fmdump_flt_ops.do_label) == 0)
698 		ops = &fmdump_flt_ops;
699 	else if (strcmp(fmd_log_label(lp), fmdump_asru_ops.do_label) == 0)
700 		ops = &fmdump_asru_ops;
701 	else
702 		ops = &fmdump_err_ops;
703 
704 	if (!opt_a && ops == &fmdump_flt_ops) {
705 		fltfv[fltfc].filt_func = log_filter_silent;
706 		fltfv[fltfc].filt_arg = NULL;
707 		allfv[allfc++] = fltfv[fltfc++];
708 	}
709 
710 	if (opt_V) {
711 		arg.da_fmt = &ops->do_formats[FMDUMP_VERB2];
712 		iflags |= FMD_LOG_XITER_REFS;
713 	} else if (opt_v) {
714 		arg.da_fmt = &ops->do_formats[FMDUMP_VERB1];
715 	} else if (opt_m) {
716 		arg.da_fmt = &ops->do_formats[FMDUMP_MSG];
717 	} else
718 		arg.da_fmt = &ops->do_formats[FMDUMP_SHORT];
719 
720 	if (opt_m && arg.da_fmt->do_func == NULL) {
721 		(void) fprintf(stderr, "%s: -m mode is not supported for "
722 		    "log of type %s: %s\n", g_pname, fmd_log_label(lp), ifile);
723 		return (FMDUMP_EXIT_USAGE);
724 	}
725 
726 	arg.da_fv = errfv;
727 	arg.da_fc = errfc;
728 	arg.da_fp = stdout;
729 
730 	if (iflags & FMD_LOG_XITER_OFFS)
731 		fmdump_printf(arg.da_fp, "%16s ", "OFFSET");
732 
733 	if (arg.da_fmt->do_hdr)
734 		fmdump_printf(arg.da_fp, "%s\n", arg.da_fmt->do_hdr);
735 
736 	if (opt_e && opt_u) {
737 		iflags |= FMD_LOG_XITER_REFS;
738 		func = xref_iter;
739 		farg = &arg;
740 		filtc = fltfc;
741 		filtv = fltfv;
742 	} else {
743 		func = arg.da_fmt->do_func;
744 		farg = arg.da_fp;
745 		filtc = allfc;
746 		filtv = allfv;
747 	}
748 
749 	if (iflags & FMD_LOG_XITER_OFFS) {
750 		lyr.dy_func = func;
751 		lyr.dy_arg = farg;
752 		lyr.dy_fp = arg.da_fp;
753 		func = xoff_iter;
754 		farg = &lyr;
755 	}
756 
757 	for (llp = rotated_logs; llp != NULL; llp = llp->next) {
758 		fmd_log_t *rlp;
759 
760 		if ((rlp = fmd_log_open(FMD_LOG_VERSION, llp->path, &err))
761 		    == NULL) {
762 			(void) fprintf(stderr, "%s: failed to open %s: %s\n",
763 			    g_pname, llp->path, fmd_log_errmsg(NULL, err));
764 			g_errs++;
765 			continue;
766 		}
767 
768 		recs = 0;
769 		if (fmd_log_xiter(rlp, iflags, filtc, filtv,
770 		    func, error, farg, &recs) != 0) {
771 			(void) fprintf(stderr,
772 			    "%s: failed to dump %s: %s\n", g_pname, llp->path,
773 			    fmd_log_errmsg(rlp, fmd_log_errno(rlp)));
774 			g_errs++;
775 		}
776 		g_recs += recs;
777 
778 		fmd_log_close(rlp);
779 	}
780 
781 	do {
782 		recs = 0;
783 		if (fmd_log_xiter(lp, iflags, filtc, filtv,
784 		    func, error, farg, &recs) != 0) {
785 			(void) fprintf(stderr,
786 			    "%s: failed to dump %s: %s\n", g_pname, ifile,
787 			    fmd_log_errmsg(lp, fmd_log_errno(lp)));
788 			g_errs++;
789 		}
790 		g_recs += recs;
791 
792 		if (opt_f)
793 			(void) sleep(1);
794 
795 	} while (opt_f);
796 
797 	if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO))
798 		(void) fprintf(stderr, "%s: %s is empty\n", g_pname, ifile);
799 
800 	if (g_thp != NULL)
801 		topo_close(g_thp);
802 
803 	fmd_log_close(lp);
804 	fmd_msg_fini(g_msg);
805 
806 	return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS);
807 }
808