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 */ 27 28 #include <alloca.h> 29 #include <unistd.h> 30 #include <limits.h> 31 #include <strings.h> 32 #include <stdlib.h> 33 #include <stdarg.h> 34 #include <stdio.h> 35 #include <errno.h> 36 #include <time.h> 37 #include <ctype.h> 38 #include <regex.h> 39 #include <dirent.h> 40 #include <pthread.h> 41 42 #include <fmdump.h> 43 44 #define FMDUMP_EXIT_SUCCESS 0 45 #define FMDUMP_EXIT_FATAL 1 46 #define FMDUMP_EXIT_USAGE 2 47 #define FMDUMP_EXIT_ERROR 3 48 49 const char *g_pname; 50 ulong_t g_errs; 51 ulong_t g_recs; 52 char *g_root; 53 54 struct topo_hdl *g_thp; 55 fmd_msg_hdl_t *g_msg; 56 57 /*PRINTFLIKE2*/ 58 void 59 fmdump_printf(FILE *fp, const char *format, ...) 60 { 61 va_list ap; 62 63 va_start(ap, format); 64 65 if (vfprintf(fp, format, ap) < 0) { 66 (void) fprintf(stderr, "%s: failed to print record: %s\n", 67 g_pname, strerror(errno)); 68 g_errs++; 69 } 70 71 va_end(ap); 72 } 73 74 void 75 fmdump_vwarn(const char *format, va_list ap) 76 { 77 int err = errno; 78 79 (void) fprintf(stderr, "%s: warning: ", g_pname); 80 (void) vfprintf(stderr, format, ap); 81 82 if (strchr(format, '\n') == NULL) 83 (void) fprintf(stderr, ": %s\n", strerror(err)); 84 85 g_errs++; 86 } 87 88 /*PRINTFLIKE1*/ 89 void 90 fmdump_warn(const char *format, ...) 91 { 92 va_list ap; 93 94 va_start(ap, format); 95 fmdump_vwarn(format, ap); 96 va_end(ap); 97 } 98 99 static void 100 fmdump_exit(int err, int exitcode, const char *format, va_list ap) 101 { 102 (void) fprintf(stderr, "%s: ", g_pname); 103 104 (void) vfprintf(stderr, format, ap); 105 106 if (strchr(format, '\n') == NULL) 107 (void) fprintf(stderr, ": %s\n", strerror(err)); 108 109 exit(exitcode); 110 } 111 112 /*PRINTFLIKE1*/ 113 static void 114 fmdump_fatal(const char *format, ...) 115 { 116 int err = errno; 117 118 va_list ap; 119 120 va_start(ap, format); 121 fmdump_exit(err, FMDUMP_EXIT_FATAL, format, ap); 122 va_end(ap); 123 } 124 125 /*PRINTFLIKE1*/ 126 static void 127 fmdump_usage(const char *format, ...) 128 { 129 130 int err = errno; 131 132 va_list ap; 133 134 va_start(ap, format); 135 fmdump_exit(err, FMDUMP_EXIT_USAGE, format, ap); 136 va_end(ap); 137 } 138 139 char * 140 fmdump_date(char *buf, size_t len, const fmd_log_record_t *rp) 141 { 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); 145 } else { 146 time_t tod = (time_t)rp->rec_sec; 147 time_t now = time(NULL); 148 if (tod > now+60 || 149 tod < now - 6L*30L*24L*60L*60L) { /* 6 months ago */ 150 (void) strftime(buf, len, "%b %d %Y %T", 151 localtime(&tod)); 152 } else { 153 size_t sz; 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)); 157 } 158 } 159 160 return (buf); 161 } 162 163 char * 164 fmdump_year(char *buf, size_t len, const fmd_log_record_t *rp) 165 { 166 #ifdef _ILP32 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); 170 } else { 171 #endif 172 time_t tod = (time_t)rp->rec_sec; 173 (void) strftime(buf, len, "%b %d %Y %T", localtime(&tod)); 174 #ifdef _ILP32 175 } 176 #endif 177 return (buf); 178 } 179 180 /* BEGIN CSTYLED */ 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]] " 184 "[file]...\n " 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 " 193 "Output options:\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"; 205 /* END CSTYLED */ 206 207 static int 208 usage(FILE *fp) 209 { 210 (void) fprintf(fp, synopsis, g_pname); 211 return (FMDUMP_EXIT_USAGE); 212 } 213 214 /*ARGSUSED*/ 215 static int 216 error(fmd_log_t *lp, void *private) 217 { 218 fmdump_warn("skipping record: %s\n", 219 fmd_log_errmsg(lp, fmd_log_errno(lp))); 220 return (0); 221 } 222 223 /* 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"). 230 */ 231 static struct timeval * 232 gettimeopt(const char *arg) 233 { 234 const struct { 235 const char *name; 236 hrtime_t mul; 237 } suffix[] = { 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) }, 252 { NULL } 253 }; 254 255 struct timeval *tvp = malloc(sizeof (struct timeval)); 256 struct timeval tod; 257 struct tm tm; 258 char *p; 259 260 if (tvp == NULL) 261 fmdump_fatal("failed to allocate memory"); 262 263 if (gettimeofday(&tod, NULL) != 0) 264 fmdump_fatal("failed to get tod"); 265 266 /* 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. 269 */ 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) { 292 293 hrtime_t nsec; 294 int i; 295 296 errno = 0; 297 nsec = strtol(arg, (char **)&p, 10); 298 299 if (errno != 0 || nsec == 0 || p == arg || *p == '\0') 300 fmdump_usage("illegal time format -- %s\n", arg); 301 302 for (i = 0; suffix[i].name != NULL; i++) { 303 if (strcasecmp(suffix[i].name, p) == 0) { 304 nsec *= suffix[i].mul; 305 break; 306 } 307 } 308 309 if (suffix[i].name == NULL) 310 fmdump_usage("illegal time format -- %s\n", arg); 311 312 tvp->tv_sec = nsec / NANOSEC; 313 tvp->tv_usec = (nsec % NANOSEC) / (NANOSEC / MICROSEC); 314 315 if (tvp->tv_sec > tod.tv_sec) 316 fmdump_usage("time delta precedes UTC time origin " 317 "-- %s\n", arg); 318 319 tvp->tv_sec = tod.tv_sec - tvp->tv_sec; 320 321 } else if (*p == '\0' || *p == '.') { 322 /* 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. 325 */ 326 if (tm.tm_year == 0) { 327 int h = tm.tm_hour; 328 int m = tm.tm_min; 329 int s = tm.tm_sec; 330 int b = tm.tm_mon; 331 int d = tm.tm_mday; 332 333 bcopy(localtime(&tod.tv_sec), &tm, sizeof (tm)); 334 tm.tm_isdst = 0; /* see strptime(3C) and below */ 335 336 if (d > 0) { 337 tm.tm_mon = b; 338 tm.tm_mday = d; 339 } 340 341 tm.tm_hour = h; 342 tm.tm_min = m; 343 tm.tm_sec = s; 344 } 345 346 errno = 0; 347 tvp->tv_sec = mktime(&tm); 348 tvp->tv_usec = 0; 349 350 if (tvp->tv_sec == -1L && errno != 0) 351 fmdump_fatal("failed to compose time %s", arg); 352 353 /* 354 * If our mktime() set tm_isdst, adjust the result for DST by 355 * subtracting the offset between the main and alternate zones. 356 */ 357 if (tm.tm_isdst) 358 tvp->tv_sec -= timezone - altzone; 359 360 if (p[0] == '.') { 361 arg = p; 362 errno = 0; 363 tvp->tv_usec = 364 (suseconds_t)(strtod(arg, &p) * (double)MICROSEC); 365 366 if (errno != 0 || p == arg || *p != '\0') 367 fmdump_usage("illegal time suffix -- .%s\n", 368 arg); 369 } 370 371 } else { 372 fmdump_usage("unexpected suffix after time %s -- %s\n", arg, p); 373 } 374 375 return (tvp); 376 } 377 378 /* 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. 382 */ 383 int 384 xref_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) 385 { 386 const fmd_log_record_t *xrp = rp->rec_xrefs; 387 fmdump_arg_t *dap = arg; 388 int i, rv = 0; 389 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); 393 } 394 395 return (rv); 396 } 397 398 int 399 xoff_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) 400 { 401 fmdump_lyr_t *dyp = arg; 402 403 fmdump_printf(dyp->dy_fp, "%16llx ", (u_longlong_t)rp->rec_off); 404 return (dyp->dy_func(lp, rp, dyp->dy_arg)); 405 } 406 407 /* 408 * Initialize fmd_log_filter_nvarg_t from -n name=value argument string. 409 */ 410 static fmd_log_filter_nvarg_t * 411 setupnamevalue(char *namevalue) 412 { 413 fmd_log_filter_nvarg_t *argt; 414 char *value; 415 regex_t *value_regex = NULL; 416 char errstr[128]; 417 int rv; 418 419 if ((value = strchr(namevalue, '=')) == NULL) { 420 value_regex = NULL; 421 } else { 422 *value++ = '\0'; /* separate name and value string */ 423 424 /* 425 * Skip white space before value to facilitate direct 426 * cut/paste from previous fmdump output. 427 */ 428 while (isspace(*value)) 429 value++; 430 431 if ((value_regex = malloc(sizeof (regex_t))) == NULL) 432 fmdump_fatal("failed to allocate memory"); 433 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, 438 sizeof (errstr)); 439 free(value_regex); 440 fmdump_usage("unexpected regular expression in " 441 "%s: %s\n", value, errstr); 442 } 443 } 444 445 if ((argt = malloc(sizeof (fmd_log_filter_nvarg_t))) == NULL) 446 fmdump_fatal("failed to allocate memory"); 447 448 argt->nvarg_name = namevalue; /* now just name */ 449 argt->nvarg_value = value; 450 argt->nvarg_value_regex = value_regex; 451 return (argt); 452 } 453 454 /* 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. 457 */ 458 /*ARGSUSED*/ 459 int 460 log_filter_silent(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) 461 { 462 int opt_A = (arg != NULL); 463 boolean_t msg; 464 char *class; 465 466 /* 467 * If -A was used then apply this filter only to events of list class 468 */ 469 if (opt_A) { 470 if (nvlist_lookup_string(rp->rec_nvl, FM_CLASS, &class) != 0 || 471 strncmp(class, FM_LIST_EVENT ".", 472 sizeof (FM_LIST_EVENT)) != 0) 473 return (1); 474 } 475 476 return (nvlist_lookup_boolean_value(rp->rec_nvl, 477 FM_SUSPECT_MESSAGE, &msg) != 0 || msg != 0); 478 } 479 480 struct loglink { 481 char *path; 482 long suffix; 483 struct loglink *next; 484 }; 485 486 static void 487 addlink(struct loglink **llp, char *dirname, char *logname, long suffix) 488 { 489 struct loglink *newp; 490 size_t len; 491 char *str; 492 493 newp = malloc(sizeof (struct loglink)); 494 len = strlen(dirname) + strlen(logname) + 2; 495 str = malloc(len); 496 if (newp == NULL || str == NULL) 497 fmdump_fatal("failed to allocate memory"); 498 499 (void) snprintf(str, len, "%s/%s", dirname, logname); 500 newp->path = str; 501 newp->suffix = suffix; 502 503 while (*llp != NULL && suffix < (*llp)->suffix) 504 llp = &(*llp)->next; 505 506 newp->next = *llp; 507 *llp = newp; 508 } 509 510 /* 511 * Find and return all the rotated logs. 512 */ 513 static struct loglink * 514 get_rotated_logs(char *logpath) 515 { 516 char dirname[PATH_MAX], *logname, *endptr; 517 DIR *dirp; 518 struct dirent *dp; 519 long len, suffix; 520 struct loglink *head = NULL; 521 522 (void) strlcpy(dirname, logpath, sizeof (dirname)); 523 logname = strrchr(dirname, '/'); 524 *logname++ = '\0'; 525 len = strlen(logname); 526 527 if ((dirp = opendir(dirname)) == NULL) { 528 fmdump_warn("failed to opendir `%s'", dirname); 529 g_errs++; 530 return (NULL); 531 } 532 533 while ((dp = readdir(dirp)) != NULL) { 534 /* 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. 538 */ 539 if (strlen(dp->d_name) < len + 2 || 540 strncmp(dp->d_name, logname, len) != 0 || 541 dp->d_name[len] != '.') 542 continue; 543 544 /* 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 549 * the current one. 550 */ 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); 556 } 557 558 (void) closedir(dirp); 559 560 return (head); 561 } 562 563 /* 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). 571 * 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. 579 * 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. 583 * 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. 589 * 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. 594 * 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: 598 * 599 * - record available: the next record from this pipeline is available 600 * for comparison and consumption 601 * 602 * - done: this pipeline will produce no more records 603 * 604 * - polling: this pipeline is polling for new records and will 605 * make them available as output if/when any are observed 606 * 607 * - processing: output state will be updated shortly 608 * 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. 615 */ 616 617 /* 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. 621 */ 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 */ 636 }; 637 638 enum fmdump_pipestate { 639 FMDUMP_PIPE_PROCESSING = 0x1000, 640 FMDUMP_PIPE_RECORDAVAIL, 641 FMDUMP_PIPE_POLLING, 642 FMDUMP_PIPE_DONE 643 }; 644 645 /* 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 649 * record to output. 650 */ 651 struct fmdump_srlzer_slot { 652 enum fmdump_pipestate ss_state; 653 uint64_t ss_sec; 654 uint64_t ss_nsec; 655 }; 656 657 /* 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. 662 */ 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 */ 671 }; 672 673 /* 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 678 * label. 679 */ 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; 685 } logtypes[] = { 686 { 687 "error", 688 B_TRUE, 689 "errlog", 690 &fmdump_err_ops 691 }, 692 { 693 "fault", 694 B_TRUE, 695 "fltlog", 696 &fmdump_flt_ops 697 }, 698 { 699 "info", 700 B_TRUE, 701 "infolog", 702 &fmdump_info_ops 703 }, 704 { 705 "info", 706 B_TRUE, 707 "infolog_hival", 708 &fmdump_info_ops 709 }, 710 { 711 "asru", 712 B_FALSE, /* not included unless in file list */ 713 NULL, 714 &fmdump_asru_ops /* but we need ops when it is */ 715 } 716 }; 717 718 /* 719 * Disable logtypes per environment setting. Does not apply when a list 720 * of logs is provided on the command line. 721 */ 722 static void 723 do_disables(void) 724 { 725 char *env = getenv("FMDUMP_AGGREGATE_IGNORE"); 726 char *dup, *start, *tofree; 727 int i; 728 729 if (env == NULL) 730 return; 731 732 tofree = dup = strdup(env); 733 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) 738 continue; 739 740 if (strcmp(start, logtypes[i].lt_label) == 0 || 741 strcmp(start, logtypes[i].lt_logname) == 0) { 742 logtypes[i].lt_enabled = B_FALSE; 743 } 744 } 745 } 746 747 free(tofree); 748 } 749 750 static void 751 srlzer_enter(struct fmdump_pipeline *pl) 752 { 753 struct fmdump_srlzer *srlzer = pl->pl_srlzer; 754 755 (void) pthread_mutex_lock(&srlzer->ds_lock); 756 } 757 758 static void 759 srlzer_exit(struct fmdump_pipeline *pl) 760 { 761 struct fmdump_srlzer *srlzer = pl->pl_srlzer; 762 763 ASSERT(MUTEX_HELD(&srlzer->ds_lock)); 764 (void) pthread_mutex_unlock(&srlzer->ds_lock); 765 } 766 767 static struct fmdump_pipeline * 768 srlzer_choose(struct fmdump_srlzer *srlzer) 769 { 770 struct fmdump_srlzer_slot *slot, *oldest; 771 int oldestidx = -1; 772 int first = 1; 773 int i; 774 775 ASSERT(MUTEX_HELD(&srlzer->ds_lock)); 776 777 for (i = 0, slot = &srlzer->ds_slot[0]; i < srlzer->ds_pipecnt; 778 i++, slot++) { 779 if (slot->ss_state != FMDUMP_PIPE_RECORDAVAIL) 780 continue; 781 782 if (first) { 783 oldest = slot; 784 oldestidx = i; 785 first = 0; 786 continue; 787 } 788 789 if (slot->ss_sec < oldest->ss_sec || 790 slot->ss_sec == oldest->ss_sec && 791 slot->ss_nsec < oldest->ss_nsec) { 792 oldest = slot; 793 oldestidx = i; 794 } 795 } 796 797 return (oldestidx >= 0 ? &srlzer->ds_pipearr[oldestidx] : NULL); 798 } 799 800 static void 801 pipeline_stall(struct fmdump_pipeline *pl) 802 { 803 struct fmdump_srlzer *srlzer = pl->pl_srlzer; 804 805 ASSERT(MUTEX_HELD(&srlzer->ds_lock)); 806 (void) pthread_cond_wait(&pl->pl_cv, &srlzer->ds_lock); 807 } 808 809 static void 810 pipeline_continue(struct fmdump_pipeline *pl) 811 { 812 struct fmdump_srlzer *srlzer = pl->pl_srlzer; 813 814 ASSERT(MUTEX_HELD(&srlzer->ds_lock)); 815 (void) pthread_cond_signal(&srlzer->ds_pipearr[pl->pl_srlzeridx].pl_cv); 816 } 817 818 /* 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 824 * render output. 825 */ 826 static int 827 pipeline_output(struct fmdump_pipeline *pl, const fmd_log_record_t *rp) 828 { 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; 833 834 ASSERT(MUTEX_HELD(&srlzer->ds_lock)); 835 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++; 841 842 /* 843 * Once all pipelines are polling we just render in arrival order. 844 */ 845 if (srlzer->ds_pollcnt == srlzer->ds_pipecnt) 846 return (1); 847 848 /* 849 * If not all pipelines have asserted an output yet then the 850 * caller must block. 851 */ 852 if (srlzer->ds_nrecordavail + srlzer->ds_ndone + 853 srlzer->ds_pollcnt < srlzer->ds_pipecnt) 854 return (0); 855 856 /* 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. 861 */ 862 wpl = srlzer_choose(srlzer); 863 ASSERT(wpl != NULL); 864 865 if (wpl == pl) 866 return (1); 867 868 /* Wake the oldest, and return 0 to put the caller to sleep */ 869 pipeline_continue(wpl); 870 871 return (0); 872 } 873 874 static void 875 pipeline_mark_consumed(struct fmdump_pipeline *pl) 876 { 877 struct fmdump_srlzer *srlzer = pl->pl_srlzer; 878 879 ASSERT(MUTEX_HELD(&srlzer->ds_lock)); 880 srlzer->ds_slot[pl->pl_srlzeridx].ss_state = FMDUMP_PIPE_PROCESSING; 881 srlzer->ds_nrecordavail--; 882 } 883 884 static void 885 pipeline_done(struct fmdump_pipeline *pl) 886 { 887 struct fmdump_srlzer *srlzer = pl->pl_srlzer; 888 struct fmdump_pipeline *wpl; 889 890 srlzer_enter(pl); 891 892 srlzer->ds_slot[pl->pl_srlzeridx].ss_state = FMDUMP_PIPE_DONE; 893 srlzer->ds_ndone++; 894 wpl = srlzer_choose(srlzer); 895 if (wpl != NULL) 896 pipeline_continue(wpl); 897 898 srlzer_exit(pl); 899 } 900 901 static void 902 pipeline_pollmode(struct fmdump_pipeline *pl) 903 { 904 struct fmdump_srlzer *srlzer = pl->pl_srlzer; 905 struct fmdump_pipeline *wpl; 906 907 if (srlzer->ds_slot[pl->pl_srlzeridx].ss_state == FMDUMP_PIPE_POLLING) 908 return; 909 910 srlzer_enter(pl); 911 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); 916 917 srlzer_exit(pl); 918 } 919 920 static int 921 pipeline_err(fmd_log_t *lp, void *arg) 922 { 923 struct fmdump_pipeline *pl = (struct fmdump_pipeline *)arg; 924 925 fmdump_warn("skipping record in %s: %s\n", pl->pl_processing, 926 fmd_log_errmsg(lp, fmd_log_errno(lp))); 927 g_errs++; 928 929 return (0); 930 } 931 932 static int 933 pipeline_cb(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) 934 { 935 struct fmdump_pipeline *pl = (struct fmdump_pipeline *)arg; 936 int rc; 937 938 fmd_log_rec_f *func = pl->pl_arg.da_fmt->do_func; 939 940 srlzer_enter(pl); 941 942 if (!pipeline_output(pl, rp)) 943 pipeline_stall(pl); 944 945 rc = func(lp, rp, pl->pl_arg.da_fp); 946 pipeline_mark_consumed(pl); 947 948 srlzer_exit(pl); 949 950 return (rc); 951 } 952 953 static void 954 pipeline_process(struct fmdump_pipeline *pl, char *logpath, boolean_t follow) 955 { 956 fmd_log_header_t log; 957 fmd_log_t *lp; 958 int err; 959 int i; 960 961 pl->pl_processing = logpath; 962 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)); 966 g_errs++; 967 return; 968 } 969 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; 974 pl->pl_arg.da_fmt = 975 &pl->pl_ops->do_formats[pl->pl_fmt]; 976 break; 977 } 978 } 979 980 if (pl->pl_ops == NULL) { 981 fmdump_warn("unknown log type %s for %s\n", 982 log.log_label, logpath); 983 g_errs++; 984 return; 985 } 986 987 do { 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, 990 NULL) != 0) { 991 fmdump_warn("failed to dump %s: %s\n", 992 logpath, fmd_log_errmsg(lp, fmd_log_errno(lp))); 993 g_errs++; 994 fmd_log_close(lp); 995 return; 996 } 997 998 if (follow) { 999 pipeline_pollmode(pl); 1000 (void) sleep(1); 1001 } 1002 1003 } while (follow); 1004 1005 fmd_log_close(lp); 1006 } 1007 1008 static void * 1009 pipeline_thr(void *arg) 1010 { 1011 struct fmdump_pipeline *pl = (struct fmdump_pipeline *)arg; 1012 struct loglink *ll; 1013 1014 (void) pthread_mutex_lock(&pl->pl_lock); 1015 pl->pl_started = 1; 1016 (void) pthread_mutex_unlock(&pl->pl_lock); 1017 (void) pthread_cond_signal(&pl->pl_cv); 1018 1019 for (ll = pl->pl_rotated; ll != NULL; ll = ll->next) 1020 pipeline_process(pl, ll->path, B_FALSE); 1021 1022 pipeline_process(pl, pl->pl_logpath, pl->pl_follow); 1023 pipeline_done(pl); 1024 1025 return (NULL); 1026 } 1027 1028 1029 static int 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) 1033 { 1034 struct fmdump_pipeline *pipeline, *pl; 1035 struct fmdump_srlzer srlzer; 1036 uint32_t npipe; 1037 int fmt; 1038 int i; 1039 1040 if (ifiles != NULL) { 1041 npipe = n_ifiles; 1042 pipeline = calloc(npipe, sizeof (struct fmdump_pipeline)); 1043 if (!pipeline) 1044 fmdump_fatal("failed to allocate memory"); 1045 1046 for (i = 0; i < n_ifiles; i++) 1047 pipeline[i].pl_logpath = ifiles[i]; 1048 } else { 1049 pipeline = calloc(sizeof (logtypes) / sizeof (logtypes[0]), 1050 sizeof (struct fmdump_pipeline)); 1051 if (!pipeline) 1052 fmdump_fatal("failed to allocate memory"); 1053 1054 do_disables(); 1055 1056 npipe = 0; 1057 for (i = 0; i < sizeof (logtypes) / sizeof (logtypes[0]); i++) { 1058 struct fmdump_logtype *ltp = &logtypes[i]; 1059 char *logpath; 1060 1061 if (ltp->lt_enabled == B_FALSE) 1062 continue; 1063 1064 if ((logpath = malloc(PATH_MAX)) == NULL) 1065 fmdump_fatal("failed to allocate memory"); 1066 1067 (void) snprintf(logpath, PATH_MAX, 1068 "%s/var/fm/fmd/%s", 1069 g_root ? g_root : "", ltp->lt_logname); 1070 1071 pipeline[npipe].pl_rotated = 1072 get_rotated_logs(logpath); 1073 1074 pipeline[npipe++].pl_logpath = logpath; 1075 } 1076 } 1077 1078 if (opt_V) 1079 fmt = opt_p ? FMDUMP_PRETTY : opt_j ? FMDUMP_JSON : 1080 FMDUMP_VERB2; 1081 else if (opt_v) 1082 fmt = FMDUMP_VERB1; 1083 else 1084 fmt = FMDUMP_SHORT; 1085 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); 1093 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; 1101 pl->pl_fmt = fmt; 1102 pl->pl_arg.da_fv = fv; 1103 pl->pl_arg.da_fc = fc; 1104 pl->pl_arg.da_fp = stdout; 1105 1106 (void) pthread_mutex_lock(&pl->pl_lock); 1107 1108 if (pthread_create(&pl->pl_thr, NULL, 1109 pipeline_thr, (void *)pl) != 0) 1110 fmdump_fatal("pthread_create for pipeline %d failed", 1111 i); 1112 } 1113 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); 1117 1118 (void) pthread_mutex_unlock(&pl->pl_lock); 1119 } 1120 1121 for (i = 0, pl = &pipeline[0]; i < npipe; i++, pl++) 1122 (void) pthread_join(pl->pl_thr, NULL); 1123 1124 if (ifiles == NULL) { 1125 for (i = 0; i < npipe; i++) 1126 free(pipeline[i].pl_logpath); 1127 } 1128 1129 free(srlzer.ds_slot); 1130 1131 free(pipeline); 1132 1133 return (FMDUMP_EXIT_SUCCESS); 1134 } 1135 1136 static void 1137 cleanup(char **ifiles, int n_ifiles) 1138 { 1139 int i; 1140 1141 if (ifiles == NULL) 1142 return; 1143 1144 for (i = 0; i < n_ifiles; i++) { 1145 if (ifiles[i] != NULL) { 1146 free(ifiles[i]); 1147 ifiles[i] = NULL; 1148 } 1149 } 1150 1151 free(ifiles); 1152 } 1153 1154 int 1155 main(int argc, char *argv[]) 1156 { 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; 1160 int opt_A = 0; 1161 char **ifiles = NULL; 1162 char *ifile = NULL; 1163 int n_ifiles; 1164 int ifileidx = 0; 1165 int iflags = 0; 1166 1167 fmdump_arg_t arg; 1168 fmdump_lyr_t lyr; 1169 const fmdump_ops_t *ops; 1170 fmd_log_filter_t *filtv; 1171 uint_t filtc; 1172 1173 fmd_log_filter_t *errfv, *fltfv, *allfv; 1174 uint_t errfc = 0, fltfc = 0, allfc = 0; 1175 1176 fmd_log_header_t log; 1177 fmd_log_rec_f *func; 1178 void *farg; 1179 fmd_log_t *lp; 1180 int c, err; 1181 off64_t off = 0; 1182 ulong_t recs; 1183 struct loglink *rotated_logs = NULL, *llp; 1184 1185 g_pname = argv[0]; 1186 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); 1190 1191 while (optind < argc) { 1192 while ((c = 1193 getopt(argc, argv, "Aac:efHiIjmn:O:pR:t:T:u:vV")) != EOF) { 1194 switch (c) { 1195 case 'A': 1196 opt_A++; 1197 break; 1198 case 'a': 1199 opt_a++; 1200 break; 1201 case 'c': 1202 errfv[errfc].filt_func = fmd_log_filter_class; 1203 errfv[errfc].filt_arg = optarg; 1204 allfv[allfc++] = errfv[errfc++]; 1205 break; 1206 case 'e': 1207 if (opt_i) 1208 return (usage(stderr)); 1209 opt_e++; 1210 break; 1211 case 'f': 1212 opt_f++; 1213 break; 1214 case 'H': 1215 opt_H++; 1216 break; 1217 case 'i': 1218 if (opt_e || opt_I) 1219 return (usage(stderr)); 1220 opt_i++; 1221 break; 1222 case 'I': 1223 if (opt_e || opt_i) 1224 return (usage(stderr)); 1225 opt_I++; 1226 break; 1227 case 'j': 1228 if (opt_p) 1229 return (usage(stderr)); 1230 opt_j++; 1231 break; 1232 case 'm': 1233 opt_m++; 1234 break; 1235 case 'O': 1236 off = strtoull(optarg, NULL, 16); 1237 iflags |= FMD_LOG_XITER_OFFS; 1238 break; 1239 case 'p': 1240 if (opt_j) 1241 return (usage(stderr)); 1242 opt_p++; 1243 break; 1244 case 'R': 1245 g_root = optarg; 1246 break; 1247 case 't': 1248 errfv[errfc].filt_func = fmd_log_filter_after; 1249 errfv[errfc].filt_arg = gettimeopt(optarg); 1250 allfv[allfc++] = errfv[errfc++]; 1251 break; 1252 case 'T': 1253 errfv[errfc].filt_func = fmd_log_filter_before; 1254 errfv[errfc].filt_arg = gettimeopt(optarg); 1255 allfv[allfc++] = errfv[errfc++]; 1256 break; 1257 case 'u': 1258 fltfv[fltfc].filt_func = fmd_log_filter_uuid; 1259 fltfv[fltfc].filt_arg = optarg; 1260 allfv[allfc++] = fltfv[fltfc++]; 1261 opt_u++; 1262 opt_a++; /* -u implies -a */ 1263 break; 1264 case 'n': { 1265 fltfv[fltfc].filt_func = fmd_log_filter_nv; 1266 fltfv[fltfc].filt_arg = setupnamevalue(optarg); 1267 allfv[allfc++] = fltfv[fltfc++]; 1268 break; 1269 } 1270 case 'v': 1271 opt_v++; 1272 break; 1273 case 'V': 1274 opt_V++; 1275 break; 1276 default: 1277 return (usage(stderr)); 1278 } 1279 } 1280 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"); 1284 1285 if (optind < argc) { 1286 char *dest; 1287 1288 if (ifiles == NULL) { 1289 n_ifiles = argc - optind; 1290 ifiles = calloc(n_ifiles, sizeof (char *)); 1291 if (ifiles == NULL) { 1292 fmdump_fatal( 1293 "failed to allocate memory for " 1294 "%d input file%s", n_ifiles, 1295 n_ifiles > 1 ? "s" : ""); 1296 } 1297 } 1298 1299 if (ifileidx > 0 && !opt_A) 1300 fmdump_usage("illegal argument -- %s\n", 1301 argv[optind]); 1302 1303 if ((dest = malloc(PATH_MAX)) == NULL) 1304 fmdump_fatal("failed to allocate memory"); 1305 1306 (void) strlcpy(dest, argv[optind++], PATH_MAX); 1307 ifiles[ifileidx++] = dest; 1308 } 1309 } 1310 1311 if (opt_A) { 1312 int rc; 1313 1314 if (!opt_a) { 1315 fltfv[fltfc].filt_func = log_filter_silent; 1316 fltfv[fltfc].filt_arg = (void *)1; 1317 allfv[allfc++] = fltfv[fltfc++]; 1318 } 1319 1320 rc = aggregate(ifiles, n_ifiles, opt_f, 1321 allfv, allfc, 1322 opt_v, opt_V, opt_p, opt_j); 1323 1324 cleanup(ifiles, n_ifiles); 1325 return (rc); 1326 } else { 1327 if (ifiles == NULL) { 1328 if ((ifile = calloc(1, PATH_MAX)) == NULL) 1329 fmdump_fatal("failed to allocate memory"); 1330 } else { 1331 ifile = ifiles[0]; 1332 } 1333 } 1334 1335 1336 if (*ifile == '\0') { 1337 const char *pfx, *sfx; 1338 1339 if (opt_u || (!opt_e && !opt_i && !opt_I)) { 1340 pfx = "flt"; 1341 sfx = ""; 1342 } else { 1343 if (opt_e) { 1344 pfx = "err"; 1345 sfx = ""; 1346 } else { 1347 pfx = "info"; 1348 sfx = opt_I ? "_hival" : ""; 1349 } 1350 } 1351 1352 (void) snprintf(ifile, PATH_MAX, "%s/var/fm/fmd/%slog%s", 1353 g_root ? g_root : "", pfx, sfx); 1354 /* 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 1357 * order. 1358 */ 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"); 1364 } 1365 1366 if ((g_msg = fmd_msg_init(g_root, FMD_MSG_VERSION)) == NULL) 1367 fmdump_fatal("failed to initialize libfmd_msg"); 1368 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)); 1372 } 1373 1374 if (opt_H) { 1375 fmd_log_header(lp, &log); 1376 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); 1385 1386 return (FMDUMP_EXIT_SUCCESS); 1387 } 1388 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))); 1392 } 1393 1394 if (opt_e && opt_u) 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; 1402 else 1403 ops = &fmdump_err_ops; 1404 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++]; 1409 } 1410 1411 if (opt_V) { 1412 arg.da_fmt = 1413 &ops->do_formats[opt_p ? FMDUMP_PRETTY : 1414 opt_j ? FMDUMP_JSON : FMDUMP_VERB2]; 1415 iflags |= FMD_LOG_XITER_REFS; 1416 } else if (opt_v) { 1417 arg.da_fmt = &ops->do_formats[FMDUMP_VERB1]; 1418 } else if (opt_m) { 1419 arg.da_fmt = &ops->do_formats[FMDUMP_MSG]; 1420 } else 1421 arg.da_fmt = &ops->do_formats[FMDUMP_SHORT]; 1422 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); 1426 } 1427 1428 arg.da_fv = errfv; 1429 arg.da_fc = errfc; 1430 arg.da_fp = stdout; 1431 1432 if (iflags & FMD_LOG_XITER_OFFS) 1433 fmdump_printf(arg.da_fp, "%16s ", "OFFSET"); 1434 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); 1437 1438 if (opt_e && opt_u) { 1439 iflags |= FMD_LOG_XITER_REFS; 1440 func = xref_iter; 1441 farg = &arg; 1442 filtc = fltfc; 1443 filtv = fltfv; 1444 } else { 1445 func = arg.da_fmt->do_func; 1446 farg = arg.da_fp; 1447 filtc = allfc; 1448 filtv = allfv; 1449 } 1450 1451 if (iflags & FMD_LOG_XITER_OFFS) { 1452 lyr.dy_func = func; 1453 lyr.dy_arg = farg; 1454 lyr.dy_fp = arg.da_fp; 1455 func = xoff_iter; 1456 farg = &lyr; 1457 } 1458 1459 for (llp = rotated_logs; llp != NULL; llp = llp->next) { 1460 fmd_log_t *rlp; 1461 1462 if ((rlp = fmd_log_open(FMD_LOG_VERSION, llp->path, &err)) 1463 == NULL) { 1464 fmdump_warn("failed to open %s: %s\n", 1465 llp->path, fmd_log_errmsg(NULL, err)); 1466 g_errs++; 1467 continue; 1468 } 1469 1470 recs = 0; 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))); 1475 g_errs++; 1476 } 1477 g_recs += recs; 1478 1479 fmd_log_close(rlp); 1480 } 1481 1482 do { 1483 recs = 0; 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))); 1488 g_errs++; 1489 } 1490 g_recs += recs; 1491 1492 if (opt_f) 1493 (void) sleep(1); 1494 1495 } while (opt_f); 1496 1497 if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO)) 1498 fmdump_warn("%s is empty\n", ifile); 1499 1500 if (g_thp != NULL) 1501 topo_close(g_thp); 1502 1503 fmd_log_close(lp); 1504 fmd_msg_fini(g_msg); 1505 1506 if (ifiles == NULL) 1507 free(ifile); 1508 else 1509 cleanup(ifiles, n_ifiles); 1510 1511 return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS); 1512 } 1513