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