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