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 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 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 39 #include <fmdump.h> 40 41 #define FMDUMP_EXIT_SUCCESS 0 42 #define FMDUMP_EXIT_FATAL 1 43 #define FMDUMP_EXIT_USAGE 2 44 #define FMDUMP_EXIT_ERROR 3 45 46 const char *g_pname; 47 ulong_t g_errs; 48 ulong_t g_recs; 49 char *g_root; 50 struct topo_hdl *g_thp; 51 52 /*PRINTFLIKE2*/ 53 void 54 fmdump_printf(FILE *fp, const char *format, ...) 55 { 56 va_list ap; 57 58 va_start(ap, format); 59 60 if (vfprintf(fp, format, ap) < 0) { 61 (void) fprintf(stderr, "%s: failed to print record: %s\n", 62 g_pname, strerror(errno)); 63 g_errs++; 64 } 65 66 va_end(ap); 67 } 68 69 void 70 fmdump_vwarn(const char *format, va_list ap) 71 { 72 int err = errno; 73 74 (void) fprintf(stderr, "%s: warning: ", g_pname); 75 (void) vfprintf(stderr, format, ap); 76 77 if (strchr(format, '\n') == NULL) 78 (void) fprintf(stderr, ": %s\n", strerror(err)); 79 80 g_errs++; 81 } 82 83 /*PRINTFLIKE1*/ 84 void 85 fmdump_warn(const char *format, ...) 86 { 87 va_list ap; 88 89 va_start(ap, format); 90 fmdump_vwarn(format, ap); 91 va_end(ap); 92 } 93 94 char * 95 fmdump_date(char *buf, size_t len, const fmd_log_record_t *rp) 96 { 97 if (rp->rec_sec > LONG_MAX) { 98 fmdump_warn("record time is too large for 32-bit utility\n"); 99 (void) snprintf(buf, len, "0x%llx", rp->rec_sec); 100 } else { 101 time_t tod = (time_t)rp->rec_sec; 102 time_t now = time(NULL); 103 if (tod > now+60 || 104 tod < now - 6L*30L*24L*60L*60L) { /* 6 months ago */ 105 (void) strftime(buf, len, "%b %d %Y %T", 106 localtime(&tod)); 107 } else { 108 size_t sz; 109 sz = strftime(buf, len, "%b %d %T", localtime(&tod)); 110 (void) snprintf(buf + sz, len - sz, ".%4.4llu", 111 rp->rec_nsec / (NANOSEC / 10000)); 112 } 113 } 114 115 return (buf); 116 } 117 118 char * 119 fmdump_year(char *buf, size_t len, const fmd_log_record_t *rp) 120 { 121 #ifdef _ILP32 122 if (rp->rec_sec > LONG_MAX) { 123 fmdump_warn("record time is too large for 32-bit utility\n"); 124 (void) snprintf(buf, len, "0x%llx", rp->rec_sec); 125 } else { 126 #endif 127 time_t tod = (time_t)rp->rec_sec; 128 (void) strftime(buf, len, "%b %d %Y %T", localtime(&tod)); 129 #ifdef _ILP32 130 } 131 #endif 132 return (buf); 133 } 134 135 static int 136 usage(FILE *fp) 137 { 138 (void) fprintf(fp, "Usage: %s [-efvV] [-c class] [-R root] [-t time] " 139 "[-T time] [-u uuid] [file]\n", g_pname); 140 141 (void) fprintf(fp, 142 "\t-c select events that match the specified class\n" 143 "\t-e display error log content instead of fault log content\n" 144 "\t-f follow growth of log file by waiting for additional data\n" 145 "\t-R set root directory for pathname expansions\n" 146 "\t-t select events that occurred after the specified time\n" 147 "\t-T select events that occurred before the specified time\n" 148 "\t-u select events that match the specified uuid\n" 149 "\t-v set verbose mode: display additional event detail\n" 150 "\t-V set very verbose mode: display complete event contents\n"); 151 152 return (FMDUMP_EXIT_USAGE); 153 } 154 155 /*ARGSUSED*/ 156 static int 157 error(fmd_log_t *lp, void *private) 158 { 159 fmdump_warn("skipping record: %s\n", 160 fmd_log_errmsg(lp, fmd_log_errno(lp))); 161 return (0); 162 } 163 164 /* 165 * Yet another disgusting argument parsing function (TM). We attempt to parse 166 * a time argument in a variety of strptime(3C) formats, in which case it is 167 * interpreted as a local time and is converted to a timeval using mktime(3C). 168 * If those formats fail, we look to see if the time is a decimal integer 169 * followed by one of our magic suffixes, in which case the time is interpreted 170 * as a time delta *before* the current time-of-day (i.e. "1h" = "1 hour ago"). 171 */ 172 static struct timeval * 173 gettimeopt(const char *arg) 174 { 175 const struct { 176 const char *name; 177 hrtime_t mul; 178 } suffix[] = { 179 { "ns", NANOSEC / NANOSEC }, 180 { "nsec", NANOSEC / NANOSEC }, 181 { "us", NANOSEC / MICROSEC }, 182 { "usec", NANOSEC / MICROSEC }, 183 { "ms", NANOSEC / MILLISEC }, 184 { "msec", NANOSEC / MILLISEC }, 185 { "s", NANOSEC / SEC }, 186 { "sec", NANOSEC / SEC }, 187 { "m", NANOSEC * (hrtime_t)60 }, 188 { "min", NANOSEC * (hrtime_t)60 }, 189 { "h", NANOSEC * (hrtime_t)(60 * 60) }, 190 { "hour", NANOSEC * (hrtime_t)(60 * 60) }, 191 { "d", NANOSEC * (hrtime_t)(24 * 60 * 60) }, 192 { "day", NANOSEC * (hrtime_t)(24 * 60 * 60) }, 193 { NULL } 194 }; 195 196 struct timeval *tvp = malloc(sizeof (struct timeval)); 197 struct timeval tod; 198 struct tm tm; 199 char *p; 200 201 if (tvp == NULL) { 202 (void) fprintf(stderr, "%s: failed to allocate memory: %s\n", 203 g_pname, strerror(errno)); 204 exit(FMDUMP_EXIT_FATAL); 205 } 206 207 if (gettimeofday(&tod, NULL) != 0) { 208 (void) fprintf(stderr, "%s: failed to get tod: %s\n", 209 g_pname, strerror(errno)); 210 exit(FMDUMP_EXIT_FATAL); 211 } 212 213 /* 214 * First try a variety of strptime() calls. If these all fail, we'll 215 * try parsing an integer followed by one of our suffix[] strings. 216 * NOTE: any form using %y must appear *before* the equivalent %Y form; 217 * otherwise %Y will accept the two year digits but infer century zero. 218 * Any form ending in %y must additionally check isdigit(*p) to ensure 219 * that it does not inadvertently match 2 digits of a 4-digit year. 220 * 221 * Beware: Any strptime() sequence containing consecutive %x sequences 222 * may fall victim to SCCS expanding it as a keyword! If this happens 223 * we use separate string constant that ANSI C will concatenate. 224 */ 225 if ((p = strptime(arg, "%m/%d/%y" "%t" "%H:%M:%S", &tm)) == NULL && 226 (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M:%S", &tm)) == NULL && 227 (p = strptime(arg, "%m/%d/%y" "%t" "%H:%M", &tm)) == NULL && 228 (p = strptime(arg, "%m/%d/%Y" "%t" "%H:%M", &tm)) == NULL && 229 ((p = strptime(arg, "%m/%d/%y", &tm)) == NULL || isdigit(*p)) && 230 (p = strptime(arg, "%m/%d/%Y", &tm)) == NULL && 231 (p = strptime(arg, "%y-%m-%dT%H:%M:%S", &tm)) == NULL && 232 (p = strptime(arg, "%Y-%m-%dT%H:%M:%S", &tm)) == NULL && 233 (p = strptime(arg, "%y-%m-%dT%H:%M", &tm)) == NULL && 234 (p = strptime(arg, "%Y-%m-%dT%H:%M", &tm)) == NULL && 235 (p = strptime(arg, "%y-%m-%d", &tm)) == NULL && 236 (p = strptime(arg, "%Y-%m-%d", &tm)) == NULL && 237 (p = strptime(arg, "%d%b%y" "%t" "%H:%M:%S", &tm)) == NULL && 238 (p = strptime(arg, "%d%b%Y" "%t" "%H:%M:%S", &tm)) == NULL && 239 (p = strptime(arg, "%d%b%y" "%t" "%H:%M", &tm)) == NULL && 240 (p = strptime(arg, "%d%b%Y" "%t" "%H:%M", &tm)) == NULL && 241 ((p = strptime(arg, "%d%b%y", &tm)) == NULL || isdigit(*p)) && 242 (p = strptime(arg, "%d%b%Y", &tm)) == NULL && 243 (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL && 244 (p = strptime(arg, "%b%t%d" "%t" "%H:%M:%S", &tm)) == NULL && 245 (p = strptime(arg, "%H:%M:%S", &tm)) == NULL && 246 (p = strptime(arg, "%H:%M", &tm)) == NULL) { 247 248 hrtime_t nsec; 249 int i; 250 251 errno = 0; 252 nsec = strtol(arg, (char **)&p, 10); 253 254 if (errno != 0 || nsec == 0 || p == arg || *p == '\0') { 255 (void) fprintf(stderr, "%s: illegal time " 256 "format -- %s\n", g_pname, arg); 257 exit(FMDUMP_EXIT_USAGE); 258 } 259 260 for (i = 0; suffix[i].name != NULL; i++) { 261 if (strcasecmp(suffix[i].name, p) == 0) { 262 nsec *= suffix[i].mul; 263 break; 264 } 265 } 266 267 if (suffix[i].name == NULL) { 268 (void) fprintf(stderr, "%s: illegal time " 269 "format -- %s\n", g_pname, arg); 270 exit(FMDUMP_EXIT_USAGE); 271 } 272 273 tvp->tv_sec = nsec / NANOSEC; 274 tvp->tv_usec = (nsec % NANOSEC) / (NANOSEC / MICROSEC); 275 276 if (tvp->tv_sec > tod.tv_sec) { 277 (void) fprintf(stderr, "%s: time delta precedes " 278 "UTC time origin -- %s\n", g_pname, arg); 279 exit(FMDUMP_EXIT_USAGE); 280 } 281 282 tvp->tv_sec = tod.tv_sec - tvp->tv_sec; 283 284 } else if (*p == '\0' || *p == '.') { 285 /* 286 * If tm_year is zero, we matched [%b %d] %H:%M[:%S]; use 287 * the result of localtime(&tod.tv_sec) to fill in the rest. 288 */ 289 if (tm.tm_year == 0) { 290 int h = tm.tm_hour; 291 int m = tm.tm_min; 292 int s = tm.tm_sec; 293 int b = tm.tm_mon; 294 int d = tm.tm_mday; 295 296 bcopy(localtime(&tod.tv_sec), &tm, sizeof (tm)); 297 tm.tm_isdst = 0; /* see strptime(3C) and below */ 298 299 if (d > 0) { 300 tm.tm_mon = b; 301 tm.tm_mday = d; 302 } 303 304 tm.tm_hour = h; 305 tm.tm_min = m; 306 tm.tm_sec = s; 307 } 308 309 errno = 0; 310 tvp->tv_sec = mktime(&tm); 311 tvp->tv_usec = 0; 312 313 if (tvp->tv_sec == -1L && errno != 0) { 314 (void) fprintf(stderr, "%s: failed to compose " 315 "time %s: %s\n", g_pname, arg, strerror(errno)); 316 exit(FMDUMP_EXIT_ERROR); 317 } 318 319 /* 320 * If our mktime() set tm_isdst, adjust the result for DST by 321 * subtracting the offset between the main and alternate zones. 322 */ 323 if (tm.tm_isdst) 324 tvp->tv_sec -= timezone - altzone; 325 326 if (p[0] == '.') { 327 arg = p; 328 errno = 0; 329 tvp->tv_usec = 330 (suseconds_t)(strtod(arg, &p) * (double)MICROSEC); 331 332 if (errno != 0 || p == arg || *p != '\0') { 333 (void) fprintf(stderr, "%s: illegal time " 334 "suffix -- .%s\n", g_pname, arg); 335 exit(FMDUMP_EXIT_USAGE); 336 } 337 } 338 339 } else { 340 (void) fprintf(stderr, "%s: unexpected suffix after " 341 "time %s -- %s\n", g_pname, arg, p); 342 exit(FMDUMP_EXIT_USAGE); 343 } 344 345 return (tvp); 346 } 347 348 /* 349 * If the -u option is specified in combination with the -e option, we iterate 350 * over each record in the fault log with a matching UUID finding xrefs to the 351 * error log, and then use this function to iterate over every xref'd record. 352 */ 353 int 354 xref_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) 355 { 356 const fmd_log_record_t *xrp = rp->rec_xrefs; 357 fmdump_arg_t *dap = arg; 358 int i, rv = 0; 359 360 for (i = 0; rv == 0 && i < rp->rec_nrefs; i++, xrp++) { 361 if (fmd_log_filter(lp, dap->da_fc, dap->da_fv, xrp)) 362 rv = dap->da_fmt->do_func(lp, xrp, dap->da_fp); 363 } 364 365 return (rv); 366 } 367 368 int 369 xoff_iter(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) 370 { 371 fmdump_lyr_t *dyp = arg; 372 373 fmdump_printf(dyp->dy_fp, "%16llx ", (u_longlong_t)rp->rec_off); 374 return (dyp->dy_func(lp, rp, dyp->dy_arg)); 375 } 376 377 /* 378 * If the -a option is not present, filter out fault records that correspond 379 * to events that the producer requested not be messaged for administrators. 380 */ 381 /*ARGSUSED*/ 382 int 383 log_filter_silent(fmd_log_t *lp, const fmd_log_record_t *rp, void *arg) 384 { 385 boolean_t msg; 386 387 return (nvlist_lookup_boolean_value(rp->rec_nvl, 388 FM_SUSPECT_MESSAGE, &msg) != 0 || msg != 0); 389 } 390 391 int 392 main(int argc, char *argv[]) 393 { 394 int opt_a = 0, opt_e = 0, opt_f = 0, opt_H = 0; 395 int opt_u = 0, opt_v = 0, opt_V = 0; 396 397 char ifile[PATH_MAX] = ""; 398 int iflags = 0; 399 400 fmdump_arg_t arg; 401 fmdump_lyr_t lyr; 402 const fmdump_ops_t *ops; 403 fmd_log_filter_t *filtv; 404 uint_t filtc; 405 406 fmd_log_filter_t *errfv, *fltfv, *allfv; 407 uint_t errfc = 0, fltfc = 0, allfc = 0; 408 409 fmd_log_header_t log; 410 fmd_log_rec_f *func; 411 void *farg; 412 fmd_log_t *lp; 413 int c, err; 414 off64_t off = 0; 415 416 g_pname = argv[0]; 417 418 errfv = alloca(sizeof (fmd_log_filter_t) * argc); 419 fltfv = alloca(sizeof (fmd_log_filter_t) * argc); 420 allfv = alloca(sizeof (fmd_log_filter_t) * argc); 421 422 while (optind < argc) { 423 while ((c = getopt(argc, argv, "ac:efHO:R:t:T:u:vV")) != EOF) { 424 switch (c) { 425 case 'a': 426 opt_a++; 427 break; 428 case 'c': 429 errfv[errfc].filt_func = fmd_log_filter_class; 430 errfv[errfc].filt_arg = optarg; 431 allfv[allfc++] = errfv[errfc++]; 432 break; 433 case 'e': 434 opt_e++; 435 break; 436 case 'f': 437 opt_f++; 438 break; 439 case 'H': 440 opt_H++; 441 break; 442 case 'O': 443 off = strtoull(optarg, NULL, 16); 444 iflags |= FMD_LOG_XITER_OFFS; 445 break; 446 case 'R': 447 g_root = optarg; 448 break; 449 case 't': 450 errfv[errfc].filt_func = fmd_log_filter_after; 451 errfv[errfc].filt_arg = gettimeopt(optarg); 452 allfv[allfc++] = errfv[errfc++]; 453 break; 454 case 'T': 455 errfv[errfc].filt_func = fmd_log_filter_before; 456 errfv[errfc].filt_arg = gettimeopt(optarg); 457 allfv[allfc++] = errfv[errfc++]; 458 break; 459 case 'u': 460 fltfv[fltfc].filt_func = fmd_log_filter_uuid; 461 fltfv[fltfc].filt_arg = optarg; 462 allfv[allfc++] = fltfv[fltfc++]; 463 opt_u++; 464 opt_a++; /* -u implies -a */ 465 break; 466 case 'v': 467 opt_v++; 468 break; 469 case 'V': 470 opt_V++; 471 break; 472 default: 473 return (usage(stderr)); 474 } 475 } 476 477 if (optind < argc) { 478 if (*ifile != '\0') { 479 (void) fprintf(stderr, "%s: illegal " 480 "argument -- %s\n", g_pname, argv[optind]); 481 return (FMDUMP_EXIT_USAGE); 482 } else { 483 (void) strlcpy(ifile, 484 argv[optind++], sizeof (ifile)); 485 } 486 } 487 } 488 489 if (*ifile == '\0') { 490 (void) snprintf(ifile, sizeof (ifile), "%s/var/fm/fmd/%slog", 491 g_root ? g_root : "", opt_e && !opt_u ? "err" : "flt"); 492 } else if (g_root != NULL) { 493 (void) fprintf(stderr, "%s: -R option is not appropriate " 494 "when file operand is present\n", g_pname); 495 return (FMDUMP_EXIT_USAGE); 496 } 497 498 if ((lp = fmd_log_open(FMD_LOG_VERSION, ifile, &err)) == NULL) { 499 (void) fprintf(stderr, "%s: failed to open %s: %s\n", 500 g_pname, ifile, fmd_log_errmsg(NULL, err)); 501 return (FMDUMP_EXIT_FATAL); 502 } 503 504 if (opt_H) { 505 fmd_log_header(lp, &log); 506 507 (void) printf("EXD_CREATOR = %s\n", log.log_creator); 508 (void) printf("EXD_HOSTNAME = %s\n", log.log_hostname); 509 (void) printf("EXD_FMA_LABEL = %s\n", log.log_label); 510 (void) printf("EXD_FMA_VERSION = %s\n", log.log_version); 511 (void) printf("EXD_FMA_OSREL = %s\n", log.log_osrelease); 512 (void) printf("EXD_FMA_OSVER = %s\n", log.log_osversion); 513 (void) printf("EXD_FMA_PLAT = %s\n", log.log_platform); 514 (void) printf("EXD_FMA_UUID = %s\n", log.log_uuid); 515 516 return (FMDUMP_EXIT_SUCCESS); 517 } 518 519 if (off != 0 && fmd_log_seek(lp, off) != 0) { 520 (void) fprintf(stderr, "%s: failed to seek %s: %s\n", 521 g_pname, ifile, fmd_log_errmsg(lp, fmd_log_errno(lp))); 522 return (FMDUMP_EXIT_FATAL); 523 } 524 525 if (opt_e && opt_u) 526 ops = &fmdump_err_ops; 527 else if (strcmp(fmd_log_label(lp), fmdump_flt_ops.do_label) == 0) 528 ops = &fmdump_flt_ops; 529 else if (strcmp(fmd_log_label(lp), fmdump_asru_ops.do_label) == 0) 530 ops = &fmdump_asru_ops; 531 else 532 ops = &fmdump_err_ops; 533 534 if (!opt_a && ops == &fmdump_flt_ops) { 535 fltfv[fltfc].filt_func = log_filter_silent; 536 fltfv[fltfc].filt_arg = NULL; 537 allfv[allfc++] = fltfv[fltfc++]; 538 } 539 540 if (opt_V) { 541 arg.da_fmt = &ops->do_formats[FMDUMP_VERB2]; 542 iflags |= FMD_LOG_XITER_REFS; 543 } else if (opt_v) { 544 arg.da_fmt = &ops->do_formats[FMDUMP_VERB1]; 545 } else 546 arg.da_fmt = &ops->do_formats[FMDUMP_SHORT]; 547 548 arg.da_fv = errfv; 549 arg.da_fc = errfc; 550 arg.da_fp = stdout; 551 552 if (iflags & FMD_LOG_XITER_OFFS) 553 fmdump_printf(arg.da_fp, "%16s ", "OFFSET"); 554 555 if (arg.da_fmt->do_hdr) 556 fmdump_printf(arg.da_fp, "%s\n", arg.da_fmt->do_hdr); 557 558 if (opt_e && opt_u) { 559 iflags |= FMD_LOG_XITER_REFS; 560 func = xref_iter; 561 farg = &arg; 562 filtc = fltfc; 563 filtv = fltfv; 564 } else { 565 func = arg.da_fmt->do_func; 566 farg = arg.da_fp; 567 filtc = allfc; 568 filtv = allfv; 569 } 570 571 if (iflags & FMD_LOG_XITER_OFFS) { 572 lyr.dy_func = func; 573 lyr.dy_arg = farg; 574 lyr.dy_fp = arg.da_fp; 575 func = xoff_iter; 576 farg = &lyr; 577 } 578 579 do { 580 if (fmd_log_xiter(lp, iflags, filtc, filtv, 581 func, error, farg, &g_recs) != 0) { 582 (void) fprintf(stderr, 583 "%s: failed to dump %s: %s\n", g_pname, ifile, 584 fmd_log_errmsg(lp, fmd_log_errno(lp))); 585 g_errs++; 586 } 587 588 if (opt_f) 589 (void) sleep(1); 590 591 } while (opt_f); 592 593 if (!opt_f && g_recs == 0 && isatty(STDOUT_FILENO)) 594 (void) fprintf(stderr, "%s: %s is empty\n", g_pname, ifile); 595 596 fmd_log_close(lp); 597 return (g_errs ? FMDUMP_EXIT_ERROR : FMDUMP_EXIT_SUCCESS); 598 } 599