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.
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.
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]
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.
44 #define FMDUMP_EXIT_SUCCESS 0
45 #define FMDUMP_EXIT_FATAL 1
46 #define FMDUMP_EXIT_USAGE 2
47 #define FMDUMP_EXIT_ERROR 3
54 struct topo_hdl
*g_thp
;
59 fmdump_printf(FILE *fp
, const char *format
, ...)
65 if (vfprintf(fp
, format
, ap
) < 0) {
66 (void) fprintf(stderr
, "%s: failed to print record: %s\n",
67 g_pname
, strerror(errno
));
75 fmdump_vwarn(const char *format
, va_list ap
)
79 (void) fprintf(stderr
, "%s: warning: ", g_pname
);
80 (void) vfprintf(stderr
, format
, ap
);
82 if (strchr(format
, '\n') == NULL
)
83 (void) fprintf(stderr
, ": %s\n", strerror(err
));
90 fmdump_warn(const char *format
, ...)
95 fmdump_vwarn(format
, ap
);
100 fmdump_exit(int err
, int exitcode
, const char *format
, va_list ap
)
102 (void) fprintf(stderr
, "%s: ", g_pname
);
104 (void) vfprintf(stderr
, format
, ap
);
106 if (strchr(format
, '\n') == NULL
)
107 (void) fprintf(stderr
, ": %s\n", strerror(err
));
114 fmdump_fatal(const char *format
, ...)
120 va_start(ap
, format
);
121 fmdump_exit(err
, FMDUMP_EXIT_FATAL
, format
, ap
);
127 fmdump_usage(const char *format
, ...)
134 va_start(ap
, format
);
135 fmdump_exit(err
, FMDUMP_EXIT_USAGE
, format
, ap
);
140 fmdump_date(char *buf
, size_t len
, const fmd_log_record_t
*rp
)
142 if (rp
->rec_sec
> LONG_MAX
) {
143 fmdump_warn("record time is too large for 32-bit utility\n");
144 (void) snprintf(buf
, len
, "0x%llx", rp
->rec_sec
);
146 time_t tod
= (time_t)rp
->rec_sec
;
147 time_t now
= time(NULL
);
149 tod
< now
- 6L*30L*24L*60L*60L) { /* 6 months ago */
150 (void) strftime(buf
, len
, "%b %d %Y %T",
154 sz
= strftime(buf
, len
, "%b %d %T", localtime(&tod
));
155 (void) snprintf(buf
+ sz
, len
- sz
, ".%4.4llu",
156 rp
->rec_nsec
/ (NANOSEC
/ 10000));
164 fmdump_year(char *buf
, size_t len
, const fmd_log_record_t
*rp
)
167 if (rp
->rec_sec
> LONG_MAX
) {
168 fmdump_warn("record time is too large for 32-bit utility\n");
169 (void) snprintf(buf
, len
, "0x%llx", rp
->rec_sec
);
172 time_t tod
= (time_t)rp
->rec_sec
;
173 (void) strftime(buf
, len
, "%b %d %Y %T", localtime(&tod
));
181 static const char *synopsis
=
182 "Usage: %s [[-e | -i | -I] | -A ] [-f] [-mvVp] [-c class] [-R root]\n"
183 "\t [-t time ][-T time] [-u uuid] [-n name[.name]*[=value]] "
185 "Log selection: [-e | -i | -I] or one [file]; default is the fault log\n"
186 "\t-e display error log content\n"
187 "\t-i display infolog content\n"
188 "\t-I display the high-value-infolog content\n"
189 "\t-R set root directory for pathname expansions\n "
190 "Command behaviour:\n"
191 "\t-A Aggregate specified [file]s or, if no [file], all known logs\n"
192 "\t-f follow growth of log file by waiting for additional data\n "
194 "\t-m display human-readable messages (only for fault logs)\n"
195 "\t-v set verbose mode: display additional event detail\n"
196 "\t-V set very verbose mode: display complete event contents\n"
197 "\t-p Used with -V: apply some output prettification\n"
198 "\t-j Used with -V: emit JSON-formatted output\n "
199 "Selection filters:\n"
200 "\t-c select events that match the specified class\n"
201 "\t-t select events that occurred after the specified time\n"
202 "\t-T select events that occurred before the specified time\n"
203 "\t-u select events that match the specified diagnosis uuid\n"
204 "\t-n select events containing named nvpair (with matching value)\n";
210 (void) fprintf(fp
, synopsis
, g_pname
);
211 return (FMDUMP_EXIT_USAGE
);
216 error(fmd_log_t
*lp
, void *private)
218 fmdump_warn("skipping record: %s\n",
219 fmd_log_errmsg(lp
, fmd_log_errno(lp
)));
224 * Yet another disgusting argument parsing function (TM). We attempt to parse
225 * a time argument in a variety of strptime(3C) formats, in which case it is
226 * interpreted as a local time and is converted to a timeval using mktime(3C).
227 * If those formats fail, we look to see if the time is a decimal integer
228 * followed by one of our magic suffixes, in which case the time is interpreted
229 * as a time delta *before* the current time-of-day (i.e. "1h" = "1 hour ago").
231 static struct timeval
*
232 gettimeopt(const char *arg
)
238 { "ns", NANOSEC
/ NANOSEC
},
239 { "nsec", NANOSEC
/ NANOSEC
},
240 { "us", NANOSEC
/ MICROSEC
},
241 { "usec", NANOSEC
/ MICROSEC
},
242 { "ms", NANOSEC
/ MILLISEC
},
243 { "msec", NANOSEC
/ MILLISEC
},
244 { "s", NANOSEC
/ SEC
},
245 { "sec", NANOSEC
/ SEC
},
246 { "m", NANOSEC
* (hrtime_t
)60 },
247 { "min", NANOSEC
* (hrtime_t
)60 },
248 { "h", NANOSEC
* (hrtime_t
)(60 * 60) },
249 { "hour", NANOSEC
* (hrtime_t
)(60 * 60) },
250 { "d", NANOSEC
* (hrtime_t
)(24 * 60 * 60) },
251 { "day", NANOSEC
* (hrtime_t
)(24 * 60 * 60) },
255 struct timeval
*tvp
= malloc(sizeof (struct timeval
));
261 fmdump_fatal("failed to allocate memory");
263 if (gettimeofday(&tod
, NULL
) != 0)
264 fmdump_fatal("failed to get tod");
267 * First try a variety of strptime() calls. If these all fail, we'll
268 * try parsing an integer followed by one of our suffix[] strings.
270 if ((p
= strptime(arg
, "%m/%d/%Y %H:%M:%S", &tm
)) == NULL
&&
271 (p
= strptime(arg
, "%m/%d/%y %H:%M:%S", &tm
)) == NULL
&&
272 (p
= strptime(arg
, "%m/%d/%Y %H:%M", &tm
)) == NULL
&&
273 (p
= strptime(arg
, "%m/%d/%y %H:%M", &tm
)) == NULL
&&
274 (p
= strptime(arg
, "%m/%d/%Y", &tm
)) == NULL
&&
275 (p
= strptime(arg
, "%m/%d/%y", &tm
)) == NULL
&&
276 (p
= strptime(arg
, "%Y-%m-%dT%H:%M:%S", &tm
)) == NULL
&&
277 (p
= strptime(arg
, "%y-%m-%dT%H:%M:%S", &tm
)) == NULL
&&
278 (p
= strptime(arg
, "%Y-%m-%dT%H:%M", &tm
)) == NULL
&&
279 (p
= strptime(arg
, "%y-%m-%dT%H:%M", &tm
)) == NULL
&&
280 (p
= strptime(arg
, "%Y-%m-%d", &tm
)) == NULL
&&
281 (p
= strptime(arg
, "%y-%m-%d", &tm
)) == NULL
&&
282 (p
= strptime(arg
, "%d%b%Y %H:%M:%S", &tm
)) == NULL
&&
283 (p
= strptime(arg
, "%d%b%y %H:%M:%S", &tm
)) == NULL
&&
284 (p
= strptime(arg
, "%d%b%Y %H:%M", &tm
)) == NULL
&&
285 (p
= strptime(arg
, "%d%b%y %H:%M", &tm
)) == NULL
&&
286 (p
= strptime(arg
, "%d%b%Y", &tm
)) == NULL
&&
287 (p
= strptime(arg
, "%d%b%y", &tm
)) == NULL
&&
288 (p
= strptime(arg
, "%b %d %H:%M:%S", &tm
)) == NULL
&&
289 (p
= strptime(arg
, "%b %d %H:%M:%S", &tm
)) == NULL
&&
290 (p
= strptime(arg
, "%H:%M:%S", &tm
)) == NULL
&&
291 (p
= strptime(arg
, "%H:%M", &tm
)) == NULL
) {
297 nsec
= strtol(arg
, (char **)&p
, 10);
299 if (errno
!= 0 || nsec
== 0 || p
== arg
|| *p
== '\0')
300 fmdump_usage("illegal time format -- %s\n", arg
);
302 for (i
= 0; suffix
[i
].name
!= NULL
; i
++) {
303 if (strcasecmp(suffix
[i
].name
, p
) == 0) {
304 nsec
*= suffix
[i
].mul
;
309 if (suffix
[i
].name
== NULL
)
310 fmdump_usage("illegal time format -- %s\n", arg
);
312 tvp
->tv_sec
= nsec
/ NANOSEC
;
313 tvp
->tv_usec
= (nsec
% NANOSEC
) / (NANOSEC
/ MICROSEC
);
315 if (tvp
->tv_sec
> tod
.tv_sec
)
316 fmdump_usage("time delta precedes UTC time origin "
319 tvp
->tv_sec
= tod
.tv_sec
- tvp
->tv_sec
;
321 } else if (*p
== '\0' || *p
== '.') {
323 * If tm_year is zero, we matched [%b %d] %H:%M[:%S]; use
324 * the result of localtime(&tod.tv_sec) to fill in the rest.
326 if (tm
.tm_year
== 0) {
333 bcopy(localtime(&tod
.tv_sec
), &tm
, sizeof (tm
));
334 tm
.tm_isdst
= 0; /* see strptime(3C) and below */
347 tvp
->tv_sec
= mktime(&tm
);
350 if (tvp
->tv_sec
== -1L && errno
!= 0)
351 fmdump_fatal("failed to compose time %s", arg
);
354 * If our mktime() set tm_isdst, adjust the result for DST by
355 * subtracting the offset between the main and alternate zones.
358 tvp
->tv_sec
-= timezone
- altzone
;
364 (suseconds_t
)(strtod(arg
, &p
) * (double)MICROSEC
);
366 if (errno
!= 0 || p
== arg
|| *p
!= '\0')
367 fmdump_usage("illegal time suffix -- .%s\n",
372 fmdump_usage("unexpected suffix after time %s -- %s\n", arg
, p
);
379 * If the -u option is specified in combination with the -e option, we iterate
380 * over each record in the fault log with a matching UUID finding xrefs to the
381 * error log, and then use this function to iterate over every xref'd record.
384 xref_iter(fmd_log_t
*lp
, const fmd_log_record_t
*rp
, void *arg
)
386 const fmd_log_record_t
*xrp
= rp
->rec_xrefs
;
387 fmdump_arg_t
*dap
= arg
;
390 for (i
= 0; rv
== 0 && i
< rp
->rec_nrefs
; i
++, xrp
++) {
391 if (fmd_log_filter(lp
, dap
->da_fc
, dap
->da_fv
, xrp
))
392 rv
= dap
->da_fmt
->do_func(lp
, xrp
, dap
->da_fp
);
399 xoff_iter(fmd_log_t
*lp
, const fmd_log_record_t
*rp
, void *arg
)
401 fmdump_lyr_t
*dyp
= arg
;
403 fmdump_printf(dyp
->dy_fp
, "%16llx ", (u_longlong_t
)rp
->rec_off
);
404 return (dyp
->dy_func(lp
, rp
, dyp
->dy_arg
));
408 * Initialize fmd_log_filter_nvarg_t from -n name=value argument string.
410 static fmd_log_filter_nvarg_t
*
411 setupnamevalue(char *namevalue
)
413 fmd_log_filter_nvarg_t
*argt
;
415 regex_t
*value_regex
= NULL
;
419 if ((value
= strchr(namevalue
, '=')) == NULL
) {
422 *value
++ = '\0'; /* separate name and value string */
425 * Skip white space before value to facilitate direct
426 * cut/paste from previous fmdump output.
428 while (isspace(*value
))
431 if ((value_regex
= malloc(sizeof (regex_t
))) == NULL
)
432 fmdump_fatal("failed to allocate memory");
434 /* compile regular expression for possible string match */
435 if ((rv
= regcomp(value_regex
, value
,
436 REG_NOSUB
|REG_NEWLINE
)) != 0) {
437 (void) regerror(rv
, value_regex
, errstr
,
440 fmdump_usage("unexpected regular expression in "
441 "%s: %s\n", value
, errstr
);
445 if ((argt
= malloc(sizeof (fmd_log_filter_nvarg_t
))) == NULL
)
446 fmdump_fatal("failed to allocate memory");
448 argt
->nvarg_name
= namevalue
; /* now just name */
449 argt
->nvarg_value
= value
;
450 argt
->nvarg_value_regex
= value_regex
;
455 * If the -a option is not present, filter out fault records that correspond
456 * to events that the producer requested not be messaged for administrators.
460 log_filter_silent(fmd_log_t
*lp
, const fmd_log_record_t
*rp
, void *arg
)
462 int opt_A
= (arg
!= NULL
);
467 * If -A was used then apply this filter only to events of list class
470 if (nvlist_lookup_string(rp
->rec_nvl
, FM_CLASS
, &class) != 0 ||
471 strncmp(class, FM_LIST_EVENT
".",
472 sizeof (FM_LIST_EVENT
)) != 0)
476 return (nvlist_lookup_boolean_value(rp
->rec_nvl
,
477 FM_SUSPECT_MESSAGE
, &msg
) != 0 || msg
!= 0);
483 struct loglink
*next
;
487 addlink(struct loglink
**llp
, char *dirname
, char *logname
, long suffix
)
489 struct loglink
*newp
;
493 newp
= malloc(sizeof (struct loglink
));
494 len
= strlen(dirname
) + strlen(logname
) + 2;
496 if (newp
== NULL
|| str
== NULL
)
497 fmdump_fatal("failed to allocate memory");
499 (void) snprintf(str
, len
, "%s/%s", dirname
, logname
);
501 newp
->suffix
= suffix
;
503 while (*llp
!= NULL
&& suffix
< (*llp
)->suffix
)
511 * Find and return all the rotated logs.
513 static struct loglink
*
514 get_rotated_logs(char *logpath
)
516 char dirname
[PATH_MAX
], *logname
, *endptr
;
520 struct loglink
*head
= NULL
;
522 (void) strlcpy(dirname
, logpath
, sizeof (dirname
));
523 logname
= strrchr(dirname
, '/');
525 len
= strlen(logname
);
527 if ((dirp
= opendir(dirname
)) == NULL
) {
528 fmdump_warn("failed to opendir `%s'", dirname
);
533 while ((dp
= readdir(dirp
)) != NULL
) {
535 * Search the log directory for logs named "<logname>.0",
536 * "<logname>.1", etc and add to the link in the
537 * reverse numeric order.
539 if (strlen(dp
->d_name
) < len
+ 2 ||
540 strncmp(dp
->d_name
, logname
, len
) != 0 ||
541 dp
->d_name
[len
] != '.')
545 * "*.0-" file normally should not be seen. It may
546 * exist when user manually run 'fmadm rotate'.
547 * In such case, we put it at the end of the list so
548 * it'll be dumped after all the rotated logs, before
551 if (strcmp(dp
->d_name
+ len
+ 1, "0-") == 0)
552 addlink(&head
, dirname
, dp
->d_name
, -1);
553 else if ((suffix
= strtol(dp
->d_name
+ len
+ 1,
554 &endptr
, 10)) >= 0 && *endptr
== '\0')
555 addlink(&head
, dirname
, dp
->d_name
, suffix
);
558 (void) closedir(dirp
);
564 * Aggregate log files. If ifiles is not NULL then one or more files
565 * were listed on the command line, and we will merge just those files.
566 * Otherwise we will merge all known log file types, and include the
567 * rotated logs for each type (you can suppress the inclusion of
568 * some logtypes through use of FMDUMP_AGGREGATE_IGNORE in the process
569 * environment, setting it to a comma-separated list of log labels and/or
570 * log filenames to ignore).
572 * We will not attempt to perform a chronological sort across all log records
573 * of all files. Indeed, we won't even sort individual log files -
574 * we will not re-order events differently to how they appeared in their
575 * original log file. This is because log files are already inherently
576 * ordered by the order in which fmd receives and processes events.
577 * So we determine the output order by comparing the "next" record
578 * off the top of each log file.
580 * We will construct a number of log record source "pipelines". As above,
581 * the next record to render in the overall output is that from the
582 * pipeline with the oldest event.
584 * For the case that input logfiles were listed on the command line, each
585 * pipeline will process exactly one of those logfiles. Distinct pipelines
586 * may process logfiles of the same "type" - eg if two "error" logs and
587 * one "fault" logs are specified then there'll be two pipelines producing
588 * events from "error" logs.
590 * If we are merging all known log types then we will construct exactly
591 * one pipeline for each known log type - one for error, one for fault, etc.
592 * Each pipeline will process first the rotated logs of that type and then
593 * move on to the current log of that type.
595 * The output from all pipelines flows into a serializer which selects
596 * the next record once all pipelines have asserted their output state.
597 * The output state of a pipeline is one of:
599 * - record available: the next record from this pipeline is available
600 * for comparison and consumption
602 * - done: this pipeline will produce no more records
604 * - polling: this pipeline is polling for new records and will
605 * make them available as output if/when any are observed
607 * - processing: output state will be updated shortly
609 * A pipeline iterates over each file queued to it using fmd_log_xiter.
610 * We do this in a separate thread for each pipeline. The callback on
611 * each iteration must update the serializer to let it know that
612 * a new record is available. In the serializer thread we decide whether
613 * we have all records expected have arrived and it is time to choose
614 * the next output record.
618 * A pipeline descriptor. The pl_cv condition variable is used together
619 * with pl_lock for initial synchronisation, and thereafter with the
620 * lock for the serializer for pausing and continuing this pipeline.
622 struct fmdump_pipeline
{
623 pthread_mutex_t pl_lock
; /* used only in pipeline startup */
624 int pl_started
; /* sync with main thread on startup */
625 pthread_t pl_thr
; /* our processing thread */
626 pthread_cond_t pl_cv
; /* see above */
627 struct loglink
*pl_rotated
; /* rotated logs to process first */
628 char *pl_logpath
; /* target path to process */
629 char *pl_processing
; /* path currently being processed */
630 struct fmdump_srlzer
*pl_srlzer
; /* link to serializer */
631 int pl_srlzeridx
; /* serializer index for this pipeline */
632 const fmdump_ops_t
*pl_ops
; /* ops for the log type we're given */
633 int pl_fmt
; /* FMDUMP_{SHORT,VERB1,VERB2,PRETTY} */
634 boolean_t pl_follow
; /* go into poll mode at log end */
635 fmdump_arg_t pl_arg
; /* arguments */
638 enum fmdump_pipestate
{
639 FMDUMP_PIPE_PROCESSING
= 0x1000,
640 FMDUMP_PIPE_RECORDAVAIL
,
646 * Each pipeline has an associated output slot in the serializer. This
647 * must be updated with the serializer locked. After update evaluate
648 * whether there are enough slots decided that we should select a
651 struct fmdump_srlzer_slot
{
652 enum fmdump_pipestate ss_state
;
658 * All pipelines are linked to a single serializer. The serializer
659 * structure must be updated under the ds_lock; this mutex is also
660 * paired with the pl_cv of individual pipelines (one mutex, many condvars)
661 * in pausing and continuing individual pipelines.
663 struct fmdump_srlzer
{
664 struct fmdump_pipeline
*ds_pipearr
; /* pipeline array */
665 pthread_mutex_t ds_lock
; /* see above */
666 uint32_t ds_pipecnt
; /* number of pipelines */
667 uint32_t ds_pollcnt
; /* pipelines in poll mode */
668 uint32_t ds_nrecordavail
; /* pipelines with a record */
669 uint32_t ds_ndone
; /* completed pipelines */
670 struct fmdump_srlzer_slot
*ds_slot
; /* slot array */
674 * All known log types. When aggregation is requested an no file list
675 * is provided we will process the logs identified here (if lt_enabled
676 * is true and not over-ridden by environment settings). We also
677 * use this in determining the appropriate ops structure for each distinct
680 static struct fmdump_logtype
{
681 const char *lt_label
; /* label from log header */
682 boolean_t lt_enabled
; /* include in merge? */
683 const char *lt_logname
; /* var/fm/fmd/%s */
684 const fmdump_ops_t
*lt_ops
;
712 B_FALSE
, /* not included unless in file list */
714 &fmdump_asru_ops
/* but we need ops when it is */
719 * Disable logtypes per environment setting. Does not apply when a list
720 * of logs is provided on the command line.
725 char *env
= getenv("FMDUMP_AGGREGATE_IGNORE");
726 char *dup
, *start
, *tofree
;
732 tofree
= dup
= strdup(env
);
734 while (dup
!= NULL
) {
735 start
= strsep(&dup
, ",");
736 for (i
= 0; i
< sizeof (logtypes
) / sizeof (logtypes
[0]); i
++) {
737 if (logtypes
[i
].lt_logname
== NULL
)
740 if (strcmp(start
, logtypes
[i
].lt_label
) == 0 ||
741 strcmp(start
, logtypes
[i
].lt_logname
) == 0) {
742 logtypes
[i
].lt_enabled
= B_FALSE
;
751 srlzer_enter(struct fmdump_pipeline
*pl
)
753 struct fmdump_srlzer
*srlzer
= pl
->pl_srlzer
;
755 (void) pthread_mutex_lock(&srlzer
->ds_lock
);
759 srlzer_exit(struct fmdump_pipeline
*pl
)
761 struct fmdump_srlzer
*srlzer
= pl
->pl_srlzer
;
763 ASSERT(MUTEX_HELD(&srlzer
->ds_lock
));
764 (void) pthread_mutex_unlock(&srlzer
->ds_lock
);
767 static struct fmdump_pipeline
*
768 srlzer_choose(struct fmdump_srlzer
*srlzer
)
770 struct fmdump_srlzer_slot
*slot
, *oldest
;
775 ASSERT(MUTEX_HELD(&srlzer
->ds_lock
));
777 for (i
= 0, slot
= &srlzer
->ds_slot
[0]; i
< srlzer
->ds_pipecnt
;
779 if (slot
->ss_state
!= FMDUMP_PIPE_RECORDAVAIL
)
789 if (slot
->ss_sec
< oldest
->ss_sec
||
790 slot
->ss_sec
== oldest
->ss_sec
&&
791 slot
->ss_nsec
< oldest
->ss_nsec
) {
797 return (oldestidx
>= 0 ? &srlzer
->ds_pipearr
[oldestidx
] : NULL
);
801 pipeline_stall(struct fmdump_pipeline
*pl
)
803 struct fmdump_srlzer
*srlzer
= pl
->pl_srlzer
;
805 ASSERT(MUTEX_HELD(&srlzer
->ds_lock
));
806 (void) pthread_cond_wait(&pl
->pl_cv
, &srlzer
->ds_lock
);
810 pipeline_continue(struct fmdump_pipeline
*pl
)
812 struct fmdump_srlzer
*srlzer
= pl
->pl_srlzer
;
814 ASSERT(MUTEX_HELD(&srlzer
->ds_lock
));
815 (void) pthread_cond_signal(&srlzer
->ds_pipearr
[pl
->pl_srlzeridx
].pl_cv
);
819 * Called on each pipeline record iteration to make a new record
820 * available for input to the serializer. Returns 0 to indicate that
821 * the caller must stall the pipeline, or 1 to indicate that the
822 * caller should go ahead and render their record. If this record
823 * addition fills the serializer then choose a pipeline that must
827 pipeline_output(struct fmdump_pipeline
*pl
, const fmd_log_record_t
*rp
)
829 struct fmdump_srlzer
*srlzer
= pl
->pl_srlzer
;
830 struct fmdump_srlzer_slot
*slot
;
831 struct fmdump_pipeline
*wpl
;
832 int thisidx
= pl
->pl_srlzeridx
;
834 ASSERT(MUTEX_HELD(&srlzer
->ds_lock
));
836 slot
= &srlzer
->ds_slot
[thisidx
];
837 slot
->ss_state
= FMDUMP_PIPE_RECORDAVAIL
;
838 slot
->ss_sec
= rp
->rec_sec
;
839 slot
->ss_nsec
= rp
->rec_nsec
;
840 srlzer
->ds_nrecordavail
++;
843 * Once all pipelines are polling we just render in arrival order.
845 if (srlzer
->ds_pollcnt
== srlzer
->ds_pipecnt
)
849 * If not all pipelines have asserted an output yet then the
852 if (srlzer
->ds_nrecordavail
+ srlzer
->ds_ndone
+
853 srlzer
->ds_pollcnt
< srlzer
->ds_pipecnt
)
857 * Right so it's time to turn the crank by choosing which of the
858 * filled line of slots should produce output. If it is the slot
859 * for our caller then return their index to them, otherwise return
860 * -1 to the caller to make them block and cv_signal the winner.
862 wpl
= srlzer_choose(srlzer
);
868 /* Wake the oldest, and return 0 to put the caller to sleep */
869 pipeline_continue(wpl
);
875 pipeline_mark_consumed(struct fmdump_pipeline
*pl
)
877 struct fmdump_srlzer
*srlzer
= pl
->pl_srlzer
;
879 ASSERT(MUTEX_HELD(&srlzer
->ds_lock
));
880 srlzer
->ds_slot
[pl
->pl_srlzeridx
].ss_state
= FMDUMP_PIPE_PROCESSING
;
881 srlzer
->ds_nrecordavail
--;
885 pipeline_done(struct fmdump_pipeline
*pl
)
887 struct fmdump_srlzer
*srlzer
= pl
->pl_srlzer
;
888 struct fmdump_pipeline
*wpl
;
892 srlzer
->ds_slot
[pl
->pl_srlzeridx
].ss_state
= FMDUMP_PIPE_DONE
;
894 wpl
= srlzer_choose(srlzer
);
896 pipeline_continue(wpl
);
902 pipeline_pollmode(struct fmdump_pipeline
*pl
)
904 struct fmdump_srlzer
*srlzer
= pl
->pl_srlzer
;
905 struct fmdump_pipeline
*wpl
;
907 if (srlzer
->ds_slot
[pl
->pl_srlzeridx
].ss_state
== FMDUMP_PIPE_POLLING
)
912 srlzer
->ds_slot
[pl
->pl_srlzeridx
].ss_state
= FMDUMP_PIPE_POLLING
;
913 if (++srlzer
->ds_pollcnt
+ srlzer
->ds_nrecordavail
==
914 srlzer
->ds_pipecnt
&& (wpl
= srlzer_choose(srlzer
)) != NULL
)
915 pipeline_continue(wpl
);
921 pipeline_err(fmd_log_t
*lp
, void *arg
)
923 struct fmdump_pipeline
*pl
= (struct fmdump_pipeline
*)arg
;
925 fmdump_warn("skipping record in %s: %s\n", pl
->pl_processing
,
926 fmd_log_errmsg(lp
, fmd_log_errno(lp
)));
933 pipeline_cb(fmd_log_t
*lp
, const fmd_log_record_t
*rp
, void *arg
)
935 struct fmdump_pipeline
*pl
= (struct fmdump_pipeline
*)arg
;
938 fmd_log_rec_f
*func
= pl
->pl_arg
.da_fmt
->do_func
;
942 if (!pipeline_output(pl
, rp
))
945 rc
= func(lp
, rp
, pl
->pl_arg
.da_fp
);
946 pipeline_mark_consumed(pl
);
954 pipeline_process(struct fmdump_pipeline
*pl
, char *logpath
, boolean_t follow
)
956 fmd_log_header_t log
;
961 pl
->pl_processing
= logpath
;
963 if ((lp
= fmd_log_open(FMD_LOG_VERSION
, logpath
, &err
)) == NULL
) {
964 fmdump_warn("failed to open %s: %s\n",
965 logpath
, fmd_log_errmsg(NULL
, err
));
970 fmd_log_header(lp
, &log
);
971 for (i
= 0; i
< sizeof (logtypes
) / sizeof (logtypes
[0]); i
++) {
972 if (strcmp(log
.log_label
, logtypes
[i
].lt_label
) == 0) {
973 pl
->pl_ops
= logtypes
[i
].lt_ops
;
975 &pl
->pl_ops
->do_formats
[pl
->pl_fmt
];
980 if (pl
->pl_ops
== NULL
) {
981 fmdump_warn("unknown log type %s for %s\n",
982 log
.log_label
, logpath
);
988 if (fmd_log_xiter(lp
, FMD_LOG_XITER_REFS
, pl
->pl_arg
.da_fc
,
989 pl
->pl_arg
.da_fv
, pipeline_cb
, pipeline_err
, (void *)pl
,
991 fmdump_warn("failed to dump %s: %s\n",
992 logpath
, fmd_log_errmsg(lp
, fmd_log_errno(lp
)));
999 pipeline_pollmode(pl
);
1009 pipeline_thr(void *arg
)
1011 struct fmdump_pipeline
*pl
= (struct fmdump_pipeline
*)arg
;
1014 (void) pthread_mutex_lock(&pl
->pl_lock
);
1016 (void) pthread_mutex_unlock(&pl
->pl_lock
);
1017 (void) pthread_cond_signal(&pl
->pl_cv
);
1019 for (ll
= pl
->pl_rotated
; ll
!= NULL
; ll
= ll
->next
)
1020 pipeline_process(pl
, ll
->path
, B_FALSE
);
1022 pipeline_process(pl
, pl
->pl_logpath
, pl
->pl_follow
);
1030 aggregate(char **ifiles
, int n_ifiles
, int opt_f
,
1031 fmd_log_filter_t
*fv
, uint_t fc
,
1032 int opt_v
, int opt_V
, int opt_p
, int opt_j
)
1034 struct fmdump_pipeline
*pipeline
, *pl
;
1035 struct fmdump_srlzer srlzer
;
1040 if (ifiles
!= NULL
) {
1042 pipeline
= calloc(npipe
, sizeof (struct fmdump_pipeline
));
1044 fmdump_fatal("failed to allocate memory");
1046 for (i
= 0; i
< n_ifiles
; i
++)
1047 pipeline
[i
].pl_logpath
= ifiles
[i
];
1049 pipeline
= calloc(sizeof (logtypes
) / sizeof (logtypes
[0]),
1050 sizeof (struct fmdump_pipeline
));
1052 fmdump_fatal("failed to allocate memory");
1057 for (i
= 0; i
< sizeof (logtypes
) / sizeof (logtypes
[0]); i
++) {
1058 struct fmdump_logtype
*ltp
= &logtypes
[i
];
1061 if (ltp
->lt_enabled
== B_FALSE
)
1064 if ((logpath
= malloc(PATH_MAX
)) == NULL
)
1065 fmdump_fatal("failed to allocate memory");
1067 (void) snprintf(logpath
, PATH_MAX
,
1069 g_root
? g_root
: "", ltp
->lt_logname
);
1071 pipeline
[npipe
].pl_rotated
=
1072 get_rotated_logs(logpath
);
1074 pipeline
[npipe
++].pl_logpath
= logpath
;
1079 fmt
= opt_p
? FMDUMP_PRETTY
: opt_j
? FMDUMP_JSON
:
1086 bzero(&srlzer
, sizeof (srlzer
));
1087 srlzer
.ds_pipearr
= pipeline
;
1088 srlzer
.ds_pipecnt
= npipe
;
1089 srlzer
.ds_slot
= calloc(npipe
, sizeof (struct fmdump_srlzer_slot
));
1090 if (!srlzer
.ds_slot
)
1091 fmdump_fatal("failed to allocate memory");
1092 (void) pthread_mutex_init(&srlzer
.ds_lock
, NULL
);
1094 for (i
= 0, pl
= &pipeline
[0]; i
< npipe
; i
++, pl
++) {
1095 (void) pthread_mutex_init(&pl
->pl_lock
, NULL
);
1096 (void) pthread_cond_init(&pl
->pl_cv
, NULL
);
1097 srlzer
.ds_slot
[i
].ss_state
= FMDUMP_PIPE_PROCESSING
;
1098 pl
->pl_srlzer
= &srlzer
;
1099 pl
->pl_srlzeridx
= i
;
1100 pl
->pl_follow
= opt_f
? B_TRUE
: B_FALSE
;
1102 pl
->pl_arg
.da_fv
= fv
;
1103 pl
->pl_arg
.da_fc
= fc
;
1104 pl
->pl_arg
.da_fp
= stdout
;
1106 (void) pthread_mutex_lock(&pl
->pl_lock
);
1108 if (pthread_create(&pl
->pl_thr
, NULL
,
1109 pipeline_thr
, (void *)pl
) != 0)
1110 fmdump_fatal("pthread_create for pipeline %d failed",
1114 for (i
= 0, pl
= &pipeline
[0]; i
< npipe
; i
++, pl
++) {
1115 while (!pl
->pl_started
)
1116 (void) pthread_cond_wait(&pl
->pl_cv
, &pl
->pl_lock
);
1118 (void) pthread_mutex_unlock(&pl
->pl_lock
);
1121 for (i
= 0, pl
= &pipeline
[0]; i
< npipe
; i
++, pl
++)
1122 (void) pthread_join(pl
->pl_thr
, NULL
);
1124 if (ifiles
== NULL
) {
1125 for (i
= 0; i
< npipe
; i
++)
1126 free(pipeline
[i
].pl_logpath
);
1129 free(srlzer
.ds_slot
);
1133 return (FMDUMP_EXIT_SUCCESS
);
1137 cleanup(char **ifiles
, int n_ifiles
)
1144 for (i
= 0; i
< n_ifiles
; i
++) {
1145 if (ifiles
[i
] != NULL
) {
1155 main(int argc
, char *argv
[])
1157 int opt_a
= 0, opt_e
= 0, opt_f
= 0, opt_H
= 0, opt_m
= 0, opt_p
= 0;
1158 int opt_u
= 0, opt_v
= 0, opt_V
= 0, opt_j
= 0;
1159 int opt_i
= 0, opt_I
= 0;
1161 char **ifiles
= NULL
;
1169 const fmdump_ops_t
*ops
;
1170 fmd_log_filter_t
*filtv
;
1173 fmd_log_filter_t
*errfv
, *fltfv
, *allfv
;
1174 uint_t errfc
= 0, fltfc
= 0, allfc
= 0;
1176 fmd_log_header_t log
;
1177 fmd_log_rec_f
*func
;
1183 struct loglink
*rotated_logs
= NULL
, *llp
;
1187 errfv
= alloca(sizeof (fmd_log_filter_t
) * argc
);
1188 fltfv
= alloca(sizeof (fmd_log_filter_t
) * argc
);
1189 allfv
= alloca(sizeof (fmd_log_filter_t
) * argc
);
1191 while (optind
< argc
) {
1193 getopt(argc
, argv
, "Aac:efHiIjmn:O:pR:t:T:u:vV")) != EOF
) {
1202 errfv
[errfc
].filt_func
= fmd_log_filter_class
;
1203 errfv
[errfc
].filt_arg
= optarg
;
1204 allfv
[allfc
++] = errfv
[errfc
++];
1208 return (usage(stderr
));
1219 return (usage(stderr
));
1224 return (usage(stderr
));
1229 return (usage(stderr
));
1236 off
= strtoull(optarg
, NULL
, 16);
1237 iflags
|= FMD_LOG_XITER_OFFS
;
1241 return (usage(stderr
));
1248 errfv
[errfc
].filt_func
= fmd_log_filter_after
;
1249 errfv
[errfc
].filt_arg
= gettimeopt(optarg
);
1250 allfv
[allfc
++] = errfv
[errfc
++];
1253 errfv
[errfc
].filt_func
= fmd_log_filter_before
;
1254 errfv
[errfc
].filt_arg
= gettimeopt(optarg
);
1255 allfv
[allfc
++] = errfv
[errfc
++];
1258 fltfv
[fltfc
].filt_func
= fmd_log_filter_uuid
;
1259 fltfv
[fltfc
].filt_arg
= optarg
;
1260 allfv
[allfc
++] = fltfv
[fltfc
++];
1262 opt_a
++; /* -u implies -a */
1265 fltfv
[fltfc
].filt_func
= fmd_log_filter_nv
;
1266 fltfv
[fltfc
].filt_arg
= setupnamevalue(optarg
);
1267 allfv
[allfc
++] = fltfv
[fltfc
++];
1277 return (usage(stderr
));
1281 if (opt_A
&& (opt_e
|| opt_i
|| opt_I
|| opt_m
|| opt_u
))
1282 fmdump_usage("-A excludes all of "
1283 "-e, -i, -I, -m and -u\n");
1285 if (optind
< argc
) {
1288 if (ifiles
== NULL
) {
1289 n_ifiles
= argc
- optind
;
1290 ifiles
= calloc(n_ifiles
, sizeof (char *));
1291 if (ifiles
== NULL
) {
1293 "failed to allocate memory for "
1294 "%d input file%s", n_ifiles
,
1295 n_ifiles
> 1 ? "s" : "");
1299 if (ifileidx
> 0 && !opt_A
)
1300 fmdump_usage("illegal argument -- %s\n",
1303 if ((dest
= malloc(PATH_MAX
)) == NULL
)
1304 fmdump_fatal("failed to allocate memory");
1306 (void) strlcpy(dest
, argv
[optind
++], PATH_MAX
);
1307 ifiles
[ifileidx
++] = dest
;
1315 fltfv
[fltfc
].filt_func
= log_filter_silent
;
1316 fltfv
[fltfc
].filt_arg
= (void *)1;
1317 allfv
[allfc
++] = fltfv
[fltfc
++];
1320 rc
= aggregate(ifiles
, n_ifiles
, opt_f
,
1322 opt_v
, opt_V
, opt_p
, opt_j
);
1324 cleanup(ifiles
, n_ifiles
);
1327 if (ifiles
== NULL
) {
1328 if ((ifile
= calloc(1, PATH_MAX
)) == NULL
)
1329 fmdump_fatal("failed to allocate memory");
1336 if (*ifile
== '\0') {
1337 const char *pfx
, *sfx
;
1339 if (opt_u
|| (!opt_e
&& !opt_i
&& !opt_I
)) {
1348 sfx
= opt_I
? "_hival" : "";
1352 (void) snprintf(ifile
, PATH_MAX
, "%s/var/fm/fmd/%slog%s",
1353 g_root
? g_root
: "", pfx
, sfx
);
1355 * logadm may rotate the logs. When no input file is specified,
1356 * we try to dump all the rotated logs as well in the right
1359 if (!opt_H
&& off
== 0)
1360 rotated_logs
= get_rotated_logs(ifile
);
1361 } else if (g_root
!= NULL
) {
1362 fmdump_usage("-R option is not appropriate "
1363 "when file operand is present\n");
1366 if ((g_msg
= fmd_msg_init(g_root
, FMD_MSG_VERSION
)) == NULL
)
1367 fmdump_fatal("failed to initialize libfmd_msg");
1369 if ((lp
= fmd_log_open(FMD_LOG_VERSION
, ifile
, &err
)) == NULL
) {
1370 fmdump_fatal("failed to open %s: %s\n", ifile
,
1371 fmd_log_errmsg(NULL
, err
));
1375 fmd_log_header(lp
, &log
);
1377 (void) printf("EXD_CREATOR = %s\n", log
.log_creator
);
1378 (void) printf("EXD_HOSTNAME = %s\n", log
.log_hostname
);
1379 (void) printf("EXD_FMA_LABEL = %s\n", log
.log_label
);
1380 (void) printf("EXD_FMA_VERSION = %s\n", log
.log_version
);
1381 (void) printf("EXD_FMA_OSREL = %s\n", log
.log_osrelease
);
1382 (void) printf("EXD_FMA_OSVER = %s\n", log
.log_osversion
);
1383 (void) printf("EXD_FMA_PLAT = %s\n", log
.log_platform
);
1384 (void) printf("EXD_FMA_UUID = %s\n", log
.log_uuid
);
1386 return (FMDUMP_EXIT_SUCCESS
);
1389 if (off
!= 0 && fmd_log_seek(lp
, off
) != 0) {
1390 fmdump_fatal("failed to seek %s: %s\n", ifile
,
1391 fmd_log_errmsg(lp
, fmd_log_errno(lp
)));
1395 ops
= &fmdump_err_ops
;
1396 else if (strcmp(fmd_log_label(lp
), fmdump_flt_ops
.do_label
) == 0)
1397 ops
= &fmdump_flt_ops
;
1398 else if (strcmp(fmd_log_label(lp
), fmdump_asru_ops
.do_label
) == 0)
1399 ops
= &fmdump_asru_ops
;
1400 else if (strcmp(fmd_log_label(lp
), fmdump_info_ops
.do_label
) == 0)
1401 ops
= &fmdump_info_ops
;
1403 ops
= &fmdump_err_ops
;
1405 if (!opt_a
&& ops
== &fmdump_flt_ops
) {
1406 fltfv
[fltfc
].filt_func
= log_filter_silent
;
1407 fltfv
[fltfc
].filt_arg
= NULL
;
1408 allfv
[allfc
++] = fltfv
[fltfc
++];
1413 &ops
->do_formats
[opt_p
? FMDUMP_PRETTY
:
1414 opt_j
? FMDUMP_JSON
: FMDUMP_VERB2
];
1415 iflags
|= FMD_LOG_XITER_REFS
;
1417 arg
.da_fmt
= &ops
->do_formats
[FMDUMP_VERB1
];
1419 arg
.da_fmt
= &ops
->do_formats
[FMDUMP_MSG
];
1421 arg
.da_fmt
= &ops
->do_formats
[FMDUMP_SHORT
];
1423 if (opt_m
&& arg
.da_fmt
->do_func
== NULL
) {
1424 fmdump_usage("-m mode is not supported for "
1425 "log of type %s: %s\n", fmd_log_label(lp
), ifile
);
1432 if (iflags
& FMD_LOG_XITER_OFFS
)
1433 fmdump_printf(arg
.da_fp
, "%16s ", "OFFSET");
1435 if (arg
.da_fmt
->do_hdr
&& !(opt_V
&& ops
== &fmdump_flt_ops
))
1436 fmdump_printf(arg
.da_fp
, "%s\n", arg
.da_fmt
->do_hdr
);
1438 if (opt_e
&& opt_u
) {
1439 iflags
|= FMD_LOG_XITER_REFS
;
1445 func
= arg
.da_fmt
->do_func
;
1451 if (iflags
& FMD_LOG_XITER_OFFS
) {
1454 lyr
.dy_fp
= arg
.da_fp
;
1459 for (llp
= rotated_logs
; llp
!= NULL
; llp
= llp
->next
) {
1462 if ((rlp
= fmd_log_open(FMD_LOG_VERSION
, llp
->path
, &err
))
1464 fmdump_warn("failed to open %s: %s\n",
1465 llp
->path
, fmd_log_errmsg(NULL
, err
));
1471 if (fmd_log_xiter(rlp
, iflags
, filtc
, filtv
,
1472 func
, error
, farg
, &recs
) != 0) {
1473 fmdump_warn("failed to dump %s: %s\n", llp
->path
,
1474 fmd_log_errmsg(rlp
, fmd_log_errno(rlp
)));
1484 if (fmd_log_xiter(lp
, iflags
, filtc
, filtv
,
1485 func
, error
, farg
, &recs
) != 0) {
1486 fmdump_warn("failed to dump %s: %s\n", ifile
,
1487 fmd_log_errmsg(lp
, fmd_log_errno(lp
)));
1497 if (!opt_f
&& g_recs
== 0 && isatty(STDOUT_FILENO
))
1498 fmdump_warn("%s is empty\n", ifile
);
1504 fmd_msg_fini(g_msg
);
1509 cleanup(ifiles
, n_ifiles
);
1511 return (g_errs
? FMDUMP_EXIT_ERROR
: FMDUMP_EXIT_SUCCESS
);