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 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
set_mntpt(entity_t * ep)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
set_ksnames(entity_t * entities,int nentities,char ** fstypes,int nfstypes)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
set_dispfunc(void (** dspfunc)(char *,vopstats_t *,vopstats_t *,int),void (* newfunc)(char *,vopstats_t *,vopstats_t *,int))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
main(int argc,char * argv[])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