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

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

/*
 * Copyright 2018 Nexenta Systems, Inc.
 * Copyright 2020 RackTop Systems, Inc.
 */

#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>
#include <sys/door.h>
#include <sys/sdt.h>
#include <nfs/nfssys.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 tunable 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
 */
bitmap4 supported_attrs[3];

static void
init_supported_attrs(void)
{
        supported_attrs[0] = supported_attrs[1] = supported_attrs[2] =
            rfs4_supported_attrs;

        /* restrict to nfsv4.0 */
        supported_attrs[0] &= ~(FATTR4_SUPPATTR_EXCLCREAT_MASK_LOCAL |
            FATTR4_SEC_LABEL_MASK_LOCAL);

        /* restrict to nfsv4.1 */
        supported_attrs[1] &= ~FATTR4_SEC_LABEL_MASK_LOCAL;
}

void
rfs4_attr_init(void)
{
        int i;
        struct nfs4_svgetit_arg sarg;
        struct compound_state cs;
        struct statvfs64 sb;

        rfs4_init_compound_state(&cs);
        /*
         * This is global state checking, called once. We might be in
         * non-global-zone context here (say a modload happens from a zone
         * process) so in this case, we want the global-zone root vnode.
         */
        cs.vp = rootvp;
        cs.fh.nfs_fh4_val = NULL;
        cs.cr = kcred;
        cs.minorversion = NFS_PROT_V4_MINORVERSION(NFS_VERS_4_2);

        /*
         * 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;
        sarg.is_referral = B_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;
                }
        }

        init_supported_attrs();
}

/*
 * 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;
        bitmap4 supported = supported_attrs[sarg->cs->minorversion];

        switch (cmd) {
        case NFS4ATTR_SUPPORTED:
                if (sarg->op == NFS4ATTR_SETIT)
                        error = EINVAL;
                break;          /* this attr is supported */
        case NFS4ATTR_GETIT:
                na->supported_attrs = supported;
                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 != supported) {
                        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;
        timespec_t vis_change;

        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);

        if (nfs_visible_change(cs->exi, vp, &vis_change)) {
                fattr4_change visch;
                NFS4_SET_FATTR4_CHANGE(visch, vis_change);
                if (visch > *changep)
                        *changep = visch;
        }

        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 generic xattr.  If VFS_XATTR
                 * not set, no need to call pathconf for _PC_XATTR_EXISTS..
                 *
                 * However the VFS_XATTR flag doesn't indicate sysattr support
                 * so always check for sysattrs and then only do the
                 * _PC_XATTR_EXISTS pathconf if needed.
                 */

                val = 0;
                error = VOP_PATHCONF(sarg->cs->vp, _PC_SATTR_EXISTS,
                    &val, sarg->cs->cr, NULL);
                if ((error || val == 0) &&
                    sarg->cs->vp->v_vfsp->vfs_flag & VFS_XATTR) {
                        error = VOP_PATHCONF(sarg->cs->vp,
                            _PC_XATTR_EXISTS, &val, sarg->cs->cr, NULL);
                        if (error)
                                break;
                }
                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_SATTR_EXISTS,
                            &val, sarg->cs->cr, NULL);
                        if (error || val == 0)
                                error = VOP_PATHCONF(sarg->cs->vp,
                                    _PC_XATTR_EXISTS, &val,
                                    sarg->cs->cr, NULL);
                        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->is_referral) {
                        na->fsid.major = 1;
                        na->fsid.minor = 0;
                } else 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->is_referral) {
                        if (na->fsid.major != 1 ||
                            na->fsid.minor != 0)
                                error = -1;
                } else 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, NULL);
                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, NULL);
                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;
                vs_ace4.vsa_aclentsz = vs_ace4.vsa_aclcnt * sizeof (ace_t);
                /* 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, NULL);
                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);
                        if (error != 0)
                                break;
                        (void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
                        error = VOP_SETSECATTR(vp, &vs_native,
                            0, sarg->cs->cr, NULL);
                        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);
                        if (error != 0)
                                break;
                        (void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
                        error = VOP_SETSECATTR(vp, &vs_native,
                            0, sarg->cs->cr, NULL);
                        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 insensitive.
 */
/* 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, NULL);
                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, NULL);
                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 or zone's root, must untraverse.
         *
         * NOTE: Not doing reality checks on curzone vs. compound
         * state vnode because it will mismatch once at initialization
         * if a non-global-zone triggers the module load, BUT in that case
         * the vp is literally "/" which has VROOT set.
         */
        if ((vp->v_flag & VROOT) || VN_IS_CURZONEROOT(vp)) {

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

                /*
                 * If vp/stubvp are same, we must be at system-or-zone
                 * 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_IS_CURZONEROOT(vp));
                        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);
}

static void
rfs4_free_pathname4(pathname4 *pn4)
{
        int i, len;
        utf8string *utf8s;

        if (pn4 == NULL || (len = pn4->pathname4_len) == 0 ||
            (utf8s = pn4->pathname4_val) == NULL)
                return;

        for (i = 0; i < len; i++, utf8s++) {
                if (utf8s->utf8string_val == NULL ||
                    utf8s->utf8string_len == 0)
                        continue;

                kmem_free(utf8s->utf8string_val, utf8s->utf8string_len);
                utf8s->utf8string_val = NULL;
        }

        kmem_free(pn4->pathname4_val,
            sizeof (utf8string) * pn4->pathname4_len);
        pn4->pathname4_val = 0;
}

static void
rfs4_free_fs_location4(fs_location4 *fsl4)
{
        if (fsl4 == NULL)
                return;

        rfs4_free_pathname4((pathname4 *)&fsl4->server_len);
        rfs4_free_pathname4(&fsl4->rootpath);
}

void
rfs4_free_fs_locations4(fs_locations4 *fsls4)
{
        int i, len;
        fs_location4 *fsl4;

        if (fsls4 == NULL)
                return;

        /* free fs_root */
        rfs4_free_pathname4(&fsls4->fs_root);

        if ((len = fsls4->locations_len) == 0 ||
            (fsl4 = fsls4->locations_val) == NULL)
                return;

        /* free fs_location4 */
        for (i = 0; i < len; i++) {
                rfs4_free_fs_location4(fsl4);
                fsl4++;
        }

        kmem_free(fsls4->locations_val, sizeof (fs_location4) * len);
        fsls4->locations_val = NULL;
}

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

        if (RFS4_MANDATTR_ONLY)
                return (ENOTSUP);

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

        case NFS4ATTR_GETIT:
        {
                kstat_named_t *stat =
                    sarg->cs->exi->exi_ne->ne_globals->svstat[NFS_V4];

                fsl = fetch_referral(sarg->cs->vp, sarg->cs->cr);
                if (fsl == NULL)
                        (void) memset(&(na->fs_locations), 0,
                            sizeof (fs_locations4));
                else {
                        na->fs_locations = *fsl;
                        kmem_free(fsl, sizeof (fs_locations4));
                }
                stat[NFS_REFERRALS].value.ui64++;
                break;
        }
        case NFS4ATTR_FREEIT:
                if (sarg->op == NFS4ATTR_SETIT || sarg->op == NFS4ATTR_VERIT)
                        error = EINVAL;
                rfs4_free_fs_locations4(&na->fs_locations);
                break;

        case NFS4ATTR_SETIT:
        case NFS4ATTR_VERIT:
                /*
                 * read-only attr
                 */
                error = EINVAL;
                break;
        }
        return (error);
}

/* 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, NULL);
                if (error)
                        break;

                /*
                 * If the underlying file system does not support
                 * _PC_FILESIZEBITS, return a reasonable default. Note that
                 * error code on VOP_PATHCONF will be 0, even if the underlying
                 * file system does not support _PC_FILESIZEBITS.
                 */
                if (val == (ulong_t)-1) {
                        na->maxfilesize = MAXOFF32_T;
                } else {
                        if (val >= (sizeof (uint64_t) * 8))
                                na->maxfilesize = INT64_MAX;
                        else
                                na->maxfilesize = ((1LL << (val - 1)) - 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, NULL);
                if (error)
                        break;
                /*
                 * If the underlying file system does not support
                 * _PC_FILESIZEBITS, return a reasonable default. Note that
                 * error code on VOP_PATHCONF will be 0, even if the underlying
                 * file system does not support _PC_FILESIZEBITS.
                 */
                if (val == (ulong_t)-1) {
                        maxfilesize = MAXOFF32_T;
                } else {
                        if (val >= (sizeof (uint64_t) * 8))
                                maxfilesize = INT64_MAX;
                        else
                                maxfilesize = ((1LL << (val - 1)) - 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, NULL);
                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, NULL);
                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, NULL);
                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, NULL);
                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(8), 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(8), 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);
}

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

        /* Not supported for nfs4.0 */
        if (sarg->cs->minorversion == 0)
                return (ENOTSUP);

        switch (cmd) {
        case NFS4ATTR_SUPPORTED:
                if (sarg->op == NFS4ATTR_SETIT)
                        error = EINVAL;
                break;          /* this attr is supported */
        case NFS4ATTR_GETIT:
                na->supp_exclcreat = RFS4_SUPPATTR_EXCLCREAT;
                break;
        case NFS4ATTR_SETIT:
                /*
                 * read-only attr
                 */
                error = EINVAL;
                break;
        case NFS4ATTR_VERIT:
                if (na->supp_exclcreat != RFS4_SUPPATTR_EXCLCREAT)
                        error = -1; /* no match */
                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;
        nfs4_ntov_map[56].sv_getit = rfs4_fattr4_suppattr_exclcreat;
}