usr/src/cmd/fs.d/df.c
author lling
Wed, 19 Sep 2007 10:32:40 -0700
changeset 5094 71a3e95fb9e2
parent 2082 76b439ec3ac1
child 5378 111aa1baa84a
permissions -rw-r--r--
PSARC 2007/342 Enhanced ZFS Pool Properties PSARC 2007/482 zpool upgrade -V 6565437 zpool property extensions 6561384 want 'zpool upgrade -V <version>' to upgrade to specific version number 6582755 zfs.h has some incorrect version macros 6595601 libzfs headers declare functions which don't exist 6603938 libzfs is using VERIFY() again 6538984 duplicated messages when get pool properties from an unsupported pool version

/*
 * 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 (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <dlfcn.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <locale.h>
#include <libintl.h>
#include <stdlib.h>
#include <ftw.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/statvfs.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/mnttab.h>
#include <sys/mntent.h>
#include <sys/vfstab.h>
#include <sys/wait.h>
#include <sys/mkdev.h>
#include <sys/int_limits.h>
#include <sys/zone.h>
#include <libzfs.h>

#include "fslib.h"

extern char *default_fstype(char *);

/*
 * General notice:
 * String pointers in this code may point to statically allocated memory
 * or dynamically allocated memory. Furthermore, a dynamically allocated
 * string may be pointed to by more than one pointer. This does not pose
 * a problem because malloc'ed memory is never free'd (so we don't need
 * to remember which pointers point to malloc'ed memory).
 */

/*
 * TRANSLATION_NOTE
 * Only strings passed as arguments to the TRANSLATE macro need to
 * be translated.
 */

#ifndef MNTTYPE_LOFS
#define	MNTTYPE_LOFS		"lofs"
#endif

#define	EQ(s1, s2)		(strcmp(s1, s2) == 0)
#define	NEW(type)		xmalloc(sizeof (type))
#define	CLEAR(var)		(void) memset(&(var), 0, sizeof (var))
#define	MAX(a, b)		((a) > (b) ? (a) : (b))
#define	MAX3(a, b, c)		MAX(a, MAX(b, c))
#define	TRANSLATE(s)		new_string(gettext(s))

#define	MAX_OPTIONS		36
#define	N_FSTYPES		20
#define	MOUNT_TABLE_ENTRIES	40	/* initial allocation */
#define	MSGBUF_SIZE		1024
#define	LINEBUF_SIZE		256	/* either input or output lines */

#define	BLOCK_SIZE		512	/* when reporting in terms of blocks */

#define	DEVNM_CMD		"devnm"
#define	FS_LIBPATH		"/usr/lib/fs/"
#define	MOUNT_TAB		"/etc/mnttab"
#define	VFS_TAB			"/etc/vfstab"
#define	REMOTE_FS		"/etc/dfs/fstypes"

#define	NUL			'\0'
#define	FALSE			0
#define	TRUE			1

/*
 * Formatting constants
 */
#define	IBCS2_FILESYSTEM_WIDTH	15	/* Truncate to match ISC/SCO */
#define	IBCS2_MOUNT_POINT_WIDTH	10	/* Truncate to match ISC/SCO */
#define	FILESYSTEM_WIDTH	20
#define	MOUNT_POINT_WIDTH	19
#define	SPECIAL_DEVICE_WIDTH	18
#define	FSTYPE_WIDTH		8
#define	BLOCK_WIDTH		8
#define	NFILES_WIDTH		8
#ifdef XPG4
#define	KBYTE_WIDTH		11
#define	AVAILABLE_WIDTH		10
#else
#define	KBYTE_WIDTH		7
#define	AVAILABLE_WIDTH		6
#endif
#define	SCALED_WIDTH		6
#define	CAPACITY_WIDTH		9
#define	BSIZE_WIDTH		6
#define	FRAGSIZE_WIDTH		7
#define	FSID_WIDTH		7
#define	FLAG_WIDTH		8
#define	NAMELEN_WIDTH		7
#define	MNT_SPEC_WIDTH		MOUNT_POINT_WIDTH + SPECIAL_DEVICE_WIDTH + 2

/*
 * Flags for the errmsg() function
 */
#define	ERR_NOFLAGS		0x0
#define	ERR_NONAME		0x1	/* don't include the program name */
					/* as a prefix */
#define	ERR_FATAL		0x2	/* call exit after printing the */
					/* message */
#define	ERR_PERROR		0x4	/* append an errno explanation to */
					/* the message */
#define	ERR_USAGE		0x8	/* print the usage line after the */
					/* message */

#define	NUMBER_WIDTH		40

/*
 * A numbuf_t is used when converting a number to a string representation
 */
typedef char numbuf_t[ NUMBER_WIDTH ];

/*
 * We use bool_int instead of int to make clear which variables are
 * supposed to be boolean
 */
typedef int bool_int;

struct mtab_entry {
	bool_int	mte_dev_is_valid;
	dev_t		mte_dev;
	bool_int	mte_ignore;	/* the "ignore" option was set */
	struct extmnttab	*mte_mount;
};


struct df_request {
	bool_int		dfr_valid;
	char			*dfr_cmd_arg;	/* what the user specified */
	struct mtab_entry	*dfr_mte;
	char			*dfr_fstype;
	int			dfr_index;	/* to make qsort stable	*/
};

#define	DFR_MOUNT_POINT(dfrp)	(dfrp)->dfr_mte->mte_mount->mnt_mountp
#define	DFR_SPECIAL(dfrp)	(dfrp)->dfr_mte->mte_mount->mnt_special
#define	DFR_FSTYPE(dfrp)	(dfrp)->dfr_mte->mte_mount->mnt_fstype
#define	DFR_ISMOUNTEDFS(dfrp)	((dfrp)->dfr_mte != NULL)

#define	DFRP(p)			((struct df_request *)(p))

typedef void (*output_func)(struct df_request *, struct statvfs64 *);

struct df_output {
	output_func	dfo_func;	/* function that will do the output */
	int		dfo_flags;
};

/*
 * Output flags
 */
#define	DFO_NOFLAGS	0x0
#define	DFO_HEADER	0x1		/* output preceded by header */
#define	DFO_STATVFS	0x2		/* must do a statvfs64(2) */


static char	*program_name;
static char	df_options[MAX_OPTIONS] = "-";
static size_t	df_options_len = 1;
static char	*o_option_arg;			/* arg to the -o option */
static char	*FSType;
static char	*remote_fstypes[N_FSTYPES+1];	/* allocate an extra one */
						/* to use as a terminator */

/*
 * The following three variables support an in-memory copy of the mount table
 * to speedup searches.
 */
static struct mtab_entry	*mount_table;	/* array of mtab_entry's */
static size_t			mount_table_entries;
static size_t			mount_table_allocated_entries;

static bool_int		F_option;
static bool_int		V_option;
static bool_int		P_option;	/* Added for XCU4 compliance */
static bool_int		Z_option;
static bool_int		v_option;
#ifdef	_iBCS2
char			*sysv3_set;
#endif /* _iBCS2 */
static bool_int		a_option;
static bool_int		b_option;
static bool_int		e_option;
static bool_int		g_option;
static bool_int		h_option;
static bool_int		k_option;
static bool_int		l_option;
static bool_int		n_option;
static bool_int		t_option;
static bool_int		o_option;

static bool_int		tty_output;
static bool_int		use_scaling;
static int		scale;

static void usage(void);
static void do_devnm(int, char **);
static void do_df(int, char **)	__NORETURN;
static void parse_options(int, char **);
static char *basename(char *);

static libzfs_handle_t *(*_libzfs_init)(boolean_t);
static zfs_handle_t *(*_zfs_open)(libzfs_handle_t *, const char *, int);
static void (*_zfs_close)(zfs_handle_t *);
static uint64_t (*_zfs_prop_get_int)(zfs_handle_t *, zfs_prop_t);
static libzfs_handle_t *g_zfs;

/*
 * Dynamically check for libzfs, in case the user hasn't installed the SUNWzfs
 * packages.  A basic utility such as df shouldn't depend on optional
 * filesystems.
 */
static boolean_t
load_libzfs(void)
{
	void *hdl;

	if (_libzfs_init != NULL)
		return (g_zfs != NULL);

	if ((hdl = dlopen("libzfs.so", RTLD_LAZY)) != NULL) {
		_libzfs_init = (libzfs_handle_t *(*)(boolean_t))dlsym(hdl,
		    "libzfs_init");
		_zfs_open = (zfs_handle_t *(*)())dlsym(hdl, "zfs_open");
		_zfs_close = (void (*)())dlsym(hdl, "zfs_close");
		_zfs_prop_get_int = (uint64_t (*)())
		    dlsym(hdl, "zfs_prop_get_int");

		if (_libzfs_init != NULL) {
			assert(_zfs_open != NULL);
			assert(_zfs_close != NULL);
			assert(_zfs_prop_get_int != NULL);

			g_zfs = _libzfs_init(B_FALSE);
		}
	}

	return (g_zfs != NULL);
}

int
main(int argc, char *argv[])
{
	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)		/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	program_name = basename(argv[0]);

#ifdef	_iBCS2
	sysv3_set = getenv("SYSV3");
#endif	/* _iBCS2 */

	if (EQ(program_name, DEVNM_CMD))
		do_devnm(argc, argv);

	parse_options(argc, argv);

	/*
	 * The k_option implies SunOS 4.x compatibility: when the special
	 * device name is too long the line will be split except when the
	 * output has been redirected.
	 * This is also valid for the -h option.
	 */

	if (use_scaling || k_option || P_option || v_option)
		tty_output = isatty(1);

	do_df(argc - optind, &argv[optind]);
	/* NOTREACHED */
}


/*
 * Prints an error message to stderr.
 */
/* VARARGS2 */
static void
errmsg(int flags, char *fmt, ...)
{
	char buf[MSGBUF_SIZE];
	va_list ap;
	int cc;
	int offset;

	if (flags & ERR_NONAME)
		offset = 0;
	else
		offset = sprintf(buf, "%s: ", program_name);

	va_start(ap, fmt);
	cc = vsprintf(&buf[offset], gettext(fmt), ap);
	offset += cc;
	va_end(ap);

	if (flags & ERR_PERROR) {
		if (buf[offset-1] != ' ')
			(void) strcat(buf, " ");
		(void) strcat(buf, strerror(errno));
	}
	(void) fprintf(stderr, "%s\n", buf);
	if (flags & ERR_USAGE)
		usage();
	if (flags & ERR_FATAL)
		exit(1);
}


static void
usage(void)
{
#ifdef  XPG4
	errmsg(ERR_NONAME,
	    "Usage: %s [-F FSType] [-abeghklntPVZ] [-o FSType-specific_options]"
	    " [directory | block_device | resource]", program_name);
#else
	errmsg(ERR_NONAME,
	    "Usage: %s [-F FSType] [-abeghklntVvZ] [-o FSType-specific_options]"
	    " [directory | block_device | resource]", program_name);
#endif
	exit(1);
	/* NOTREACHED */
}


static char *
new_string(char *s)
{
	char *p = NULL;

	if (s) {
		p = strdup(s);
		if (p)
			return (p);
		errmsg(ERR_FATAL, "out of memory");
		/* NOTREACHED */
	}
	return (p);
}


/*
 * Allocate memory using malloc but terminate if the allocation fails
 */
static void *
xmalloc(size_t size)
{
	void *p = malloc(size);

	if (p)
		return (p);
	errmsg(ERR_FATAL, "out of memory");
	/* NOTREACHED */
	return (NULL);
}


/*
 * Allocate memory using realloc but terminate if the allocation fails
 */
static void *
xrealloc(void *ptr, size_t size)
{
	void *p = realloc(ptr, size);

	if (p)
		return (p);
	errmsg(ERR_FATAL, "out of memory");
	/* NOTREACHED */
	return (NULL);
}


/*
 * fopen the specified file for reading but terminate if the fopen fails
 */
static FILE *
xfopen(char *file)
{
	FILE *fp = fopen(file, "r");

	if (fp == NULL)
		errmsg(ERR_FATAL + ERR_PERROR, "failed to open %s:", file);
	return (fp);
}


/*
 * Read remote file system types from REMOTE_FS into the
 * remote_fstypes array.
 */
static void
init_remote_fs(void)
{
	FILE	*fp;
	char	line_buf[LINEBUF_SIZE];
	size_t	fstype_index = 0;

	if ((fp = fopen(REMOTE_FS, "r")) == NULL) {
		errmsg(ERR_NOFLAGS,
		    "Warning: can't open %s, ignored", REMOTE_FS);
		return;
	}

	while (fgets(line_buf, sizeof (line_buf), fp) != NULL) {
		char buf[LINEBUF_SIZE];

		(void) sscanf(line_buf, "%s", buf);
		remote_fstypes[fstype_index++] = new_string(buf);

		if (fstype_index == N_FSTYPES)
			break;
	}
	(void) fclose(fp);
}


/*
 * Returns TRUE if fstype is a remote file system type;
 * otherwise, returns FALSE.
 */
static int
is_remote_fs(char *fstype)
{
	char **p;
	static bool_int remote_fs_initialized;

	if (! remote_fs_initialized) {
		init_remote_fs();
		remote_fs_initialized = TRUE;
	}

	for (p = remote_fstypes; *p; p++)
		if (EQ(fstype, *p))
			return (TRUE);
	return (FALSE);
}


static char *
basename(char *s)
{
	char *p = strrchr(s, '/');

	return (p ? p+1 : s);
}


/*
 * Create a new "struct extmnttab" and make sure that its fields point
 * to malloc'ed memory
 */
static struct extmnttab *
mntdup(struct extmnttab *old)
{
	struct extmnttab *new = NEW(struct extmnttab);

	new->mnt_special = new_string(old->mnt_special);
	new->mnt_mountp  = new_string(old->mnt_mountp);
	new->mnt_fstype  = new_string(old->mnt_fstype);
	new->mnt_mntopts = new_string(old->mnt_mntopts);
	new->mnt_time    = new_string(old->mnt_time);
	new->mnt_major   = old->mnt_major;
	new->mnt_minor   = old->mnt_minor;
	return (new);
}


static void
mtab_error(char *mtab_file, int status)
{
	if (status == MNT_TOOLONG)
		errmsg(ERR_NOFLAGS, "a line in %s exceeds %d characters",
		    mtab_file, MNT_LINE_MAX);
	else if (status == MNT_TOOMANY)
		errmsg(ERR_NOFLAGS,
		    "a line in %s has too many fields", mtab_file);
	else if (status == MNT_TOOFEW)
		errmsg(ERR_NOFLAGS,
		    "a line in %s has too few fields", mtab_file);
	else
		errmsg(ERR_NOFLAGS,
		    "error while reading %s: %d", mtab_file, status);
	exit(1);
	/* NOTREACHED */
}


/*
 * Read the mount table from the specified file.
 * We keep the table in memory for faster lookups.
 */
static void
mtab_read_file(void)
{
	char		*mtab_file = MOUNT_TAB;
	FILE		*fp;
	struct extmnttab	mtab;
	int		status;

	fp = xfopen(mtab_file);

	resetmnttab(fp);
	mount_table_allocated_entries = MOUNT_TABLE_ENTRIES;
	mount_table_entries = 0;
	mount_table = xmalloc(
	    mount_table_allocated_entries * sizeof (struct mtab_entry));

	while ((status = getextmntent(fp, &mtab, sizeof (struct extmnttab)))
	    == 0) {
		struct mtab_entry *mtep;

		if (mount_table_entries == mount_table_allocated_entries) {
			mount_table_allocated_entries += MOUNT_TABLE_ENTRIES;
			mount_table = xrealloc(mount_table,
			    mount_table_allocated_entries *
			    sizeof (struct mtab_entry));
		}
		mtep = &mount_table[mount_table_entries++];
		mtep->mte_mount = mntdup(&mtab);
		mtep->mte_dev_is_valid = FALSE;
		mtep->mte_ignore = (hasmntopt((struct mnttab *)&mtab,
		    MNTOPT_IGNORE) != NULL);
	}

	(void) fclose(fp);

	if (status == -1)			/* reached EOF */
		return;
	mtab_error(mtab_file, status);
	/* NOTREACHED */
}


/*
 * We use this macro when we want to record the option for the purpose of
 * passing it to the FS-specific df
 */
#define	SET_OPTION(opt)		opt##_option = TRUE, \
				df_options[df_options_len++] = arg

static void
parse_options(int argc, char *argv[])
{
	int arg;

	opterr = 0;	/* getopt shouldn't complain about unknown options */

#ifdef XPG4
	while ((arg = getopt(argc, argv, "F:o:abehkVtgnlPZ")) != EOF) {
#else
	while ((arg = getopt(argc, argv, "F:o:abehkVtgnlvZ")) != EOF) {
#endif
		if (arg == 'F') {
			if (F_option)
				errmsg(ERR_FATAL + ERR_USAGE,
				    "more than one FSType specified");
			F_option = 1;
			FSType = optarg;
		} else if (arg == 'V' && ! V_option) {
			V_option = TRUE;
		} else if (arg == 'v' && ! v_option) {
			v_option = TRUE;
#ifdef XPG4
		} else if (arg == 'P' && ! P_option) {
			SET_OPTION(P);
#endif
		} else if (arg == 'a' && ! a_option) {
			SET_OPTION(a);
		} else if (arg == 'b' && ! b_option) {
			SET_OPTION(b);
		} else if (arg == 'e' && ! e_option) {
			SET_OPTION(e);
		} else if (arg == 'g' && ! g_option) {
			SET_OPTION(g);
		} else if (arg == 'h') {
			use_scaling = TRUE;
			scale = 1024;
		} else if (arg == 'k' && ! k_option) {
			SET_OPTION(k);
		} else if (arg == 'l' && ! l_option) {
			SET_OPTION(l);
		} else if (arg == 'n' && ! n_option) {
			SET_OPTION(n);
		} else if (arg == 't' && ! t_option) {
			SET_OPTION(t);
		} else if (arg == 'o') {
			if (o_option)
				errmsg(ERR_FATAL + ERR_USAGE,
				"the -o option can only be specified once");
			o_option = TRUE;
			o_option_arg = optarg;
		} else if (arg == 'Z') {
			SET_OPTION(Z);
		} else if (arg == '?') {
			errmsg(ERR_USAGE, "unknown option: %c", optopt);
		}
	}

	/*
	 * Option sanity checks
	 */
	if (g_option && o_option)
		errmsg(ERR_FATAL, "-o and -g options are incompatible");
	if (l_option && o_option)
		errmsg(ERR_FATAL, "-o and -l options are incompatible");
	if (n_option && o_option)
		errmsg(ERR_FATAL, "-o and -n options are incompatible");
	if (use_scaling && o_option)
		errmsg(ERR_FATAL, "-o and -h options are incompatible");
}



/*
 * Check if the user-specified argument is a resource name.
 * A resource name is whatever is placed in the mnt_special field of
 * struct mnttab. In the case of NFS, a resource name has the form
 * hostname:pathname
 * We try to find an exact match between the user-specified argument
 * and the mnt_special field of a mount table entry.
 * We also use the heuristic of removing the basename from the user-specified
 * argument and repeating the test until we get a match. This works
 * fine for NFS but may fail for other remote file system types. However,
 * it is guaranteed that the function will not fail if the user specifies
 * the exact resource name.
 * If successful, this function sets the 'dfr_mte' field of '*dfrp'
 */
static void
resource_mount_entry(struct df_request *dfrp)
{
	char *name;

	/*
	 * We need our own copy since we will modify the string
	 */
	name = new_string(dfrp->dfr_cmd_arg);

	for (;;) {
		char *p;
		int i;

		/*
		 * Compare against all known mount points.
		 * We start from the most recent mount, which is at the
		 * end of the array.
		 */
		for (i = mount_table_entries - 1; i >= 0; i--) {
			struct mtab_entry *mtep = &mount_table[i];

			if (EQ(name, mtep->mte_mount->mnt_special)) {
				dfrp->dfr_mte = mtep;
				break;
			}
		}

		/*
		 * Remove the last component of the pathname.
		 * If there is no such component, this is not a resource name.
		 */
		p = strrchr(name, '/');
		if (p == NULL)
			break;
		*p = NUL;
	}
}



/*
 * Try to match the command line argument which is a block special device
 * with the special device of one of the mounted file systems.
 * If one is found, set the appropriate field of 'dfrp' to the mount
 * table entry.
 */
static void
bdev_mount_entry(struct df_request *dfrp)
{
	int i;
	char *special = dfrp->dfr_cmd_arg;

	/*
	 * Compare against all known mount points.
	 * We start from the most recent mount, which is at the
	 * end of the array.
	 */
	for (i = mount_table_entries - 1; i >= 0; i--) {
		struct mtab_entry *mtep = &mount_table[i];

		if (EQ(special, mtep->mte_mount->mnt_special)) {
			dfrp->dfr_mte = mtep;
			break;
		}
	}
}

static struct mtab_entry *
devid_matches(int i, dev_t devno)
{
	struct mtab_entry	*mtep = &mount_table[i];
	struct extmnttab	*mtp = mtep->mte_mount;
	/* int	len = strlen(mtp->mnt_mountp); */

	if (EQ(mtp->mnt_fstype, MNTTYPE_SWAP))
		return (NULL);
	/*
	 * check if device numbers match. If there is a cached device number
	 * in the mtab_entry, use it, otherwise get the device number
	 * either from the mnttab entry or by stat'ing the mount point.
	 */
	if (! mtep->mte_dev_is_valid) {
		struct stat64 st;
		dev_t dev = NODEV;

		dev = makedev(mtp->mnt_major, mtp->mnt_minor);
		if (dev == 0)
			dev = NODEV;
		if (dev == NODEV) {
			if (stat64(mtp->mnt_mountp, &st) == -1) {
				return (NULL);
			} else {
				dev = st.st_dev;
			}
		}
		mtep->mte_dev = dev;
		mtep->mte_dev_is_valid = TRUE;
	}
	if (mtep->mte_dev == devno) {
		return (mtep);
	}
	return (NULL);
}

/*
 * Find the mount point under which the user-specified path resides
 * and set the 'dfr_mte' field of '*dfrp' to point to the mount table entry.
 */
static void
path_mount_entry(struct df_request *dfrp, dev_t devno)
{
	char			dirpath[MAXPATHLEN];
	char			*dir = dfrp->dfr_cmd_arg;
	struct mtab_entry	*match, *tmatch;
	int i;

	/*
	 * Expand the given path to get a canonical version (i.e. an absolute
	 * path without symbolic links).
	 */
	if (realpath(dir, dirpath) == NULL) {
		errmsg(ERR_PERROR, "cannot canonicalize %s:", dir);
		return;
	}
	/*
	 * If the mnt point is lofs, search from the top of entries from
	 * /etc/mnttab and return the first entry that matches the devid
	 * For non-lofs mount points, return the first entry from the bottom
	 * of the entries in /etc/mnttab that matches on the devid field
	 */
	match = NULL;
	if (dfrp->dfr_fstype && EQ(dfrp->dfr_fstype, MNTTYPE_LOFS)) {
		for (i = 0; i < mount_table_entries; i++) {
			if (match = devid_matches(i, devno))
				break;
		}
	} else {
		for (i = mount_table_entries - 1; i >= 0; i--) {
			if (tmatch = devid_matches(i, devno)) {
				/*
				 * If executing in a zone, there might be lofs
				 * mounts for which the real mount point is
				 * invisible; accept the "best fit" for this
				 * devid.
				 */
				match = tmatch;
				if (!EQ(match->mte_mount->mnt_fstype,
				    MNTTYPE_LOFS)) {
					break;
				}
			}
		}
	}
	if (! match) {
		errmsg(ERR_NOFLAGS,
		    "Could not find mount point for %s", dir);
		return;
	}
	dfrp->dfr_mte = match;
}

/*
 * Execute a single FS-specific df command for all given requests
 * Return 0 if successful, 1 otherwise.
 */
static int
run_fs_specific_df(struct df_request request_list[], int entries)
{
	int	i;
	int	argv_index;
	char	**argv;
	size_t	size;
	pid_t	pid;
	int	status;
	char	cmd_path[MAXPATHLEN];
	char	*fstype;

	if (entries == 0)
		return (0);

	fstype = request_list[0].dfr_fstype;

	if (F_option && ! EQ(FSType, fstype))
		return (0);

	(void) sprintf(cmd_path, "%s%s/df", FS_LIBPATH, fstype);
	/*
	 * Argv entries:
	 *		1 for the path
	 *		2 for -o <options>
	 *		1 for the generic options that we propagate
	 *		1 for the terminating NULL pointer
	 *		n for the number of user-specified arguments
	 */
	size = (5 + entries) * sizeof (char *);
	argv = xmalloc(size);
	(void) memset(argv, 0, size);

	argv[0] = cmd_path;
	argv_index = 1;
	if (o_option) {
		argv[argv_index++] = "-o";
		argv[argv_index++] = o_option_arg;
	}

	/*
	 * Check if we need to propagate any generic options
	 */
	if (df_options_len > 1)
		argv[argv_index++] = df_options;

	/*
	 * If there is a user-specified path, we pass that to the
	 * FS-specific df. Otherwise, we are guaranteed to have a mount
	 * point, since a request without a user path implies that
	 * we are reporting only on mounted file systems.
	 */
	for (i = 0; i < entries; i++) {
		struct df_request *dfrp = &request_list[i];

		argv[argv_index++] = (dfrp->dfr_cmd_arg == NULL)
		    ? DFR_MOUNT_POINT(dfrp)
		    : dfrp->dfr_cmd_arg;
	}

	if (V_option) {
		for (i = 0; i < argv_index-1; i++)
			(void) printf("%s ", argv[i]);
		(void) printf("%s\n", argv[i]);
		return (0);
	}

	pid = fork();

	if (pid == -1) {
		errmsg(ERR_PERROR, "cannot fork process:");
		return (1);
	} else if (pid == 0) {
		(void) execv(cmd_path, argv);
		if (errno == ENOENT)
			errmsg(ERR_NOFLAGS,
			    "operation not applicable for FSType %s",
			    fstype);
		else
			errmsg(ERR_PERROR, "cannot execute %s:", cmd_path);
		exit(2);
	}

	/*
	 * Reap the child
	 */
	for (;;) {
		pid_t wpid = waitpid(pid, &status, 0);

		if (wpid == -1)
			if (errno == EINTR)
				continue;
			else {
				errmsg(ERR_PERROR, "waitpid error:");
				return (1);
			}
		else
			break;
	}

	return ((WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : 1);
}



/*
 * Remove from the request list all requests that do not apply.
 * Notice that the subsequent processing of the requests depends on
 * the sanity checking performed by this function.
 */
static int
prune_list(struct df_request request_list[],
		size_t n_requests,
		size_t *valid_requests)
{
	size_t	i;
	size_t	n_valid = 0;
	int	errors = 0;

	for (i = 0; i < n_requests; i++) {
		struct df_request *dfrp = &request_list[i];

		/*
		 * Skip file systems that are not mounted if either the
		 * -l or -n options were specified. If none of these options
		 * are present, the appropriate FS-specific df will be invoked.
		 */
		if (! DFR_ISMOUNTEDFS(dfrp)) {
			if (l_option || n_option) {
				errmsg(ERR_NOFLAGS,
				    "%s option incompatible with unmounted "
				    "special device (%s)",
				    l_option ? "-l" : "-n", dfrp->dfr_cmd_arg);
				dfrp->dfr_valid = FALSE;
				errors++;
			}
			else
				n_valid++;
			continue;
		}

		/*
		 * Check for inconsistency between the argument of -F and
		 * the actual file system type.
		 * If there is an inconsistency and the user specified a
		 * path, this is an error since we are asked to interpret
		 * the path using the wrong file system type. If there is
		 * no path associated with this request, we quietly ignore it.
		 */
		if (F_option && ! EQ(dfrp->dfr_fstype, FSType)) {
			dfrp->dfr_valid = FALSE;
			if (dfrp->dfr_cmd_arg != NULL) {
				errmsg(ERR_NOFLAGS,
				"Warning: %s mounted as a %s file system",
				    dfrp->dfr_cmd_arg, dfrp->dfr_fstype);
				errors++;
			}
			continue;
		}

		/*
		 * Skip remote file systems if the -l option is present
		 */
		if (l_option && is_remote_fs(dfrp->dfr_fstype)) {
			if (dfrp->dfr_cmd_arg != NULL) {
				errmsg(ERR_NOFLAGS,
				    "Warning: %s is not a local file system",
				    dfrp->dfr_cmd_arg);
				errors++;
			}
			dfrp->dfr_valid = FALSE;
			continue;
		}

		/*
		 * Skip file systems mounted as "ignore" unless the -a option
		 * is present, or the user explicitly specified them on
		 * the command line.
		 */
		if (dfrp->dfr_mte->mte_ignore &&
		    ! (a_option || dfrp->dfr_cmd_arg)) {
			dfrp->dfr_valid = FALSE;
			continue;
		}

		n_valid++;
	}
	*valid_requests = n_valid;
	return (errors);
}


/*
 * Print the appropriate header for the requested output format.
 * Options are checked in order of their precedence.
 */
static void
print_header(void)
{
	if (use_scaling) { /* this comes from the -h option */
		int arg = 'h';

		(void) printf("%-*s %*s %*s %*s %-*s %s\n",
		    FILESYSTEM_WIDTH, TRANSLATE("Filesystem"),
#ifdef XPG4
		    SCALED_WIDTH, TRANSLATE("Size"),
		    SCALED_WIDTH, TRANSLATE("Used"),
		    AVAILABLE_WIDTH, TRANSLATE("Available"),
		    CAPACITY_WIDTH, TRANSLATE("Capacity"),
#else
		    SCALED_WIDTH, TRANSLATE("size"),
		    SCALED_WIDTH, TRANSLATE("used"),
		    AVAILABLE_WIDTH, TRANSLATE("avail"),
		    CAPACITY_WIDTH, TRANSLATE("capacity"),
#endif
		    TRANSLATE("Mounted on"));
		SET_OPTION(h);
		return;
	}
	if (k_option) {
		int arg = 'h';

		(void) printf(gettext("%-*s %*s %*s %*s %-*s %s\n"),
		    FILESYSTEM_WIDTH, TRANSLATE("Filesystem"),
#ifdef XPG4
		    KBYTE_WIDTH, TRANSLATE("1024-blocks"),
		    KBYTE_WIDTH, TRANSLATE("Used"),
		    KBYTE_WIDTH, TRANSLATE("Available"),
		    CAPACITY_WIDTH, TRANSLATE("Capacity"),
#else
		    KBYTE_WIDTH, TRANSLATE("kbytes"),
		    KBYTE_WIDTH, TRANSLATE("used"),
		    KBYTE_WIDTH, TRANSLATE("avail"),
		    CAPACITY_WIDTH, TRANSLATE("capacity"),
#endif
		    TRANSLATE("Mounted on"));
		SET_OPTION(h);
		return;
	}
	/* Added for XCU4 compliance */
	if (P_option) {
		int arg = 'h';

		(void) printf(gettext("%-*s %*s %*s %*s %-*s %s\n"),
		    FILESYSTEM_WIDTH, TRANSLATE("Filesystem"),
		    KBYTE_WIDTH, TRANSLATE("512-blocks"),
		    KBYTE_WIDTH, TRANSLATE("Used"),
		    KBYTE_WIDTH, TRANSLATE("Available"),
		    CAPACITY_WIDTH, TRANSLATE("Capacity"),
		    TRANSLATE("Mounted on"));

		SET_OPTION(h);
		return;
	}
	/* End XCU4 */
	if (v_option) {
		(void) printf("%-*s %-*s %*s %*s %*s %-*s\n",
		    IBCS2_MOUNT_POINT_WIDTH, TRANSLATE("Mount Dir"),
		    IBCS2_FILESYSTEM_WIDTH, TRANSLATE("Filesystem"),
		    BLOCK_WIDTH, TRANSLATE("blocks"),
		    BLOCK_WIDTH, TRANSLATE("used"),
		    BLOCK_WIDTH, TRANSLATE("free"),
		    CAPACITY_WIDTH, TRANSLATE(" %used"));
		return;
	}
	if (e_option) {
		(void) printf(gettext("%-*s %*s\n"),
		    FILESYSTEM_WIDTH, TRANSLATE("Filesystem"),
		    BLOCK_WIDTH, TRANSLATE("ifree"));
		return;
	}
	if (b_option) {
		(void) printf(gettext("%-*s %*s\n"),
		    FILESYSTEM_WIDTH, TRANSLATE("Filesystem"),
		    BLOCK_WIDTH, TRANSLATE("avail"));
		return;
	}
}


/*
 * Convert an unsigned long long to a string representation and place the
 * result in the caller-supplied buffer.
 * The given number is in units of "unit_from" size, but the
 * converted number will be in units of "unit_to" size. The unit sizes
 * must be powers of 2.
 * The value "(unsigned long long)-1" is a special case and is always
 * converted to "-1".
 * Returns a pointer to the caller-supplied buffer.
 */
static char *
number_to_string(
			char *buf,		/* put the result here */
			unsigned long long number, /* convert this number */
			int unit_from,		/* from units of this size */
			int unit_to)		/* to units of this size */
{
	if ((long long)number == (long long)-1)
		(void) strcpy(buf, "-1");
	else {
		if (unit_from == unit_to)
			(void) sprintf(buf, "%llu", number);
		else if (unit_from < unit_to)
			(void) sprintf(buf, "%llu",
			    number / (unsigned long long)(unit_to / unit_from));
		else
			(void) sprintf(buf, "%llu",
			    number * (unsigned long long)(unit_from / unit_to));
	}
	return (buf);
}

/*
 * Convert an unsigned long long to a string representation and place the
 * result in the caller-supplied buffer.
 * The given number is in units of "unit_from" size,
 * this will first be converted to a number in 1024 or 1000 byte size,
 * depending on the scaling factor.
 * Then the number is scaled down until it is small enough to be in a good
 * human readable format i.e. in the range 0 thru scale-1.
 * If it's smaller than 10 there's room enough to provide one decimal place.
 * The value "(unsigned long long)-1" is a special case and is always
 * converted to "-1".
 * Returns a pointer to the caller-supplied buffer.
 */
static char *
number_to_scaled_string(
			numbuf_t buf,		/* put the result here */
			unsigned long long number, /* convert this number */
			int unit_from,
			int scale)
{
	unsigned long long save = 0;
	char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */
	char *uom = M;    /* unit of measurement, initially 'K' (=M[0]) */

	if ((long long)number == (long long)-1) {
		(void) strcpy(buf, "-1");
		return (buf);
	}

	/*
	 * Convert number from unit_from to given scale (1024 or 1000).
	 * This means multiply number by unit_from and divide by scale.
	 *
	 * Would like to multiply by unit_from and then divide by scale,
	 * but if the first multiplication would overflow, then need to
	 * divide by scale and then multiply by unit_from.
	 */
	if (number > (UINT64_MAX / (unsigned long long)unit_from)) {
		number = (number / (unsigned long long)scale) *
		    (unsigned long long)unit_from;
	} else {
		number = (number * (unsigned long long)unit_from) /
		    (unsigned long long)scale;
	}

	/*
	 * Now we have number as a count of scale units.
	 * Stop scaling when we reached exa bytes, then something is
	 * probably wrong with our number.
	 */

	while ((number >= scale) && (*uom != 'E')) {
		uom++; /* next unit of measurement */
		save = number;
		number = (number + (scale / 2)) / scale;
	}
	/* check if we should output a decimal place after the point */
	if (save && ((save / scale) < 10)) {
		/* sprintf() will round for us */
		float fnum = (float)save / scale;
		(void) sprintf(buf, "%2.1f%c", fnum, *uom);
	} else {
		(void) sprintf(buf, "%4llu%c", number, *uom);
	}
	return (buf);
}

/*
 * The statvfs() implementation allows us to return only two values, the total
 * number of blocks and the number of blocks free.  The equation 'used = total -
 * free' will not work for ZFS filesystems, due to the nature of pooled storage.
 * We choose to return values in the statvfs structure that will produce correct
 * results for 'used' and 'available', but not 'total'.  This function will open
 * the underlying ZFS dataset if necessary and get the real value.
 */
static void
adjust_total_blocks(struct df_request *dfrp, fsblkcnt64_t *total,
    uint64_t blocksize)
{
	zfs_handle_t	*zhp;
	char *dataset, *slash;
	uint64_t quota;

	if (strcmp(DFR_FSTYPE(dfrp), MNTTYPE_ZFS) != 0 ||
	    !load_libzfs())
		return;

	/*
	 * We want to get the total size for this filesystem as bounded by any
	 * quotas. In order to do this, we start at the current filesystem and
	 * work upwards until we find a dataset with a quota.  If we reach the
	 * pool itself, then the total space is the amount used plus the amount
	 * available.
	 */
	if ((dataset = strdup(DFR_SPECIAL(dfrp))) == NULL)
		return;

	slash = dataset + strlen(dataset);
	do {
		*slash = '\0';

		if ((zhp = _zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET))
		    == NULL) {
			free(dataset);
			return;
		}

		if ((quota = _zfs_prop_get_int(zhp, ZFS_PROP_QUOTA)) != 0) {
			*total = quota / blocksize;
			_zfs_close(zhp);
			free(dataset);
			return;
		}

		_zfs_close(zhp);

	} while ((slash = strrchr(dataset, '/')) != NULL);


	if ((zhp = _zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET)) == NULL) {
		free(dataset);
		return;
	}

	*total = (_zfs_prop_get_int(zhp, ZFS_PROP_USED) +
	    _zfs_prop_get_int(zhp, ZFS_PROP_AVAILABLE)) / blocksize;

	_zfs_close(zhp);
	free(dataset);
}

/*
 * The output will appear properly columnized regardless of the names of
 * the various fields
 */
static void
g_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	fsblkcnt64_t	available_blocks	= fsp->f_bavail;
	fsblkcnt64_t	total_blocks = fsp->f_blocks;
	numbuf_t	total_blocks_buf;
	numbuf_t	total_files_buf;
	numbuf_t	free_blocks_buf;
	numbuf_t	available_blocks_buf;
	numbuf_t	free_files_buf;
	numbuf_t	fname_buf;
	char		*temp_buf;

#define	DEFINE_STR_LEN(var)			\
	static char *var##_str;			\
	static size_t var##_len

#define	SET_STR_LEN(name, var)\
	if (! var##_str) {\
		var##_str = TRANSLATE(name); \
		var##_len = strlen(var##_str); \
	}

	DEFINE_STR_LEN(block_size);
	DEFINE_STR_LEN(frag_size);
	DEFINE_STR_LEN(total_blocks);
	DEFINE_STR_LEN(free_blocks);
	DEFINE_STR_LEN(available);
	DEFINE_STR_LEN(total_files);
	DEFINE_STR_LEN(free_files);
	DEFINE_STR_LEN(fstype);
	DEFINE_STR_LEN(fsys_id);
	DEFINE_STR_LEN(fname);
	DEFINE_STR_LEN(flag);

	/*
	 * TRANSLATION_NOTE
	 * The first argument of each of the following macro invocations is a
	 * string that needs to be translated.
	 */
	SET_STR_LEN("block size", block_size);
	SET_STR_LEN("frag size", frag_size);
	SET_STR_LEN("total blocks", total_blocks);
	SET_STR_LEN("free blocks", free_blocks);
	SET_STR_LEN("available", available);
	SET_STR_LEN("total files", total_files);
	SET_STR_LEN("free files", free_files);
	SET_STR_LEN("fstype", fstype);
	SET_STR_LEN("filesys id", fsys_id);
	SET_STR_LEN("filename length", fname);
	SET_STR_LEN("flag", flag);

#define	NCOL1_WIDTH	(int)MAX3(BLOCK_WIDTH, NFILES_WIDTH, FSTYPE_WIDTH)
#define	NCOL2_WIDTH	(int)MAX3(BLOCK_WIDTH, FSID_WIDTH, FLAG_WIDTH) + 2
#define	NCOL3_WIDTH	(int)MAX3(BSIZE_WIDTH, BLOCK_WIDTH, NAMELEN_WIDTH)
#define	NCOL4_WIDTH	(int)MAX(FRAGSIZE_WIDTH, NFILES_WIDTH)

#define	SCOL1_WIDTH	(int)MAX3(total_blocks_len, free_files_len, fstype_len)
#define	SCOL2_WIDTH	(int)MAX3(free_blocks_len, fsys_id_len, flag_len)
#define	SCOL3_WIDTH	(int)MAX3(block_size_len, available_len, fname_len)
#define	SCOL4_WIDTH	(int)MAX(frag_size_len, total_files_len)

	temp_buf = xmalloc(
	    MAX(MOUNT_POINT_WIDTH, strlen(DFR_MOUNT_POINT(dfrp)))
	    + MAX(SPECIAL_DEVICE_WIDTH, strlen(DFR_SPECIAL(dfrp)))
	    + 20); /* plus slop - nulls & formatting */
	(void) sprintf(temp_buf, "%-*s(%-*s):",
	    MOUNT_POINT_WIDTH, DFR_MOUNT_POINT(dfrp),
	    SPECIAL_DEVICE_WIDTH, DFR_SPECIAL(dfrp));

	(void) printf("%-*s %*lu %-*s %*lu %-*s\n",
	    NCOL1_WIDTH + 1 + SCOL1_WIDTH + 1 + NCOL2_WIDTH + 1 +  SCOL2_WIDTH,
	    temp_buf,
	    NCOL3_WIDTH, fsp->f_bsize, SCOL3_WIDTH, block_size_str,
	    NCOL4_WIDTH, fsp->f_frsize, SCOL4_WIDTH, frag_size_str);
	free(temp_buf);

	/*
	 * Adjust available_blocks value -  it can be less than 0 on
	 * a 4.x file system. Reset it to 0 in order to avoid printing
	 * negative numbers.
	 */
	if ((long long)available_blocks < (long long)0)
		available_blocks = (fsblkcnt64_t)0;

	adjust_total_blocks(dfrp, &total_blocks, fsp->f_frsize);

	(void) printf("%*s %-*s %*s %-*s %*s %-*s %*s %-*s\n",
	    NCOL1_WIDTH, number_to_string(total_blocks_buf,
	    total_blocks, fsp->f_frsize, 512),
	    SCOL1_WIDTH, total_blocks_str,
	    NCOL2_WIDTH, number_to_string(free_blocks_buf,
	    fsp->f_bfree, fsp->f_frsize, 512),
	    SCOL2_WIDTH, free_blocks_str,
	    NCOL3_WIDTH, number_to_string(available_blocks_buf,
	    available_blocks, fsp->f_frsize, 512),
	    SCOL3_WIDTH, available_str,
	    NCOL4_WIDTH, number_to_string(total_files_buf,
	    fsp->f_files, 1, 1),
	    SCOL4_WIDTH, total_files_str);

	(void) printf("%*s %-*s %*lu %-*s %s\n",
	    NCOL1_WIDTH, number_to_string(free_files_buf,
	    fsp->f_ffree, 1, 1),
	    SCOL1_WIDTH, free_files_str,
	    NCOL2_WIDTH, fsp->f_fsid, SCOL2_WIDTH, fsys_id_str,
	    fsp->f_fstr);

	(void) printf("%*s %-*s %#*.*lx %-*s %*s %-*s\n\n",
	    NCOL1_WIDTH, fsp->f_basetype, SCOL1_WIDTH, fstype_str,
	    NCOL2_WIDTH, NCOL2_WIDTH-2, fsp->f_flag, SCOL2_WIDTH, flag_str,
	    NCOL3_WIDTH, number_to_string(fname_buf,
	    (unsigned long long)fsp->f_namemax, 1, 1),
	    SCOL3_WIDTH, fname_str);
}


static void
k_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	fsblkcnt64_t total_blocks		= fsp->f_blocks;
	fsblkcnt64_t	free_blocks		= fsp->f_bfree;
	fsblkcnt64_t	available_blocks	= fsp->f_bavail;
	fsblkcnt64_t	used_blocks;
	char 		*file_system		= DFR_SPECIAL(dfrp);
	numbuf_t	total_blocks_buf;
	numbuf_t	used_blocks_buf;
	numbuf_t	available_blocks_buf;
	char 		capacity_buf[LINEBUF_SIZE];

	/*
	 * If the free block count is -1, don't trust anything but the total
	 * number of blocks.
	 */
	if (free_blocks == (fsblkcnt64_t)-1) {
		used_blocks = (fsblkcnt64_t)-1;
		(void) strcpy(capacity_buf, "  100%");
	} else {
		fsblkcnt64_t reserved_blocks = free_blocks - available_blocks;

		used_blocks	= total_blocks - free_blocks;

		/*
		 * The capacity estimation is bogus when available_blocks is 0
		 * and the super-user has allocated more space. The reason
		 * is that reserved_blocks is inaccurate in that case, because
		 * when the super-user allocates space, free_blocks is updated
		 * but available_blocks is not (since it can't drop below 0).
		 *
		 * XCU4 and POSIX.2 require that any fractional result of the
		 * capacity estimation be rounded to the next highest integer,
		 * hence the addition of 0.5.
		 */
		(void) sprintf(capacity_buf, "%5.0f%%",
		    (total_blocks == 0) ? 0.0 :
		    ((double)used_blocks /
		    (double)(total_blocks - reserved_blocks))
		    * 100.0 + 0.5);
	}

	/*
	 * The available_blocks can be less than 0 on a 4.x file system.
	 * Reset it to 0 in order to avoid printing negative numbers.
	 */
	if ((long long)available_blocks < (long long)0)
		available_blocks = (fsblkcnt64_t)0;
	/*
	 * Print long special device names (usually NFS mounts) in a line
	 * by themselves when the output is directed to a terminal.
	 */
	if (tty_output && strlen(file_system) > (size_t)FILESYSTEM_WIDTH) {
		(void) printf("%s\n", file_system);
		file_system = "";
	}

	adjust_total_blocks(dfrp, &total_blocks, fsp->f_frsize);

	if (use_scaling) { /* comes from the -h option */
	(void) printf("%-*s %*s %*s %*s %-*s %-s\n",
	    FILESYSTEM_WIDTH, file_system,
	    SCALED_WIDTH, number_to_scaled_string(total_blocks_buf,
	    total_blocks, fsp->f_frsize, scale),
	    SCALED_WIDTH, number_to_scaled_string(used_blocks_buf,
	    used_blocks, fsp->f_frsize, scale),
	    AVAILABLE_WIDTH, number_to_scaled_string(available_blocks_buf,
	    available_blocks, fsp->f_frsize, scale),
	    CAPACITY_WIDTH, capacity_buf,
	    DFR_MOUNT_POINT(dfrp));
		return;
	}

	if (v_option) {
	(void) printf("%-*.*s %-*.*s %*lld %*lld %*lld %-.*s\n",
	    IBCS2_MOUNT_POINT_WIDTH, IBCS2_MOUNT_POINT_WIDTH,
	    DFR_MOUNT_POINT(dfrp),
	    IBCS2_FILESYSTEM_WIDTH, IBCS2_FILESYSTEM_WIDTH, file_system,
	    BLOCK_WIDTH, total_blocks,
	    BLOCK_WIDTH, used_blocks,
	    BLOCK_WIDTH, available_blocks,
	    CAPACITY_WIDTH,	capacity_buf);
		return;
	}

	if (P_option && !k_option) {
	(void) printf("%-*s %*s %*s %*s %-*s %-s\n",
	    FILESYSTEM_WIDTH, file_system,
	    KBYTE_WIDTH, number_to_string(total_blocks_buf,
	    total_blocks, fsp->f_frsize, 512),
	    KBYTE_WIDTH, number_to_string(used_blocks_buf,
	    used_blocks, fsp->f_frsize, 512),
	    KBYTE_WIDTH, number_to_string(available_blocks_buf,
	    available_blocks, fsp->f_frsize, 512),
	    CAPACITY_WIDTH, capacity_buf,
	    DFR_MOUNT_POINT(dfrp));
	} else {
	(void) printf("%-*s %*s %*s %*s %-*s %-s\n",
	    FILESYSTEM_WIDTH, file_system,
	    KBYTE_WIDTH, number_to_string(total_blocks_buf,
	    total_blocks, fsp->f_frsize, 1024),
	    KBYTE_WIDTH, number_to_string(used_blocks_buf,
	    used_blocks, fsp->f_frsize, 1024),
	    KBYTE_WIDTH, number_to_string(available_blocks_buf,
	    available_blocks, fsp->f_frsize, 1024),
	    CAPACITY_WIDTH,	capacity_buf,
	    DFR_MOUNT_POINT(dfrp));
	}
}

/*
 * The following is for internationalization support.
 */
static bool_int strings_initialized;
static char 	*files_str;
static char	*blocks_str;
static char	*total_str;
static char	*kilobytes_str;

static void
strings_init(void)
{
	total_str = TRANSLATE("total");
#ifdef	_iBCS2
	/* ISC/SCO print i-nodes instead of files */
	if (sysv3_set)
		files_str = TRANSLATE("i-nodes");
	else
#endif	/* _iBCS2 */
		files_str = TRANSLATE("files");
	blocks_str = TRANSLATE("blocks");
	kilobytes_str = TRANSLATE("kilobytes");
	strings_initialized = TRUE;
}

#define	STRINGS_INIT()		if (!strings_initialized) strings_init()


static void
t_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	fsblkcnt64_t	total_blocks = fsp->f_blocks;
	numbuf_t	total_blocks_buf;
	numbuf_t	total_files_buf;
	numbuf_t	free_blocks_buf;
	numbuf_t	free_files_buf;

	STRINGS_INIT();

	adjust_total_blocks(dfrp, &total_blocks, fsp->f_frsize);

	(void) printf("%-*s(%-*s): %*s %s %*s %s\n",
	    MOUNT_POINT_WIDTH, DFR_MOUNT_POINT(dfrp),
	    SPECIAL_DEVICE_WIDTH, DFR_SPECIAL(dfrp),
	    BLOCK_WIDTH, number_to_string(free_blocks_buf,
	    fsp->f_bfree, fsp->f_frsize, 512),
	    blocks_str,
	    NFILES_WIDTH, number_to_string(free_files_buf,
	    fsp->f_ffree, 1, 1),
	    files_str);
	/*
	 * The total column used to use the same space as the mnt pt & special
	 * dev fields. However, this doesn't work with massive special dev
	 * fields * (eg > 500 chars) causing an enormous amount of white space
	 * before the total column (see bug 4100411). So the code was
	 * simplified to set the total column at the usual gap.
	 * This had the side effect of fixing a bug where the previously
	 * used static buffer was overflowed by the same massive special dev.
	 */
	(void) printf("%*s: %*s %s %*s %s\n",
	    MNT_SPEC_WIDTH, total_str,
	    BLOCK_WIDTH, number_to_string(total_blocks_buf,
	    total_blocks, fsp->f_frsize, 512),
	    blocks_str,
	    NFILES_WIDTH, number_to_string(total_files_buf,
	    fsp->f_files, 1, 1),
	    files_str);
}


static void
eb_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	numbuf_t free_files_buf;
	numbuf_t free_kbytes_buf;

	STRINGS_INIT();

	(void) printf("%-*s(%-*s): %*s %s\n",
	    MOUNT_POINT_WIDTH, DFR_MOUNT_POINT(dfrp),
	    SPECIAL_DEVICE_WIDTH, DFR_SPECIAL(dfrp),
	    MAX(KBYTE_WIDTH, NFILES_WIDTH),
	    number_to_string(free_kbytes_buf,
	    fsp->f_bfree, fsp->f_frsize, 1024),
	    kilobytes_str);
	(void) printf("%-*s(%-*s): %*s %s\n",
	    MOUNT_POINT_WIDTH, DFR_MOUNT_POINT(dfrp),
	    SPECIAL_DEVICE_WIDTH, DFR_SPECIAL(dfrp),
	    MAX(NFILES_WIDTH, NFILES_WIDTH),
	    number_to_string(free_files_buf, fsp->f_ffree, 1, 1),
	    files_str);
}


static void
e_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	numbuf_t free_files_buf;

	(void) printf("%-*s %*s\n",
	    FILESYSTEM_WIDTH, DFR_SPECIAL(dfrp),
	    NFILES_WIDTH,
	    number_to_string(free_files_buf, fsp->f_ffree, 1, 1));
}


static void
b_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	numbuf_t free_blocks_buf;

	(void) printf("%-*s %*s\n",
	    FILESYSTEM_WIDTH, DFR_SPECIAL(dfrp),
	    BLOCK_WIDTH, number_to_string(free_blocks_buf,
	    fsp->f_bfree, fsp->f_frsize, 1024));
}


/* ARGSUSED */
static void
n_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	(void) printf("%-*s: %-*s\n",
	    MOUNT_POINT_WIDTH, DFR_MOUNT_POINT(dfrp),
	    FSTYPE_WIDTH, dfrp->dfr_fstype);
}


static void
default_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	numbuf_t free_blocks_buf;
	numbuf_t free_files_buf;

	STRINGS_INIT();

	(void) printf("%-*s(%-*s):%*s %s %*s %s\n",
	    MOUNT_POINT_WIDTH, DFR_MOUNT_POINT(dfrp),
	    SPECIAL_DEVICE_WIDTH, DFR_SPECIAL(dfrp),
	    BLOCK_WIDTH, number_to_string(free_blocks_buf,
	    fsp->f_bfree, fsp->f_frsize, 512),
	    blocks_str,
	    NFILES_WIDTH, number_to_string(free_files_buf,
	    fsp->f_ffree, 1, 1),
	    files_str);
}


/* ARGSUSED */
static void
V_output(struct df_request *dfrp, struct statvfs64 *fsp)
{
	char temp_buf[LINEBUF_SIZE];

	if (df_options_len > 1)
		(void) strcat(strcpy(temp_buf, df_options), " ");
	else
		temp_buf[0] = NUL;

	(void) printf("%s -F %s %s%s\n",
	    program_name, dfrp->dfr_fstype, temp_buf,
	    dfrp->dfr_cmd_arg ? dfrp->dfr_cmd_arg: DFR_SPECIAL(dfrp));
}


/*
 * This function is used to sort the array of df_requests according to fstype
 */
static int
df_reqcomp(const void *p1, const void *p2)
{
	int v = strcmp(DFRP(p1)->dfr_fstype, DFRP(p2)->dfr_fstype);

	if (v != 0)
		return (v);
	else
		return (DFRP(p1)->dfr_index - DFRP(p2)->dfr_index);
}


static void
vfs_error(char *file, int status)
{
	if (status == VFS_TOOLONG)
		errmsg(ERR_NOFLAGS, "a line in %s exceeds %d characters",
		    file, MNT_LINE_MAX);
	else if (status == VFS_TOOMANY)
		errmsg(ERR_NOFLAGS, "a line in %s has too many fields", file);
	else if (status == VFS_TOOFEW)
		errmsg(ERR_NOFLAGS, "a line in %s has too few fields", file);
	else
		errmsg(ERR_NOFLAGS, "error while reading %s: %d", file, status);
}


/*
 * Try to determine the fstype for the specified block device.
 * Return in order of decreasing preference:
 *	file system type from vfstab
 *	file system type as specified by -F option
 *	default file system type
 */
static char *
find_fstype(char *special)
{
	struct vfstab	vtab;
	FILE		*fp;
	int		status;
	char		*vfstab_file = VFS_TAB;

	fp = xfopen(vfstab_file);
	status = getvfsspec(fp, &vtab, special);
	(void) fclose(fp);
	if (status > 0)
		vfs_error(vfstab_file, status);

	if (status == 0) {
		if (F_option && ! EQ(FSType, vtab.vfs_fstype))
			errmsg(ERR_NOFLAGS,
			"warning: %s is of type %s", special, vtab.vfs_fstype);
		return (new_string(vtab.vfs_fstype));
	}
	else
		return (F_option ? FSType : default_fstype(special));
}

/*
 * When this function returns, the following fields are filled for all
 * valid entries in the requests[] array:
 *		dfr_mte		(if the file system is mounted)
 *		dfr_fstype
 *		dfr_index
 *
 * The function returns the number of errors that occurred while building
 * the request list.
 */
static int
create_request_list(
			int argc,
			char *argv[],
			struct df_request *requests_p[],
			size_t *request_count)
{
	struct df_request	*requests;
	struct df_request	*dfrp;
	size_t			size;
	size_t 			i;
	size_t 			request_index = 0;
	size_t			max_requests;
	int			errors = 0;

	/*
	 * If no args, use the mounted file systems, otherwise use the
	 * user-specified arguments.
	 */
	if (argc == 0) {
		mtab_read_file();
		max_requests = mount_table_entries;
	} else
		max_requests = argc;

	size = max_requests * sizeof (struct df_request);
	requests = xmalloc(size);
	(void) memset(requests, 0, size);

	if (argc == 0) {
		/*
		 * If -Z wasn't specified, we skip mounts in other
		 * zones.  This obviously is a noop in a non-global
		 * zone.
		 */
		boolean_t showall = (getzoneid() != GLOBAL_ZONEID) || Z_option;
		struct zone_summary *zsp;

		if (!showall) {
			zsp = fs_get_zone_summaries();
			if (zsp == NULL)
				errmsg(ERR_FATAL,
				    "unable to retrieve list of zones");
		}

		for (i = 0; i < mount_table_entries; i++) {
			struct extmnttab *mtp = mount_table[i].mte_mount;

			if (EQ(mtp->mnt_fstype, MNTTYPE_SWAP))
				continue;

			if (!showall) {
				if (fs_mount_in_other_zone(zsp,
				    mtp->mnt_mountp))
					continue;
			}
			dfrp = &requests[request_index++];
			dfrp->dfr_mte		= &mount_table[i];
			dfrp->dfr_fstype	= mtp->mnt_fstype;
			dfrp->dfr_index		= i;
			dfrp->dfr_valid		= TRUE;
		}
	} else {
		struct stat64 *arg_stat; /* array of stat structures	*/
		bool_int *valid_stat;	/* which structures are valid	*/

		arg_stat = xmalloc(argc * sizeof (struct stat64));
		valid_stat = xmalloc(argc * sizeof (bool_int));

		/*
		 * Obtain stat64 information for each argument before
		 * constructing the list of mounted file systems. By
		 * touching all these places we force the automounter
		 * to establish any mounts required to access the arguments,
		 * so that the corresponding mount table entries will exist
		 * when we look for them.
		 * It is still possible that the automounter may timeout
		 * mounts between the time we read the mount table and the
		 * time we process the request. Even in that case, when
		 * we issue the statvfs64(2) for the mount point, the file
		 * system will be mounted again. The only problem will
		 * occur if the automounter maps change in the meantime
		 * and the mount point is eliminated.
		 */
		for (i = 0; i < argc; i++)
			valid_stat[i] = (stat64(argv[i], &arg_stat[i]) == 0);

		mtab_read_file();

		for (i = 0; i < argc; i++) {
			char *arg = argv[i];

			dfrp = &requests[request_index];

			dfrp->dfr_index = request_index;
			dfrp->dfr_cmd_arg = arg;

			if (valid_stat[i]) {
				if (S_ISBLK(arg_stat[i].st_mode)) {
					bdev_mount_entry(dfrp);
					dfrp->dfr_valid = TRUE;
				} else if (S_ISDIR(arg_stat[i].st_mode) ||
				    S_ISREG(arg_stat[i].st_mode) ||
				    S_ISFIFO(arg_stat[i].st_mode)) {
					path_mount_entry(dfrp,
					    arg_stat[i].st_dev);
					if (! DFR_ISMOUNTEDFS(dfrp)) {
						errors++;
						continue;
					}
					dfrp->dfr_valid = TRUE;
				}
			} else {
				resource_mount_entry(dfrp);
				dfrp->dfr_valid = DFR_ISMOUNTEDFS(dfrp);
			}

			/*
			 * If we haven't managed to verify that the request
			 * is valid, we must have gotten a bad argument.
			 */
			if (!dfrp->dfr_valid) {
				errmsg(ERR_NOFLAGS,
				    "(%-10s) not a block device, directory or "
				    "mounted resource", arg);
				errors++;
				continue;
			}

			/*
			 * Determine the file system type.
			 */
			if (DFR_ISMOUNTEDFS(dfrp))
				dfrp->dfr_fstype =
				    dfrp->dfr_mte->mte_mount->mnt_fstype;
			else
				dfrp->dfr_fstype =
				    find_fstype(dfrp->dfr_cmd_arg);

			request_index++;
		}
	}
	*requests_p = requests;
	*request_count = request_index;
	return (errors);
}


/*
 * Select the appropriate function and flags to use for output.
 * Notice that using both -e and -b options produces a different form of
 * output than either of those two options alone; this is the behavior of
 * the SVR4 df.
 */
static struct df_output *
select_output(void)
{
	static struct df_output dfo;

	/*
	 * The order of checking options follows the option precedence
	 * rules as they are listed in the man page.
	 */
	if (use_scaling) { /* comes from the -h option */
		dfo.dfo_func = k_output;
		dfo.dfo_flags = DFO_HEADER + DFO_STATVFS;
	} else if (V_option) {
		dfo.dfo_func = V_output;
		dfo.dfo_flags = DFO_NOFLAGS;
	} else if (g_option) {
		dfo.dfo_func = g_output;
		dfo.dfo_flags = DFO_STATVFS;
	} else if (k_option || P_option || v_option) {
		dfo.dfo_func = k_output;
		dfo.dfo_flags = DFO_HEADER + DFO_STATVFS;
	} else if (t_option) {
		dfo.dfo_func = t_output;
		dfo.dfo_flags = DFO_STATVFS;
	} else if (b_option && e_option) {
		dfo.dfo_func = eb_output;
		dfo.dfo_flags = DFO_STATVFS;
	} else if (b_option) {
		dfo.dfo_func = b_output;
		dfo.dfo_flags = DFO_HEADER + DFO_STATVFS;
	} else if (e_option) {
		dfo.dfo_func = e_output;
		dfo.dfo_flags = DFO_HEADER + DFO_STATVFS;
	} else if (n_option) {
		dfo.dfo_func = n_output;
		dfo.dfo_flags = DFO_NOFLAGS;
	} else {
		dfo.dfo_func = default_output;
		dfo.dfo_flags = DFO_STATVFS;
	}
	return (&dfo);
}


/*
 * The (argc,argv) pair contains all the non-option arguments
 */
static void
do_df(int argc, char *argv[])
{
	size_t			i;
	struct df_request	*requests;		/* array of requests */
	size_t			n_requests;
	struct df_request	*dfrp;
	int			errors;

	errors = create_request_list(argc, argv, &requests, &n_requests);

	if (n_requests == 0)
		exit(errors);

	/*
	 * If we are going to run the FSType-specific df command,
	 * rearrange the requests so that we can issue a single command
	 * per file system type.
	 */
	if (o_option) {
		size_t j;

		/*
		 * qsort is not a stable sorting method (i.e. requests of
		 * the same file system type may be swapped, and hence appear
		 * in the output in a different order from the one in which
		 * they were listed in the command line). In order to force
		 * stability, we use the dfr_index field which is unique
		 * for each request.
		 */
		qsort(requests,
		    n_requests, sizeof (struct df_request), df_reqcomp);
		for (i = 0; i < n_requests; i = j) {
			char *fstype = requests[i].dfr_fstype;

			for (j = i+1; j < n_requests; j++)
				if (! EQ(fstype, requests[j].dfr_fstype))
					break;

			/*
			 * At this point, requests in the range [i,j) are
			 * of the same type.
			 *
			 * If the -F option was used, and the user specified
			 * arguments, the filesystem types must match
			 *
			 * XXX: the alternative of doing this check here is to
			 * 	invoke prune_list, but then we have to
			 *	modify this code to ignore invalid requests.
			 */
			if (F_option && ! EQ(fstype, FSType)) {
				size_t k;

				for (k = i; k < j; k++) {
					dfrp = &requests[k];
					if (dfrp->dfr_cmd_arg != NULL) {
						errmsg(ERR_NOFLAGS,
						    "Warning: %s mounted as a "
						    "%s file system",
						    dfrp->dfr_cmd_arg,
						    dfrp->dfr_fstype);
						errors++;
					}
				}
			} else
				errors += run_fs_specific_df(&requests[i], j-i);
		}
	} else {
		size_t valid_requests;

		/*
		 * We have to prune the request list to avoid printing a header
		 * if there are no valid requests
		 */
		errors += prune_list(requests, n_requests, &valid_requests);

		if (valid_requests) {
			struct df_output *dfop = select_output();

			/* indicates if we already printed out a header line */
			int printed_header = 0;

			for (i = 0; i < n_requests; i++) {
				dfrp = &requests[i];
				if (! dfrp->dfr_valid)
					continue;

				/*
				 * If we don't have a mount point,
				 * this must be a block device.
				 */
				if (DFR_ISMOUNTEDFS(dfrp)) {
					struct statvfs64 stvfs;

					if ((dfop->dfo_flags & DFO_STATVFS) &&
					    statvfs64(DFR_MOUNT_POINT(dfrp),
					    &stvfs) == -1) {
						errmsg(ERR_PERROR,
						    "cannot statvfs %s:",
						    DFR_MOUNT_POINT(dfrp));
						errors++;
						continue;
					}
					if ((!printed_header) &&
					    (dfop->dfo_flags & DFO_HEADER)) {
						print_header();
						printed_header = 1;
					}

					(*dfop->dfo_func)(dfrp, &stvfs);
				} else {
					/*
					 *  -h option only works for
					 *  mounted filesystems
					 */
					if (use_scaling) {
						errmsg(ERR_NOFLAGS,
		"-h option incompatible with unmounted special device (%s)",
						    dfrp->dfr_cmd_arg);
						errors++;
						continue;
					}
					errors += run_fs_specific_df(dfrp, 1);
				}
			}
		}
	}
	exit(errors);
}


/*
 * The rest of this file implements the devnm command
 */

static char *
find_dev_name(char *file, dev_t dev)
{
	struct df_request dfreq;

	dfreq.dfr_cmd_arg = file;
	dfreq.dfr_fstype = 0;
	dfreq.dfr_mte = NULL;
	path_mount_entry(&dfreq, dev);
	return (DFR_ISMOUNTEDFS(&dfreq) ? DFR_SPECIAL(&dfreq) : NULL);
}


static void
do_devnm(int argc, char *argv[])
{
	int arg;
	int errors = 0;
	char *dev_name;

	if (argc == 1)
		errmsg(ERR_NONAME, "Usage: %s name ...", DEVNM_CMD);

	mtab_read_file();

	for (arg = 1; arg < argc; arg++) {
		char *file = argv[arg];
		struct stat64 st;

		if (stat64(file, &st) == -1) {
			errmsg(ERR_PERROR, "%s: ", file);
			errors++;
			continue;
		}

		if (! is_remote_fs(st.st_fstype) &&
		    ! EQ(st.st_fstype, MNTTYPE_TMPFS) &&
		    (dev_name = find_dev_name(file, st.st_dev)))
			(void) printf("%s %s\n", dev_name, file);
		else
			errmsg(ERR_NOFLAGS,
			    "%s not found", file);
	}
	exit(errors);
	/* NOTREACHED */
}