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