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