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