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