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