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