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