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