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