xref: /illumos-gate/usr/src/cmd/fm/fmdump/common/fmdump.c (revision b6805bf78d2bbbeeaea8909a05623587b42d58b3)
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 /*
23  * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
25  */
26 
27 #include <alloca.h>
28 #include <unistd.h>
29 #include <limits.h>
30 #include <strings.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <errno.h>
35 #include <time.h>
36 #include <ctype.h>
37 #include <regex.h>
38 #include <dirent.h>
39 #include <pthread.h>
40 
41 #include <fmdump.h>
42 
43 #define	FMDUMP_EXIT_SUCCESS	0
44 #define	FMDUMP_EXIT_FATAL	1
45 #define	FMDUMP_EXIT_USAGE	2
46 #define	FMDUMP_EXIT_ERROR	3
47 
48 const char *g_pname;
49 ulong_t g_errs;
50 ulong_t g_recs;
51 char *g_root;
52 
53 struct topo_hdl *g_thp;
54 fmd_msg_hdl_t *g_msg;
55 
56 /*PRINTFLIKE2*/
57 void
58 fmdump_printf(FILE *fp, const char *format, ...)
59 {
60 	va_list ap;
61 
62 	va_start(ap, format);
63 
64 	if (vfprintf(fp, format, ap) < 0) {
65 		(void) fprintf(stderr, "%s: failed to print record: %s\n",
66 		    g_pname, strerror(errno));
67 		g_errs++;
68 	}
69 
70 	va_end(ap);
71 }
72 
73 void
74 fmdump_vwarn(const char *format, va_list ap)
75 {
76 	int err = errno;
77 
78 	(void) fprintf(stderr, "%s: warning: ", g_pname);
79 	(void) vfprintf(stderr, format, ap);
80 
81 	if (strchr(format, '\n') == NULL)
82 		(void) fprintf(stderr, ": %s\n", strerror(err));
83 
84 	g_errs++;
85 }
86 
87 /*PRINTFLIKE1*/
88 void
89 fmdump_warn(const char *format, ...)
90 {
91 	va_list ap;
92 
93 	va_start(ap, format);
94 	fmdump_vwarn(format, ap);
95 	va_end(ap);
96 }
97 
98 static void
99 fmdump_exit(int err, int exitcode, const char *format, va_list ap)
100 {
101 	(void) fprintf(stderr, "%s: ", g_pname);
102 
103 	(void) vfprintf(stderr, format, ap);
104 
105 	if (strchr(format, '\n') == NULL)
106 		(void) fprintf(stderr, ": %s\n", strerror(err));
107 
108 	exit(exitcode);
109 }
110 
111 /*PRINTFLIKE1*/
112 static void
113 fmdump_fatal(const char *format, ...)
114 {
115 	int err = errno;
116 
117 	va_list ap;
118 
119 	va_start(ap, format);
120 	fmdump_exit(err, FMDUMP_EXIT_FATAL, format, ap);
121 	va_end(ap);
122 }
123 
124 /*PRINTFLIKE1*/
125 static void
126 fmdump_usage(const char *format, ...)
127 {
128 
129 	int err = errno;
130 
131 	va_list ap;
132 
133 	va_start(ap, format);
134 	fmdump_exit(err, FMDUMP_EXIT_USAGE, format, ap);
135 	va_end(ap);
136 }
137 
138 char *
139 fmdump_date(char *buf, size_t len, const fmd_log_record_t *rp)
140 {
141 	if (rp->rec_sec > LONG_MAX) {
142 		fmdump_warn("record time is too large for 32-bit utility\n");
143 		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
144 	} else {
145 		time_t tod = (time_t)rp->rec_sec;
146 		time_t now = time(NULL);
147 		if (tod > now+60 ||
148 		    tod < now - 6L*30L*24L*60L*60L) { /* 6 months ago */
149 			(void) strftime(buf, len, "%b %d %Y %T",
150 			    localtime(&tod));
151 		} else {
152 			size_t sz;
153 			sz = strftime(buf, len, "%b %d %T", localtime(&tod));
154 			(void) snprintf(buf + sz, len - sz, ".%4.4llu",
155 			    rp->rec_nsec / (NANOSEC / 10000));
156 		}
157 	}
158 
159 	return (buf);
160 }
161 
162 char *
163 fmdump_year(char *buf, size_t len, const fmd_log_record_t *rp)
164 {
165 #ifdef _ILP32
166 	if (rp->rec_sec > LONG_MAX) {
167 		fmdump_warn("record time is too large for 32-bit utility\n");
168 		(void) snprintf(buf, len, "0x%llx", rp->rec_sec);
169 	} else {
170 #endif
171 		time_t tod = (time_t)rp->rec_sec;
172 		(void) strftime(buf, len, "%b %d %Y %T", localtime(&tod));
173 #ifdef _ILP32
174 	}
175 #endif
176 	return (buf);
177 }
178 
179 /* BEGIN CSTYLED */
180 static const char *synopsis =
181 "Usage: %s [[-e | -i | -I] | -A ] [-f] [-mvVp] [-c class] [-R root]\n"
182 	"\t      [-t time ][-T time] [-u uuid] [-n name[.name]*[=value]] "
183 							"[file]...\n    "
184     "Log selection: [-e | -i | -I] or one [file]; default is the fault log\n"
185 	"\t-e  display error log content\n"
186 	"\t-i  display infolog content\n"
187 	"\t-I  display the high-value-infolog content\n"
188 	"\t-R  set root directory for pathname expansions\n    "
189     "Command behaviour:\n"
190 	"\t-A  Aggregate specified [file]s or, if no [file], all known logs\n"
191 	"\t-f  follow growth of log file by waiting for additional data\n    "
192     "Output options:\n"
193 	"\t-m  display human-readable messages (only for fault logs)\n"
194 	"\t-v  set verbose mode: display additional event detail\n"
195 	"\t-V  set very verbose mode: display complete event contents\n"
196 	"\t-p  Used with -V: apply some output prettification\n    "
197     "Selection filters:\n"
198 	"\t-c  select events that match the specified class\n"
199 	"\t-t  select events that occurred after the specified time\n"
200 	"\t-T  select events that occurred before the specified time\n"
201 	"\t-u  select events that match the specified diagnosis uuid\n"
202 	"\t-n  select events containing named nvpair (with matching value)\n";
203 /* END CSTYLED */
204 
205 static int
206 usage(FILE *fp)
207 {
208 	(void) fprintf(fp, synopsis, g_pname);
209 	return (FMDUMP_EXIT_USAGE);
210 }
211 
212 /*ARGSUSED*/
213 static int
214 error(fmd_log_t *lp, void *private)
215 {
216 	fmdump_warn("skipping record: %s\n",
217 	    fmd_log_errmsg(lp, fmd_log_errno(lp)));
218 	return (0);
219 }
220 
221 /*
222  * Yet another disgusting argument parsing function (TM).  We attempt to parse
223  * a time argument in a variety of strptime(3C) formats, in which case it is
224  * interpreted as a local time and is converted to a timeval using mktime(3C).
225  * If those formats fail, we look to see if the time is a decimal integer
226  * followed by one of our magic suffixes, in which case the time is interpreted
227  * as a time delta *before* the current time-of-day (i.e. "1h" = "1 hour ago").
228  */
229 static struct timeval *
230 gettimeopt(const char *arg)
231 {
232 	const struct {
233 		const char *name;
234 		hrtime_t mul;
235 	} suffix[] = {
236 		{ "ns",		NANOSEC / NANOSEC },
237 		{ "nsec",	NANOSEC / NANOSEC },
238 		{ "us",		NANOSEC / MICROSEC },
239 		{ "usec",	NANOSEC / MICROSEC },
240 		{ "ms",		NANOSEC / MILLISEC },
241 		{ "msec",	NANOSEC / MILLISEC },
242 		{ "s",		NANOSEC / SEC },
243 		{ "sec",	NANOSEC / SEC },
244 		{ "m",		NANOSEC * (hrtime_t)60 },
245 		{ "min",	NANOSEC * (hrtime_t)60 },
246 		{ "h",		NANOSEC * (hrtime_t)(60 * 60) },
247 		{ "hour",	NANOSEC * (hrtime_t)(60 * 60) },
248 		{ "d",		NANOSEC * (hrtime_t)(24 * 60 * 60) },
249 		{ "day",	NANOSEC * (hrtime_t)(24 * 60 * 60) },
250 		{ NULL }
251 	};
252 
253 	struct timeval *tvp = malloc(sizeof (struct timeval));
254 	struct timeval tod;
255 	struct tm tm;
256 	char *p;
257 
258 	if (tvp == NULL)
259 		fmdump_fatal("failed to allocate memory");
260 
261 	if (gettimeofday(&tod, NULL) != 0)
262 		fmdump_fatal("failed to get tod");
263 
264 	/*
265 	 * First try a variety of strptime() calls.  If these all fail, we'll
266 	 * try parsing an integer followed by one of our suffix[] strings.
267 	 */
268 	if ((p = strptime(arg, "%m/%d/%Y %H:%M:%S", &tm)) == NULL &&
269 	    (p = strptime(arg, "%m/%d/%y %H:%M:%S", &tm)) == NULL &&
270 	    (p = strptime(arg, "%m/%d/%Y %H:%M", &tm)) == NULL &&
271 	    (p = strptime(arg, "%m/%d/%y %H:%M", &tm)) == NULL &&
272 	    (p = strptime(arg, "%m/%d/%Y", &tm)) == NULL &&
273 	    (p = strptime(arg, "%m/%d/%y", &tm)) == NULL &&
274 	    (p = strptime(arg, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL &&
275 	    (p = strptime(arg, "%y-%m-%dT%H:%M:%S", &tm)) == NULL &&
276 	    (p = strptime(arg, "%Y-%m-%dT%H:%M", &tm)) == NULL &&
277 	    (p = strptime(arg, "%y-%m-%dT%H:%M", &tm)) == NULL &&
278 	    (p = strptime(arg, "%Y-%m-%d", &tm)) == NULL &&
279 	    (p = strptime(arg, "%y-%m-%d", &tm)) == NULL &&
280 	    (p = strptime(arg, "%d%b%Y %H:%M:%S", &tm)) == NULL &&
281 	    (p = strptime(arg, "%d%b%y %H:%M:%S", &tm)) == NULL &&
282 	    (p = strptime(arg, "%d%b%Y %H:%M", &tm)) == NULL &&
283 	    (p = strptime(arg, "%d%b%y %H:%M", &tm)) == NULL &&
284 	    (p = strptime(arg, "%d%b%Y", &tm)) == NULL &&
285 	    (p = strptime(arg, "%d%b%y", &tm)) == NULL &&
286 	    (p = strptime(arg, "%b %d %H:%M:%S", &tm)) == NULL &&
287 	    (p = strptime(arg, "%b %d %H:%M:%S", &tm)) == NULL &&
288 	    (p = strptime(arg, "%H:%M:%S", &tm)) == NULL &&
289 	    (p = strptime(arg, "%H:%M", &tm)) == NULL) {
290 
291 		hrtime_t nsec;
292 		int i;
293 
294 		errno = 0;
295 		nsec = strtol(arg, (char **)&p, 10);
296 
297 		if (errno != 0 || nsec == 0 || p == arg || *p == '\0')
298 			fmdump_usage("illegal time format -- %s\n", arg);
299 
300 		for (i = 0; suffix[i].name != NULL; i++) {
301 			if (strcasecmp(suffix[i].name, p) == 0) {
302 				nsec *= suffix[i].mul;
303 				break;
304 			}
305 		}
306 
307 		if (suffix[i].name == NULL)
308 			fmdump_usage("illegal time format -- %s\n", arg);
309 
310 		tvp->tv_sec = nsec / NANOSEC;
311 		tvp->tv_usec = (nsec % NANOSEC) / (NANOSEC / MICROSEC);
312 
313 		if (tvp->tv_sec > tod.tv_sec)
314 			fmdump_usage("time delta precedes UTC time origin "
315 			    "-- %s\n", arg);
316 
317 		tvp->tv_sec = tod.tv_sec - tvp->tv_sec;
318 
319 	} else if (*p == '\0' || *p == '.') {
320 		/*
321 		 * If tm_year is zero, we matched [%b %d] %H:%M[:%S]; use
322 		 * the result of localtime(&tod.tv_sec) to fill in the rest.
323 		 */
324 		if (tm.tm_year == 0) {
325 			int h = tm.tm_hour;
326 			int m = tm.tm_min;
327 			int s = tm.tm_sec;
328 			int b = tm.tm_mon;
329 			int d = tm.tm_mday;
330 
331 			bcopy(localtime(&tod.tv_sec), &tm, sizeof (tm));
332 			tm.tm_isdst = 0; /* see strptime(3C) and below */
333 
334 			if (d > 0) {
335 				tm.tm_mon = b;
336 				tm.tm_mday = d;
337 			}
338 
339 			tm.tm_hour = h;
340 			tm.tm_min = m;
341 			tm.tm_sec = s;
342 		}
343 
344 		errno = 0;
345 		tvp->tv_sec = mktime(&tm);
346 		tvp->tv_usec = 0;
347 
348 		if (tvp->tv_sec == -1L && errno != 0)
349 			fmdump_fatal("failed to compose time %s", arg);
350 
351 		/*
352 		 * If our mktime() set tm_isdst, adjust the result for DST by
353 		 * subtracting the offset between the main and alternate zones.
354 		 */
355 		if (tm.tm_isdst)
356 			tvp->tv_sec -= timezone - altzone;
357 
358 		if (p[0] == '.') {
359 			arg = p;
360 			errno = 0;
361 			tvp->tv_usec =
362 			    (suseconds_t)(strtod(arg, &p) * (double)MICROSEC);
363 
364 			if (errno != 0 || p == arg || *p != '\0')
365 				fmdump_usage("illegal time suffix -- .%s\n",
366 				    arg);
367 		}
368 
369 	} else {
370 		fmdump_usage("unexpected suffix after time %s -- %s\n", arg, p);
371 	}
372 
373 	return (tvp);
374 }
375 
376 /*
377  * If the -u option is specified in combination with the -e option, we iterate
378  * over each record in the fault log with a matching UUID finding xrefs to the
379  * error log, and then use this function to iterate over every xref'd record.
380  */
381 int
382 xref_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
383 {
384 	const fmd_log_record_t *xrp = rp->rec_xrefs;
385 	fmdump_arg_t *dap = arg;
386 	int i, rv = 0;
387 
388 	for (i = 0; rv == 0 && i < rp->rec_nrefs; i++, xrp++) {
389 		if (fmd_log_filter(lp, dap->da_fc, dap->da_fv, xrp))
390 			rv = dap->da_fmt->do_func(lp, xrp, dap->da_fp);
391 	}
392 
393 	return (rv);
394 }
395 
396 int
397 xoff_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
398 {
399 	fmdump_lyr_t *dyp = arg;
400 
401 	fmdump_printf(dyp->dy_fp, "%16llx ", (u_longlong_t)rp->rec_off);
402 	return (dyp->dy_func(lp, rp, dyp->dy_arg));
403 }
404 
405 /*
406  * Initialize fmd_log_filter_nvarg_t from -n name=value argument string.
407  */
408 static fmd_log_filter_nvarg_t *
409 setupnamevalue(char *namevalue)
410 {
411 	fmd_log_filter_nvarg_t	*argt;
412 	char			*value;
413 	regex_t			*value_regex = NULL;
414 	char			errstr[128];
415 	int			rv;
416 
417 	if ((value = strchr(namevalue, '=')) == NULL) {
418 		value_regex = NULL;
419 	} else {
420 		*value++ = '\0';	/* separate name and value string */
421 
422 		/*
423 		 * Skip white space before value to facilitate direct
424 		 * cut/paste from previous fmdump output.
425 		 */
426 		while (isspace(*value))
427 			value++;
428 
429 		if ((value_regex = malloc(sizeof (regex_t))) == NULL)
430 			fmdump_fatal("failed to allocate memory");
431 
432 		/* compile regular expression for possible string match */
433 		if ((rv = regcomp(value_regex, value,
434 		    REG_NOSUB|REG_NEWLINE)) != 0) {
435 			(void) regerror(rv, value_regex, errstr,
436 			    sizeof (errstr));
437 			free(value_regex);
438 			fmdump_usage("unexpected regular expression in "
439 			    "%s: %s\n", value, errstr);
440 		}
441 	}
442 
443 	if ((argt = malloc(sizeof (fmd_log_filter_nvarg_t))) == NULL)
444 		fmdump_fatal("failed to allocate memory");
445 
446 	argt->nvarg_name = namevalue;		/* now just name */
447 	argt->nvarg_value = value;
448 	argt->nvarg_value_regex = value_regex;
449 	return (argt);
450 }
451 
452 /*
453  * If the -a option is not present, filter out fault records that correspond
454  * to events that the producer requested not be messaged for administrators.
455  */
456 /*ARGSUSED*/
457 int
458 log_filter_silent(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
459 {
460 	int opt_A = (arg != NULL);
461 	boolean_t msg;
462 	char *class;
463 
464 	/*
465 	 * If -A was used then apply this filter only to events of list class
466 	 */
467 	if (opt_A) {
468 		if (nvlist_lookup_string(rp->rec_nvl, FM_CLASS, &class) != 0 ||
469 		    strncmp(class, FM_LIST_EVENT ".",
470 		    sizeof (FM_LIST_EVENT)) != 0)
471 			return (1);
472 	}
473 
474 	return (nvlist_lookup_boolean_value(rp->rec_nvl,
475 	    FM_SUSPECT_MESSAGE, &msg) != 0 || msg != 0);
476 }
477 
478 struct loglink {
479 	char		*path;
480 	long		suffix;
481 	struct loglink	*next;
482 };
483 
484 static void
485 addlink(struct loglink **llp, char *dirname, char *logname, long suffix)
486 {
487 	struct loglink *newp;
488 	size_t len;
489 	char *str;
490 
491 	newp = malloc(sizeof (struct loglink));
492 	len = strlen(dirname) + strlen(logname) + 2;
493 	str = malloc(len);
494 	if (newp == NULL || str == NULL)
495 		fmdump_fatal("failed to allocate memory");
496 
497 	(void) snprintf(str, len, "%s/%s", dirname, logname);
498 	newp->path = str;
499 	newp->suffix = suffix;
500 
501 	while (*llp != NULL && suffix < (*llp)->suffix)
502 		llp = &(*llp)->next;
503 
504 	newp->next = *llp;
505 	*llp = newp;
506 }
507 
508 /*
509  * Find and return all the rotated logs.
510  */
511 static struct loglink *
512 get_rotated_logs(char *logpath)
513 {
514 	char dirname[PATH_MAX], *logname, *endptr;
515 	DIR *dirp;
516 	struct dirent *dp;
517 	long len, suffix;
518 	struct loglink *head = NULL;
519 
520 	(void) strlcpy(dirname, logpath, sizeof (dirname));
521 	logname = strrchr(dirname, '/');
522 	*logname++ = '\0';
523 	len = strlen(logname);
524 
525 	if ((dirp = opendir(dirname)) == NULL) {
526 		fmdump_warn("failed to opendir `%s'", dirname);
527 		g_errs++;
528 		return (NULL);
529 	}
530 
531 	while ((dp = readdir(dirp)) != NULL) {
532 		/*
533 		 * Search the log directory for logs named "<logname>.0",
534 		 * "<logname>.1", etc and add to the link in the
535 		 * reverse numeric order.
536 		 */
537 		if (strlen(dp->d_name) < len + 2 ||
538 		    strncmp(dp->d_name, logname, len) != 0 ||
539 		    dp->d_name[len] != '.')
540 			continue;
541 
542 		/*
543 		 * "*.0-" file normally should not be seen.  It may
544 		 * exist when user manually run 'fmadm rotate'.
545 		 * In such case, we put it at the end of the list so
546 		 * it'll be dumped after all the rotated logs, before
547 		 * the current one.
548 		 */
549 		if (strcmp(dp->d_name + len + 1, "0-") == 0)
550 			addlink(&head, dirname, dp->d_name, -1);
551 		else if ((suffix = strtol(dp->d_name + len + 1,
552 		    &endptr, 10)) >= 0 && *endptr == '\0')
553 			addlink(&head, dirname, dp->d_name, suffix);
554 	}
555 
556 	(void) closedir(dirp);
557 
558 	return (head);
559 }
560 
561 /*
562  * Aggregate log files.  If ifiles is not NULL then one or more files
563  * were listed on the command line, and we will merge just those files.
564  * Otherwise we will merge all known log file types, and include the
565  * rotated logs for each type (you can suppress the inclusion of
566  * some logtypes through use of FMDUMP_AGGREGATE_IGNORE in the process
567  * environment, setting it to a comma-separated list of log labels and/or
568  * log filenames to ignore).
569  *
570  * We will not attempt to perform a chronological sort across all log records
571  * of all files.  Indeed, we won't even sort individual log files -
572  * we will not re-order events differently to how they appeared in their
573  * original log file.  This is because log files are already inherently
574  * ordered by the order in which fmd receives and processes events.
575  * So we determine the output order by comparing the "next" record
576  * off the top of each log file.
577  *
578  * We will construct a number of log record source "pipelines".  As above,
579  * the next record to render in the overall output is that from the
580  * pipeline with the oldest event.
581  *
582  * For the case that input logfiles were listed on the command line, each
583  * pipeline will process exactly one of those logfiles.  Distinct pipelines
584  * may process logfiles of the same "type" - eg if two "error" logs and
585  * one "fault" logs are specified then there'll be two pipelines producing
586  * events from "error" logs.
587  *
588  * If we are merging all known log types then we will construct exactly
589  * one pipeline for each known log type - one for error, one for fault, etc.
590  * Each pipeline will process first the rotated logs of that type and then
591  * move on to the current log of that type.
592  *
593  * The output from all pipelines flows into a serializer which selects
594  * the next record once all pipelines have asserted their output state.
595  * The output state of a pipeline is one of:
596  *
597  *	- record available: the next record from this pipeline is available
598  *	  for comparison and consumption
599  *
600  *	- done: this pipeline will produce no more records
601  *
602  *	- polling: this pipeline is polling for new records and will
603  *	  make them available as output if/when any are observed
604  *
605  *	- processing: output state will be updated shortly
606  *
607  * A pipeline iterates over each file queued to it using fmd_log_xiter.
608  * We do this in a separate thread for each pipeline.  The callback on
609  * each iteration must update the serializer to let it know that
610  * a new record is available.  In the serializer thread we decide whether
611  * we have all records expected have arrived and it is time to choose
612  * the next output record.
613  */
614 
615 /*
616  * A pipeline descriptor.  The pl_cv condition variable is used together
617  * with pl_lock for initial synchronisation, and thereafter with the
618  * lock for the serializer for pausing and continuing this pipeline.
619  */
620 struct fmdump_pipeline {
621 	pthread_mutex_t pl_lock;	/* used only in pipeline startup */
622 	int pl_started;			/* sync with main thread on startup */
623 	pthread_t pl_thr;		/* our processing thread */
624 	pthread_cond_t pl_cv;		/* see above */
625 	struct loglink *pl_rotated;	/* rotated logs to process first */
626 	char *pl_logpath;		/* target path to process */
627 	char *pl_processing;		/* path currently being processed */
628 	struct fmdump_srlzer *pl_srlzer;	/* link to serializer */
629 	int pl_srlzeridx;		/* serializer index for this pipeline */
630 	const fmdump_ops_t *pl_ops;	/* ops for the log type we're given */
631 	int pl_fmt;			/* FMDUMP_{SHORT,VERB1,VERB2,PRETTY} */
632 	boolean_t pl_follow;		/* go into poll mode at log end */
633 	fmdump_arg_t pl_arg;		/* arguments */
634 };
635 
636 enum fmdump_pipestate {
637 	FMDUMP_PIPE_PROCESSING = 0x1000,
638 	FMDUMP_PIPE_RECORDAVAIL,
639 	FMDUMP_PIPE_POLLING,
640 	FMDUMP_PIPE_DONE
641 };
642 
643 /*
644  * Each pipeline has an associated output slot in the serializer.  This
645  * must be updated with the serializer locked.  After update evaluate
646  * whether there are enough slots decided that we should select a
647  * record to output.
648  */
649 struct fmdump_srlzer_slot {
650 	enum fmdump_pipestate ss_state;
651 	uint64_t ss_sec;
652 	uint64_t ss_nsec;
653 };
654 
655 /*
656  * All pipelines are linked to a single serializer.  The serializer
657  * structure must be updated under the ds_lock; this mutex is also
658  * paired with the pl_cv of individual pipelines (one mutex, many condvars)
659  * in pausing and continuing individual pipelines.
660  */
661 struct fmdump_srlzer {
662 	struct fmdump_pipeline *ds_pipearr;	/* pipeline array */
663 	pthread_mutex_t ds_lock;		/* see above */
664 	uint32_t ds_pipecnt;			/* number of pipelines */
665 	uint32_t ds_pollcnt;			/* pipelines in poll mode */
666 	uint32_t ds_nrecordavail;		/* pipelines with a record */
667 	uint32_t ds_ndone;			/* completed pipelines */
668 	struct fmdump_srlzer_slot *ds_slot;	/* slot array */
669 };
670 
671 /*
672  * All known log types.  When aggregation is requested an no file list
673  * is provided we will process the logs identified here (if lt_enabled
674  * is true and not over-ridden by environment settings).  We also
675  * use this in determining the appropriate ops structure for each distinct
676  * label.
677  */
678 static struct fmdump_logtype {
679 	const char *lt_label;		/* label from log header */
680 	boolean_t lt_enabled;		/* include in merge? */
681 	const char *lt_logname;		/* var/fm/fmd/%s */
682 	const fmdump_ops_t *lt_ops;
683 } logtypes[] = {
684 	{
685 		"error",
686 		B_TRUE,
687 		"errlog",
688 		&fmdump_err_ops
689 	},
690 	{
691 		"fault",
692 		B_TRUE,
693 		"fltlog",
694 		&fmdump_flt_ops
695 	},
696 	{
697 		"info",
698 		B_TRUE,
699 		"infolog",
700 		&fmdump_info_ops
701 	},
702 	{
703 		"info",
704 		B_TRUE,
705 		"infolog_hival",
706 		&fmdump_info_ops
707 	},
708 	{
709 		"asru",
710 		B_FALSE,		/* not included unless in file list */
711 		NULL,
712 		&fmdump_asru_ops	/* but we need ops when it is */
713 	}
714 };
715 
716 /*
717  * Disable logtypes per environment setting.  Does not apply when a list
718  * of logs is provided on the command line.
719  */
720 static void
721 do_disables(void)
722 {
723 	char *env = getenv("FMDUMP_AGGREGATE_IGNORE");
724 	char *dup, *start, *tofree;
725 	int i;
726 
727 	if (env == NULL)
728 		return;
729 
730 	tofree = dup = strdup(env);
731 
732 	while (dup != NULL) {
733 		start = strsep(&dup, ",");
734 		for (i = 0; i < sizeof (logtypes) / sizeof (logtypes[0]); i++) {
735 			if (logtypes[i].lt_logname == NULL)
736 				continue;
737 
738 			if (strcmp(start, logtypes[i].lt_label) == 0 ||
739 			    strcmp(start, logtypes[i].lt_logname) == 0) {
740 				logtypes[i].lt_enabled = B_FALSE;
741 			}
742 		}
743 	}
744 
745 	free(tofree);
746 }
747 
748 static void
749 srlzer_enter(struct fmdump_pipeline *pl)
750 {
751 	struct fmdump_srlzer *srlzer = pl->pl_srlzer;
752 
753 	(void) pthread_mutex_lock(&srlzer->ds_lock);
754 }
755 
756 static void
757 srlzer_exit(struct fmdump_pipeline *pl)
758 {
759 	struct fmdump_srlzer *srlzer = pl->pl_srlzer;
760 
761 	ASSERT(MUTEX_HELD(&srlzer->ds_lock));
762 	(void) pthread_mutex_unlock(&srlzer->ds_lock);
763 }
764 
765 static struct fmdump_pipeline *
766 srlzer_choose(struct fmdump_srlzer *srlzer)
767 {
768 	struct fmdump_srlzer_slot *slot, *oldest;
769 	int oldestidx = -1;
770 	int first = 1;
771 	int i;
772 
773 	ASSERT(MUTEX_HELD(&srlzer->ds_lock));
774 
775 	for (i = 0, slot = &srlzer->ds_slot[0]; i < srlzer->ds_pipecnt;
776 	    i++, slot++) {
777 		if (slot->ss_state != FMDUMP_PIPE_RECORDAVAIL)
778 			continue;
779 
780 		if (first) {
781 			oldest = slot;
782 			oldestidx = i;
783 			first = 0;
784 			continue;
785 		}
786 
787 		if (slot->ss_sec < oldest->ss_sec ||
788 		    slot->ss_sec == oldest->ss_sec &&
789 		    slot->ss_nsec < oldest->ss_nsec) {
790 			oldest = slot;
791 			oldestidx = i;
792 		}
793 	}
794 
795 	return (oldestidx >= 0 ? &srlzer->ds_pipearr[oldestidx] : NULL);
796 }
797 
798 static void
799 pipeline_stall(struct fmdump_pipeline *pl)
800 {
801 	struct fmdump_srlzer *srlzer = pl->pl_srlzer;
802 
803 	ASSERT(MUTEX_HELD(&srlzer->ds_lock));
804 	(void) pthread_cond_wait(&pl->pl_cv, &srlzer->ds_lock);
805 }
806 
807 static void
808 pipeline_continue(struct fmdump_pipeline *pl)
809 {
810 	struct fmdump_srlzer *srlzer = pl->pl_srlzer;
811 
812 	ASSERT(MUTEX_HELD(&srlzer->ds_lock));
813 	(void) pthread_cond_signal(&srlzer->ds_pipearr[pl->pl_srlzeridx].pl_cv);
814 }
815 
816 /*
817  * Called on each pipeline record iteration to make a new record
818  * available for input to the serializer.  Returns 0 to indicate that
819  * the caller must stall the pipeline, or 1 to indicate that the
820  * caller should go ahead and render their record.  If this record
821  * addition fills the serializer then choose a pipeline that must
822  * render output.
823  */
824 static int
825 pipeline_output(struct fmdump_pipeline *pl, const fmd_log_record_t *rp)
826 {
827 	struct fmdump_srlzer *srlzer = pl->pl_srlzer;
828 	struct fmdump_srlzer_slot *slot;
829 	struct fmdump_pipeline *wpl;
830 	int thisidx = pl->pl_srlzeridx;
831 
832 	ASSERT(MUTEX_HELD(&srlzer->ds_lock));
833 
834 	slot = &srlzer->ds_slot[thisidx];
835 	slot->ss_state = FMDUMP_PIPE_RECORDAVAIL;
836 	slot->ss_sec = rp->rec_sec;
837 	slot->ss_nsec = rp->rec_nsec;
838 	srlzer->ds_nrecordavail++;
839 
840 	/*
841 	 * Once all pipelines are polling we just render in arrival order.
842 	 */
843 	if (srlzer->ds_pollcnt == srlzer->ds_pipecnt)
844 		return (1);
845 
846 	/*
847 	 * If not all pipelines have asserted an output yet then the
848 	 * caller must block.
849 	 */
850 	if (srlzer->ds_nrecordavail + srlzer->ds_ndone +
851 	    srlzer->ds_pollcnt < srlzer->ds_pipecnt)
852 		return (0);
853 
854 	/*
855 	 * Right so it's time to turn the crank by choosing which of the
856 	 * filled line of slots should produce output.  If it is the slot
857 	 * for our caller then return their index to them, otherwise return
858 	 * -1 to the caller to make them block and cv_signal the winner.
859 	 */
860 	wpl = srlzer_choose(srlzer);
861 	ASSERT(wpl != NULL);
862 
863 	if (wpl == pl)
864 		return (1);
865 
866 	/* Wake the oldest, and return 0 to put the caller to sleep */
867 	pipeline_continue(wpl);
868 
869 	return (0);
870 }
871 
872 static void
873 pipeline_mark_consumed(struct fmdump_pipeline *pl)
874 {
875 	struct fmdump_srlzer *srlzer = pl->pl_srlzer;
876 
877 	ASSERT(MUTEX_HELD(&srlzer->ds_lock));
878 	srlzer->ds_slot[pl->pl_srlzeridx].ss_state = FMDUMP_PIPE_PROCESSING;
879 	srlzer->ds_nrecordavail--;
880 }
881 
882 static void
883 pipeline_done(struct fmdump_pipeline *pl)
884 {
885 	struct fmdump_srlzer *srlzer = pl->pl_srlzer;
886 	struct fmdump_pipeline *wpl;
887 
888 	srlzer_enter(pl);
889 
890 	srlzer->ds_slot[pl->pl_srlzeridx].ss_state = FMDUMP_PIPE_DONE;
891 	srlzer->ds_ndone++;
892 	wpl = srlzer_choose(srlzer);
893 	if (wpl != NULL)
894 		pipeline_continue(wpl);
895 
896 	srlzer_exit(pl);
897 }
898 
899 static void
900 pipeline_pollmode(struct fmdump_pipeline *pl)
901 {
902 	struct fmdump_srlzer *srlzer = pl->pl_srlzer;
903 	struct fmdump_pipeline *wpl;
904 
905 	if (srlzer->ds_slot[pl->pl_srlzeridx].ss_state == FMDUMP_PIPE_POLLING)
906 		return;
907 
908 	srlzer_enter(pl);
909 
910 	srlzer->ds_slot[pl->pl_srlzeridx].ss_state = FMDUMP_PIPE_POLLING;
911 	if (++srlzer->ds_pollcnt + srlzer->ds_nrecordavail ==
912 	    srlzer->ds_pipecnt && (wpl = srlzer_choose(srlzer)) != NULL)
913 		pipeline_continue(wpl);
914 
915 	srlzer_exit(pl);
916 }
917 
918 static int
919 pipeline_err(fmd_log_t *lp, void *arg)
920 {
921 	struct fmdump_pipeline *pl = (struct fmdump_pipeline *)arg;
922 
923 	fmdump_warn("skipping record in %s: %s\n", pl->pl_processing,
924 	    fmd_log_errmsg(lp, fmd_log_errno(lp)));
925 	g_errs++;
926 
927 	return (0);
928 }
929 
930 static int
931 pipeline_cb(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg)
932 {
933 	struct fmdump_pipeline *pl = (struct fmdump_pipeline *)arg;
934 	int rc;
935 
936 	fmd_log_rec_f *func = pl->pl_arg.da_fmt->do_func;
937 
938 	srlzer_enter(pl);
939 
940 	if (!pipeline_output(pl, rp))
941 		pipeline_stall(pl);
942 
943 	rc = func(lp, rp, pl->pl_arg.da_fp);
944 	pipeline_mark_consumed(pl);
945 
946 	srlzer_exit(pl);
947 
948 	return (rc);
949 }
950 
951 static void
952 pipeline_process(struct fmdump_pipeline *pl, char *logpath, boolean_t follow)
953 {
954 	fmd_log_header_t log;
955 	fmd_log_t *lp;
956 	int err;
957 	int i;
958 
959 	pl->pl_processing = logpath;
960 
961 	if ((lp = fmd_log_open(FMD_LOG_VERSION, logpath, &err)) == NULL) {
962 		fmdump_warn("failed to open %s: %s\n",
963 		    logpath, fmd_log_errmsg(NULL, err));
964 		g_errs++;
965 		return;
966 	}
967 
968 	fmd_log_header(lp, &log);
969 	for (i = 0; i < sizeof (logtypes) / sizeof (logtypes[0]); i++) {
970 		if (strcmp(log.log_label, logtypes[i].lt_label) == 0) {
971 			pl->pl_ops = logtypes[i].lt_ops;
972 			pl->pl_arg.da_fmt =
973 			    &pl->pl_ops->do_formats[pl->pl_fmt];
974 			break;
975 		}
976 	}
977 
978 	if (pl->pl_ops == NULL) {
979 		fmdump_warn("unknown log type %s for %s\n",
980 		    log.log_label, logpath);
981 		g_errs++;
982 		return;
983 	}
984 
985 	do {
986 		if (fmd_log_xiter(lp, FMD_LOG_XITER_REFS, pl->pl_arg.da_fc,
987 		    pl->pl_arg.da_fv, pipeline_cb, pipeline_err, (void *)pl,
988 		    NULL) != 0) {
989 			fmdump_warn("failed to dump %s: %s\n",
990 			    logpath, fmd_log_errmsg(lp, fmd_log_errno(lp)));
991 			g_errs++;
992 			fmd_log_close(lp);
993 			return;
994 		}
995 
996 		if (follow) {
997 			pipeline_pollmode(pl);
998 			(void) sleep(1);
999 		}
1000 
1001 	} while (follow);
1002 
1003 	fmd_log_close(lp);
1004 }
1005 
1006 static void *
1007 pipeline_thr(void *arg)
1008 {
1009 	struct fmdump_pipeline *pl = (struct fmdump_pipeline *)arg;
1010 	struct loglink *ll;
1011 
1012 	(void) pthread_mutex_lock(&pl->pl_lock);
1013 	pl->pl_started = 1;
1014 	(void) pthread_mutex_unlock(&pl->pl_lock);
1015 	(void) pthread_cond_signal(&pl->pl_cv);
1016 
1017 	for (ll = pl->pl_rotated; ll != NULL; ll = ll->next)
1018 		pipeline_process(pl, ll->path, B_FALSE);
1019 
1020 	pipeline_process(pl, pl->pl_logpath, pl->pl_follow);
1021 	pipeline_done(pl);
1022 
1023 	return (NULL);
1024 }
1025 
1026 
1027 static int
1028 aggregate(char **ifiles, int n_ifiles, int opt_f,
1029     fmd_log_filter_t *fv, uint_t fc,
1030     int opt_v, int opt_V, int opt_p)
1031 {
1032 	struct fmdump_pipeline *pipeline, *pl;
1033 	struct fmdump_srlzer srlzer;
1034 	uint32_t npipe;
1035 	int fmt;
1036 	int i;
1037 
1038 	if (ifiles != NULL) {
1039 		npipe = n_ifiles;
1040 		pipeline = calloc(npipe, sizeof (struct fmdump_pipeline));
1041 		if (!pipeline)
1042 			fmdump_fatal("failed to allocate memory");
1043 
1044 		for (i = 0; i < n_ifiles; i++)
1045 			pipeline[i].pl_logpath = ifiles[i];
1046 	} else {
1047 		pipeline = calloc(sizeof (logtypes) / sizeof (logtypes[0]),
1048 		    sizeof (struct fmdump_pipeline));
1049 		if (!pipeline)
1050 			fmdump_fatal("failed to allocate memory");
1051 
1052 		do_disables();
1053 
1054 		npipe = 0;
1055 		for (i = 0; i < sizeof (logtypes) / sizeof (logtypes[0]); i++) {
1056 			struct fmdump_logtype *ltp = &logtypes[i];
1057 			char *logpath;
1058 
1059 			if (ltp->lt_enabled == B_FALSE)
1060 				continue;
1061 
1062 			if ((logpath = malloc(PATH_MAX)) == NULL)
1063 				fmdump_fatal("failed to allocate memory");
1064 
1065 			(void) snprintf(logpath, PATH_MAX,
1066 			    "%s/var/fm/fmd/%s",
1067 			    g_root ? g_root : "", ltp->lt_logname);
1068 
1069 			pipeline[npipe].pl_rotated =
1070 			    get_rotated_logs(logpath);
1071 
1072 			pipeline[npipe++].pl_logpath = logpath;
1073 		}
1074 	}
1075 
1076 	if (opt_V)
1077 		fmt = opt_p ? FMDUMP_PRETTY : FMDUMP_VERB2;
1078 	else if (opt_v)
1079 		fmt = FMDUMP_VERB1;
1080 	else
1081 		fmt = FMDUMP_SHORT;
1082 
1083 	bzero(&srlzer, sizeof (srlzer));
1084 	srlzer.ds_pipearr = pipeline;
1085 	srlzer.ds_pipecnt = npipe;
1086 	srlzer.ds_slot = calloc(npipe, sizeof (struct fmdump_srlzer_slot));
1087 	if (!srlzer.ds_slot)
1088 		fmdump_fatal("failed to allocate memory");
1089 	(void) pthread_mutex_init(&srlzer.ds_lock, NULL);
1090 
1091 	for (i = 0, pl = &pipeline[0]; i < npipe; i++, pl++) {
1092 		(void) pthread_mutex_init(&pl->pl_lock, NULL);
1093 		(void) pthread_cond_init(&pl->pl_cv, NULL);
1094 		srlzer.ds_slot[i].ss_state = FMDUMP_PIPE_PROCESSING;
1095 		pl->pl_srlzer = &srlzer;
1096 		pl->pl_srlzeridx = i;
1097 		pl->pl_follow = opt_f ? B_TRUE : B_FALSE;
1098 		pl->pl_fmt = fmt;
1099 		pl->pl_arg.da_fv = fv;
1100 		pl->pl_arg.da_fc = fc;
1101 		pl->pl_arg.da_fp = stdout;
1102 
1103 		(void) pthread_mutex_lock(&pl->pl_lock);
1104 
1105 		if (pthread_create(&pl->pl_thr, NULL,
1106 		    pipeline_thr, (void *)pl) != 0)
1107 			fmdump_fatal("pthread_create for pipeline %d failed",
1108 			    i);
1109 	}
1110 
1111 	for (i = 0, pl = &pipeline[0]; i < npipe; i++, pl++) {
1112 		while (!pl->pl_started)
1113 			(void) pthread_cond_wait(&pl->pl_cv, &pl->pl_lock);
1114 
1115 		(void) pthread_mutex_unlock(&pl->pl_lock);
1116 	}
1117 
1118 	for (i = 0, pl = &pipeline[0]; i < npipe; i++, pl++)
1119 		(void) pthread_join(pl->pl_thr, NULL);
1120 
1121 	if (ifiles == NULL) {
1122 		for (i = 0; i < npipe; i++)
1123 			free(pipeline[i].pl_logpath);
1124 	}
1125 
1126 	free(srlzer.ds_slot);
1127 
1128 	free(pipeline);
1129 
1130 	return (FMDUMP_EXIT_SUCCESS);
1131 }
1132 
1133 static void
1134 cleanup(char **ifiles, int n_ifiles)
1135 {
1136 	int i;
1137 
1138 	if (ifiles == NULL)
1139 		return;
1140 
1141 	for (i = 0; i < n_ifiles; i++) {
1142 		if (ifiles[i] != NULL) {
1143 			free(ifiles[i]);
1144 			ifiles[i] = NULL;
1145 		}
1146 	}
1147 
1148 	free(ifiles);
1149 }
1150 
1151 int
1152 main(int argc, char *argv[])
1153 {
1154 	int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0, opt_m = 0, opt_p = 0;
1155 	int opt_u = 0, opt_v = 0, opt_V = 0;
1156 	int opt_i = 0, opt_I = 0;
1157 	int opt_A = 0;
1158 	char **ifiles = NULL;
1159 	char *ifile = NULL;
1160 	int n_ifiles;
1161 	int ifileidx = 0;
1162 	int iflags = 0;
1163 
1164 	fmdump_arg_t arg;
1165 	fmdump_lyr_t lyr;
1166 	const fmdump_ops_t *ops;
1167 	fmd_log_filter_t *filtv;
1168 	uint_t filtc;
1169 
1170 	fmd_log_filter_t *errfv, *fltfv, *allfv;
1171 	uint_t errfc = 0, fltfc = 0, allfc = 0;
1172 
1173 	fmd_log_header_t log;
1174 	fmd_log_rec_f *func;
1175 	void *farg;
1176 	fmd_log_t *lp;
1177 	int c, err;
1178 	off64_t off = 0;
1179 	ulong_t recs;
1180 	struct loglink *rotated_logs = NULL, *llp;
1181 
1182 	g_pname = argv[0];
1183 
1184 	errfv = alloca(sizeof (fmd_log_filter_t) * argc);
1185 	fltfv = alloca(sizeof (fmd_log_filter_t) * argc);
1186 	allfv = alloca(sizeof (fmd_log_filter_t) * argc);
1187 
1188 	while (optind < argc) {
1189 		while ((c =
1190 		    getopt(argc, argv, "Aac:efHiImn:O:pR:t:T:u:vV")) != EOF) {
1191 			switch (c) {
1192 			case 'A':
1193 				opt_A++;
1194 				break;
1195 			case 'a':
1196 				opt_a++;
1197 				break;
1198 			case 'c':
1199 				errfv[errfc].filt_func = fmd_log_filter_class;
1200 				errfv[errfc].filt_arg = optarg;
1201 				allfv[allfc++] = errfv[errfc++];
1202 				break;
1203 			case 'e':
1204 				if (opt_i)
1205 					return (usage(stderr));
1206 				opt_e++;
1207 				break;
1208 			case 'f':
1209 				opt_f++;
1210 				break;
1211 			case 'H':
1212 				opt_H++;
1213 				break;
1214 			case 'i':
1215 				if (opt_e || opt_I)
1216 					return (usage(stderr));
1217 				opt_i++;
1218 				break;
1219 			case 'I':
1220 				if (opt_e || opt_i)
1221 					return (usage(stderr));
1222 				opt_I++;
1223 				break;
1224 			case 'm':
1225 				opt_m++;
1226 				break;
1227 			case 'O':
1228 				off = strtoull(optarg, NULL, 16);
1229 				iflags |= FMD_LOG_XITER_OFFS;
1230 				break;
1231 			case 'p':
1232 				opt_p++;
1233 				break;
1234 			case 'R':
1235 				g_root = optarg;
1236 				break;
1237 			case 't':
1238 				errfv[errfc].filt_func = fmd_log_filter_after;
1239 				errfv[errfc].filt_arg = gettimeopt(optarg);
1240 				allfv[allfc++] = errfv[errfc++];
1241 				break;
1242 			case 'T':
1243 				errfv[errfc].filt_func = fmd_log_filter_before;
1244 				errfv[errfc].filt_arg = gettimeopt(optarg);
1245 				allfv[allfc++] = errfv[errfc++];
1246 				break;
1247 			case 'u':
1248 				fltfv[fltfc].filt_func = fmd_log_filter_uuid;
1249 				fltfv[fltfc].filt_arg = optarg;
1250 				allfv[allfc++] = fltfv[fltfc++];
1251 				opt_u++;
1252 				opt_a++; /* -u implies -a */
1253 				break;
1254 			case 'n': {
1255 				fltfv[fltfc].filt_func = fmd_log_filter_nv;
1256 				fltfv[fltfc].filt_arg = setupnamevalue(optarg);
1257 				allfv[allfc++] = fltfv[fltfc++];
1258 				break;
1259 			}
1260 			case 'v':
1261 				opt_v++;
1262 				break;
1263 			case 'V':
1264 				opt_V++;
1265 				break;
1266 			default:
1267 				return (usage(stderr));
1268 			}
1269 		}
1270 
1271 		if (opt_A && (opt_e || opt_i || opt_I || opt_m || opt_u))
1272 			fmdump_usage("-A excludes all of "
1273 			    "-e, -i, -I, -m and -u\n");
1274 
1275 		if (optind < argc) {
1276 			char *dest;
1277 
1278 			if (ifiles == NULL) {
1279 				n_ifiles = argc - optind;
1280 				ifiles = calloc(n_ifiles, sizeof (char *));
1281 				if (ifiles == NULL) {
1282 					fmdump_fatal(
1283 					    "failed to allocate memory for "
1284 					    "%d input file%s", n_ifiles,
1285 					    n_ifiles > 1 ? "s" : "");
1286 				}
1287 			}
1288 
1289 			if (ifileidx > 0 && !opt_A)
1290 				fmdump_usage("illegal argument -- %s\n",
1291 				    argv[optind]);
1292 
1293 			if ((dest = malloc(PATH_MAX)) == NULL)
1294 				fmdump_fatal("failed to allocate memory");
1295 
1296 			(void) strlcpy(dest, argv[optind++], PATH_MAX);
1297 			ifiles[ifileidx++] = dest;
1298 		}
1299 	}
1300 
1301 	if (opt_A) {
1302 		int rc;
1303 
1304 		if (!opt_a) {
1305 			fltfv[fltfc].filt_func = log_filter_silent;
1306 			fltfv[fltfc].filt_arg = (void *)1;
1307 			allfv[allfc++] = fltfv[fltfc++];
1308 		}
1309 
1310 		rc = aggregate(ifiles, n_ifiles, opt_f,
1311 		    allfv, allfc,
1312 		    opt_v, opt_V, opt_p);
1313 
1314 		cleanup(ifiles, n_ifiles);
1315 		return (rc);
1316 	} else {
1317 		if (ifiles == NULL) {
1318 			if ((ifile = calloc(1, PATH_MAX)) == NULL)
1319 				fmdump_fatal("failed to allocate memory");
1320 		} else {
1321 			ifile = ifiles[0];
1322 		}
1323 	}
1324 
1325 
1326 	if (*ifile == '\0') {
1327 		const char *pfx, *sfx;
1328 
1329 		if (opt_u || (!opt_e && !opt_i && !opt_I)) {
1330 			pfx = "flt";
1331 			sfx = "";
1332 		} else {
1333 			if (opt_e) {
1334 				pfx = "err";
1335 				sfx = "";
1336 			} else {
1337 				pfx = "info";
1338 				sfx = opt_I ? "_hival" : "";
1339 			}
1340 		}
1341 
1342 		(void) snprintf(ifile, PATH_MAX, "%s/var/fm/fmd/%slog%s",
1343 		    g_root ? g_root : "", pfx, sfx);
1344 		/*
1345 		 * logadm may rotate the logs.  When no input file is specified,
1346 		 * we try to dump all the rotated logs as well in the right
1347 		 * order.
1348 		 */
1349 		if (!opt_H && off == 0)
1350 			rotated_logs = get_rotated_logs(ifile);
1351 	} else if (g_root != NULL) {
1352 		fmdump_usage("-R option is not appropriate "
1353 		    "when file operand is present\n");
1354 	}
1355 
1356 	if ((g_msg = fmd_msg_init(g_root, FMD_MSG_VERSION)) == NULL)
1357 		fmdump_fatal("failed to initialize libfmd_msg");
1358 
1359 	if ((lp = fmd_log_open(FMD_LOG_VERSION, ifile, &err)) == NULL) {
1360 		fmdump_fatal("failed to open %s: %s\n", ifile,
1361 		    fmd_log_errmsg(NULL, err));
1362 	}
1363 
1364 	if (opt_H) {
1365 		fmd_log_header(lp, &log);
1366 
1367 		(void) printf("EXD_CREATOR = %s\n", log.log_creator);
1368 		(void) printf("EXD_HOSTNAME = %s\n", log.log_hostname);
1369 		(void) printf("EXD_FMA_LABEL = %s\n", log.log_label);
1370 		(void) printf("EXD_FMA_VERSION = %s\n", log.log_version);
1371 		(void) printf("EXD_FMA_OSREL = %s\n", log.log_osrelease);
1372 		(void) printf("EXD_FMA_OSVER = %s\n", log.log_osversion);
1373 		(void) printf("EXD_FMA_PLAT = %s\n", log.log_platform);
1374 		(void) printf("EXD_FMA_UUID = %s\n", log.log_uuid);
1375 
1376 		return (FMDUMP_EXIT_SUCCESS);
1377 	}
1378 
1379 	if (off != 0 && fmd_log_seek(lp, off) != 0) {
1380 		fmdump_fatal("failed to seek %s: %s\n", ifile,
1381 		    fmd_log_errmsg(lp, fmd_log_errno(lp)));
1382 	}
1383 
1384 	if (opt_e && opt_u)
1385 		ops = &fmdump_err_ops;
1386 	else if (strcmp(fmd_log_label(lp), fmdump_flt_ops.do_label) == 0)
1387 		ops = &fmdump_flt_ops;
1388 	else if (strcmp(fmd_log_label(lp), fmdump_asru_ops.do_label) == 0)
1389 		ops = &fmdump_asru_ops;
1390 	else if (strcmp(fmd_log_label(lp), fmdump_info_ops.do_label) == 0)
1391 		ops = &fmdump_info_ops;
1392 	else
1393 		ops = &fmdump_err_ops;
1394 
1395 	if (!opt_a && ops == &fmdump_flt_ops) {
1396 		fltfv[fltfc].filt_func = log_filter_silent;
1397 		fltfv[fltfc].filt_arg = NULL;
1398 		allfv[allfc++] = fltfv[fltfc++];
1399 	}
1400 
1401 	if (opt_V) {
1402 		arg.da_fmt =
1403 		    &ops->do_formats[opt_p ? FMDUMP_PRETTY : FMDUMP_VERB2];
1404 		iflags |= FMD_LOG_XITER_REFS;
1405 	} else if (opt_v) {
1406 		arg.da_fmt = &ops->do_formats[FMDUMP_VERB1];
1407 	} else if (opt_m) {
1408 		arg.da_fmt = &ops->do_formats[FMDUMP_MSG];
1409 	} else
1410 		arg.da_fmt = &ops->do_formats[FMDUMP_SHORT];
1411 
1412 	if (opt_m && arg.da_fmt->do_func == NULL) {
1413 		fmdump_usage("-m mode is not supported for "
1414 		    "log of type %s: %s\n", fmd_log_label(lp), ifile);
1415 	}
1416 
1417 	arg.da_fv = errfv;
1418 	arg.da_fc = errfc;
1419 	arg.da_fp = stdout;
1420 
1421 	if (iflags & FMD_LOG_XITER_OFFS)
1422 		fmdump_printf(arg.da_fp, "%16s ", "OFFSET");
1423 
1424 	if (arg.da_fmt->do_hdr && !(opt_V && ops == &fmdump_flt_ops))
1425 		fmdump_printf(arg.da_fp, "%s\n", arg.da_fmt->do_hdr);
1426 
1427 	if (opt_e && opt_u) {
1428 		iflags |= FMD_LOG_XITER_REFS;
1429 		func = xref_iter;
1430 		farg = &arg;
1431 		filtc = fltfc;
1432 		filtv = fltfv;
1433 	} else {
1434 		func = arg.da_fmt->do_func;
1435 		farg = arg.da_fp;
1436 		filtc = allfc;
1437 		filtv = allfv;
1438 	}
1439 
1440 	if (iflags & FMD_LOG_XITER_OFFS) {
1441 		lyr.dy_func = func;
1442 		lyr.dy_arg = farg;
1443 		lyr.dy_fp = arg.da_fp;
1444 		func = xoff_iter;
1445 		farg = &lyr;
1446 	}
1447 
1448 	for (llp = rotated_logs; llp != NULL; llp = llp->next) {
1449 		fmd_log_t *rlp;
1450 
1451 		if ((rlp = fmd_log_open(FMD_LOG_VERSION, llp->path, &err))
1452 		    == NULL) {
1453 			fmdump_warn("failed to open %s: %s\n",
1454 			    llp->path, fmd_log_errmsg(NULL, err));
1455 			g_errs++;
1456 			continue;
1457 		}
1458 
1459 		recs = 0;
1460 		if (fmd_log_xiter(rlp, iflags, filtc, filtv,
1461 		    func, error, farg, &recs) != 0) {
1462 			fmdump_warn("failed to dump %s: %s\n", llp->path,
1463 			    fmd_log_errmsg(rlp, fmd_log_errno(rlp)));
1464 			g_errs++;
1465 		}
1466 		g_recs += recs;
1467 
1468 		fmd_log_close(rlp);
1469 	}
1470 
1471 	do {
1472 		recs = 0;
1473 		if (fmd_log_xiter(lp, iflags, filtc, filtv,
1474 		    func, error, farg, &recs) != 0) {
1475 			fmdump_warn("failed to dump %s: %s\n", ifile,
1476 			    fmd_log_errmsg(lp, fmd_log_errno(lp)));
1477 			g_errs++;
1478 		}
1479 		g_recs += recs;
1480 
1481 		if (opt_f)
1482 			(void) sleep(1);
1483 
1484 	} while (opt_f);
1485 
1486 	if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO))
1487 		fmdump_warn("%s is empty\n", ifile);
1488 
1489 	if (g_thp != NULL)
1490 		topo_close(g_thp);
1491 
1492 	fmd_log_close(lp);
1493 	fmd_msg_fini(g_msg);
1494 
1495 	if (ifiles == NULL)
1496 		free(ifile);
1497 	else
1498 		cleanup(ifiles, n_ifiles);
1499 
1500 	return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS);
1501 }
1502