usr/src/uts/common/fs/nfs/nfs4_srv_attr.c
author xs154138
Thu, 25 May 2006 02:58:46 -0700
changeset 2058 40d3788a5679
parent 1232 1a9b1651d839
child 5331 3047ad28a67b
permissions -rw-r--r--
6362990 server returns OK on NVERIFY of maxlink/maxfilesize with NFSv4/ZFS 6366301 CREATE with owner_group attribute is not set correctly with NFSv4/ZFS 6373971 xdr_cnfs_resop4_wrap is dead code

/*
 * 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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <sys/systm.h>
#include <sys/cmn_err.h>
#include <nfs/nfs.h>
#include <nfs/export.h>
#include <nfs/nfs4.h>
#include <sys/ddi.h>

void	rfs4_init_compound_state(struct compound_state *);

bitmap4 rfs4_supported_attrs;
int MSG_PRT_DEBUG = FALSE;

/* If building with DEBUG enabled, enable mandattr tuneable by default */
#ifdef DEBUG
#ifndef RFS4_SUPPORT_MANDATTR_ONLY
#define	RFS4_SUPPORT_MANDATTR_ONLY
#endif
#endif

/*
 * If building with mandattr only code, disable it by default.
 * To enable, set rfs4_mandattr_only in /etc/system and reboot.
 * When building without mandattr ifdef, the compiler should
 * optimize away the the comparisons because RFS4_MANDATTR_ONLY
 * is defined to be 0.
 */
#ifdef RFS4_SUPPORT_MANDATTR_ONLY
#define	NFS4_LAST_MANDATTR FATTR4_RDATTR_ERROR
#define	RFS4_MANDATTR_ONLY rfs4_mandattr_only
int rfs4_mandattr_only = 0;
#else
#define	RFS4_MANDATTR_ONLY 0
#endif


static void rfs4_ntov_init(void);
static int rfs4_fattr4_supported_attrs();
static int rfs4_fattr4_type();
static int rfs4_fattr4_fh_expire_type();
static int rfs4_fattr4_change();
static int rfs4_fattr4_size();
static int rfs4_fattr4_link_support();
static int rfs4_fattr4_symlink_support();
static int rfs4_fattr4_named_attr();
static int rfs4_fattr4_fsid();
static int rfs4_fattr4_unique_handles();
static int rfs4_fattr4_lease_time();
static int rfs4_fattr4_rdattr_error();
static int rfs4_fattr4_acl();
static int rfs4_fattr4_aclsupport();
static int rfs4_fattr4_archive();
static int rfs4_fattr4_cansettime();
static int rfs4_fattr4_case_insensitive();
static int rfs4_fattr4_case_preserving();
static int rfs4_fattr4_chown_restricted();
static int rfs4_fattr4_filehandle();
static int rfs4_fattr4_fileid();
static int rfs4_fattr4_files_avail();
static int rfs4_fattr4_files_free();
static int rfs4_fattr4_files_total();
static int rfs4_fattr4_fs_locations();
static int rfs4_fattr4_hidden();
static int rfs4_fattr4_homogeneous();
static int rfs4_fattr4_maxfilesize();
static int rfs4_fattr4_maxlink();
static int rfs4_fattr4_maxname();
static int rfs4_fattr4_maxread();
static int rfs4_fattr4_maxwrite();
static int rfs4_fattr4_mimetype();
static int rfs4_fattr4_mode();
static int rfs4_fattr4_no_trunc();
static int rfs4_fattr4_numlinks();
static int rfs4_fattr4_owner();
static int rfs4_fattr4_owner_group();
static int rfs4_fattr4_quota_avail_hard();
static int rfs4_fattr4_quota_avail_soft();
static int rfs4_fattr4_quota_used();
static int rfs4_fattr4_rawdev();
static int rfs4_fattr4_space_avail();
static int rfs4_fattr4_space_free();
static int rfs4_fattr4_space_total();
static int rfs4_fattr4_space_used();
static int rfs4_fattr4_system();
static int rfs4_fattr4_time_access();
static int rfs4_fattr4_time_access_set();
static int rfs4_fattr4_time_backup();
static int rfs4_fattr4_time_create();
static int rfs4_fattr4_time_delta();
static int rfs4_fattr4_time_metadata();
static int rfs4_fattr4_time_modify();
static int rfs4_fattr4_time_modify_set();

/*
 * Initialize the supported attributes
 */
void
rfs4_attr_init()
{
	int i;
	struct nfs4_svgetit_arg sarg;
	struct compound_state cs;
	struct statvfs64 sb;

	rfs4_init_compound_state(&cs);
	cs.vp = rootvp;
	cs.fh.nfs_fh4_val = NULL;
	cs.cr = kcred;

	/*
	 * Get all the supported attributes
	 */
	sarg.op = NFS4ATTR_SUPPORTED;
	sarg.cs = &cs;
	sarg.vap->va_mask = AT_ALL;
	sarg.sbp = &sb;
	sarg.flag = 0;
	sarg.rdattr_error = NFS4_OK;
	sarg.rdattr_error_req = FALSE;

	rfs4_ntov_init();

	rfs4_supported_attrs = 0;
	for (i = 0; i < NFS4_MAXNUM_ATTRS; i++) {
#ifdef RFS4_SUPPORT_MANDATTR_ONLY
		if (rfs4_mandattr_only == TRUE && i > NFS4_LAST_MANDATTR)
			continue;
#endif
		if ((*nfs4_ntov_map[i].sv_getit)(NFS4ATTR_SUPPORTED,
		    &sarg, NULL) == 0) {
			rfs4_supported_attrs |= nfs4_ntov_map[i].fbit;
		}
	}
}

/*
 * The following rfs4_fattr4_* functions convert between the fattr4
 * arguments/attributes and the system (e.g. vattr) values. The following
 * commands are currently in use:
 *
 * NFS4ATTR_SUPPORTED: checks if the attribute in question is supported:
 *	sarg.op = SUPPORTED - all supported attrs
 *	sarg.op = GETIT - only supported readable attrs
 *	sarg.op = SETIT - only supported writable attrs
 *
 * NFS4ATTR_GETIT: getattr type conversion - convert system values
 * (e.g. vattr struct) to fattr4 type values to be returned to the
 * user - usually in response to nfsv4 getattr request.
 *
 * NFS4ATTR_SETIT: convert fattr4 type values to system values to use by
 * setattr. Allows only read/write and write attributes,
 * even if not supported by the filesystem. Note that ufs only allows setattr
 * of owner/group, mode, size, atime/mtime.
 *
 * NFS4ATTR_VERIT: convert fattr4 type values to system values to use by
 * verify/nverify. Implemented to allow
 * almost everything that can be returned by getattr into known structs
 * (like vfsstat64 or vattr_t), that is, both read only and read/write attrs.
 * The function will return -1 if it found that the arguments don't match.
 * This applies to system-wide values that don't require a VOP_GETATTR
 * or other further checks to verify. It will return no error if they
 * either match or were retrieved successfully for later checking.
 *
 * NFS4ATTR_FREEIT: free up any space allocated by either of the above.
 * The sargp->op should be either NFS4ATTR_GETIT or NFS4ATTR_SETIT
 * to indicate which op was used to allocate the space.
 *
 * XXX Note: these functions are currently used by the server only. A
 * XXX different method of conversion is used on the client side.
 * XXX Eventually combining the two (possibly by adding NFS4ATTR_CLNT_GETIT
 * XXX and SETIT) may be a cleaner approach.
 */

/*
 * Mandatory attributes
 */

/* ARGSUSED */
static int
rfs4_fattr4_supported_attrs(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->supported_attrs = rfs4_supported_attrs;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		/*
		 * Compare the input bitmap to the server's bitmap
		 */
		if (na->supported_attrs != rfs4_supported_attrs) {
			error = -1;	/* no match */
		}
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/*
 * Translate vnode vtype to nfsv4_ftype.
 */
static nfs_ftype4 vt_to_nf4[] = {
	0, NF4REG, NF4DIR, NF4BLK, NF4CHR, NF4LNK, NF4FIFO, 0, 0, NF4SOCK, 0
};

/* ARGSUSED */
static int
rfs4_fattr4_type(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int		error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_TYPE)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_TYPE);

		/*
		 * if xattr flag not set, use v4_to_nf4 mapping;
		 * otherwise verify xattr flag is in sync with va_type
		 * and set xattr types.
		 */
		if (! (sarg->xattr & (FH4_NAMEDATTR | FH4_ATTRDIR)))
			na->type = vt_to_nf4[sarg->vap->va_type];
		else {
			/*
			 * FH4 flag was set.  Dir type maps to attrdir,
			 * and all other types map to namedattr.
			 */
			if (sarg->vap->va_type == VDIR)
				na->type = NF4ATTRDIR;
			else
				na->type = NF4NAMEDATTR;
		}
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		/*
		 * Compare the input type to the object type on server
		 */
		ASSERT(sarg->vap->va_mask & AT_TYPE);
		if (sarg->vap->va_type != nf4_to_vt[na->type])
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
fattr4_get_fh_expire_type(struct exportinfo *exi, uint32_t *fh_expire_typep)
{
#ifdef	VOLATILE_FH_TEST
	int	ex_flags;

	if (exi == NULL)
		return (ESTALE);
	ex_flags = exi->exi_export.ex_flags;
	if ((ex_flags & (EX_VOLFH | EX_VOLRNM | EX_VOLMIG | EX_NOEXPOPEN))
		== 0) {
		*fh_expire_typep = FH4_PERSISTENT;
		return (0);
	}
	*fh_expire_typep = 0;

	if (ex_flags & EX_NOEXPOPEN) {
		/* file handles should not expire with open - not used */
		*fh_expire_typep = FH4_NOEXPIRE_WITH_OPEN;
	}
	if (ex_flags & EX_VOLFH) {
		/*
		 * file handles may expire any time - on share here.
		 * If volatile any, no need to check other flags.
		 */
		*fh_expire_typep |= FH4_VOLATILE_ANY;
		return (0);
	}
	if (ex_flags & EX_VOLRNM) {
		/* file handles may expire on rename */
		*fh_expire_typep |= FH4_VOL_RENAME;
	}
	if (ex_flags & EX_VOLMIG) {
		/* file handles may expire on migration - not used */
		*fh_expire_typep |= FH4_VOL_MIGRATION;
	}
#else	/* not VOLATILE_FH_TEST */
	*fh_expire_typep = FH4_PERSISTENT;
#endif	/* VOLATILE_FH_TEST */

	return (0);
}

/*
 * At this point the only volatile filehandles we allow (for test purposes
 * only) are either fh's that expire when the filesystem is shared (reshared),
 * fh's that expire on a rename and persistent ones.
 */
/* ARGSUSED */
static int
rfs4_fattr4_fh_expire_type(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	uint32_t fh_expire_type;
	int error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		error = fattr4_get_fh_expire_type(sarg->cs->exi,
				&na->fh_expire_type);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		error = fattr4_get_fh_expire_type(sarg->cs->exi,
				&fh_expire_type);
		if (!error && (na->fh_expire_type != fh_expire_type))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

static int
fattr4_get_change(struct nfs4_svgetit_arg *sarg, fattr4_change *changep)
{
	vattr_t vap2[1], *vap = sarg->vap;
	struct compound_state *cs = sarg->cs;
	vnode_t *vp = cs->vp;
	nfsstat4 status;

	if ((vap->va_mask & AT_CTIME) == 0) {
		if (sarg->rdattr_error && (vp == NULL)) {
			return (-1);	/* may be okay if rdattr_error */
		}
		ASSERT(vp != NULL);
		vap = vap2;
		vap->va_mask = AT_CTIME;
		status = rfs4_vop_getattr(vp, vap, 0, cs->cr);
		if (status != NFS4_OK)
			return (geterrno4(status));
	}
	NFS4_SET_FATTR4_CHANGE(*changep, vap->va_ctime)
	return (0);
}

/* ARGSUSED */
static int
rfs4_fattr4_change(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;
	fattr4_change change;
	uint_t mask;
	vattr_t *vap = sarg->vap;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		error = fattr4_get_change(sarg, &na->change);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		mask = vap->va_mask;
		vap->va_mask &= ~AT_CTIME;	/* force a VOP_GETATTR */
		error = fattr4_get_change(sarg, &change);
		vap->va_mask = mask;
		if (!error && (na->change != change))
			error = -1;
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_size(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_SIZE)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_SIZE);
		na->size = sarg->vap->va_size;
		break;
	case NFS4ATTR_SETIT:
		ASSERT(sarg->vap->va_mask & AT_SIZE);
		sarg->vap->va_size = na->size;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_SIZE);
		if (sarg->vap->va_size != na->size)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/*
 * XXX - need VOP extension to ask file system (e.g. pcfs) if it supports
 * hard links.
 */
/* ARGSUSED */
static int
rfs4_fattr4_link_support(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->link_support = TRUE;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (!na->link_support)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/*
 * XXX - need VOP extension to ask file system (e.g. pcfs) if it supports
 * sym links.
 */
/* ARGSUSED */
static int
rfs4_fattr4_symlink_support(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->symlink_support = TRUE;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (!na->symlink_support)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_named_attr(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;
	ulong_t val;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->cs->vp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->cs->vp != NULL);

		/*
		 * Solaris xattr model requires that VFS_XATTR is set
		 * in file systems enabled for xattr.  If VFS_XATTR
		 * not set, no need to call pathconf.
		 */
		if (sarg->cs->vp->v_vfsp->vfs_flag & VFS_XATTR) {
			error = VOP_PATHCONF(sarg->cs->vp, _PC_XATTR_EXISTS,
					&val, sarg->cs->cr);
			if (error)
				break;
		} else
			val = 0;
		na->named_attr = (val ? TRUE : FALSE);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->cs->vp != NULL);
		if (sarg->cs->vp->v_vfsp->vfs_flag & VFS_XATTR) {
			error = VOP_PATHCONF(sarg->cs->vp, _PC_XATTR_EXISTS,
					&val, sarg->cs->cr);
			if (error)
				break;
		} else
			val = 0;
		if (na->named_attr != (val ? TRUE : FALSE))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_fsid(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;
	int *pmaj = (int *)&na->fsid.major;

	/*
	 * fsid_t is 64bits so it fits completely in fattr4_fsid.major.
	 * fattr4_fsid.minor is always set to 0 since it isn't needed (yet).
	 */
	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->cs->exi->exi_volatile_dev) {
			pmaj[0] = sarg->cs->exi->exi_fsid.val[0];
			pmaj[1] = sarg->cs->exi->exi_fsid.val[1];
			na->fsid.minor = 0;
		} else {
			na->fsid.major = getmajor(sarg->vap->va_fsid);
			na->fsid.minor = getminor(sarg->vap->va_fsid);
		}
		break;
	case NFS4ATTR_SETIT:
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (sarg->cs->exi->exi_volatile_dev) {
			if (pmaj[0] != sarg->cs->exi->exi_fsid.val[0] ||
			    pmaj[1] != sarg->cs->exi->exi_fsid.val[1] ||
			    na->fsid.minor != 0)
				error = -1;
		} else {
			if (na->fsid.major != getmajor(sarg->vap->va_fsid) ||
			    na->fsid.minor != getminor(sarg->vap->va_fsid))
				error = -1;
		}
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_unique_handles(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	/*
	 * XXX
	 * For now, we can't support this. Problem of /export, beinging
	 * a file system, /export/a and /export/b shared separately,
	 * and /export/a/l and /export/b/l are ahrd links of each other.
	 */
	int error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->unique_handles = FALSE;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (na->unique_handles)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_lease_time(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->lease_time = rfs4_lease_time;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (na->lease_time != rfs4_lease_time)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_rdattr_error(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if ((sarg->op == NFS4ATTR_SETIT) ||
			(sarg->op == NFS4ATTR_VERIT))
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		ASSERT(sarg->rdattr_error_req);
		na->rdattr_error = sarg->rdattr_error;
		break;
	case NFS4ATTR_SETIT:
	case NFS4ATTR_VERIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/*
 * Server side compare of a filehandle from the wire to a native
 * server filehandle.
 */
static int
rfs4fhcmp(nfs_fh4 *wirefh, nfs_fh4 *srvfh)
{
	nfs_fh4_fmt_t fh;

	ASSERT(IS_P2ALIGNED(wirefh->nfs_fh4_val, sizeof (uint32_t)));

	bzero(&fh, sizeof (nfs_fh4_fmt_t));
	if (!xdr_inline_decode_nfs_fh4((uint32_t *)wirefh->nfs_fh4_val, &fh,
	    wirefh->nfs_fh4_len))
		return (1);

	return (bcmp(srvfh->nfs_fh4_val, &fh, srvfh->nfs_fh4_len));
}

/* ARGSUSED */
static int
rfs4_fattr4_filehandle(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	nfs_fh4 *fh;

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			return (EINVAL);
		return (0);	/* this attr is supported */
	case NFS4ATTR_GETIT:
		/*
		 * If sarg->cs->fh is all zeros then should makefh a new
		 * one, otherwise, copy that one over.
		 */
		fh = &sarg->cs->fh;
		if (sarg->cs->fh.nfs_fh4_len == 0) {
			if (sarg->rdattr_error && (sarg->cs->vp == NULL))
				return (-1);	/* okay if rdattr_error */
			ASSERT(sarg->cs->vp != NULL);
			na->filehandle.nfs_fh4_val =
			    kmem_alloc(NFS_FH4_LEN, KM_SLEEP);
			return (makefh4(&na->filehandle, sarg->cs->vp,
			    sarg->cs->exi));
		}
		na->filehandle.nfs_fh4_val =
		    kmem_alloc(fh->nfs_fh4_len, KM_SLEEP);
		nfs_fh4_copy(fh, &na->filehandle);
		return (0);
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		return (EINVAL);
	case NFS4ATTR_VERIT:
		/*
		 * A verify of a filehandle will have the client sending
		 * the raw format which needs to be compared to the
		 * native format.
		 */
		if (rfs4fhcmp(&na->filehandle, &sarg->cs->fh) == 1)
			return (-1);	/* no match */
		return (0);
	case NFS4ATTR_FREEIT:
		if (sarg->op != NFS4ATTR_GETIT)
			return (0);
		if (na->filehandle.nfs_fh4_val == NULL)
			return (0);
		kmem_free(na->filehandle.nfs_fh4_val,
		    na->filehandle.nfs_fh4_len);
		na->filehandle.nfs_fh4_val = NULL;
		na->filehandle.nfs_fh4_len = 0;
		return (0);
	}
	return (0);
}

/*
 * Recommended attributes
 */

/* ARGSUSED */
static int
rfs4_fattr4_acl(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;
	vsecattr_t vs_native, vs_ace4;
	ulong_t whichacl;
	nfsstat4 status;
	vattr_t va, *vap = sarg->vap;
	vnode_t *vp = sarg->cs->vp;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		break;

	case NFS4ATTR_VERIT:
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (vp == NULL)) {
			return (-1);
		}
		ASSERT(vp != NULL);
		bzero(&vs_native, sizeof (vs_native));

		/* see which ACLs fs supports */
		error = VOP_PATHCONF(vp, _PC_ACL_ENABLED, &whichacl,
		    sarg->cs->cr);
		if (error != 0) {
			/*
			 * If we got an error, then the filesystem
			 * likely does not understand the _PC_ACL_ENABLED
			 * pathconf.  In this case, we fall back to trying
			 * POSIX-draft (aka UFS-style) ACLs, since that's
			 * the behavior used by earlier version of NFS.
			 */
			error = 0;
			whichacl = _ACL_ACLENT_ENABLED;
		}

		if (!(whichacl & (_ACL_ACE_ENABLED | _ACL_ACLENT_ENABLED))) {
			/*
			 * If the file system supports neither ACE nor
			 * ACLENT ACLs we will fall back to UFS-style ACLs
			 * like we did above if there was an error upon
			 * calling VOP_PATHCONF.
			 *
			 * ACE and ACLENT type ACLs are the only interfaces
			 * supported thus far.  If any other bits are set on
			 * 'whichacl' upon return from VOP_PATHCONF, we will
			 * ignore them.
			 */
			whichacl = _ACL_ACLENT_ENABLED;
		}

		if (whichacl & _ACL_ACE_ENABLED)
			vs_native.vsa_mask = VSA_ACE | VSA_ACECNT;
		else if (whichacl & _ACL_ACLENT_ENABLED)
			vs_native.vsa_mask = VSA_ACL | VSA_ACLCNT |
			    VSA_DFACL | VSA_DFACLCNT;

		if (error != 0)
			break;

		/* get the ACL, and translate it into nfsace4 style */
		error = VOP_GETSECATTR(vp, &vs_native,
		    0, sarg->cs->cr);
		if (error != 0)
			break;
		if (whichacl & _ACL_ACE_ENABLED) {
			error = vs_acet_to_ace4(&vs_native, &vs_ace4, TRUE);
			vs_acet_destroy(&vs_native);
		} else {
			error = vs_aent_to_ace4(&vs_native, &vs_ace4,
			    vp->v_type == VDIR, TRUE);
			vs_aent_destroy(&vs_native);
		}
		if (error != 0)
			break;

		if (cmd == NFS4ATTR_GETIT) {
			na->acl.fattr4_acl_len = vs_ace4.vsa_aclcnt;
			/* see case NFS4ATTR_FREEIT for this being freed */
			na->acl.fattr4_acl_val = vs_ace4.vsa_aclentp;
		} else {
			if (na->acl.fattr4_acl_len != vs_ace4.vsa_aclcnt)
				error = -1; /* no match */
			else if (ln_ace4_cmp(na->acl.fattr4_acl_val,
			    vs_ace4.vsa_aclentp,
			    vs_ace4.vsa_aclcnt) != 0)
				error = -1; /* no match */
		}

		break;

	case NFS4ATTR_SETIT:
		if (sarg->rdattr_error && (vp == NULL)) {
			return (-1);
		}
		ASSERT(vp != NULL);

		/* prepare vs_ace4 from fattr4 data */
		bzero(&vs_ace4, sizeof (vs_ace4));
		vs_ace4.vsa_mask = VSA_ACE | VSA_ACECNT;
		vs_ace4.vsa_aclcnt = na->acl.fattr4_acl_len;
		vs_ace4.vsa_aclentp = na->acl.fattr4_acl_val;

		/* make sure we have correct owner/group */
		if ((vap->va_mask & (AT_UID | AT_GID)) !=
		    (AT_UID | AT_GID)) {
			vap = &va;
			vap->va_mask = AT_UID | AT_GID;
			status = rfs4_vop_getattr(vp,
			    vap, 0, sarg->cs->cr);
			if (status != NFS4_OK)
				return (geterrno4(status));
		}

		/* see which ACLs the fs supports */
		error = VOP_PATHCONF(vp, _PC_ACL_ENABLED, &whichacl,
		    sarg->cs->cr);
		if (error != 0) {
			/*
			 * If we got an error, then the filesystem
			 * likely does not understand the _PC_ACL_ENABLED
			 * pathconf.  In this case, we fall back to trying
			 * POSIX-draft (aka UFS-style) ACLs, since that's
			 * the behavior used by earlier version of NFS.
			 */
			error = 0;
			whichacl = _ACL_ACLENT_ENABLED;
		}

		if (!(whichacl & (_ACL_ACLENT_ENABLED | _ACL_ACE_ENABLED))) {
			/*
			 * If the file system supports neither ACE nor
			 * ACLENT ACLs we will fall back to UFS-style ACLs
			 * like we did above if there was an error upon
			 * calling VOP_PATHCONF.
			 *
			 * ACE and ACLENT type ACLs are the only interfaces
			 * supported thus far.  If any other bits are set on
			 * 'whichacl' upon return from VOP_PATHCONF, we will
			 * ignore them.
			 */
			whichacl = _ACL_ACLENT_ENABLED;
		}

		if (whichacl & _ACL_ACE_ENABLED) {
			error = vs_ace4_to_acet(&vs_ace4, &vs_native,
			    vap->va_uid, vap->va_gid, TRUE, FALSE);
			if (error != 0)
				break;
			(void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
			error = VOP_SETSECATTR(vp, &vs_native,
			    0, sarg->cs->cr);
			VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
			vs_acet_destroy(&vs_native);
		} else if (whichacl & _ACL_ACLENT_ENABLED) {
			error = vs_ace4_to_aent(&vs_ace4, &vs_native,
			    vap->va_uid, vap->va_gid, vp->v_type == VDIR, TRUE,
			    FALSE);
			if (error != 0)
				break;
			(void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
			error = VOP_SETSECATTR(vp, &vs_native,
			    0, sarg->cs->cr);
			VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
			vs_aent_destroy(&vs_native);
		}
		break;

	case NFS4ATTR_FREEIT:
		if (sarg->op == NFS4ATTR_GETIT) {
			vs_ace4.vsa_mask = VSA_ACE | VSA_ACECNT;
			vs_ace4.vsa_aclcnt = na->acl.fattr4_acl_len;
			vs_ace4.vsa_aclentp = na->acl.fattr4_acl_val;
			vs_ace4_destroy(&vs_ace4);
		}
		break;
	}

	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_aclsupport(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;	/* supported */
	case NFS4ATTR_GETIT:
		na->aclsupport = ACL4_SUPPORT_ALLOW_ACL |
		    ACL4_SUPPORT_DENY_ACL;
		break;
	case NFS4ATTR_SETIT:
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (na->aclsupport != (ACL4_SUPPORT_ALLOW_ACL |
		    ACL4_SUPPORT_DENY_ACL))
			error = -1;	/* no match */
		break;
	}

	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_archive(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_cansettime(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->cansettime = TRUE;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (!na->cansettime)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/*
 * XXX - need VOP extension to ask file system (e.g. pcfs) if it supports
 * case insenstive.
 */
/* ARGSUSED */
static int
rfs4_fattr4_case_insensitive(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->case_insensitive = FALSE;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (!na->case_insensitive)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_case_preserving(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->case_preserving = TRUE;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (!na->case_preserving)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* fattr4_chown_restricted should reall be fattr4_chown_allowed */
/* ARGSUSED */
static int
rfs4_fattr4_chown_restricted(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;
	ulong_t val;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->cs->vp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->cs->vp != NULL);
		error = VOP_PATHCONF(sarg->cs->vp,
				_PC_CHOWN_RESTRICTED, &val, sarg->cs->cr);
		if (error)
			break;

		na->chown_restricted = (val == 1);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->cs->vp != NULL);
		error = VOP_PATHCONF(sarg->cs->vp,
				_PC_CHOWN_RESTRICTED, &val, sarg->cs->cr);
		if (error)
			break;
		if (na->chown_restricted != (val == 1))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_fileid(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_NODEID)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_NODEID);
		na->fileid = sarg->vap->va_nodeid;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_NODEID);
		if (sarg->vap->va_nodeid != na->fileid)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_get_mntdfileid(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg)
{
	int error = 0;
	vattr_t	*vap, va;
	vnode_t *stubvp = NULL, *vp;

	vp = sarg->cs->vp;
	sarg->mntdfid_set = FALSE;

	/* VROOT object, must untraverse */
	if (vp->v_flag & VROOT) {

		/* extra hold for vp since untraverse might rele */
		VN_HOLD(vp);
		stubvp = untraverse(vp);

		/*
		 * If vp/stubvp are same, we must be at system
		 * root because untraverse returned same vp
		 * for a VROOT object.  sarg->vap was setup
		 * before we got here, so there's no need to do
		 * another getattr -- just use the one in sarg.
		 */
		if (VN_CMP(vp, stubvp)) {
			ASSERT(VN_CMP(vp, rootdir));
			vap = sarg->vap;
		} else {
			va.va_mask = AT_NODEID;
			vap = &va;
			error = rfs4_vop_getattr(stubvp, vap, 0, sarg->cs->cr);
		}

		/*
		 * Done with stub, time to rele.  If vp and stubvp
		 * were the same, then we need to rele either vp or
		 * stubvp.  If they weren't the same, then untraverse()
		 * already took case of the extra hold on vp, and only
		 * the stub needs to be rele'd.  Both cases are handled
		 * by unconditionally rele'ing the stub.
		 */
		VN_RELE(stubvp);
	} else
		vap = sarg->vap;

	/*
	 * At this point, vap should contain "correct" AT_NODEID --
	 * (for V_ROOT case, nodeid of stub, for non-VROOT case,
	 * nodeid of vp).  If error or AT_NODEID not available, then
	 * make the obligatory (yet mysterious) rdattr_error
	 * check that is so common in the attr code.
	 */
	if (!error && (vap->va_mask & AT_NODEID)) {
		sarg->mounted_on_fileid = vap->va_nodeid;
		sarg->mntdfid_set = TRUE;
	} else if (sarg->rdattr_error)
		error = -1;

	/*
	 * error describes these cases:
	 *	0 : success
	 *	-1: failure due to previous attr processing error (rddir only).
	 *	* : new attr failure  (if rddir, caller will set rdattr_error)
	 */
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_mounted_on_fileid(nfs4_attr_cmd_t cmd,
	struct nfs4_svgetit_arg *sarg, union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
	case NFS4ATTR_VERIT:
		if (! sarg->mntdfid_set)
			error = rfs4_get_mntdfileid(cmd, sarg);

		if (! error && sarg->mntdfid_set) {
			if (cmd == NFS4ATTR_GETIT)
				na->mounted_on_fileid = sarg->mounted_on_fileid;
			else
				if (na->mounted_on_fileid !=
				    sarg->mounted_on_fileid)
					error = -1;
		}
		break;
	case NFS4ATTR_SETIT:
		/* read-only attr */
		error = EINVAL;
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_files_avail(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->sbp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->sbp != NULL);
		na->files_avail = sarg->sbp->f_favail;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_favail != na->files_avail)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_files_free(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->sbp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->sbp != NULL);
		na->files_free = sarg->sbp->f_ffree;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_ffree != na->files_free)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_files_total(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->sbp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->sbp != NULL);
		na->files_total = sarg->sbp->f_files;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_files != na->files_total)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_fs_locations(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_hidden(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_homogeneous(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->homogeneous = TRUE; /* XXX - need a VOP extension */
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (!na->homogeneous)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_maxfilesize(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;
	ulong_t val;
	fattr4_maxfilesize maxfilesize;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->cs->vp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->cs->vp != NULL);
		error = VOP_PATHCONF(sarg->cs->vp, _PC_FILESIZEBITS, &val,
				sarg->cs->cr);
		if (error)
			break;
		if (val >= (sizeof (uint64_t) * 8))
			na->maxfilesize = UINT64_MAX;
		else
			na->maxfilesize = ((1LL << val) - 1);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->cs->vp != NULL);
		error = VOP_PATHCONF(sarg->cs->vp, _PC_FILESIZEBITS, &val,
				sarg->cs->cr);
		if (error)
			break;
		if (val >= (sizeof (uint64_t) * 8))
			maxfilesize = UINT64_MAX;
		else
			maxfilesize = ((1LL << val) - 1);
		if (na->maxfilesize != maxfilesize)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_maxlink(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;
	ulong_t val;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->cs->vp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->cs->vp != NULL);
		error = VOP_PATHCONF(sarg->cs->vp, _PC_LINK_MAX, &val,
				sarg->cs->cr);
		if (error == 0) {
			na->maxlink = val;
		}
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->cs->vp != NULL);
		error = VOP_PATHCONF(sarg->cs->vp, _PC_LINK_MAX, &val,
				sarg->cs->cr);
		if (!error && (na->maxlink != (uint32_t)val))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_maxname(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;
	ulong_t val;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->cs->vp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->cs->vp != NULL);
		error = VOP_PATHCONF(sarg->cs->vp, _PC_NAME_MAX, &val,
				sarg->cs->cr);
		if (error == 0) {
			na->maxname = val;
		}
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->cs->vp != NULL);
		error = VOP_PATHCONF(sarg->cs->vp, _PC_NAME_MAX, &val,
				sarg->cs->cr);
		if (!error && (na->maxname != val))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_maxread(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->maxread = rfs4_tsize(sarg->cs->req);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (na->maxread != rfs4_tsize(sarg->cs->req))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_maxwrite(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->maxwrite = rfs4_tsize(sarg->cs->req);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (na->maxwrite != rfs4_tsize(sarg->cs->req))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_mimetype(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_mode(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_MODE)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_MODE);
		na->mode = sarg->vap->va_mode;
		break;
	case NFS4ATTR_SETIT:
		ASSERT(sarg->vap->va_mask & AT_MODE);
		sarg->vap->va_mode = na->mode;
		/*
		 * If the filesystem is exported with nosuid, then mask off
		 * the setuid and setgid bits.
		 */
		if (sarg->cs->vp->v_type == VREG &&
			(sarg->cs->exi->exi_export.ex_flags & EX_NOSUID))
			sarg->vap->va_mode &= ~(VSUID | VSGID);
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_MODE);
		if (sarg->vap->va_mode != na->mode)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_no_trunc(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->no_trunc = TRUE;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if (!na->no_trunc)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_numlinks(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_NLINK)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_NLINK);
		na->numlinks = sarg->vap->va_nlink;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_NLINK);
		if (sarg->vap->va_nlink != na->numlinks)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_owner(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;
	uid_t	uid;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_UID)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_UID);

		/*
		 * There are well defined polices for what happens on server-
		 * side GETATTR when uid to attribute string conversion cannot
		 * occur. Please refer to nfs4_idmap.c for details.
		 */
		error = nfs_idmap_uid_str(sarg->vap->va_uid, &na->owner, TRUE);
		switch (error) {
		case ECONNREFUSED:
			error = NFS4ERR_DELAY;
			break;
		default:
			break;
		}
		break;

	case NFS4ATTR_SETIT:
		ASSERT(sarg->vap->va_mask & AT_UID);

		/*
		 * There are well defined policies for what happens on server-
		 * side SETATTR of 'owner' when a "user@domain" mapping cannot
		 * occur. Please refer to nfs4_idmap.c for details.
		 *
		 * Any other errors, such as the mapping not being found by
		 * nfsmapid(1m), and interrupted clnt_call, etc, will result
		 * in NFS4ERR_BADOWNER.
		 *
		 * XXX need to return consistent errors, perhaps all
		 * server side attribute routines should return NFS4ERR*.
		 */
		error = nfs_idmap_str_uid(&na->owner, &sarg->vap->va_uid, TRUE);
		switch (error) {
		case NFS4_OK:
		case ENOTSUP:
			/*
			 * Ignore warning that we are the
			 * nfsmapid (can't happen on srv)
			 */
			error = 0;
			MSG_PRT_DEBUG = FALSE;
			break;

		case ECOMM:
		case ECONNREFUSED:
			if (!MSG_PRT_DEBUG) {
				/*
				 * printed just once per daemon death,
				 * inform the user and then stay silent
				 */
				cmn_err(CE_WARN, "!Unable to contact "
				    "nfsmapid");
				MSG_PRT_DEBUG = TRUE;
			}
			error = NFS4ERR_DELAY;
			break;

		case EINVAL:
			error = NFS4ERR_INVAL;
			break;

		default:
			error = NFS4ERR_BADOWNER;
			break;
		}
		break;

	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_UID);
		error = nfs_idmap_str_uid(&na->owner, &uid, TRUE);
		/*
		 * Ignore warning that we are the nfsmapid (can't happen on srv)
		 */
		if (error == ENOTSUP)
			error = 0;
		if (error)
			error = -1;	/* no match */
		else if (sarg->vap->va_uid != uid)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		if (sarg->op == NFS4ATTR_GETIT) {
			if (na->owner.utf8string_val) {
				UTF8STRING_FREE(na->owner)
				bzero(&na->owner, sizeof (na->owner));
			}
		}
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_owner_group(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;
	gid_t	gid;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_GID)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_GID);

		/*
		 * There are well defined polices for what happens on server-
		 * side GETATTR when gid to attribute string conversion cannot
		 * occur. Please refer to nfs4_idmap.c for details.
		 */
		error = nfs_idmap_gid_str(sarg->vap->va_gid, &na->owner_group,
		    TRUE);
		switch (error) {
		case ECONNREFUSED:
			error = NFS4ERR_DELAY;
			break;
		default:
			break;
		}
		break;

	case NFS4ATTR_SETIT:
		ASSERT(sarg->vap->va_mask & AT_GID);

		/*
		 * There are well defined policies for what happens on server-
		 * side SETATTR of 'owner_group' when a "group@domain" mapping
		 * cannot occur. Please refer to nfs4_idmap.c for details.
		 *
		 * Any other errors, such as the mapping not being found by
		 * nfsmapid(1m), and interrupted clnt_call, etc, will result
		 * in NFS4ERR_BADOWNER.
		 *
		 * XXX need to return consistent errors, perhaps all
		 * server side attribute routines should return NFS4ERR*.
		 */
		error = nfs_idmap_str_gid(&na->owner_group, &sarg->vap->va_gid,
		    TRUE);
		switch (error) {
		case NFS4_OK:
		case ENOTSUP:
			/*
			 * Ignore warning that we are the
			 * nfsmapid (can't happen on srv)
			 */
			error = 0;
			MSG_PRT_DEBUG = FALSE;
			break;

		case ECOMM:
		case ECONNREFUSED:
			if (!MSG_PRT_DEBUG) {
				/*
				 * printed just once per daemon death,
				 * inform the user and then stay silent
				 */
				cmn_err(CE_WARN, "!Unable to contact "
				    "nfsmapid");
				MSG_PRT_DEBUG = TRUE;
			}
			error = NFS4ERR_DELAY;
			break;

		case EINVAL:
			error = NFS4ERR_INVAL;
			break;

		default:
			error = NFS4ERR_BADOWNER;
			break;
		}
		break;

	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_GID);
		error = nfs_idmap_str_gid(&na->owner_group, &gid, TRUE);
		/*
		 * Ignore warning that we are the nfsmapid (can't happen on srv)
		 */
		if (error == ENOTSUP)
			error = 0;
		if (error)
			error = -1;	/* no match */
		else if (sarg->vap->va_gid != gid)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		if (sarg->op == NFS4ATTR_GETIT) {
			if (na->owner_group.utf8string_val) {
				UTF8STRING_FREE(na->owner_group)
				bzero(&na->owner_group,
					sizeof (na->owner_group));
			}
		}
		break;
	}
	return (error);
}

/* XXX - quota attributes should be supportable on Solaris 2 */
/* ARGSUSED */
static int
rfs4_fattr4_quota_avail_hard(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_quota_avail_soft(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_quota_used(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_rawdev(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_RDEV)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_RDEV);
		na->rawdev.specdata1 =  (uint32)getmajor(sarg->vap->va_rdev);
		na->rawdev.specdata2 =  (uint32)getminor(sarg->vap->va_rdev);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_RDEV);
		if ((na->rawdev.specdata1 !=
			(uint32)getmajor(sarg->vap->va_rdev)) ||
		    (na->rawdev.specdata2 !=
			(uint32)getminor(sarg->vap->va_rdev)))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_space_avail(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->sbp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_bavail != (fsblkcnt64_t)-1) {
			na->space_avail =
				(fattr4_space_avail) sarg->sbp->f_frsize *
				(fattr4_space_avail) sarg->sbp->f_bavail;
		} else {
			na->space_avail =
				(fattr4_space_avail) sarg->sbp->f_bavail;
		}
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_bavail != na->space_avail)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_space_free(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && (sarg->sbp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_bfree != (fsblkcnt64_t)-1) {
			na->space_free =
				(fattr4_space_free) sarg->sbp->f_frsize *
				(fattr4_space_free) sarg->sbp->f_bfree;
		} else {
			na->space_free =
				(fattr4_space_free) sarg->sbp->f_bfree;
		}
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_bfree != na->space_free)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_space_total(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error_req && (sarg->sbp == NULL)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_blocks != (fsblkcnt64_t)-1) {
			na->space_total =
				(fattr4_space_total) sarg->sbp->f_frsize *
				(fattr4_space_total) sarg->sbp->f_blocks;
		} else {
			na->space_total =
				(fattr4_space_total) sarg->sbp->f_blocks;
		}
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->sbp != NULL);
		if (sarg->sbp->f_blocks != na->space_total)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_space_used(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_NBLOCKS)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_NBLOCKS);
		na->space_used =  (fattr4_space_used) DEV_BSIZE *
			(fattr4_space_used) sarg->vap->va_nblocks;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_NBLOCKS);
		if (sarg->vap->va_nblocks != na->space_used)
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_system(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_time_access(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;
	timestruc_t atime;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_ATIME)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_ATIME);
		error = nfs4_time_vton(&sarg->vap->va_atime, &na->time_access);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_ATIME);
		error = nfs4_time_ntov(&na->time_access, &atime);
		if (error)
			break;
		if (bcmp(&atime, &sarg->vap->va_atime, sizeof (atime)))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/*
 * XXX - need to support the setting of access time
 */
/* ARGSUSED */
static int
rfs4_fattr4_time_access_set(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;
	settime4 *ta;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if ((sarg->op == NFS4ATTR_GETIT) ||
		    (sarg->op == NFS4ATTR_VERIT))
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
	case NFS4ATTR_VERIT:
		/*
		 * write only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_SETIT:
		ASSERT(sarg->vap->va_mask & AT_ATIME);
		/*
		 * Set access time (by server or by client)
		 */
		ta = &na->time_access_set;
		if (ta->set_it == SET_TO_CLIENT_TIME4) {
			error = nfs4_time_ntov(&ta->time, &sarg->vap->va_atime);
		} else if (ta->set_it == SET_TO_SERVER_TIME4) {
			gethrestime(&sarg->vap->va_atime);
		} else {
			error = EINVAL;
		}
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_time_backup(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_time_create(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	return (ENOTSUP);
}

/* ARGSUSED */
static int
rfs4_fattr4_time_delta(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int error = 0;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		na->time_delta.seconds = 0;
		na->time_delta.nseconds = 1000;
		break;
	case NFS4ATTR_SETIT:
		/*
		 * write only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		if ((na->time_delta.seconds != 0) ||
		    (na->time_delta.nseconds != 1000))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_time_metadata(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;
	timestruc_t ctime;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_CTIME)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_CTIME);
		error = nfs4_time_vton(&sarg->vap->va_ctime,
				&na->time_metadata);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_CTIME);
		error = nfs4_time_ntov(&na->time_metadata, &ctime);
		if (error)
			break;
		if (bcmp(&ctime, &sarg->vap->va_ctime, sizeof (ctime)))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/* ARGSUSED */
static int
rfs4_fattr4_time_modify(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;
	timestruc_t mtime;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if (sarg->op == NFS4ATTR_SETIT)
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
		if (sarg->rdattr_error && !(sarg->vap->va_mask & AT_MTIME)) {
			error = -1;	/* may be okay if rdattr_error */
			break;
		}
		ASSERT(sarg->vap->va_mask & AT_MTIME);
		error = nfs4_time_vton(&sarg->vap->va_mtime, &na->time_modify);
		break;
	case NFS4ATTR_SETIT:
		/*
		 * read-only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_VERIT:
		ASSERT(sarg->vap->va_mask & AT_MTIME);
		error = nfs4_time_ntov(&na->time_modify, &mtime);
		if (error)
			break;
		if (bcmp(&mtime, &sarg->vap->va_mtime, sizeof (mtime)))
			error = -1;	/* no match */
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}

/*
 * XXX - need to add support for setting modify time
 */
/* ARGSUSED */
static int
rfs4_fattr4_time_modify_set(nfs4_attr_cmd_t cmd, struct nfs4_svgetit_arg *sarg,
	union nfs4_attr_u *na)
{
	int	error = 0;
	settime4 *tm;

	if (RFS4_MANDATTR_ONLY)
		return (ENOTSUP);

	switch (cmd) {
	case NFS4ATTR_SUPPORTED:
		if ((sarg->op == NFS4ATTR_GETIT) ||
		    (sarg->op == NFS4ATTR_VERIT))
			error = EINVAL;
		break;		/* this attr is supported */
	case NFS4ATTR_GETIT:
	case NFS4ATTR_VERIT:
		/*
		 * write only attr
		 */
		error = EINVAL;
		break;
	case NFS4ATTR_SETIT:
		ASSERT(sarg->vap->va_mask & AT_MTIME);
		/*
		 * Set modify time (by server or by client)
		 */
		tm = &na->time_modify_set;
		if (tm->set_it == SET_TO_CLIENT_TIME4) {
			error = nfs4_time_ntov(&tm->time, &sarg->vap->va_mtime);
			sarg->flag = ATTR_UTIME;
		} else if (tm->set_it == SET_TO_SERVER_TIME4) {
			gethrestime(&sarg->vap->va_mtime);
		} else {
			error = EINVAL;
		}
		break;
	case NFS4ATTR_FREEIT:
		break;
	}
	return (error);
}


static void
rfs4_ntov_init(void)
{
	/* index must be same as corresponding FATTR4_* define */
	nfs4_ntov_map[0].sv_getit = rfs4_fattr4_supported_attrs;
	nfs4_ntov_map[1].sv_getit = rfs4_fattr4_type;
	nfs4_ntov_map[2].sv_getit = rfs4_fattr4_fh_expire_type;
	nfs4_ntov_map[3].sv_getit = rfs4_fattr4_change;
	nfs4_ntov_map[4].sv_getit = rfs4_fattr4_size;
	nfs4_ntov_map[5].sv_getit = rfs4_fattr4_link_support;
	nfs4_ntov_map[6].sv_getit = rfs4_fattr4_symlink_support;
	nfs4_ntov_map[7].sv_getit = rfs4_fattr4_named_attr;
	nfs4_ntov_map[8].sv_getit = rfs4_fattr4_fsid;
	nfs4_ntov_map[9].sv_getit = rfs4_fattr4_unique_handles;
	nfs4_ntov_map[10].sv_getit = rfs4_fattr4_lease_time;
	nfs4_ntov_map[11].sv_getit = rfs4_fattr4_rdattr_error;
	nfs4_ntov_map[12].sv_getit = rfs4_fattr4_acl;
	nfs4_ntov_map[13].sv_getit = rfs4_fattr4_aclsupport;
	nfs4_ntov_map[14].sv_getit = rfs4_fattr4_archive;
	nfs4_ntov_map[15].sv_getit = rfs4_fattr4_cansettime;
	nfs4_ntov_map[16].sv_getit = rfs4_fattr4_case_insensitive;
	nfs4_ntov_map[17].sv_getit = rfs4_fattr4_case_preserving;
	nfs4_ntov_map[18].sv_getit = rfs4_fattr4_chown_restricted;
	nfs4_ntov_map[19].sv_getit = rfs4_fattr4_filehandle;
	nfs4_ntov_map[20].sv_getit = rfs4_fattr4_fileid;
	nfs4_ntov_map[21].sv_getit = rfs4_fattr4_files_avail;
	nfs4_ntov_map[22].sv_getit = rfs4_fattr4_files_free;
	nfs4_ntov_map[23].sv_getit = rfs4_fattr4_files_total;
	nfs4_ntov_map[24].sv_getit = rfs4_fattr4_fs_locations;
	nfs4_ntov_map[25].sv_getit = rfs4_fattr4_hidden;
	nfs4_ntov_map[26].sv_getit = rfs4_fattr4_homogeneous;
	nfs4_ntov_map[27].sv_getit = rfs4_fattr4_maxfilesize;
	nfs4_ntov_map[28].sv_getit = rfs4_fattr4_maxlink;
	nfs4_ntov_map[29].sv_getit = rfs4_fattr4_maxname;
	nfs4_ntov_map[30].sv_getit = rfs4_fattr4_maxread;
	nfs4_ntov_map[31].sv_getit = rfs4_fattr4_maxwrite;
	nfs4_ntov_map[32].sv_getit = rfs4_fattr4_mimetype;
	nfs4_ntov_map[33].sv_getit = rfs4_fattr4_mode;
	nfs4_ntov_map[34].sv_getit = rfs4_fattr4_no_trunc;
	nfs4_ntov_map[35].sv_getit = rfs4_fattr4_numlinks;
	nfs4_ntov_map[36].sv_getit = rfs4_fattr4_owner;
	nfs4_ntov_map[37].sv_getit = rfs4_fattr4_owner_group;
	nfs4_ntov_map[38].sv_getit = rfs4_fattr4_quota_avail_hard;
	nfs4_ntov_map[39].sv_getit = rfs4_fattr4_quota_avail_soft;
	nfs4_ntov_map[40].sv_getit = rfs4_fattr4_quota_used;
	nfs4_ntov_map[41].sv_getit = rfs4_fattr4_rawdev;
	nfs4_ntov_map[42].sv_getit = rfs4_fattr4_space_avail;
	nfs4_ntov_map[43].sv_getit = rfs4_fattr4_space_free;
	nfs4_ntov_map[44].sv_getit = rfs4_fattr4_space_total;
	nfs4_ntov_map[45].sv_getit = rfs4_fattr4_space_used;
	nfs4_ntov_map[46].sv_getit = rfs4_fattr4_system;
	nfs4_ntov_map[47].sv_getit = rfs4_fattr4_time_access;
	nfs4_ntov_map[48].sv_getit = rfs4_fattr4_time_access_set;
	nfs4_ntov_map[49].sv_getit = rfs4_fattr4_time_backup;
	nfs4_ntov_map[50].sv_getit = rfs4_fattr4_time_create;
	nfs4_ntov_map[51].sv_getit = rfs4_fattr4_time_delta;
	nfs4_ntov_map[52].sv_getit = rfs4_fattr4_time_metadata;
	nfs4_ntov_map[53].sv_getit = rfs4_fattr4_time_modify;
	nfs4_ntov_map[54].sv_getit = rfs4_fattr4_time_modify_set;
	nfs4_ntov_map[55].sv_getit = rfs4_fattr4_mounted_on_fileid;
}