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
usage()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
dispflag_policy(int printhdr,int dispflag)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
dflt_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
io_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
vm_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
attr_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
naming_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
vop_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
get_vopstats(kstat_ctl_t * kc,char * ksname,vopstats_t * vsp,kstat_t ** kspp)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
is_exception(char * fsname)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
build_fstype_list(char *** fstypep)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
parse_operands(int argc,char ** argv,int optind,long * interval,long * count,entity_t ** entityp)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 char *endptr;
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 errno = 0;
543 out_of_range = 0;
544 *interval = strtol(argv[optind], &endptr, 10);
545 if (*endptr && !isdigit((int)*endptr)) {
546 /* Operand was not a number */
547 (*entityp)[nentities++].e_name = strdup(argv[optind]);
548 continue;
549 } else if (errno == ERANGE || *interval <= 0 ||
550 *interval > MAXLONG) {
551 /* Operand was a number, just out of range */
552 out_of_range++;
553 }
554
555 /*
556 * The last operand we saw was a number. If it happened to
557 * be the last operand, then it is the interval...
558 */
559 if ((argc - optind) == 1) {
560 /* ...but we need to check the range. */
561 if (out_of_range) {
562 (void) fprintf(stderr, gettext(
563 "interval must be between 1 and "
564 "%ld (inclusive)\n"), MAXLONG);
565 return (-1);
566 } else {
567 /*
568 * The value of the interval is valid. Set
569 * count to something really big so it goes
570 * virtually forever.
571 */
572 *count = MAXLONG;
573 break;
574 }
575 }
576
577 /*
578 * At this point, we *might* have the interval, but if the
579 * next operand isn't a number, then we don't have either
580 * the interval nor the count. Both must be set to the
581 * defaults. In that case, both the current and the previous
582 * operands are stat-able entities.
583 */
584 errno = 0;
585 *count = strtol(argv[optind + 1], &endptr, 10);
586 if (*endptr && !isdigit((int)*endptr)) {
587 /*
588 * Faked out! The last operand wasn't a number so
589 * the current and previous operands should be
590 * stat-able entities. We also need to reset interval.
591 */
592 *interval = 0;
593 (*entityp)[nentities++].e_name = strdup(argv[optind++]);
594 (*entityp)[nentities++].e_name = strdup(argv[optind++]);
595 } else if (out_of_range || errno == ERANGE || *count <= 0) {
596 (void) fprintf(stderr, gettext(
597 "Both interval and count must be between 1 "
598 "and %ld (inclusive)\n"), MAXLONG);
599 return (-1);
600 }
601 break; /* Done! */
602 }
603 return (nentities);
604 }
605
606 /*
607 * set_mntpt() looks at the entity's name (e_name) and finds its
608 * mountpoint. To do this, we need to build a list of mountpoints
609 * from /etc/mnttab. We only need to do this once and we don't do it
610 * if we don't need to look at any mountpoints.
611 * Returns 0 on success, non-zero if it couldn't find a mount-point.
612 */
613 int
set_mntpt(entity_t * ep)614 set_mntpt(entity_t *ep)
615 {
616 static struct mnt {
617 struct mnt *m_next;
618 char *m_mntpt;
619 ulong_t m_fsid; /* From statvfs(), set only as needed */
620 } *mnt_list = NULL; /* Linked list of mount-points */
621 struct mnt *mntp;
622 struct statvfs64 statvfsbuf;
623 char *original_name = ep->e_name;
624 char path[PATH_MAX];
625
626 if (original_name == NULL) /* Shouldn't happen */
627 return (1);
628
629 /* We only set up mnt_list the first time this is called */
630 if (mnt_list == NULL) {
631 FILE *fp;
632 struct mnttab mnttab;
633
634 if ((fp = fopen(MNTTAB, "r")) == NULL) {
635 perror(MNTTAB);
636 return (1);
637 }
638 resetmnttab(fp);
639 /*
640 * We insert at the front of the list so that when we
641 * search entries we'll have the last mounted entries
642 * first in the list so that we can match the longest
643 * mountpoint.
644 */
645 while (getmntent(fp, &mnttab) == 0) {
646 if ((mntp = malloc(sizeof (*mntp))) == NULL) {
647 perror("malloc() mount list");
648 return (1);
649 }
650 mntp->m_mntpt = strdup(mnttab.mnt_mountp);
651 mntp->m_next = mnt_list;
652 mnt_list = mntp;
653 }
654 (void) fclose(fp);
655 }
656
657 if (realpath(original_name, path) == NULL) {
658 perror(original_name);
659 return (1);
660 }
661
662 /*
663 * Now that we have the path, walk through the mnt_list and
664 * look for the first (best) match.
665 */
666 for (mntp = mnt_list; mntp; mntp = mntp->m_next) {
667 if (strncmp(path, mntp->m_mntpt, strlen(mntp->m_mntpt)) == 0) {
668 if (mntp->m_fsid == 0) {
669 if (statvfs64(mntp->m_mntpt, &statvfsbuf)) {
670 /* Can't statvfs so no match */
671 continue;
672 } else {
673 mntp->m_fsid = statvfsbuf.f_fsid;
674 }
675 }
676
677 if (ep->e_fsid != mntp->m_fsid) {
678 /* No match - Move on */
679 continue;
680 }
681
682 break;
683 }
684 }
685
686 if (mntp == NULL) {
687 (void) fprintf(stderr, gettext(
688 "Can't find mount point for %s\n"), path);
689 return (1);
690 }
691
692 ep->e_name = strdup(mntp->m_mntpt);
693 free(original_name);
694 return (0);
695 }
696
697 /*
698 * We have an array of entities that are potentially stat-able. Using
699 * the name (e_name) of the entity, attempt to construct a ksname suitable
700 * for use by kstat_lookup(3kstat) and fill it into the e_ksname member.
701 *
702 * We check the e_name against the list of file system types. If there is
703 * no match then test to see if the path is valid. If the path is valid,
704 * then determine the mountpoint.
705 */
706 void
set_ksnames(entity_t * entities,int nentities,char ** fstypes,int nfstypes)707 set_ksnames(entity_t *entities, int nentities, char **fstypes, int nfstypes)
708 {
709 int i, j;
710 struct statvfs64 statvfsbuf;
711
712 for (i = 0; i < nentities; i++) {
713 entity_t *ep = &entities[i];
714
715 /* Check the name against the list of fstypes */
716 for (j = 1; j < nfstypes; j++) {
717 if (fstypes[j] && ep->e_name &&
718 strcmp(ep->e_name, fstypes[j]) == 0) {
719 /* It's a file system type */
720 ep->e_type = ENTYPE_FSTYPE;
721 (void) snprintf(ep->e_ksname, KSTAT_STRLEN,
722 "%s%s", VOPSTATS_STR, ep->e_name);
723 /* Now allocate the vopstats array */
724 ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t));
725 if (entities[i].e_vs == NULL) {
726 perror("calloc() fstype vopstats");
727 exit(1);
728 }
729 break;
730 }
731 }
732 if (j < nfstypes) /* Found it! */
733 continue;
734
735 /*
736 * If the entity in the exception list of fstypes, then
737 * null out the entry so it isn't displayed and move along.
738 */
739 if (is_exception(ep->e_name)) {
740 ep->e_ksname[0] = 0;
741 continue;
742 }
743
744 /* If we didn't find it, see if it's a path */
745 if (ep->e_name == NULL || statvfs64(ep->e_name, &statvfsbuf)) {
746 /* Error - Make sure the entry is nulled out */
747 ep->e_ksname[0] = 0;
748 continue;
749 }
750 (void) snprintf(ep->e_ksname, KSTAT_STRLEN, "%s%lx",
751 VOPSTATS_STR, statvfsbuf.f_fsid);
752 ep->e_fsid = statvfsbuf.f_fsid;
753 if (set_mntpt(ep)) {
754 (void) fprintf(stderr,
755 gettext("Can't determine type of \"%s\"\n"),
756 ep->e_name ? ep->e_name : gettext("<NULL>"));
757 } else {
758 ep->e_type = ENTYPE_MNTPT;
759 }
760
761 /* Now allocate the vopstats array */
762 ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t));
763 if (entities[i].e_vs == NULL) {
764 perror("calloc() vopstats array");
765 exit(1);
766 }
767 }
768 }
769
770 /*
771 * The idea is that 'dspfunc' should only be modified from the default
772 * once since the display options are mutually exclusive. If 'dspfunc'
773 * only contains the default display function, then all is good and we
774 * can set it to the new display function. Otherwise, bail.
775 */
776 void
set_dispfunc(void (** dspfunc)(char *,vopstats_t *,vopstats_t *,int),void (* newfunc)(char *,vopstats_t *,vopstats_t *,int))777 set_dispfunc(
778 void (**dspfunc)(char *, vopstats_t *, vopstats_t *, int),
779 void (*newfunc)(char *, vopstats_t *, vopstats_t *, int))
780 {
781 if (*dspfunc != dflt_display) {
782 (void) fprintf(stderr, gettext(
783 "%s: Display options -{a|f|i|n|v} are mutually exclusive\n"),
784 cmdname);
785 usage();
786 }
787 *dspfunc = newfunc;
788 }
789
790 int
main(int argc,char * argv[])791 main(int argc, char *argv[])
792 {
793 int c;
794 int i, j; /* Generic counters */
795 int nentities_found;
796 int linesout = 0; /* Keeps track of lines printed */
797 int printhdr = 0; /* Print a header? 0 = no, 1 = yes */
798 int nfstypes; /* Number of fstypes */
799 int dispflag = 0; /* Flags for display control */
800 long count = 0; /* Number of iterations for display */
801 int forever; /* Run forever */
802 long interval = 0;
803 boolean_t fstypes_only = B_FALSE; /* Display fstypes only */
804 char **fstypes; /* Array of names of all fstypes */
805 int nentities; /* Number of stat-able entities */
806 entity_t *entities; /* Array of stat-able entities */
807 kstat_ctl_t *kc;
808 void (*dfunc)(char *, vopstats_t *, vopstats_t *, int) = dflt_display;
809 hrtime_t start_n; /* Start time */
810 hrtime_t period_n; /* Interval in nanoseconds */
811
812 extern int optind;
813
814 (void) setlocale(LC_ALL, "");
815 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
816 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
817 #endif
818 (void) textdomain(TEXT_DOMAIN);
819
820 /* Don't let buffering interfere with piped output. */
821 (void) setvbuf(stdout, NULL, _IOLBF, 0);
822
823 cmdname = argv[0];
824 while ((c = getopt(argc, argv, OPTIONS)) != EOF) {
825 switch (c) {
826
827 default:
828 usage();
829 break;
830
831 case 'F': /* Only display available FStypes */
832 fstypes_only = B_TRUE;
833 break;
834
835 #if PARSABLE_OUTPUT
836 case 'P': /* Parsable output */
837 dispflag |= DISP_RAW;
838 break;
839 #endif /* PARSABLE_OUTPUT */
840
841 case 'T': /* Timestamp */
842 if (optarg) {
843 if (strcmp(optarg, "u") == 0) {
844 timestamp_fmt = UDATE;
845 } else if (strcmp(optarg, "d") == 0) {
846 timestamp_fmt = DDATE;
847 }
848 }
849
850 /* If it was never set properly... */
851 if (timestamp_fmt == NODATE) {
852 (void) fprintf(stderr, gettext("%s: -T option "
853 "requires either 'u' or 'd'\n"), cmdname);
854 usage();
855 }
856 break;
857
858 case 'a':
859 set_dispfunc(&dfunc, attr_display);
860 break;
861
862 case 'f':
863 set_dispfunc(&dfunc, vop_display);
864 break;
865
866 case 'i':
867 set_dispfunc(&dfunc, io_display);
868 break;
869
870 case 'n':
871 set_dispfunc(&dfunc, naming_display);
872 break;
873
874 case 'v':
875 set_dispfunc(&dfunc, vm_display);
876 break;
877 }
878 }
879
880 #if PARSABLE_OUTPUT
881 if ((dispflag & DISP_RAW) && (timestamp_fmt != NODATE)) {
882 (void) fprintf(stderr, gettext(
883 "-P and -T options are mutually exclusive\n"));
884 usage();
885 }
886 #endif /* PARSABLE_OUTPUT */
887
888 /* Gather the list of filesystem types */
889 if ((nfstypes = build_fstype_list(&fstypes)) == 0) {
890 (void) fprintf(stderr,
891 gettext("Can't build list of fstypes\n"));
892 exit(1);
893 }
894
895 nentities = parse_operands(
896 argc, argv, optind, &interval, &count, &entities);
897 forever = count == MAXLONG;
898 period_n = (hrtime_t)interval * NANOSEC;
899
900 if (nentities == -1) /* Set of operands didn't parse properly */
901 usage();
902
903 if ((nentities == 0) && (fstypes_only == B_FALSE)) {
904 (void) fprintf(stderr, gettext(
905 "Must specify -F or at least one fstype or mount point\n"));
906 usage();
907 }
908
909 if ((nentities > 0) && (fstypes_only == B_TRUE)) {
910 (void) fprintf(stderr, gettext(
911 "Cannot use -F with fstypes or mount points\n"));
912 usage();
913 }
914
915 /*
916 * If we had no operands (except for interval/count) and we
917 * requested FStypes only (-F), then fill in the entities[]
918 * array with all available fstypes.
919 */
920 if ((nentities == 0) && (fstypes_only == B_TRUE)) {
921 if ((entities = calloc(nfstypes, sizeof (entity_t))) == NULL) {
922 perror("calloc() fstype stats");
923 exit(1);
924 }
925
926 for (i = 1; i < nfstypes; i++) {
927 if (fstypes[i]) {
928 entities[nentities].e_name = strdup(fstypes[i]);
929 nentities++;
930 }
931 }
932 }
933
934 set_ksnames(entities, nentities, fstypes, nfstypes);
935
936 if ((kc = kstat_open()) == NULL) {
937 perror("kstat_open");
938 exit(1);
939 }
940
941 /* Set start time */
942 start_n = gethrtime();
943
944 /* Initial timestamp */
945 if (timestamp_fmt != NODATE) {
946 print_timestamp(timestamp_fmt);
947 linesout++;
948 }
949
950 /*
951 * The following loop walks through the entities[] list to "prime
952 * the pump"
953 */
954 for (j = 0, printhdr = 1; j < nentities; j++) {
955 entity_t *ent = &entities[j];
956 vopstats_t *vsp = &ent->e_vs[CUR_INDEX];
957 kstat_t *ksp = NULL;
958
959 if (get_vopstats(kc, ent->e_ksname, vsp, &ksp) == 0) {
960 (*dfunc)(ent->e_name, NULL, vsp,
961 dispflag_policy(printhdr, dispflag));
962 linesout++;
963 } else {
964 /*
965 * If we can't find it the first time through, then
966 * get rid of it.
967 */
968 entities[j].e_ksname[0] = 0;
969
970 /*
971 * If we're only displaying FStypes (-F) then don't
972 * complain about any file systems that might not
973 * be loaded. Otherwise, let the user know that
974 * they chose poorly.
975 */
976 if (fstypes_only == B_FALSE) {
977 (void) fprintf(stderr, gettext(
978 "No statistics available for %s\n"),
979 entities[j].e_name);
980 }
981 }
982 printhdr = 0;
983 }
984
985 if (count > 1)
986 /* Set up signal handler for SIGCONT */
987 if (signal(SIGCONT, cont_handler) == SIG_ERR)
988 fail(1, "signal failed");
989
990
991 BUMP_INDEX(); /* Swap the previous/current indices */
992 i = 1;
993 while (forever || i++ <= count) {
994 /*
995 * No telling how many lines will be printed in any interval.
996 * There should be a minimum of HEADERLINES between any
997 * header. If we exceed that, no big deal.
998 */
999 if (linesout > HEADERLINES) {
1000 linesout = 0;
1001 printhdr = 1;
1002 }
1003 /* Have a kip */
1004 sleep_until(&start_n, period_n, forever, &caught_cont);
1005
1006 if (timestamp_fmt != NODATE) {
1007 print_timestamp(timestamp_fmt);
1008 linesout++;
1009 }
1010
1011 for (j = 0, nentities_found = 0; j < nentities; j++) {
1012 entity_t *ent = &entities[j];
1013
1014 /*
1015 * If this entry has been cleared, don't attempt
1016 * to process it.
1017 */
1018 if (ent->e_ksname[0] == 0) {
1019 continue;
1020 }
1021
1022 if (get_vopstats(kc, ent->e_ksname,
1023 &ent->e_vs[CUR_INDEX], NULL) == 0) {
1024 (*dfunc)(ent->e_name, &ent->e_vs[PREV_INDEX],
1025 &ent->e_vs[CUR_INDEX],
1026 dispflag_policy(printhdr, dispflag));
1027 linesout++;
1028 nentities_found++;
1029 } else {
1030 if (ent->e_type == ENTYPE_MNTPT) {
1031 (void) printf(gettext(
1032 "<<mount point no longer "
1033 "available: %s>>\n"), ent->e_name);
1034 } else if (ent->e_type == ENTYPE_FSTYPE) {
1035 (void) printf(gettext(
1036 "<<file system module no longer "
1037 "loaded: %s>>\n"), ent->e_name);
1038 } else {
1039 (void) printf(gettext(
1040 "<<%s no longer available>>\n"),
1041 ent->e_name);
1042 }
1043 /* Disable this so it doesn't print again */
1044 ent->e_ksname[0] = 0;
1045 }
1046 printhdr = 0; /* Always shut this off */
1047 }
1048 BUMP_INDEX(); /* Bump the previous/current indices */
1049
1050 /*
1051 * If the entities we were observing are no longer there
1052 * (file system modules unloaded, file systems unmounted)
1053 * then we're done.
1054 */
1055 if (nentities_found == 0)
1056 break;
1057 }
1058
1059 return (0);
1060 }
1061