OpenSolaris_b135/cmd/stat/fsstat/fsstat.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <kstat.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <time.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <sys/statvfs.h>
#include <sys/fstyp.h>
#include <sys/fsid.h>
#include <sys/mnttab.h>
#include <values.h>
#include <poll.h>
#include <ctype.h>
#include <libintl.h>
#include <locale.h>
#include <signal.h>

#include "statcommon.h"

/*
 * For now, parsable output is turned off.  Once we gather feedback and
 * stablize the output format, we'll turn it back on.  This prevents
 * the situation where users build tools which depend on a specific
 * format before we declare the output stable.
 */
#define	PARSABLE_OUTPUT	0

#if PARSABLE_OUTPUT
#define	OPTIONS	"FPT:afginv"
#else
#define	OPTIONS	"FT:afginv"
#endif

/* Time stamp values */
#define	NODATE	0	/* Default:  No time stamp */
#define	DDATE	1	/* Standard date format */
#define	UDATE	2	/* Internal representation of Unix time */

#define	RETRY_DELAY	250	/* Timeout for poll() */
#define	HEADERLINES	12	/* Number of lines between display headers */

#define	LBUFSZ		64	/* Generic size for local buffer */

/*
 * The following are used for the nicenum() function
 */
#define	KILO_VAL	1024
#define	ONE_INDEX	3

#define	NENTITY_INIT	1	/* Initial number of entities to allocate */

/*
 * We need to have a mechanism for an old/previous and new/current vopstat
 * structure.  We only need two per entity and we can swap between them.
 */
#define	VS_SIZE	2	/* Size of vopstat array */
#define	CUR_INDEX	(vs_i)
#define	PREV_INDEX	((vs_i == 0) ? 1 : 0)	/* Opposite of CUR_INDEX */
#define	BUMP_INDEX()	vs_i = ((vs_i == 0) ? 1 : 0)

/*
 * An "entity" is anything we're collecting statistics on, it could
 * be a mountpoint or an FS-type.
 * e_name is the name of the entity (e.g. mount point or FS-type)
 * e_ksname is the name of the associated kstat
 * e_vs is an array of vopstats.  This is used to keep track of "previous"
 * and "current" vopstats.
 */
typedef struct entity {
	char		*e_name;		/* name of entity */
	vopstats_t	*e_vs;			/* Array of vopstats */
	ulong_t		e_fsid;			/* fsid for ENTYPE_MNTPT only */
	int		e_type;			/* type of entity */
	char		e_ksname[KSTAT_STRLEN];	/* kstat name */
} entity_t;

/* Types of entities (e_type) */
#define	ENTYPE_UNKNOWN	0	/* UNKNOWN must be zero since we calloc() */
#define	ENTYPE_FSTYPE	1
#define	ENTYPE_MNTPT	2

/* If more sub-one units are added, make sure to adjust ONE_INDEX above */
static char units[] = "num KMGTPE";

char		*cmdname;	/* name of this command */
int		caught_cont = 0;	/* have caught a SIGCONT */

static uint_t	timestamp_fmt = NODATE;	/* print timestamp with stats */

static int	vs_i = 0;	/* Index of current vs[] slot */

static void
usage()
{
	(void) fprintf(stderr, gettext(
	    "Usage: %s [-a|f|i|n|v] [-T d|u] {-F | {fstype | fspath}...} "
	    "[interval [count]]\n"), cmdname);
	exit(2);
}

/*
 * Given a 64-bit number and a starting unit (e.g., n - nanoseconds),
 * convert the number to a 5-character representation including any
 * decimal point and single-character unit.  Put that representation
 * into the array "buf" (which had better be big enough).
 */
char *
nicenum(uint64_t num, char unit, char *buf)
{
	uint64_t n = num;
	int unit_index;
	int index;
	char u;

	/* If the user passed in a NUL/zero unit, use the blank value for 1 */
	if (unit == '\0')
		unit = ' ';

	unit_index = 0;
	while (units[unit_index] != unit) {
		unit_index++;
		if (unit_index > sizeof (units) - 1) {
			(void) sprintf(buf, "??");
			return (buf);
		}
	}

	index = 0;
	while (n >= KILO_VAL) {
		n = (n + (KILO_VAL / 2)) / KILO_VAL; /* Round up or down */
		index++;
		unit_index++;
	}

	if (unit_index >= sizeof (units) - 1) {
		(void) sprintf(buf, "??");
		return (buf);
	}

	u = units[unit_index];

	if (unit_index == ONE_INDEX) {
		(void) sprintf(buf, "%llu", (u_longlong_t)n);
	} else if (n < 10 && (num & (num - 1)) != 0) {
		(void) sprintf(buf, "%.2f%c",
		    (double)num / (1ULL << 10 * index), u);
	} else if (n < 100 && (num & (num - 1)) != 0) {
		(void) sprintf(buf, "%.1f%c",
		    (double)num / (1ULL << 10 * index), u);
	} else {
		(void) sprintf(buf, "%llu%c", (u_longlong_t)n, u);
	}

	return (buf);
}


#define	RAWVAL(ptr, member) ((ptr)->member.value.ui64)
#define	DELTA(member)	\
	(newvsp->member.value.ui64 - (oldvsp ? oldvsp->member.value.ui64 : 0))

#define	PRINTSTAT(isnice, nicestring, rawstring, rawval, unit, buf)	\
	(isnice) ?	 						\
		(void) printf((nicestring), nicenum(rawval, unit, buf))	\
	:								\
		(void) printf((rawstring), (rawval))

/* Values for display flag */
#define	DISP_HEADER	0x1
#define	DISP_RAW	0x2

/*
 * The policy for dealing with multiple flags is dealt with here.
 * Currently, if we are displaying raw output, then don't allow
 * headers to be printed.
 */
int
dispflag_policy(int printhdr, int dispflag)
{
	/* If we're not displaying raw output, then allow headers to print */
	if ((dispflag & DISP_RAW) == 0) {
		if (printhdr) {
			dispflag |= DISP_HEADER;
		}
	}

	return (dispflag);
}

static void
dflt_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
{
	int		niceflag = ((dispflag & DISP_RAW) == 0);
	longlong_t	nnewfile;
	longlong_t	nnamerm;
	longlong_t	nnamechg;
	longlong_t	nattrret;
	longlong_t	nattrchg;
	longlong_t	nlookup;
	longlong_t	nreaddir;
	longlong_t	ndataread;
	longlong_t	ndatawrite;
	longlong_t	readthruput;
	longlong_t	writethruput;
	char		buf[LBUFSZ];

	nnewfile = DELTA(ncreate) + DELTA(nmkdir) + DELTA(nsymlink);
	nnamerm = DELTA(nremove) + DELTA(nrmdir);
	nnamechg = DELTA(nrename) + DELTA(nlink) + DELTA(nsymlink);
	nattrret = DELTA(ngetattr) + DELTA(naccess) +
	    DELTA(ngetsecattr) + DELTA(nfid);
	nattrchg = DELTA(nsetattr) + DELTA(nsetsecattr) + DELTA(nspace);
	nlookup = DELTA(nlookup);
	nreaddir = DELTA(nreaddir);
	ndataread = DELTA(nread);
	ndatawrite = DELTA(nwrite);
	readthruput = DELTA(read_bytes);
	writethruput = DELTA(write_bytes);

	if (dispflag & DISP_HEADER) {
		(void) printf(
" new  name   name  attr  attr lookup rddir  read read  write write\n"
" file remov  chng   get   set    ops   ops   ops bytes   ops bytes\n");
	}

	PRINTSTAT(niceflag, "%5s ", "%lld:", nnewfile, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", nnamerm, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", nnamechg, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", nattrret, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", nattrchg, ' ', buf);
	PRINTSTAT(niceflag, " %5s ", "%lld:", nlookup, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", nreaddir, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", ndataread, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", readthruput, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", ndatawrite, ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", writethruput, ' ', buf);
	(void) printf("%s\n", name);
}

static void
io_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
{
	int		niceflag = ((dispflag & DISP_RAW) == 0);
	char		buf[LBUFSZ];

	if (dispflag & DISP_HEADER) {
		(void) printf(
" read read  write write rddir rddir rwlock rwulock\n"
"  ops bytes   ops bytes   ops bytes    ops     ops\n");
	}

	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nread), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(read_bytes), ' ', buf);

	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nwrite), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(write_bytes), ' ', buf);

	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(readdir_bytes), ' ', buf);

	PRINTSTAT(niceflag, " %5s   ", "%lld:", DELTA(nrwlock), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrwunlock), ' ', buf);

	(void) printf("%s\n", name);
}

static void
vm_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
{
	int		niceflag = ((dispflag & DISP_RAW) == 0);
	char		buf[LBUFSZ];

	if (dispflag & DISP_HEADER) {
		(void) printf("  map addmap delmap getpag putpag pagio\n");
	}

	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmap), ' ', buf);
	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(naddmap), ' ', buf);
	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ndelmap), ' ', buf);
	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetpage), ' ', buf);
	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nputpage), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(npageio), ' ', buf);
	(void) printf("%s\n", name);
}

static void
attr_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
{
	int		niceflag = ((dispflag & DISP_RAW) == 0);
	char		buf[LBUFSZ];

	if (dispflag & DISP_HEADER) {
		(void) printf("getattr setattr getsec  setsec\n");
	}

	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetattr), ' ', buf);
	PRINTSTAT(niceflag, "  %5s ", "%lld:", DELTA(nsetattr), ' ', buf);
	PRINTSTAT(niceflag, "  %5s ", "%lld:", DELTA(ngetsecattr), ' ', buf);
	PRINTSTAT(niceflag, "  %5s ", "%lld:", DELTA(nsetsecattr), ' ', buf);

	(void) printf("%s\n", name);
}

static void
naming_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
{
	int		niceflag = ((dispflag & DISP_RAW) == 0);
	char		buf[LBUFSZ];

	if (dispflag & DISP_HEADER) {
		(void) printf(
	"lookup creat remov  link renam mkdir rmdir rddir symlnk rdlnk\n");
	}

	PRINTSTAT(niceflag, "%5s  ", "%lld:", DELTA(nlookup), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(ncreate), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nremove), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlink), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrename), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmkdir), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrmdir), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), ' ', buf);
	PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsymlink), ' ', buf);
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreadlink), ' ', buf);
	(void) printf("%s\n", name);
}


#define	PRINT_VOPSTAT_CMN(niceflag, vop)				\
	if (niceflag)							\
		(void) printf("%10s ", #vop);				\
	PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(n##vop), ' ', buf);

#define	PRINT_VOPSTAT(niceflag, vop) 					\
	PRINT_VOPSTAT_CMN(niceflag, vop);				\
	if (niceflag)							\
		(void) printf("\n");

#define	PRINT_VOPSTAT_IO(niceflag, vop)					\
	PRINT_VOPSTAT_CMN(niceflag, vop);				\
	PRINTSTAT(niceflag, " %5s\n", "%lld:",				\
		DELTA(vop##_bytes), ' ', buf);

static void
vop_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag)
{
	int		niceflag = ((dispflag & DISP_RAW) == 0);
	char		buf[LBUFSZ];

	if (niceflag) {
		(void) printf("%s\n", name);
		(void) printf(" operation  #ops  bytes\n");
	}

	PRINT_VOPSTAT(niceflag, open);
	PRINT_VOPSTAT(niceflag, close);
	PRINT_VOPSTAT_IO(niceflag, read);
	PRINT_VOPSTAT_IO(niceflag, write);
	PRINT_VOPSTAT(niceflag, ioctl);
	PRINT_VOPSTAT(niceflag, setfl);
	PRINT_VOPSTAT(niceflag, getattr);
	PRINT_VOPSTAT(niceflag, setattr);
	PRINT_VOPSTAT(niceflag, access);
	PRINT_VOPSTAT(niceflag, lookup);
	PRINT_VOPSTAT(niceflag, create);
	PRINT_VOPSTAT(niceflag, remove);
	PRINT_VOPSTAT(niceflag, link);
	PRINT_VOPSTAT(niceflag, rename);
	PRINT_VOPSTAT(niceflag, mkdir);
	PRINT_VOPSTAT(niceflag, rmdir);
	PRINT_VOPSTAT_IO(niceflag, readdir);
	PRINT_VOPSTAT(niceflag, symlink);
	PRINT_VOPSTAT(niceflag, readlink);
	PRINT_VOPSTAT(niceflag, fsync);
	PRINT_VOPSTAT(niceflag, inactive);
	PRINT_VOPSTAT(niceflag, fid);
	PRINT_VOPSTAT(niceflag, rwlock);
	PRINT_VOPSTAT(niceflag, rwunlock);
	PRINT_VOPSTAT(niceflag, seek);
	PRINT_VOPSTAT(niceflag, cmp);
	PRINT_VOPSTAT(niceflag, frlock);
	PRINT_VOPSTAT(niceflag, space);
	PRINT_VOPSTAT(niceflag, realvp);
	PRINT_VOPSTAT(niceflag, getpage);
	PRINT_VOPSTAT(niceflag, putpage);
	PRINT_VOPSTAT(niceflag, map);
	PRINT_VOPSTAT(niceflag, addmap);
	PRINT_VOPSTAT(niceflag, delmap);
	PRINT_VOPSTAT(niceflag, poll);
	PRINT_VOPSTAT(niceflag, dump);
	PRINT_VOPSTAT(niceflag, pathconf);
	PRINT_VOPSTAT(niceflag, pageio);
	PRINT_VOPSTAT(niceflag, dumpctl);
	PRINT_VOPSTAT(niceflag, dispose);
	PRINT_VOPSTAT(niceflag, getsecattr);
	PRINT_VOPSTAT(niceflag, setsecattr);
	PRINT_VOPSTAT(niceflag, shrlock);
	PRINT_VOPSTAT(niceflag, vnevent);
	PRINT_VOPSTAT(niceflag, reqzcbuf);
	PRINT_VOPSTAT(niceflag, retzcbuf);

	if (niceflag) {
		/* Make it easier on the eyes */
		(void) printf("\n");
	} else {
		(void) printf("%s\n", name);
	}
}


/*
 * Retrieve the vopstats.  If kspp (pointer to kstat_t pointer) is non-NULL,
 * then pass it back to the caller.
 *
 * Returns 0 on success, non-zero on failure.
 */
int
get_vopstats(kstat_ctl_t *kc, char *ksname, vopstats_t *vsp, kstat_t **kspp)
{
	kstat_t		*ksp;

	if (ksname == NULL || *ksname == 0)
		return (1);

	errno = 0;
	/* wait for a possibly up-to-date chain */
	while (kstat_chain_update(kc) == -1) {
		if (errno == EAGAIN) {
			errno = 0;
			(void) poll(NULL, 0, RETRY_DELAY);
			continue;
		}
		perror("kstat_chain_update");
		exit(1);
	}

	if ((ksp = kstat_lookup(kc, NULL, -1, ksname)) == NULL) {
		return (1);
	}

	if (kstat_read(kc, ksp, vsp) == -1) {
		return (1);
	}

	if (kspp)
		*kspp = ksp;

	return (0);
}

/*
 * Given a file system type name, determine if it's part of the
 * exception list of file systems that are not to be displayed.
 */
int
is_exception(char *fsname)
{
	char **xlp;	/* Pointer into the exception list */

	static char *exception_list[] = {
		"specfs",
		"fifofs",
		"fd",
		"swapfs",
		"ctfs",
		"objfs",
		"nfsdyn",
		NULL
	};

	for (xlp = &exception_list[0]; *xlp != NULL; xlp++) {
		if (strcmp(fsname, *xlp) == 0)
			return (1);
	}

	return (0);
}

/*
 * Plain and simple, build an array of names for fstypes
 * Returns 0, if it encounters a problem.
 */
int
build_fstype_list(char ***fstypep)
{
	int	i;
	int	nfstype;
	char	buf[FSTYPSZ + 1];

	if ((nfstype = sysfs(GETNFSTYP)) < 0) {
		perror("sysfs(GETNFSTYP)");
		return (0);
	}

	if ((*fstypep = calloc(nfstype, sizeof (char *))) == NULL) {
		perror("calloc() fstypes");
		return (0);
	}

	for (i = 1; i < nfstype; i++) {
		if (sysfs(GETFSTYP, i, buf) < 0) {
			perror("sysfs(GETFSTYP)");
			return (0);
		}

		if (buf[0] == 0)
			continue;

		/* If this is part of the exception list, move on */
		if (is_exception(buf))
			continue;

		if (((*fstypep)[i] = strdup(buf)) == NULL) {
			perror("strdup() fstype name");
			return (0);
		}
	}

	return (i);
}

/*
 * After we're done with getopts(), process the rest of the
 * operands.  We have three cases and this is the priority:
 *
 * 1) [ operand... ] interval count
 * 2) [ operand... ] interval
 * 3) [ operand... ]
 *
 * The trick is that any of the operands might start with a number or even
 * be made up exclusively of numbers (and we have to handle negative numbers
 * in case a user/script gets out of line).  If we find two operands at the
 * end of the list then we claim case 1.  If we find only one operand at the
 * end made up only of number, then we claim case 2.  Otherwise, case 3.
 * BTW, argc, argv don't change.
 */
int
parse_operands(
	int		argc,
	char		**argv,
	int		optind,
	long		*interval,
	long		*count,
	entity_t	**entityp)	/* Array of stat-able entities */
{
	int	nentities = 0;	/* Number of entities found */
	int	out_of_range;	/* Set if 2nd-to-last operand out-of-range */

	if (argc == optind)
		return (nentities);	/* None found, returns 0 */
	/*
	 * We know exactly what the maximum number of entities is going
	 * to be:  argc - optind
	 */
	if ((*entityp = calloc((argc - optind), sizeof (entity_t))) == NULL) {
		perror("calloc() entities");
		return (-1);
	}

	for (/* void */; argc > optind; optind++) {
		char	*endptr;

		/* If we have more than two operands left to process */
		if ((argc - optind) > 2) {
			(*entityp)[nentities++].e_name = strdup(argv[optind]);
			continue;
		}

		/* If we're here, then we only have one or two operands left */
		errno = 0;
		out_of_range = 0;
		*interval = strtol(argv[optind], &endptr, 10);
		if (*endptr && !isdigit((int)*endptr)) {
			/* Operand was not a number */
			(*entityp)[nentities++].e_name = strdup(argv[optind]);
			continue;
		} else if (errno == ERANGE || *interval <= 0 ||
		    *interval > MAXLONG) {
			/* Operand was a number, just out of range */
			out_of_range++;
		}

		/*
		 * The last operand we saw was a number.  If it happened to
		 * be the last operand, then it is the interval...
		 */
		if ((argc - optind) == 1) {
			/* ...but we need to check the range. */
			if (out_of_range) {
				(void) fprintf(stderr, gettext(
				    "interval must be between 1 and "
				    "%ld (inclusive)\n"), MAXLONG);
				return (-1);
			} else {
				/*
				 * The value of the interval is valid. Set
				 * count to something really big so it goes
				 * virtually forever.
				 */
				*count = MAXLONG;
				break;
			}
		}

		/*
		 * At this point, we *might* have the interval, but if the
		 * next operand isn't a number, then we don't have either
		 * the interval nor the count.  Both must be set to the
		 * defaults.  In that case, both the current and the previous
		 * operands are stat-able entities.
		 */
		errno = 0;
		*count = strtol(argv[optind + 1], &endptr, 10);
		if (*endptr && !isdigit((int)*endptr)) {
			/*
			 * Faked out!  The last operand wasn't a number so
			 * the current and previous operands should be
			 * stat-able entities. We also need to reset interval.
			 */
			*interval = 0;
			(*entityp)[nentities++].e_name = strdup(argv[optind++]);
			(*entityp)[nentities++].e_name = strdup(argv[optind++]);
		} else if (out_of_range || errno == ERANGE || *count <= 0) {
			(void) fprintf(stderr, gettext(
			    "Both interval and count must be between 1 "
			    "and %ld (inclusive)\n"), MAXLONG);
			return (-1);
		}
		break;	/* Done! */
	}
	return (nentities);
}

/*
 * set_mntpt() looks at the entity's name (e_name) and finds its
 * mountpoint.  To do this, we need to build a list of mountpoints
 * from /etc/mnttab.  We only need to do this once and we don't do it
 * if we don't need to look at any mountpoints.
 * Returns 0 on success, non-zero if it couldn't find a mount-point.
 */
int
set_mntpt(entity_t *ep)
{
	static struct mnt {
		struct mnt	*m_next;
		char		*m_mntpt;
		ulong_t		m_fsid;	/* From statvfs(), set only as needed */
	} *mnt_list = NULL;	/* Linked list of mount-points */
	struct mnt *mntp;
	struct statvfs64 statvfsbuf;
	char *original_name = ep->e_name;
	char path[PATH_MAX];

	if (original_name == NULL)		/* Shouldn't happen */
		return (1);

	/* We only set up mnt_list the first time this is called */
	if (mnt_list == NULL) {
		FILE *fp;
		struct mnttab mnttab;

		if ((fp = fopen(MNTTAB, "r")) == NULL) {
			perror(MNTTAB);
			return (1);
		}
		resetmnttab(fp);
		/*
		 * We insert at the front of the list so that when we
		 * search entries we'll have the last mounted entries
		 * first in the list so that we can match the longest
		 * mountpoint.
		 */
		while (getmntent(fp, &mnttab) == 0) {
			if ((mntp = malloc(sizeof (*mntp))) == NULL) {
				perror("malloc() mount list");
				return (1);
			}
			mntp->m_mntpt = strdup(mnttab.mnt_mountp);
			mntp->m_next = mnt_list;
			mnt_list = mntp;
		}
		(void) fclose(fp);
	}

	if (realpath(original_name, path) == NULL) {
		perror(original_name);
		return (1);
	}

	/*
	 * Now that we have the path, walk through the mnt_list and
	 * look for the first (best) match.
	 */
	for (mntp = mnt_list; mntp; mntp = mntp->m_next) {
		if (strncmp(path, mntp->m_mntpt, strlen(mntp->m_mntpt)) == 0) {
			if (mntp->m_fsid == 0) {
				if (statvfs64(mntp->m_mntpt, &statvfsbuf)) {
					/* Can't statvfs so no match */
					continue;
				} else {
					mntp->m_fsid = statvfsbuf.f_fsid;
				}
			}

			if (ep->e_fsid != mntp->m_fsid) {
				/* No match - Move on */
				continue;
			}

			break;
		}
	}

	if (mntp == NULL) {
		(void) fprintf(stderr, gettext(
		    "Can't find mount point for %s\n"), path);
		return (1);
	}

	ep->e_name = strdup(mntp->m_mntpt);
	free(original_name);
	return (0);
}

/*
 * We have an array of entities that are potentially stat-able.  Using
 * the name (e_name) of the entity, attempt to construct a ksname suitable
 * for use by kstat_lookup(3kstat) and fill it into the e_ksname member.
 *
 * We check the e_name against the list of file system types.  If there is
 * no match then test to see if the path is valid.  If the path is valid,
 * then determine the mountpoint.
 */
void
set_ksnames(entity_t *entities, int nentities, char **fstypes, int nfstypes)
{
	int		i, j;
	struct statvfs64 statvfsbuf;

	for (i = 0; i < nentities; i++) {
		entity_t	*ep = &entities[i];

		/* Check the name against the list of fstypes */
		for (j = 1; j < nfstypes; j++) {
			if (fstypes[j] && ep->e_name &&
			    strcmp(ep->e_name, fstypes[j]) == 0) {
				/* It's a file system type */
				ep->e_type = ENTYPE_FSTYPE;
				(void) snprintf(ep->e_ksname, KSTAT_STRLEN,
				    "%s%s", VOPSTATS_STR, ep->e_name);
				/* Now allocate the vopstats array */
				ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t));
				if (entities[i].e_vs == NULL) {
					perror("calloc() fstype vopstats");
					exit(1);
				}
				break;
			}
		}
		if (j < nfstypes)	/* Found it! */
			continue;

		/*
		 * If the entity in the exception list of fstypes, then
		 * null out the entry so it isn't displayed and move along.
		 */
		if (is_exception(ep->e_name)) {
			ep->e_ksname[0] = 0;
			continue;
		}

		/* If we didn't find it, see if it's a path */
		if (ep->e_name == NULL || statvfs64(ep->e_name, &statvfsbuf)) {
			/* Error - Make sure the entry is nulled out */
			ep->e_ksname[0] = 0;
			continue;
		}
		(void) snprintf(ep->e_ksname, KSTAT_STRLEN, "%s%lx",
		    VOPSTATS_STR, statvfsbuf.f_fsid);
		ep->e_fsid = statvfsbuf.f_fsid;
		if (set_mntpt(ep)) {
			(void) fprintf(stderr,
			    gettext("Can't determine type of \"%s\"\n"),
			    ep->e_name ? ep->e_name : gettext("<NULL>"));
		} else {
			ep->e_type = ENTYPE_MNTPT;
		}

		/* Now allocate the vopstats array */
		ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t));
		if (entities[i].e_vs == NULL) {
			perror("calloc() vopstats array");
			exit(1);
		}
	}
}

/*
 * The idea is that 'dspfunc' should only be modified from the default
 * once since the display options are mutually exclusive.  If 'dspfunc'
 * only contains the default display function, then all is good and we
 * can set it to the new display function.  Otherwise, bail.
 */
void
set_dispfunc(
	void (**dspfunc)(char *, vopstats_t *, vopstats_t *, int),
	void (*newfunc)(char *, vopstats_t *, vopstats_t *, int))
{
	if (*dspfunc != dflt_display) {
		(void) fprintf(stderr, gettext(
		"%s: Display options -{a|f|i|n|v} are mutually exclusive\n"),
		    cmdname);
		usage();
	}
	*dspfunc = newfunc;
}

int
main(int argc, char *argv[])
{
	int		c;
	int		i, j;		/* Generic counters */
	int		nentities_found;
	int		linesout = 0;	/* Keeps track of lines printed */
	int		printhdr = 0;	/* Print a header?  0 = no, 1 = yes */
	int		nfstypes;	/* Number of fstypes */
	int		dispflag = 0;	/* Flags for display control */
	long		count = 0;	/* Number of iterations for display */
	int		forever; 	/* Run forever */
	long		interval = 0;
	boolean_t	fstypes_only = B_FALSE;	/* Display fstypes only */
	char		**fstypes;	/* Array of names of all fstypes */
	int		nentities;	/* Number of stat-able entities */
	entity_t	*entities;	/* Array of stat-able entities */
	kstat_ctl_t	*kc;
	void (*dfunc)(char *, vopstats_t *, vopstats_t *, int) = dflt_display;
	hrtime_t	start_n;	/* Start time */
	hrtime_t	period_n;	/* Interval in nanoseconds */

	extern int	optind;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
#endif
	(void) textdomain(TEXT_DOMAIN);

	/* Don't let buffering interfere with piped output. */
	(void) setvbuf(stdout, NULL, _IOLBF, 0);

	cmdname = argv[0];
	while ((c = getopt(argc, argv, OPTIONS)) != EOF) {
		switch (c) {

		default:
			usage();
			break;

		case 'F':	/* Only display available FStypes */
			fstypes_only = B_TRUE;
			break;

#if PARSABLE_OUTPUT
		case 'P':	/* Parsable output */
			dispflag |= DISP_RAW;
			break;
#endif /* PARSABLE_OUTPUT */

		case 'T':	/* Timestamp */
			if (optarg) {
				if (strcmp(optarg, "u") == 0) {
					timestamp_fmt = UDATE;
				} else if (strcmp(optarg, "d") == 0) {
					timestamp_fmt = DDATE;
				}
			}

			/* If it was never set properly... */
			if (timestamp_fmt == NODATE) {
				(void) fprintf(stderr, gettext("%s: -T option "
				    "requires either 'u' or 'd'\n"), cmdname);
				usage();
			}
			break;

		case 'a':
			set_dispfunc(&dfunc, attr_display);
			break;

		case 'f':
			set_dispfunc(&dfunc, vop_display);
			break;

		case 'i':
			set_dispfunc(&dfunc, io_display);
			break;

		case 'n':
			set_dispfunc(&dfunc, naming_display);
			break;

		case 'v':
			set_dispfunc(&dfunc, vm_display);
			break;
		}
	}

#if PARSABLE_OUTPUT
	if ((dispflag & DISP_RAW) && (timestamp_fmt != NODATE)) {
		(void) fprintf(stderr, gettext(
		    "-P and -T options are mutually exclusive\n"));
		usage();
	}
#endif /* PARSABLE_OUTPUT */

	/* Gather the list of filesystem types */
	if ((nfstypes = build_fstype_list(&fstypes)) == 0) {
		(void) fprintf(stderr,
		    gettext("Can't build list of fstypes\n"));
		exit(1);
	}

	nentities = parse_operands(
	    argc, argv, optind, &interval, &count, &entities);
	forever = count == MAXLONG;
	period_n = (hrtime_t)interval * NANOSEC;

	if (nentities == -1)	/* Set of operands didn't parse properly  */
		usage();

	if ((nentities == 0) && (fstypes_only == B_FALSE)) {
		(void) fprintf(stderr, gettext(
		    "Must specify -F or at least one fstype or mount point\n"));
		usage();
	}

	if ((nentities > 0) && (fstypes_only == B_TRUE)) {
		(void) fprintf(stderr, gettext(
		    "Cannot use -F with fstypes or mount points\n"));
		usage();
	}

	/*
	 * If we had no operands (except for interval/count) and we
	 * requested FStypes only (-F), then fill in the entities[]
	 * array with all available fstypes.
	 */
	if ((nentities == 0) && (fstypes_only == B_TRUE)) {
		if ((entities = calloc(nfstypes, sizeof (entity_t))) == NULL) {
			perror("calloc() fstype stats");
			exit(1);
		}

		for (i = 1; i < nfstypes; i++) {
			if (fstypes[i]) {
				entities[nentities].e_name = strdup(fstypes[i]);
				nentities++;
			}
		}
	}

	set_ksnames(entities, nentities, fstypes, nfstypes);

	if ((kc = kstat_open()) == NULL) {
		perror("kstat_open");
		exit(1);
	}

	/* Set start time */
	start_n = gethrtime();

	/* Initial timestamp */
	if (timestamp_fmt != NODATE) {
		print_timestamp(timestamp_fmt);
		linesout++;
	}

	/*
	 * The following loop walks through the entities[] list to "prime
	 * the pump"
	 */
	for (j = 0, printhdr = 1; j < nentities; j++) {
		entity_t *ent = &entities[j];
		vopstats_t *vsp = &ent->e_vs[CUR_INDEX];
		kstat_t *ksp = NULL;

		if (get_vopstats(kc, ent->e_ksname, vsp, &ksp) == 0) {
			(*dfunc)(ent->e_name, NULL, vsp,
			    dispflag_policy(printhdr, dispflag));
			linesout++;
		} else {
			/*
			 * If we can't find it the first time through, then
			 * get rid of it.
			 */
			entities[j].e_ksname[0] = 0;

			/*
			 * If we're only displaying FStypes (-F) then don't
			 * complain about any file systems that might not
			 * be loaded.  Otherwise, let the user know that
			 * he chose poorly.
			 */
			if (fstypes_only == B_FALSE) {
				(void) fprintf(stderr, gettext(
				    "No statistics available for %s\n"),
				    entities[j].e_name);
			}
		}
		printhdr = 0;
	}

	if (count > 1)
		/* Set up signal handler for SIGCONT */
		if (signal(SIGCONT, cont_handler) == SIG_ERR)
			fail(1, "signal failed");


	BUMP_INDEX();	/* Swap the previous/current indices */
	i = 1;
	while (forever || i++ <= count) {
		/*
		 * No telling how many lines will be printed in any interval.
		 * There should be a minimum of HEADERLINES between any
		 * header.  If we exceed that, no big deal.
		 */
		if (linesout > HEADERLINES) {
			linesout = 0;
			printhdr = 1;
		}
		/* Have a kip */
		sleep_until(&start_n, period_n, forever, &caught_cont);

		if (timestamp_fmt != NODATE) {
			print_timestamp(timestamp_fmt);
			linesout++;
		}

		for (j = 0, nentities_found = 0; j < nentities; j++) {
			entity_t *ent = &entities[j];

			/*
			 * If this entry has been cleared, don't attempt
			 * to process it.
			 */
			if (ent->e_ksname[0] == 0) {
				continue;
			}

			if (get_vopstats(kc, ent->e_ksname,
			    &ent->e_vs[CUR_INDEX], NULL) == 0) {
				(*dfunc)(ent->e_name, &ent->e_vs[PREV_INDEX],
				    &ent->e_vs[CUR_INDEX],
				    dispflag_policy(printhdr, dispflag));
				linesout++;
				nentities_found++;
			} else {
				if (ent->e_type == ENTYPE_MNTPT) {
					(void) printf(gettext(
					    "<<mount point no longer "
					    "available: %s>>\n"), ent->e_name);
				} else if (ent->e_type == ENTYPE_FSTYPE) {
					(void) printf(gettext(
					    "<<file system module no longer "
					    "loaded: %s>>\n"), ent->e_name);
				} else {
					(void) printf(gettext(
					    "<<%s no longer available>>\n"),
					    ent->e_name);
				}
				/* Disable this so it doesn't print again */
				ent->e_ksname[0] = 0;
			}
			printhdr = 0;	/* Always shut this off */
		}
		BUMP_INDEX();	/* Bump the previous/current indices */

		/*
		 * If the entities we were observing are no longer there
		 * (file system modules unloaded, file systems unmounted)
		 * then we're done.
		 */
		if (nentities_found == 0)
			break;
	}

	return (0);
}