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