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 2006 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 <stdio.h> 29 #include <kstat.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <strings.h> 33 #include <errno.h> 34 #include <limits.h> 35 #include <sys/types.h> 36 #include <time.h> 37 #include <sys/time.h> 38 #include <sys/uio.h> 39 #include <sys/vnode.h> 40 #include <sys/vfs.h> 41 #include <sys/statvfs.h> 42 #include <sys/fstyp.h> 43 #include <sys/fsid.h> 44 #include <sys/mnttab.h> 45 #include <values.h> 46 #include <poll.h> 47 #include <ctype.h> 48 #include <libintl.h> 49 50 /* 51 * For now, parsable output is turned off. Once we gather feedback and 52 * stablize the output format, we'll turn it back on. This prevents 53 * the situation where users build tools which depend on a specific 54 * format before we declare the output stable. 55 */ 56 #define PARSABLE_OUTPUT 0 57 58 #if PARSABLE_OUTPUT 59 #define OPTIONS "FPT:afginv" 60 #else 61 #define OPTIONS "FT:afginv" 62 #endif 63 64 /* Time stamp values */ 65 #define NODATE 0 /* Default: No time stamp */ 66 #define DDATE 1 /* Standard date format */ 67 #define UDATE 2 /* Internal representation of Unix time */ 68 69 #define RETRY_DELAY 250 /* Timeout for poll() */ 70 #define HEADERLINES 12 /* Number of lines between display headers */ 71 72 #define LBUFSZ 64 /* Generic size for local buffer */ 73 74 /* 75 * The following are used for the nicenum() function 76 */ 77 #define KILO_VAL 1024 78 #define ONE_INDEX 3 79 80 #define NENTITY_INIT 1 /* Initial number of entities to allocate */ 81 82 /* 83 * We need to have a mechanism for an old/previous and new/current vopstat 84 * structure. We only need two per entity and we can swap between them. 85 */ 86 #define VS_SIZE 2 /* Size of vopstat array */ 87 #define CUR_INDEX (vs_i) 88 #define PREV_INDEX ((vs_i == 0) ? 1 : 0) /* Opposite of CUR_INDEX */ 89 #define BUMP_INDEX() vs_i = ((vs_i == 0) ? 1 : 0) 90 91 /* 92 * An "entity" is anything we're collecting statistics on, it could 93 * be a mountpoint or an FS-type. 94 * e_name is the name of the entity (e.g. mount point or FS-type) 95 * e_ksname is the name of the associated kstat 96 * e_vs is an array of vopstats. This is used to keep track of "previous" 97 * and "current" vopstats. 98 */ 99 typedef struct entity { 100 char *e_name; /* name of entity */ 101 vopstats_t *e_vs; /* Array of vopstats */ 102 ulong_t e_fsid; /* fsid for ENTYPE_MNTPT only */ 103 int e_type; /* type of entity */ 104 char e_ksname[KSTAT_STRLEN]; /* kstat name */ 105 } entity_t; 106 107 /* Types of entities (e_type) */ 108 #define ENTYPE_UNKNOWN 0 /* UNKNOWN must be zero since we calloc() */ 109 #define ENTYPE_FSTYPE 1 110 #define ENTYPE_MNTPT 2 111 112 /* If more sub-one units are added, make sure to adjust ONE_INDEX above */ 113 static char units[] = "num KMGTPE"; 114 115 static char *cmdname; /* name of this command */ 116 117 static int vs_i = 0; /* Index of current vs[] slot */ 118 119 static void 120 usage() 121 { 122 (void) fprintf(stderr, gettext( 123 "Usage: %s [-a|f|i|n|v] [-T d|u] {-F | {fstype | fspath}...} " 124 "[interval [count]]\n"), cmdname); 125 exit(2); 126 } 127 128 /* 129 * Given a 64-bit number and a starting unit (e.g., n - nanoseconds), 130 * convert the number to a 5-character representation including any 131 * decimal point and single-character unit. Put that representation 132 * into the array "buf" (which had better be big enough). 133 */ 134 char * 135 nicenum(uint64_t num, char unit, char *buf) 136 { 137 uint64_t n = num; 138 int unit_index; 139 int index; 140 char u; 141 142 /* If the user passed in a NUL/zero unit, use the blank value for 1 */ 143 if (unit == '\0') 144 unit = ' '; 145 146 unit_index = 0; 147 while (units[unit_index] != unit) { 148 unit_index++; 149 if (unit_index > sizeof (units) - 1) { 150 (void) sprintf(buf, "??"); 151 return (buf); 152 } 153 } 154 155 index = 0; 156 while (n >= KILO_VAL) { 157 n = (n + (KILO_VAL / 2)) / KILO_VAL; /* Round up or down */ 158 index++; 159 unit_index++; 160 } 161 162 if (unit_index >= sizeof (units) - 1) { 163 (void) sprintf(buf, "??"); 164 return (buf); 165 } 166 167 u = units[unit_index]; 168 169 if (unit_index == ONE_INDEX) { 170 (void) sprintf(buf, "%llu", (u_longlong_t)n); 171 } else if (n < 10 && (num & (num - 1)) != 0) { 172 (void) sprintf(buf, "%.2f%c", 173 (double)num / (1ULL << 10 * index), u); 174 } else if (n < 100 && (num & (num - 1)) != 0) { 175 (void) sprintf(buf, "%.1f%c", 176 (double)num / (1ULL << 10 * index), u); 177 } else { 178 (void) sprintf(buf, "%llu%c", (u_longlong_t)n, u); 179 } 180 181 return (buf); 182 } 183 184 185 #define RAWVAL(ptr, member) ((ptr)->member.value.ui64) 186 #define DELTA(member) \ 187 (newvsp->member.value.ui64 - (oldvsp ? oldvsp->member.value.ui64 : 0)) 188 189 #define PRINTSTAT(isnice, nicestring, rawstring, rawval, unit, buf) \ 190 (isnice) ? \ 191 (void) printf((nicestring), nicenum(rawval, unit, buf)) \ 192 : \ 193 (void) printf((rawstring), (rawval)) 194 195 /* Values for display flag */ 196 #define DISP_HEADER 0x1 197 #define DISP_RAW 0x2 198 199 /* 200 * The policy for dealing with multiple flags is dealt with here. 201 * Currently, if we are displaying raw output, then don't allow 202 * headers to be printed. 203 */ 204 int 205 dispflag_policy(int printhdr, int dispflag) 206 { 207 /* If we're not displaying raw output, then allow headers to print */ 208 if ((dispflag & DISP_RAW) == 0) { 209 if (printhdr) { 210 dispflag |= DISP_HEADER; 211 } 212 } 213 214 return (dispflag); 215 } 216 217 static void 218 dflt_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 219 { 220 int niceflag = ((dispflag & DISP_RAW) == 0); 221 longlong_t nnewfile; 222 longlong_t nnamerm; 223 longlong_t nnamechg; 224 longlong_t nattrret; 225 longlong_t nattrchg; 226 longlong_t nlookup; 227 longlong_t nreaddir; 228 longlong_t ndataread; 229 longlong_t ndatawrite; 230 longlong_t readthruput; 231 longlong_t writethruput; 232 char buf[LBUFSZ]; 233 234 nnewfile = DELTA(ncreate) + DELTA(nmkdir) + DELTA(nsymlink); 235 nnamerm = DELTA(nremove) + DELTA(nrmdir); 236 nnamechg = DELTA(nrename) + DELTA(nlink) + DELTA(nsymlink); 237 nattrret = DELTA(ngetattr) + DELTA(naccess) + 238 DELTA(ngetsecattr) + DELTA(nfid); 239 nattrchg = DELTA(nsetattr) + DELTA(nsetsecattr) + DELTA(nspace); 240 nlookup = DELTA(nlookup); 241 nreaddir = DELTA(nreaddir); 242 ndataread = DELTA(nread); 243 ndatawrite = DELTA(nwrite); 244 readthruput = DELTA(read_bytes); 245 writethruput = DELTA(write_bytes); 246 247 if (dispflag & DISP_HEADER) { 248 (void) printf(gettext( 249 " new name name attr attr lookup rddir read read write write\n" 250 " file remov chng get set ops ops ops bytes ops bytes\n")); 251 } 252 253 PRINTSTAT(niceflag, "%5s ", "%lld:", nnewfile, ' ', buf); 254 PRINTSTAT(niceflag, "%5s ", "%lld:", nnamerm, ' ', buf); 255 PRINTSTAT(niceflag, "%5s ", "%lld:", nnamechg, ' ', buf); 256 PRINTSTAT(niceflag, "%5s ", "%lld:", nattrret, ' ', buf); 257 PRINTSTAT(niceflag, "%5s ", "%lld:", nattrchg, ' ', buf); 258 PRINTSTAT(niceflag, " %5s ", "%lld:", nlookup, ' ', buf); 259 PRINTSTAT(niceflag, "%5s ", "%lld:", nreaddir, ' ', buf); 260 PRINTSTAT(niceflag, "%5s ", "%lld:", ndataread, ' ', buf); 261 PRINTSTAT(niceflag, "%5s ", "%lld:", readthruput, ' ', buf); 262 PRINTSTAT(niceflag, "%5s ", "%lld:", ndatawrite, ' ', buf); 263 PRINTSTAT(niceflag, "%5s ", "%lld:", writethruput, ' ', buf); 264 (void) printf("%s\n", name); 265 } 266 267 static void 268 io_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 269 { 270 int niceflag = ((dispflag & DISP_RAW) == 0); 271 char buf[LBUFSZ]; 272 273 if (dispflag & DISP_HEADER) { 274 (void) printf(gettext( 275 " read read write write rddir rddir rwlock rwulock\n" 276 " ops bytes ops bytes ops bytes ops ops\n")); 277 } 278 279 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nread), ' ', buf); 280 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(read_bytes), ' ', buf); 281 282 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nwrite), ' ', buf); 283 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(write_bytes), ' ', buf); 284 285 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf); 286 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(readdir_bytes), ' ', buf); 287 288 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nrwlock), ' ', buf); 289 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrwunlock), ' ', buf); 290 291 (void) printf("%s\n", name); 292 } 293 294 static void 295 vm_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 296 { 297 int niceflag = ((dispflag & DISP_RAW) == 0); 298 char buf[LBUFSZ]; 299 300 if (dispflag & DISP_HEADER) { 301 (void) printf( 302 gettext(" map addmap delmap getpag putpag pagio\n")); 303 } 304 305 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmap), ' ', buf); 306 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(naddmap), ' ', buf); 307 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ndelmap), ' ', buf); 308 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetpage), ' ', buf); 309 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nputpage), ' ', buf); 310 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(npageio), ' ', buf); 311 (void) printf("%s\n", name); 312 } 313 314 static void 315 attr_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 316 { 317 int niceflag = ((dispflag & DISP_RAW) == 0); 318 char buf[LBUFSZ]; 319 320 if (dispflag & DISP_HEADER) { 321 (void) printf(gettext("getattr setattr getsec setsec\n")); 322 } 323 324 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetattr), ' ', buf); 325 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetattr), ' ', buf); 326 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetsecattr), ' ', buf); 327 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetsecattr), ' ', buf); 328 329 (void) printf("%s\n", name); 330 } 331 332 static void 333 naming_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 334 { 335 int niceflag = ((dispflag & DISP_RAW) == 0); 336 char buf[LBUFSZ]; 337 338 if (dispflag & DISP_HEADER) { 339 (void) printf(gettext( 340 "lookup creat remov link renam mkdir rmdir rddir symlnk rdlnk\n")); 341 } 342 343 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlookup), ' ', buf); 344 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(ncreate), ' ', buf); 345 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nremove), ' ', buf); 346 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlink), ' ', buf); 347 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrename), ' ', buf); 348 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmkdir), ' ', buf); 349 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrmdir), ' ', buf); 350 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf); 351 PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsymlink), ' ', buf); 352 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreadlink), ' ', buf); 353 (void) printf("%s\n", name); 354 } 355 356 357 #define PRINT_VOPSTAT_CMN(niceflag, vop) \ 358 if (niceflag) \ 359 (void) printf("%10s ", #vop); \ 360 PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(n##vop), ' ', buf); 361 362 #define PRINT_VOPSTAT(niceflag, vop) \ 363 PRINT_VOPSTAT_CMN(niceflag, vop); \ 364 if (niceflag) \ 365 (void) printf("\n"); 366 367 #define PRINT_VOPSTAT_IO(niceflag, vop) \ 368 PRINT_VOPSTAT_CMN(niceflag, vop); \ 369 PRINTSTAT(niceflag, " %5s\n", "%lld:", \ 370 DELTA(vop##_bytes), ' ', buf); 371 372 static void 373 vop_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) 374 { 375 int niceflag = ((dispflag & DISP_RAW) == 0); 376 char buf[LBUFSZ]; 377 378 if (niceflag) { 379 (void) printf("%s\n", name); 380 (void) printf(gettext(" operation #ops bytes\n")); 381 } 382 383 PRINT_VOPSTAT(niceflag, open); 384 PRINT_VOPSTAT(niceflag, close); 385 PRINT_VOPSTAT_IO(niceflag, read); 386 PRINT_VOPSTAT_IO(niceflag, write); 387 PRINT_VOPSTAT(niceflag, ioctl); 388 PRINT_VOPSTAT(niceflag, setfl); 389 PRINT_VOPSTAT(niceflag, getattr); 390 PRINT_VOPSTAT(niceflag, setattr); 391 PRINT_VOPSTAT(niceflag, access); 392 PRINT_VOPSTAT(niceflag, lookup); 393 PRINT_VOPSTAT(niceflag, create); 394 PRINT_VOPSTAT(niceflag, remove); 395 PRINT_VOPSTAT(niceflag, link); 396 PRINT_VOPSTAT(niceflag, rename); 397 PRINT_VOPSTAT(niceflag, mkdir); 398 PRINT_VOPSTAT(niceflag, rmdir); 399 PRINT_VOPSTAT_IO(niceflag, readdir); 400 PRINT_VOPSTAT(niceflag, symlink); 401 PRINT_VOPSTAT(niceflag, readlink); 402 PRINT_VOPSTAT(niceflag, fsync); 403 PRINT_VOPSTAT(niceflag, inactive); 404 PRINT_VOPSTAT(niceflag, fid); 405 PRINT_VOPSTAT(niceflag, rwlock); 406 PRINT_VOPSTAT(niceflag, rwunlock); 407 PRINT_VOPSTAT(niceflag, seek); 408 PRINT_VOPSTAT(niceflag, cmp); 409 PRINT_VOPSTAT(niceflag, frlock); 410 PRINT_VOPSTAT(niceflag, space); 411 PRINT_VOPSTAT(niceflag, realvp); 412 PRINT_VOPSTAT(niceflag, getpage); 413 PRINT_VOPSTAT(niceflag, putpage); 414 PRINT_VOPSTAT(niceflag, map); 415 PRINT_VOPSTAT(niceflag, addmap); 416 PRINT_VOPSTAT(niceflag, delmap); 417 PRINT_VOPSTAT(niceflag, poll); 418 PRINT_VOPSTAT(niceflag, dump); 419 PRINT_VOPSTAT(niceflag, pathconf); 420 PRINT_VOPSTAT(niceflag, pageio); 421 PRINT_VOPSTAT(niceflag, dumpctl); 422 PRINT_VOPSTAT(niceflag, dispose); 423 PRINT_VOPSTAT(niceflag, getsecattr); 424 PRINT_VOPSTAT(niceflag, setsecattr); 425 PRINT_VOPSTAT(niceflag, shrlock); 426 PRINT_VOPSTAT(niceflag, vnevent); 427 428 if (niceflag) { 429 /* Make it easier on the eyes */ 430 (void) printf("\n"); 431 } else { 432 (void) printf("%s\n", name); 433 } 434 } 435 436 437 /* 438 * Retrieve the vopstats. If kspp (pointer to kstat_t pointer) is non-NULL, 439 * then pass it back to the caller. 440 * 441 * Returns 0 on success, non-zero on failure. 442 */ 443 int 444 get_vopstats(kstat_ctl_t *kc, char *ksname, vopstats_t *vsp, kstat_t **kspp) 445 { 446 kstat_t *ksp; 447 448 if (ksname == NULL || *ksname == 0) 449 return (1); 450 451 errno = 0; 452 /* wait for a possibly up-to-date chain */ 453 while (kstat_chain_update(kc) == -1) { 454 if (errno == EAGAIN) { 455 errno = 0; 456 (void) poll(NULL, 0, RETRY_DELAY); 457 continue; 458 } 459 perror(gettext("kstat_chain_update")); 460 exit(1); 461 } 462 463 if ((ksp = kstat_lookup(kc, NULL, -1, ksname)) == NULL) { 464 return (1); 465 } 466 467 if (kstat_read(kc, ksp, vsp) == -1) { 468 return (1); 469 } 470 471 if (kspp) 472 *kspp = ksp; 473 474 return (0); 475 } 476 477 /* 478 * Given a file system type name, determine if it's part of the 479 * exception list of file systems that are not to be displayed. 480 */ 481 int 482 is_exception(char *fsname) 483 { 484 char **xlp; /* Pointer into the exception list */ 485 486 static char *exception_list[] = { 487 "specfs", 488 "fifofs", 489 "fd", 490 "swapfs", 491 "ctfs", 492 "objfs", 493 "nfsdyn", 494 NULL 495 }; 496 497 for (xlp = &exception_list[0]; *xlp != NULL; xlp++) { 498 if (strcmp(fsname, *xlp) == 0) 499 return (1); 500 } 501 502 return (0); 503 } 504 505 /* 506 * Plain and simple, build an array of names for fstypes 507 * Returns 0, if it encounters a problem. 508 */ 509 int 510 build_fstype_list(char ***fstypep) 511 { 512 int i; 513 int nfstype; 514 char buf[FSTYPSZ + 1]; 515 516 if ((nfstype = sysfs(GETNFSTYP)) < 0) { 517 perror(gettext("sysfs(GETNFSTYP)")); 518 return (0); 519 } 520 521 if ((*fstypep = calloc(nfstype, sizeof (char *))) == NULL) { 522 perror(gettext("calloc on fstypes")); 523 return (0); 524 } 525 526 for (i = 1; i < nfstype; i++) { 527 if (sysfs(GETFSTYP, i, buf) < 0) { 528 perror(gettext("sysfs(GETFSTYP)")); 529 return (0); 530 } 531 532 if (buf[0] == 0) 533 continue; 534 535 /* If this is part of the exception list, move on */ 536 if (is_exception(buf)) 537 continue; 538 539 if (((*fstypep)[i] = strdup(buf)) == NULL) { 540 perror(gettext("strdup() of fstype name")); 541 return (0); 542 } 543 } 544 545 return (i); 546 } 547 548 /* 549 * After we're done with getopts(), process the rest of the 550 * operands. We have three cases and this is the priority: 551 * 552 * 1) [ operand... ] interval count 553 * 2) [ operand... ] interval 554 * 3) [ operand... ] 555 * 556 * The trick is that any of the operands might start with a number or even 557 * be made up exclusively of numbers (and we have to handle negative numbers 558 * in case a user/script gets out of line). If we find two operands at the 559 * end of the list then we claim case 1. If we find only one operand at the 560 * end made up only of number, then we claim case 2. Otherwise, case 3. 561 * BTW, argc, argv don't change. 562 */ 563 int 564 parse_operands( 565 int argc, 566 char **argv, 567 int optind, 568 long *interval, 569 long *count, 570 entity_t **entityp) /* Array of stat-able entities */ 571 { 572 int nentities = 0; /* Number of entities found */ 573 int out_of_range; /* Set if 2nd-to-last operand out-of-range */ 574 575 if (argc == optind) 576 return (nentities); /* None found, returns 0 */ 577 /* 578 * We know exactly what the maximum number of entities is going 579 * to be: argc - optind 580 */ 581 if ((*entityp = calloc((argc - optind), sizeof (entity_t))) == NULL) { 582 perror(gettext("calloc")); 583 return (-1); 584 } 585 586 for (/* void */; argc > optind; optind++) { 587 char *endptr; 588 589 /* If we have more than two operands left to process */ 590 if ((argc - optind) > 2) { 591 (*entityp)[nentities++].e_name = strdup(argv[optind]); 592 continue; 593 } 594 595 /* If we're here, then we only have one or two operands left */ 596 errno = 0; 597 out_of_range = 0; 598 *interval = strtol(argv[optind], &endptr, 10); 599 if (*endptr && !isdigit((int)*endptr)) { 600 /* Operand was not a number */ 601 (*entityp)[nentities++].e_name = strdup(argv[optind]); 602 continue; 603 } else if (errno == ERANGE || *interval <= 0 || 604 *interval > MAXLONG) { 605 /* Operand was a number, just out of range */ 606 out_of_range++; 607 } 608 609 /* 610 * The last operand we saw was a number. If it happened to 611 * be the last operand, then it is the interval... 612 */ 613 if ((argc - optind) == 1) { 614 /* ...but we need to check the range. */ 615 if (out_of_range) { 616 (void) fprintf(stderr, gettext( 617 "interval must be between 1 and " 618 "%ld (inclusive)\n"), MAXLONG); 619 return (-1); 620 } else { 621 /* 622 * The value of the interval is valid. Set 623 * count to something really big so it goes 624 * virtually forever. 625 */ 626 *count = MAXLONG; 627 break; 628 } 629 } 630 631 /* 632 * At this point, we *might* have the interval, but if the 633 * next operand isn't a number, then we don't have either 634 * the interval nor the count. Both must be set to the 635 * defaults. In that case, both the current and the previous 636 * operands are stat-able entities. 637 */ 638 errno = 0; 639 *count = strtol(argv[optind + 1], &endptr, 10); 640 if (*endptr && !isdigit((int)*endptr)) { 641 /* 642 * Faked out! The last operand wasn't a number so 643 * the current and previous operands should be 644 * stat-able entities. We also need to reset interval. 645 */ 646 *interval = 0; 647 (*entityp)[nentities++].e_name = strdup(argv[optind++]); 648 (*entityp)[nentities++].e_name = strdup(argv[optind++]); 649 } else if (out_of_range || errno == ERANGE || *count <= 0) { 650 (void) fprintf(stderr, gettext( 651 "Both interval and count must be between 1 " 652 "and %ld (inclusive)\n"), MAXLONG); 653 return (-1); 654 } 655 break; /* Done! */ 656 } 657 return (nentities); 658 } 659 660 /* 661 * set_mntpt() looks at the entity's name (e_name) and finds its 662 * mountpoint. To do this, we need to build a list of mountpoints 663 * from /etc/mnttab. We only need to do this once and we don't do it 664 * if we don't need to look at any mountpoints. 665 * Returns 0 on success, non-zero if it couldn't find a mount-point. 666 */ 667 int 668 set_mntpt(entity_t *ep) 669 { 670 static struct mnt { 671 struct mnt *m_next; 672 char *m_mntpt; 673 ulong_t m_fsid; /* From statvfs(), set only as needed */ 674 } *mnt_list = NULL; /* Linked list of mount-points */ 675 struct mnt *mntp; 676 struct statvfs statvfsbuf; 677 char *original_name = ep->e_name; 678 char path[PATH_MAX]; 679 680 if (original_name == NULL) /* Shouldn't happen */ 681 return (1); 682 683 /* We only set up mnt_list the first time this is called */ 684 if (mnt_list == NULL) { 685 FILE *fp; 686 struct mnttab mnttab; 687 688 if ((fp = fopen(MNTTAB, "r")) == NULL) { 689 perror(MNTTAB); 690 return (1); 691 } 692 resetmnttab(fp); 693 /* 694 * We insert at the front of the list so that when we 695 * search entries we'll have the last mounted entries 696 * first in the list so that we can match the longest 697 * mountpoint. 698 */ 699 while (getmntent(fp, &mnttab) == 0) { 700 if ((mntp = malloc(sizeof (*mntp))) == NULL) { 701 perror(gettext("Can't create mount list")); 702 return (1); 703 } 704 mntp->m_mntpt = strdup(mnttab.mnt_mountp); 705 mntp->m_next = mnt_list; 706 mnt_list = mntp; 707 } 708 (void) fclose(fp); 709 } 710 711 if (realpath(original_name, path) == NULL) { 712 perror(original_name); 713 return (1); 714 } 715 716 /* 717 * Now that we have the path, walk through the mnt_list and 718 * look for the first (best) match. 719 */ 720 for (mntp = mnt_list; mntp; mntp = mntp->m_next) { 721 if (strncmp(path, mntp->m_mntpt, strlen(mntp->m_mntpt)) == 0) { 722 if (mntp->m_fsid == 0) { 723 if (statvfs(mntp->m_mntpt, &statvfsbuf)) { 724 /* Can't statvfs so no match */ 725 continue; 726 } else { 727 mntp->m_fsid = statvfsbuf.f_fsid; 728 } 729 } 730 731 if (ep->e_fsid != mntp->m_fsid) { 732 /* No match - Move on */ 733 continue; 734 } 735 736 break; 737 } 738 } 739 740 if (mntp == NULL) { 741 (void) fprintf(stderr, gettext( 742 "Can't find mount point for %s\n"), path); 743 return (1); 744 } 745 746 ep->e_name = strdup(mntp->m_mntpt); 747 free(original_name); 748 return (0); 749 } 750 751 /* 752 * We have an array of entities that are potentially stat-able. Using 753 * the name (e_name) of the entity, attempt to construct a ksname suitable 754 * for use by kstat_lookup(3kstat) and fill it into the e_ksname member. 755 * 756 * We check the e_name against the list of file system types. If there is 757 * no match then test to see if the path is valid. If the path is valid, 758 * then determine the mountpoint. 759 */ 760 void 761 set_ksnames(entity_t *entities, int nentities, char **fstypes, int nfstypes) 762 { 763 int i, j; 764 struct statvfs statvfsbuf; 765 766 for (i = 0; i < nentities; i++) { 767 entity_t *ep = &entities[i]; 768 769 /* Check the name against the list of fstypes */ 770 for (j = 1; j < nfstypes; j++) { 771 if (fstypes[j] && ep->e_name && 772 strcmp(ep->e_name, fstypes[j]) == 0) { 773 /* It's a file system type */ 774 ep->e_type = ENTYPE_FSTYPE; 775 (void) snprintf(ep->e_ksname, 776 KSTAT_STRLEN, "%s%s", 777 VOPSTATS_STR, ep->e_name); 778 /* Now allocate the vopstats array */ 779 ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t)); 780 if (entities[i].e_vs == NULL) { 781 perror(gettext("calloc() vopstats")); 782 exit(1); 783 } 784 break; 785 } 786 } 787 if (j < nfstypes) /* Found it! */ 788 continue; 789 790 /* 791 * If the entity in the exception list of fstypes, then 792 * null out the entry so it isn't displayed and move along. 793 */ 794 if (is_exception(ep->e_name)) { 795 ep->e_ksname[0] = 0; 796 continue; 797 } 798 799 /* If we didn't find it, see if it's a path */ 800 if (ep->e_name == NULL || statvfs(ep->e_name, &statvfsbuf)) { 801 /* Error - Make sure the entry is nulled out */ 802 ep->e_ksname[0] = 0; 803 continue; 804 } 805 (void) snprintf(ep->e_ksname, KSTAT_STRLEN, "%s%lx", 806 VOPSTATS_STR, statvfsbuf.f_fsid); 807 ep->e_fsid = statvfsbuf.f_fsid; 808 if (set_mntpt(ep)) { 809 (void) fprintf(stderr, 810 gettext("Can't determine type of \"%s\"\n"), 811 ep->e_name ? ep->e_name : gettext("<NULL>")); 812 } else { 813 ep->e_type = ENTYPE_MNTPT; 814 } 815 816 /* Now allocate the vopstats array */ 817 ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t)); 818 if (entities[i].e_vs == NULL) { 819 perror(gettext("Can't calloc vopstats")); 820 exit(1); 821 } 822 } 823 } 824 825 void 826 print_time(int type) 827 { 828 time_t t; 829 830 if (time(&t) != -1) { 831 if (type == UDATE) { 832 (void) printf("%ld\n", t); 833 } else if (type == DDATE) { 834 char *dstr; 835 836 dstr = ctime(&t); 837 if (dstr) { 838 (void) printf("%s", dstr); 839 } 840 } 841 } 842 } 843 844 /* 845 * The idea is that 'dspfunc' should only be modified from the default 846 * once since the display options are mutually exclusive. If 'dspfunc' 847 * only contains the default display function, then all is good and we 848 * can set it to the new display function. Otherwise, bail. 849 */ 850 void 851 set_dispfunc( 852 void (**dspfunc)(char *, vopstats_t *, vopstats_t *, int), 853 void (*newfunc)(char *, vopstats_t *, vopstats_t *, int)) 854 { 855 if (*dspfunc != dflt_display) { 856 (void) fprintf(stderr, gettext( 857 "%s: Display options -{a|f|i|n|v} are mutually exclusive\n"), 858 cmdname); 859 usage(); 860 } 861 *dspfunc = newfunc; 862 } 863 864 int 865 main(int argc, char *argv[]) 866 { 867 int c; 868 int i, j; /* Generic counters */ 869 int nentities_found; 870 int linesout; /* Keeps track of lines printed */ 871 int printhdr = 0; /* Print a header? 0 = no, 1 = yes */ 872 int nfstypes; /* Number of fstypes */ 873 int dispflag = 0; /* Flags for display control */ 874 int timestamp = NODATE; /* Default: no time stamp */ 875 long count = 0; /* Number of iterations for display */ 876 long interval = 0; 877 boolean_t fstypes_only = B_FALSE; /* Display fstypes only */ 878 char **fstypes; /* Array of names of all fstypes */ 879 int nentities; /* Number of stat-able entities */ 880 entity_t *entities; /* Array of stat-able entities */ 881 kstat_ctl_t *kc; 882 void (*dfunc)(char *, vopstats_t *, vopstats_t *, int) = dflt_display; 883 884 extern int optind; 885 886 cmdname = argv[0]; 887 while ((c = getopt(argc, argv, OPTIONS)) != EOF) { 888 switch (c) { 889 890 default: 891 usage(); 892 break; 893 894 case 'F': /* Only display available FStypes */ 895 fstypes_only = B_TRUE; 896 break; 897 898 #if PARSABLE_OUTPUT 899 case 'P': /* Parsable output */ 900 dispflag |= DISP_RAW; 901 break; 902 #endif /* PARSABLE_OUTPUT */ 903 904 case 'T': /* Timestamp */ 905 if (optarg) { 906 if (strcmp(optarg, "u") == 0) { 907 timestamp = UDATE; 908 } else if (strcmp(optarg, "d") == 0) { 909 timestamp = DDATE; 910 } 911 } 912 913 /* If it was never set properly... */ 914 if (timestamp == NODATE) { 915 (void) fprintf(stderr, gettext( 916 "%s: -T option requires either 'u' or 'd'\n"), 917 cmdname); 918 usage(); 919 } 920 break; 921 922 case 'a': 923 set_dispfunc(&dfunc, attr_display); 924 break; 925 926 case 'f': 927 set_dispfunc(&dfunc, vop_display); 928 break; 929 930 case 'i': 931 set_dispfunc(&dfunc, io_display); 932 break; 933 934 case 'n': 935 set_dispfunc(&dfunc, naming_display); 936 break; 937 938 case 'v': 939 set_dispfunc(&dfunc, vm_display); 940 break; 941 } 942 } 943 944 #if PARSABLE_OUTPUT 945 if ((dispflag & DISP_RAW) && (timestamp != NODATE)) { 946 (void) fprintf(stderr, gettext( 947 "-P and -T options are mutually exclusive\n")); 948 usage(); 949 } 950 #endif /* PARSABLE_OUTPUT */ 951 952 /* Gather the list of filesystem types */ 953 if ((nfstypes = build_fstype_list(&fstypes)) == 0) { 954 (void) fprintf(stderr, 955 gettext("Can't build list of fstypes\n")); 956 exit(1); 957 } 958 959 nentities = parse_operands( 960 argc, argv, optind, &interval, &count, &entities); 961 962 if (nentities == -1) /* Set of operands didn't parse properly */ 963 usage(); 964 965 if ((nentities == 0) && (fstypes_only == B_FALSE)) { 966 (void) fprintf(stderr, gettext( 967 "Must specify -F or at least one fstype or mount point\n")); 968 usage(); 969 } 970 971 if ((nentities > 0) && (fstypes_only == B_TRUE)) { 972 (void) fprintf(stderr, gettext( 973 "Cannot use -F with fstypes or mount points\n")); 974 usage(); 975 } 976 977 /* 978 * If we had no operands (except for interval/count) and we 979 * requested FStypes only (-F), then fill in the entities[] 980 * array with all available fstypes. 981 */ 982 if ((nentities == 0) && (fstypes_only == B_TRUE)) { 983 if ((entities = calloc(nfstypes, sizeof (entity_t))) == NULL) { 984 (void) fprintf(stderr, 985 gettext("Can't calloc fstype stats\n")); 986 exit(1); 987 } 988 989 for (i = 1; i < nfstypes; i++) { 990 if (fstypes[i]) { 991 entities[nentities].e_name = strdup(fstypes[i]); 992 nentities++; 993 } 994 } 995 } 996 997 set_ksnames(entities, nentities, fstypes, nfstypes); 998 999 if ((kc = kstat_open()) == NULL) { 1000 perror(gettext("kstat_open")); 1001 exit(1); 1002 } 1003 1004 /* 1005 * The following loop walks through the entities[] list to "prime 1006 * the pump" 1007 */ 1008 for (j = 0, linesout = 0; j < nentities; j++) { 1009 entity_t *ent = &entities[j]; 1010 vopstats_t *vsp = &ent->e_vs[CUR_INDEX]; 1011 kstat_t *ksp = NULL; 1012 1013 if (get_vopstats(kc, ent->e_ksname, vsp, &ksp) == 0) { 1014 (*dfunc)(ent->e_name, NULL, vsp, 1015 dispflag_policy(linesout == 0, dispflag)); 1016 linesout++; 1017 } else { 1018 /* 1019 * If we can't find it the first time through, then 1020 * get rid of it. 1021 */ 1022 entities[j].e_ksname[0] = 0; 1023 1024 /* 1025 * If we're only displaying FStypes (-F) then don't 1026 * complain about any file systems that might not 1027 * be loaded. Otherwise, let the user know that 1028 * he chose poorly. 1029 */ 1030 if (fstypes_only == B_FALSE) { 1031 (void) fprintf(stderr, gettext( 1032 "No statistics available for %s\n"), 1033 entities[j].e_name); 1034 } 1035 } 1036 } 1037 1038 BUMP_INDEX(); /* Swap the previous/current indices */ 1039 for (i = 1; i <= count; i++) { 1040 /* 1041 * No telling how many lines will be printed in any interval. 1042 * There should be a minimum of HEADERLINES between any 1043 * header. If we exceed that, no big deal. 1044 */ 1045 if (linesout > HEADERLINES) { 1046 linesout = 0; 1047 printhdr = 1; 1048 } 1049 (void) poll(NULL, 0, interval*1000); 1050 1051 if (timestamp) { 1052 print_time(timestamp); 1053 linesout++; 1054 } 1055 1056 for (j = 0, nentities_found = 0; j < nentities; j++) { 1057 entity_t *ent = &entities[j]; 1058 1059 /* 1060 * If this entry has been cleared, don't attempt 1061 * to process it. 1062 */ 1063 if (ent->e_ksname[0] == 0) { 1064 continue; 1065 } 1066 1067 if (get_vopstats(kc, ent->e_ksname, 1068 &ent->e_vs[CUR_INDEX], NULL) == 0) { 1069 (*dfunc)(ent->e_name, &ent->e_vs[PREV_INDEX], 1070 &ent->e_vs[CUR_INDEX], 1071 dispflag_policy(printhdr, dispflag)); 1072 linesout++; 1073 nentities_found++; 1074 } else { 1075 if (ent->e_type == ENTYPE_MNTPT) { 1076 (void) printf(gettext( 1077 "<<mount point no longer " 1078 "available: %s>>\n"), ent->e_name); 1079 } else if (ent->e_type == ENTYPE_FSTYPE) { 1080 (void) printf(gettext( 1081 "<<file system module no longer " 1082 "loaded: %s>>\n"), ent->e_name); 1083 } else { 1084 (void) printf(gettext( 1085 "<<%s no longer available>>\n"), 1086 ent->e_name); 1087 } 1088 /* Disable this so it doesn't print again */ 1089 ent->e_ksname[0] = 0; 1090 } 1091 printhdr = 0; /* Always shut this off */ 1092 } 1093 BUMP_INDEX(); /* Bump the previous/current indices */ 1094 1095 /* 1096 * If the entities we were observing are no longer there 1097 * (file system modules unloaded, file systems unmounted) 1098 * then we're done. 1099 */ 1100 if (nentities_found == 0) 1101 break; 1102 } 1103 1104 return (0); 1105 } 1106