/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <alloca.h>
#include <unistd.h>
#include <limits.h>
#include <strings.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <regex.h>
#include <dirent.h>

#include <fmdump.h>

#define	FMDUMP_EXIT_SUCCESS	0
#define	FMDUMP_EXIT_FATAL	1
#define	FMDUMP_EXIT_USAGE	2
#define	FMDUMP_EXIT_ERROR	3

const char *g_pname;
ulong_t g_errs;
ulong_t g_recs;
char *g_root;

struct topo_hdl *g_thp;
fmd_msg_hdl_t *g_msg;

/*PRINTFLIKE2*/
void
fmdump_printf(FILE *fp, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);

	if (vfprintf(fp, format, ap) < 0) {
		(void) fprintf(stderr, "%s: failed to print record: %s\n",
		    g_pname, strerror(errno));
		g_errs++;
	}

	va_end(ap);
}

void
fmdump_vwarn(const char *format, va_list ap)
{
	int err = errno;

	(void) fprintf(stderr, "%s: warning: ", g_pname);
	(void) vfprintf(stderr, format, ap);

	if (strchr(format, '\n') == NULL)
		(void) fprintf(stderr, ": %s\n", strerror(err));

	g_errs++;
}

/*PRINTFLIKE1*/
void
fmdump_warn(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	fmdump_vwarn(format, ap);
	va_end(ap);
}

char *
fmdump_date(char *buf, size_t len, const fmd_log_record_t *rp)
{
	if (rp->rec_sec > LONG_MAX) {
		fmdump_warn("record time is too large for 32-bit utility\n");
		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
	} else {
		time_t tod = (time_t)rp->rec_sec;
		time_t now = time(NULL);
		if (tod > now+60 ||
		    tod < now - 6L*30L*24L*60L*60L) { /* 6 months ago */
			(void) strftime(buf, len, "%b %d %Y %T",
			    localtime(&tod));
		} else {
			size_t sz;
			sz = strftime(buf, len, "%b %d %T", localtime(&tod));
			(void) snprintf(buf + sz, len - sz, ".%4.4llu",
			    rp->rec_nsec / (NANOSEC / 10000));
		}
	}

	return (buf);
}

char *
fmdump_year(char *buf, size_t len, const fmd_log_record_t *rp)
{
#ifdef _ILP32
	if (rp->rec_sec > LONG_MAX) {
		fmdump_warn("record time is too large for 32-bit utility\n");
		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
	} else {
#endif
		time_t tod = (time_t)rp->rec_sec;
		(void) strftime(buf, len, "%b %d %Y %T", localtime(&tod));
#ifdef _ILP32
	}
#endif
	return (buf);
}

static int
usage(FILE *fp)
{
	(void) fprintf(fp, "Usage: %s [-efmvV] [-c class] [-R root] [-t time] "
	    "[-T time] [-u uuid]\n\t\t[-n name[.name]*[=value]] [file]\n",
	    g_pname);

	(void) fprintf(fp,
	    "\t-c  select events that match the specified class\n"
	    "\t-e  display error log content instead of fault log content\n"
	    "\t-f  follow growth of log file by waiting for additional data\n"
	    "\t-m  display human-readable messages for the fault log\n"
	    "\t-R  set root directory for pathname expansions\n"
	    "\t-t  select events that occurred after the specified time\n"
	    "\t-T  select events that occurred before the specified time\n"
	    "\t-u  select events that match the specified uuid\n"
	    "\t-n  select events containing named nvpair "
	    "(with matching value)\n"
	    "\t-v  set verbose mode: display additional event detail\n"
	    "\t-V  set very verbose mode: display complete event contents\n");

	return (FMDUMP_EXIT_USAGE);
}

/*ARGSUSED*/
static int
error(fmd_log_t *lp, void *private)
{
	fmdump_warn("skipping record: %s\n",
	    fmd_log_errmsg(lp, fmd_log_errno(lp)));
	return (0);
}

/*
 * Yet another disgusting argument parsing function (TM).  We attempt to parse
 * a time argument in a variety of strptime(3C) formats, in which case it is
 * interpreted as a local time and is converted to a timeval using mktime(3C).
 * If those formats fail, we look to see if the time is a decimal integer
 * followed by one of our magic suffixes, in which case the time is interpreted
 * as a time delta *before* the current time-of-day (i.e. "1h" = "1 hour ago").
 */
static struct timeval *
gettimeopt(const char *arg)
{
	const struct {
		const char *name;
		hrtime_t mul;
	} suffix[] = {
		{ "ns",		NANOSEC / NANOSEC },
		{ "nsec",	NANOSEC / NANOSEC },
		{ "us",		NANOSEC / MICROSEC },
		{ "usec",	NANOSEC / MICROSEC },
		{ "ms",		NANOSEC / MILLISEC },
		{ "msec",	NANOSEC / MILLISEC },
		{ "s",		NANOSEC / SEC },
		{ "sec",	NANOSEC / SEC },
		{ "m",		NANOSEC * (hrtime_t)60 },
		{ "min",	NANOSEC * (hrtime_t)60 },
		{ "h",		NANOSEC * (hrtime_t)(60 * 60) },
		{ "hour",	NANOSEC * (hrtime_t)(60 * 60) },
		{ "d",		NANOSEC * (hrtime_t)(24 * 60 * 60) },
		{ "day",	NANOSEC * (hrtime_t)(24 * 60 * 60) },
		{ NULL }
	};

	struct timeval *tvp = malloc(sizeof (struct timeval));
	struct timeval tod;
	struct tm tm;
	char *p;

	if (tvp == NULL) {
		(void) fprintf(stderr, "%s: failed to allocate memory: %s\n",
		    g_pname, strerror(errno));
		exit(FMDUMP_EXIT_FATAL);
	}

	if (gettimeofday(&tod, NULL) != 0) {
		(void) fprintf(stderr, "%s: failed to get tod: %s\n",
		    g_pname, strerror(errno));
		exit(FMDUMP_EXIT_FATAL);
	}

	/*
	 * First try a variety of strptime() calls.  If these all fail, we'll
	 * try parsing an integer followed by one of our suffix[] strings.
	 * NOTE: any form using %y must appear *before* the equivalent %Y form;
	 * otherwise %Y will accept the two year digits but infer century zero.
	 * Any form ending in %y must additionally check isdigit(*p) to ensure
	 * that it does not inadvertently match 2 digits of a 4-digit year.
	 *
	 * Beware: Any strptime() sequence containing consecutive %x sequences
	 * may fall victim to SCCS expanding it as a keyword!  If this happens
	 * we use separate string constant that ANSI C will concatenate.
	 */
	if ((p = strptime(arg, "%m/%d/%y" "%t" "%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%m/%d/%y" "%t" "%H:%M", &tm)) == NULL &&
	    (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M", &tm)) == NULL &&
	    ((p = strptime(arg, "%m/%d/%y", &tm)) == NULL || isdigit(*p)) &&
	    (p = strptime(arg, "%m/%d/%Y", &tm)) == NULL &&
	    (p = strptime(arg, "%y-%m-%dT%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%y-%m-%dT%H:%M", &tm)) == NULL &&
	    (p = strptime(arg, "%Y-%m-%dT%H:%M", &tm)) == NULL &&
	    (p = strptime(arg, "%y-%m-%d", &tm)) == NULL &&
	    (p = strptime(arg, "%Y-%m-%d", &tm)) == NULL &&
	    (p = strptime(arg, "%d%b%y" "%t" "%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%d%b%Y" "%t" "%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%d%b%y" "%t" "%H:%M", &tm)) == NULL &&
	    (p = strptime(arg, "%d%b%Y" "%t" "%H:%M", &tm)) == NULL &&
	    ((p = strptime(arg, "%d%b%y", &tm)) == NULL || isdigit(*p)) &&
	    (p = strptime(arg, "%d%b%Y", &tm)) == NULL &&
	    (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%H:%M:%S", &tm)) == NULL &&
	    (p = strptime(arg, "%H:%M", &tm)) == NULL) {

		hrtime_t nsec;
		int i;

		errno = 0;
		nsec = strtol(arg, (char **)&p, 10);

		if (errno != 0 || nsec == 0 || p == arg || *p == '\0') {
			(void) fprintf(stderr, "%s: illegal time "
			    "format -- %s\n", g_pname, arg);
			exit(FMDUMP_EXIT_USAGE);
		}

		for (i = 0; suffix[i].name != NULL; i++) {
			if (strcasecmp(suffix[i].name, p) == 0) {
				nsec *= suffix[i].mul;
				break;
			}
		}

		if (suffix[i].name == NULL) {
			(void) fprintf(stderr, "%s: illegal time "
			    "format -- %s\n", g_pname, arg);
			exit(FMDUMP_EXIT_USAGE);
		}

		tvp->tv_sec = nsec / NANOSEC;
		tvp->tv_usec = (nsec % NANOSEC) / (NANOSEC / MICROSEC);

		if (tvp->tv_sec > tod.tv_sec) {
			(void) fprintf(stderr, "%s: time delta precedes "
			    "UTC time origin -- %s\n", g_pname, arg);
			exit(FMDUMP_EXIT_USAGE);
		}

		tvp->tv_sec = tod.tv_sec - tvp->tv_sec;

	} else if (*p == '\0' || *p == '.') {
		/*
		 * If tm_year is zero, we matched [%b %d] %H:%M[:%S]; use
		 * the result of localtime(&tod.tv_sec) to fill in the rest.
		 */
		if (tm.tm_year == 0) {
			int h = tm.tm_hour;
			int m = tm.tm_min;
			int s = tm.tm_sec;
			int b = tm.tm_mon;
			int d = tm.tm_mday;

			bcopy(localtime(&tod.tv_sec), &tm, sizeof (tm));
			tm.tm_isdst = 0; /* see strptime(3C) and below */

			if (d > 0) {
				tm.tm_mon = b;
				tm.tm_mday = d;
			}

			tm.tm_hour = h;
			tm.tm_min = m;
			tm.tm_sec = s;
		}

		errno = 0;
		tvp->tv_sec = mktime(&tm);
		tvp->tv_usec = 0;

		if (tvp->tv_sec == -1L && errno != 0) {
			(void) fprintf(stderr, "%s: failed to compose "
			    "time %s: %s\n", g_pname, arg, strerror(errno));
			exit(FMDUMP_EXIT_ERROR);
		}

		/*
		 * If our mktime() set tm_isdst, adjust the result for DST by
		 * subtracting the offset between the main and alternate zones.
		 */
		if (tm.tm_isdst)
			tvp->tv_sec -= timezone - altzone;

		if (p[0] == '.') {
			arg = p;
			errno = 0;
			tvp->tv_usec =
			    (suseconds_t)(strtod(arg, &p) * (double)MICROSEC);

			if (errno != 0 || p == arg || *p != '\0') {
				(void) fprintf(stderr, "%s: illegal time "
				    "suffix -- .%s\n", g_pname, arg);
				exit(FMDUMP_EXIT_USAGE);
			}
		}

	} else {
		(void) fprintf(stderr, "%s: unexpected suffix after "
		    "time %s -- %s\n", g_pname, arg, p);
		exit(FMDUMP_EXIT_USAGE);
	}

	return (tvp);
}

/*
 * If the -u option is specified in combination with the -e option, we iterate
 * over each record in the fault log with a matching UUID finding xrefs to the
 * error log, and then use this function to iterate over every xref'd record.
 */
int
xref_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
{
	const fmd_log_record_t *xrp = rp->rec_xrefs;
	fmdump_arg_t *dap = arg;
	int i, rv = 0;

	for (i = 0; rv == 0 && i < rp->rec_nrefs; i++, xrp++) {
		if (fmd_log_filter(lp, dap->da_fc, dap->da_fv, xrp))
			rv = dap->da_fmt->do_func(lp, xrp, dap->da_fp);
	}

	return (rv);
}

int
xoff_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
{
	fmdump_lyr_t *dyp = arg;

	fmdump_printf(dyp->dy_fp, "%16llx ", (u_longlong_t)rp->rec_off);
	return (dyp->dy_func(lp, rp, dyp->dy_arg));
}

/*
 * Initialize fmd_log_filter_nvarg_t from -n name=value argument string.
 */
static fmd_log_filter_nvarg_t *
setupnamevalue(char *namevalue)
{
	fmd_log_filter_nvarg_t	*argt;
	char			*value;
	regex_t			*value_regex = NULL;
	char			errstr[128];
	int			rv;

	if ((value = strchr(namevalue, '=')) == NULL) {
		value_regex = NULL;
	} else {
		*value++ = '\0';	/* separate name and value string */

		/*
		 * Skip white space before value to facilitate direct
		 * cut/paste from previous fmdump output.
		 */
		while (isspace(*value))
			value++;

		if ((value_regex = malloc(sizeof (regex_t))) == NULL) {
			(void) fprintf(stderr, "%s: failed to allocate memory: "
			    "%s\n", g_pname, strerror(errno));
			exit(FMDUMP_EXIT_FATAL);
		}

		/* compile regular expression for possible string match */
		if ((rv = regcomp(value_regex, value,
		    REG_NOSUB|REG_NEWLINE)) != 0) {
			(void) regerror(rv, value_regex, errstr,
			    sizeof (errstr));
			(void) fprintf(stderr, "unexpected regular expression "
			    "in %s: %s\n", value, errstr);
			free(value_regex);
			exit(FMDUMP_EXIT_USAGE);
		}
	}

	if ((argt = malloc(sizeof (fmd_log_filter_nvarg_t))) == NULL) {
		(void) fprintf(stderr, "%s: failed to allocate memory: %s\n",
		    g_pname, strerror(errno));
		exit(FMDUMP_EXIT_FATAL);
	}
	argt->nvarg_name = namevalue;		/* now just name */
	argt->nvarg_value = value;
	argt->nvarg_value_regex = value_regex;
	return (argt);
}

/*
 * If the -a option is not present, filter out fault records that correspond
 * to events that the producer requested not be messaged for administrators.
 */
/*ARGSUSED*/
int
log_filter_silent(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
{
	boolean_t msg;

	return (nvlist_lookup_boolean_value(rp->rec_nvl,
	    FM_SUSPECT_MESSAGE, &msg) != 0 || msg != 0);
}

struct loglink {
	char 		*path;
	long		suffix;
	struct loglink	*next;
};

static void
addlink(struct loglink **llp, char *dirname, char *logname, long suffix)
{
	struct loglink *newp;
	size_t len;
	char *str;

	newp = malloc(sizeof (struct loglink));
	len = strlen(dirname) + strlen(logname) + 2;
	str = malloc(len);
	if (newp == NULL || str == NULL) {
		(void) fprintf(stderr, "%s: failed to allocate memory: %s\n",
		    g_pname, strerror(errno));
		exit(FMDUMP_EXIT_FATAL);
	}

	(void) snprintf(str, len, "%s/%s", dirname, logname);
	newp->path = str;
	newp->suffix = suffix;

	while (*llp != NULL && suffix < (*llp)->suffix)
		llp = &(*llp)->next;

	newp->next = *llp;
	*llp = newp;
}

/*
 * Find and return all the rotated logs.
 */
static struct loglink *
get_rotated_logs(char *logpath)
{
	char dirname[PATH_MAX], *logname, *endptr;
	DIR *dirp;
	struct dirent *dp;
	long len, suffix;
	struct loglink *head = NULL;

	(void) strlcpy(dirname, logpath, sizeof (dirname));
	logname = strrchr(dirname, '/');
	*logname++ = '\0';
	len = strlen(logname);

	if ((dirp = opendir(dirname)) == NULL) {
		(void) fprintf(stderr, "%s: failed to opendir `%s': %s\n",
		    g_pname, dirname, strerror(errno));
		return (NULL);
	}

	while ((dp = readdir(dirp)) != NULL) {
		/*
		 * Search the log directory for logs named "<logname>.0",
		 * "<logname>.1", etc and add to the link in the
		 * reverse numeric order.
		 */
		if (strlen(dp->d_name) < len + 2 ||
		    strncmp(dp->d_name, logname, len) != 0 ||
		    dp->d_name[len] != '.')
			continue;

		/*
		 * "*.0-" file normally should not be seen.  It may
		 * exist when user manually run 'fmadm rotate'.
		 * In such case, we put it at the end of the list so
		 * it'll be dumped after all the rotated logs, before
		 * the current one.
		 */
		if (strcmp(dp->d_name + len + 1, "0-") == 0)
			addlink(&head, dirname, dp->d_name, -1);
		else if ((suffix = strtol(dp->d_name + len + 1,
		    &endptr, 10)) >= 0 && *endptr == '\0')
			addlink(&head, dirname, dp->d_name, suffix);
	}

	(void) closedir(dirp);

	return (head);
}

int
main(int argc, char *argv[])
{
	int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0, opt_m = 0;
	int opt_u = 0, opt_v = 0, opt_V = 0;

	char ifile[PATH_MAX] = "";
	int iflags = 0;

	fmdump_arg_t arg;
	fmdump_lyr_t lyr;
	const fmdump_ops_t *ops;
	fmd_log_filter_t *filtv;
	uint_t filtc;

	fmd_log_filter_t *errfv, *fltfv, *allfv;
	uint_t errfc = 0, fltfc = 0, allfc = 0;

	fmd_log_header_t log;
	fmd_log_rec_f *func;
	void *farg;
	fmd_log_t *lp;
	int c, err;
	off64_t off = 0;
	ulong_t recs;
	struct loglink *rotated_logs = NULL, *llp;

	g_pname = argv[0];

	errfv = alloca(sizeof (fmd_log_filter_t) * argc);
	fltfv = alloca(sizeof (fmd_log_filter_t) * argc);
	allfv = alloca(sizeof (fmd_log_filter_t) * argc);

	while (optind < argc) {
		while ((c =
		    getopt(argc, argv, "ac:efHmn:O:R:t:T:u:vV")) != EOF) {
			switch (c) {
			case 'a':
				opt_a++;
				break;
			case 'c':
				errfv[errfc].filt_func = fmd_log_filter_class;
				errfv[errfc].filt_arg = optarg;
				allfv[allfc++] = errfv[errfc++];
				break;
			case 'e':
				opt_e++;
				break;
			case 'f':
				opt_f++;
				break;
			case 'H':
				opt_H++;
				break;
			case 'm':
				opt_m++;
				break;
			case 'O':
				off = strtoull(optarg, NULL, 16);
				iflags |= FMD_LOG_XITER_OFFS;
				break;
			case 'R':
				g_root = optarg;
				break;
			case 't':
				errfv[errfc].filt_func = fmd_log_filter_after;
				errfv[errfc].filt_arg = gettimeopt(optarg);
				allfv[allfc++] = errfv[errfc++];
				break;
			case 'T':
				errfv[errfc].filt_func = fmd_log_filter_before;
				errfv[errfc].filt_arg = gettimeopt(optarg);
				allfv[allfc++] = errfv[errfc++];
				break;
			case 'u':
				fltfv[fltfc].filt_func = fmd_log_filter_uuid;
				fltfv[fltfc].filt_arg = optarg;
				allfv[allfc++] = fltfv[fltfc++];
				opt_u++;
				opt_a++; /* -u implies -a */
				break;
			case 'n': {
				fltfv[fltfc].filt_func = fmd_log_filter_nv;
				fltfv[fltfc].filt_arg = setupnamevalue(optarg);
				allfv[allfc++] = fltfv[fltfc++];
				break;
			}
			case 'v':
				opt_v++;
				break;
			case 'V':
				opt_V++;
				break;
			default:
				return (usage(stderr));
			}
		}

		if (optind < argc) {
			if (*ifile != '\0') {
				(void) fprintf(stderr, "%s: illegal "
				    "argument -- %s\n", g_pname, argv[optind]);
				return (FMDUMP_EXIT_USAGE);
			} else {
				(void) strlcpy(ifile,
				    argv[optind++], sizeof (ifile));
			}
		}
	}

	if (*ifile == '\0') {
		(void) snprintf(ifile, sizeof (ifile), "%s/var/fm/fmd/%slog",
		    g_root ? g_root : "", opt_e && !opt_u ? "err" : "flt");
		/*
		 * logadm may rotate the logs.  When no input file is specified,
		 * we try to dump all the rotated logs as well in the right
		 * order.
		 */
		if (!opt_H && off == 0)
			rotated_logs = get_rotated_logs(ifile);
	} else if (g_root != NULL) {
		(void) fprintf(stderr, "%s: -R option is not appropriate "
		    "when file operand is present\n", g_pname);
		return (FMDUMP_EXIT_USAGE);
	}

	if ((g_msg = fmd_msg_init(g_root, FMD_MSG_VERSION)) == NULL) {
		(void) fprintf(stderr, "%s: failed to initialize "
		    "libfmd_msg: %s\n", g_pname, strerror(errno));
		return (FMDUMP_EXIT_FATAL);
	}

	if ((lp = fmd_log_open(FMD_LOG_VERSION, ifile, &err)) == NULL) {
		(void) fprintf(stderr, "%s: failed to open %s: %s\n",
		    g_pname, ifile, fmd_log_errmsg(NULL, err));
		return (FMDUMP_EXIT_FATAL);
	}

	if (opt_H) {
		fmd_log_header(lp, &log);

		(void) printf("EXD_CREATOR = %s\n", log.log_creator);
		(void) printf("EXD_HOSTNAME = %s\n", log.log_hostname);
		(void) printf("EXD_FMA_LABEL = %s\n", log.log_label);
		(void) printf("EXD_FMA_VERSION = %s\n", log.log_version);
		(void) printf("EXD_FMA_OSREL = %s\n", log.log_osrelease);
		(void) printf("EXD_FMA_OSVER = %s\n", log.log_osversion);
		(void) printf("EXD_FMA_PLAT = %s\n", log.log_platform);
		(void) printf("EXD_FMA_UUID = %s\n", log.log_uuid);

		return (FMDUMP_EXIT_SUCCESS);
	}

	if (off != 0 && fmd_log_seek(lp, off) != 0) {
		(void) fprintf(stderr, "%s: failed to seek %s: %s\n",
		    g_pname, ifile, fmd_log_errmsg(lp, fmd_log_errno(lp)));
		return (FMDUMP_EXIT_FATAL);
	}

	if (opt_e && opt_u)
		ops = &fmdump_err_ops;
	else if (strcmp(fmd_log_label(lp), fmdump_flt_ops.do_label) == 0)
		ops = &fmdump_flt_ops;
	else if (strcmp(fmd_log_label(lp), fmdump_asru_ops.do_label) == 0)
		ops = &fmdump_asru_ops;
	else
		ops = &fmdump_err_ops;

	if (!opt_a && ops == &fmdump_flt_ops) {
		fltfv[fltfc].filt_func = log_filter_silent;
		fltfv[fltfc].filt_arg = NULL;
		allfv[allfc++] = fltfv[fltfc++];
	}

	if (opt_V) {
		arg.da_fmt = &ops->do_formats[FMDUMP_VERB2];
		iflags |= FMD_LOG_XITER_REFS;
	} else if (opt_v) {
		arg.da_fmt = &ops->do_formats[FMDUMP_VERB1];
	} else if (opt_m) {
		arg.da_fmt = &ops->do_formats[FMDUMP_MSG];
	} else
		arg.da_fmt = &ops->do_formats[FMDUMP_SHORT];

	if (opt_m && arg.da_fmt->do_func == NULL) {
		(void) fprintf(stderr, "%s: -m mode is not supported for "
		    "log of type %s: %s\n", g_pname, fmd_log_label(lp), ifile);
		return (FMDUMP_EXIT_USAGE);
	}

	arg.da_fv = errfv;
	arg.da_fc = errfc;
	arg.da_fp = stdout;

	if (iflags & FMD_LOG_XITER_OFFS)
		fmdump_printf(arg.da_fp, "%16s ", "OFFSET");

	if (arg.da_fmt->do_hdr)
		fmdump_printf(arg.da_fp, "%s\n", arg.da_fmt->do_hdr);

	if (opt_e && opt_u) {
		iflags |= FMD_LOG_XITER_REFS;
		func = xref_iter;
		farg = &arg;
		filtc = fltfc;
		filtv = fltfv;
	} else {
		func = arg.da_fmt->do_func;
		farg = arg.da_fp;
		filtc = allfc;
		filtv = allfv;
	}

	if (iflags & FMD_LOG_XITER_OFFS) {
		lyr.dy_func = func;
		lyr.dy_arg = farg;
		lyr.dy_fp = arg.da_fp;
		func = xoff_iter;
		farg = &lyr;
	}

	for (llp = rotated_logs; llp != NULL; llp = llp->next) {
		fmd_log_t *rlp;

		if ((rlp = fmd_log_open(FMD_LOG_VERSION, llp->path, &err))
		    == NULL) {
			(void) fprintf(stderr, "%s: failed to open %s: %s\n",
			    g_pname, llp->path, fmd_log_errmsg(NULL, err));
			g_errs++;
			continue;
		}

		recs = 0;
		if (fmd_log_xiter(rlp, iflags, filtc, filtv,
		    func, error, farg, &recs) != 0) {
			(void) fprintf(stderr,
			    "%s: failed to dump %s: %s\n", g_pname, llp->path,
			    fmd_log_errmsg(rlp, fmd_log_errno(rlp)));
			g_errs++;
		}
		g_recs += recs;

		fmd_log_close(rlp);
	}

	do {
		recs = 0;
		if (fmd_log_xiter(lp, iflags, filtc, filtv,
		    func, error, farg, &recs) != 0) {
			(void) fprintf(stderr,
			    "%s: failed to dump %s: %s\n", g_pname, ifile,
			    fmd_log_errmsg(lp, fmd_log_errno(lp)));
			g_errs++;
		}
		g_recs += recs;

		if (opt_f)
			(void) sleep(1);

	} while (opt_f);

	if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO))
		(void) fprintf(stderr, "%s: %s is empty\n", g_pname, ifile);

	if (g_thp != NULL)
		topo_close(g_thp);

	fmd_log_close(lp);
	fmd_msg_fini(g_msg);

	return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS);
}