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
usage()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
dispflag_policy(int printhdr,int dispflag)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
dflt_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
io_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
vm_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
attr_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
naming_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
vop_display(char * name,vopstats_t * oldvsp,vopstats_t * newvsp,int dispflag)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
get_vopstats(kstat_ctl_t * kc,char * ksname,vopstats_t * vsp,kstat_t ** kspp)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
is_exception(char * fsname)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
build_fstype_list(char *** fstypep)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
parse_operands(int argc,char ** argv,int optind,long * interval,long * count,entity_t ** entityp)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
set_mntpt(entity_t * ep)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
set_ksnames(entity_t * entities,int nentities,char ** fstypes,int nfstypes)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
set_dispfunc(void (** dspfunc)(char *,vopstats_t *,vopstats_t *,int),void (* newfunc)(char *,vopstats_t *,vopstats_t *,int))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
main(int argc,char * argv[])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