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 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 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 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 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 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 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 * 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 * 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 213 usage(FILE *fp) 214 { 215 (void) fprintf(fp, synopsis, g_pname); 216 return (FMDUMP_EXIT_USAGE); 217 } 218 219 /*ARGSUSED*/ 220 static int 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 * 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 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 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 * 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 * 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 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 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 * 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 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 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 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 * 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 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 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 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 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 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 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 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 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 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 * 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 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 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 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