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