/*	$NetBSD: genfs_vnops.c,v 1.219.4.1 2023/03/05 14:34:59 martin Exp $	*/

/*-
 * Copyright (c) 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Copyright (c) 1982, 1986, 1989, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: genfs_vnops.c,v 1.219.4.1 2023/03/05 14:34:59 martin Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/mount.h>
#include <sys/fstrans.h>
#include <sys/namei.h>
#include <sys/vnode_impl.h>
#include <sys/fcntl.h>
#include <sys/kmem.h>
#include <sys/poll.h>
#include <sys/mman.h>
#include <sys/file.h>
#include <sys/kauth.h>
#include <sys/stat.h>
#include <sys/extattr.h>

#include <miscfs/genfs/genfs.h>
#include <miscfs/genfs/genfs_node.h>
#include <miscfs/specfs/specdev.h>

static void filt_genfsdetach(struct knote *);
static int filt_genfsread(struct knote *, long);
static int filt_genfsvnode(struct knote *, long);

/*
 * Find the end of the first path component in NAME and return its
 * length.
 */
int
genfs_parsepath(void *v)
{
	struct vop_parsepath_args /* {
		struct vnode *a_dvp;
		const char *a_name;
		size_t *a_ret;
	} */ *ap = v;
	const char *name = ap->a_name;
	size_t pos;

	(void)ap->a_dvp;

	pos = 0;
	while (name[pos] != '\0' && name[pos] != '/') {
		pos++;
	}
	*ap->a_retval = pos;
	return 0;
}

int
genfs_poll(void *v)
{
	struct vop_poll_args /* {
		struct vnode *a_vp;
		int a_events;
		struct lwp *a_l;
	} */ *ap = v;

	return (ap->a_events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM));
}

int
genfs_seek(void *v)
{
	struct vop_seek_args /* {
		struct vnode *a_vp;
		off_t a_oldoff;
		off_t a_newoff;
		kauth_cred_t cred;
	} */ *ap = v;

	if (ap->a_newoff < 0)
		return (EINVAL);

	return (0);
}

int
genfs_abortop(void *v)
{
	struct vop_abortop_args /* {
		struct vnode *a_dvp;
		struct componentname *a_cnp;
	} */ *ap = v;

	(void)ap;

	return (0);
}

int
genfs_fcntl(void *v)
{
	struct vop_fcntl_args /* {
		struct vnode *a_vp;
		u_int a_command;
		void *a_data;
		int a_fflag;
		kauth_cred_t a_cred;
		struct lwp *a_l;
	} */ *ap = v;

	if (ap->a_command == F_SETFL)
		return (0);
	else
		return (EOPNOTSUPP);
}

/*ARGSUSED*/
int
genfs_badop(void *v)
{

	panic("genfs: bad op");
}

/*ARGSUSED*/
int
genfs_nullop(void *v)
{

	return (0);
}

/*ARGSUSED*/
int
genfs_einval(void *v)
{

	return (EINVAL);
}

int
genfs_erofs_link(void *v)
{
	/* also for symlink */
	struct vop_link_v2_args /* {
		struct vnode *a_dvp;
		struct vnode **a_vpp;
		struct componentname *a_cnp;
	} */ *ap = v;

	VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
	return EROFS;
}

/*
 * Called when an fs doesn't support a particular vop.
 * This takes care to vrele, vput, or vunlock passed in vnodes
 * and calls VOP_ABORTOP for a componentname (in non-rename VOP).
 */
int
genfs_eopnotsupp(void *v)
{
	struct vop_generic_args /*
		struct vnodeop_desc *a_desc;
		/ * other random data follows, presumably * /
	} */ *ap = v;
	struct vnodeop_desc *desc = ap->a_desc;
	struct vnode *vp, *vp_last = NULL;
	int flags, i, j, offset_cnp, offset_vp;

	KASSERT(desc->vdesc_offset != VOP_LOOKUP_DESCOFFSET);
	KASSERT(desc->vdesc_offset != VOP_ABORTOP_DESCOFFSET);

	/*
	 * Abort any componentname that lookup potentially left state in.
	 *
	 * As is logical, componentnames for VOP_RENAME are handled by
	 * the caller of VOP_RENAME.  Yay, rename!
	 */
	if (desc->vdesc_offset != VOP_RENAME_DESCOFFSET &&
	    (offset_vp = desc->vdesc_vp_offsets[0]) != VDESC_NO_OFFSET &&
	    (offset_cnp = desc->vdesc_componentname_offset) != VDESC_NO_OFFSET){
		struct componentname *cnp;
		struct vnode *dvp;

		dvp = *VOPARG_OFFSETTO(struct vnode **, offset_vp, ap);
		cnp = *VOPARG_OFFSETTO(struct componentname **, offset_cnp, ap);

		VOP_ABORTOP(dvp, cnp);
	}

	flags = desc->vdesc_flags;
	for (i = 0; i < VDESC_MAX_VPS; flags >>=1, i++) {
		if ((offset_vp = desc->vdesc_vp_offsets[i]) == VDESC_NO_OFFSET)
			break;	/* stop at end of list */
		if ((j = flags & VDESC_VP0_WILLPUT)) {
			vp = *VOPARG_OFFSETTO(struct vnode **, offset_vp, ap);

			/* Skip if NULL */
			if (!vp)
				continue;

			switch (j) {
			case VDESC_VP0_WILLPUT:
				/* Check for dvp == vp cases */
				if (vp == vp_last)
					vrele(vp);
				else {
					vput(vp);
					vp_last = vp;
				}
				break;
			case VDESC_VP0_WILLRELE:
				vrele(vp);
				break;
			}
		}
	}

	return (EOPNOTSUPP);
}

/*ARGSUSED*/
int
genfs_ebadf(void *v)
{

	return (EBADF);
}

/* ARGSUSED */
int
genfs_enoioctl(void *v)
{

	return (EPASSTHROUGH);
}


/*
 * Eliminate all activity associated with the requested vnode
 * and with all vnodes aliased to the requested vnode.
 */
int
genfs_revoke(void *v)
{
	struct vop_revoke_args /* {
		struct vnode *a_vp;
		int a_flags;
	} */ *ap = v;

#ifdef DIAGNOSTIC
	if ((ap->a_flags & REVOKEALL) == 0)
		panic("genfs_revoke: not revokeall");
#endif
	vrevoke(ap->a_vp);
	return (0);
}

/*
 * Lock the node (for deadfs).
 */
int
genfs_deadlock(void *v)
{
	struct vop_lock_args /* {
		struct vnode *a_vp;
		int a_flags;
	} */ *ap = v;
	vnode_t *vp = ap->a_vp;
	vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
	int flags = ap->a_flags;
	krw_t op;

	if (! ISSET(flags, LK_RETRY))
		return ENOENT;

	if (ISSET(flags, LK_DOWNGRADE)) {
		rw_downgrade(&vip->vi_lock);
	} else if (ISSET(flags, LK_UPGRADE)) {
		KASSERT(ISSET(flags, LK_NOWAIT));
		if (!rw_tryupgrade(&vip->vi_lock)) {
			return EBUSY;
		}
	} else if ((flags & (LK_EXCLUSIVE | LK_SHARED)) != 0) {
		op = (ISSET(flags, LK_EXCLUSIVE) ? RW_WRITER : RW_READER);
		if (ISSET(flags, LK_NOWAIT)) {
			if (!rw_tryenter(&vip->vi_lock, op))
				return EBUSY;
		} else {
			rw_enter(&vip->vi_lock, op);
		}
	}
	VSTATE_ASSERT_UNLOCKED(vp, VS_RECLAIMED);
	return 0;
}

/*
 * Unlock the node (for deadfs).
 */
int
genfs_deadunlock(void *v)
{
	struct vop_unlock_args /* {
		struct vnode *a_vp;
	} */ *ap = v;
	vnode_t *vp = ap->a_vp;
	vnode_impl_t *vip = VNODE_TO_VIMPL(vp);

	rw_exit(&vip->vi_lock);

	return 0;
}

/*
 * Lock the node.
 */
int
genfs_lock(void *v)
{
	struct vop_lock_args /* {
		struct vnode *a_vp;
		int a_flags;
	} */ *ap = v;
	vnode_t *vp = ap->a_vp;
	vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
	int flags = ap->a_flags;
	krw_t op;

	if (ISSET(flags, LK_DOWNGRADE)) {
		rw_downgrade(&vip->vi_lock);
	} else if (ISSET(flags, LK_UPGRADE)) {
		KASSERT(ISSET(flags, LK_NOWAIT));
		if (!rw_tryupgrade(&vip->vi_lock)) {
			return EBUSY;
		}
	} else if ((flags & (LK_EXCLUSIVE | LK_SHARED)) != 0) {
		op = (ISSET(flags, LK_EXCLUSIVE) ? RW_WRITER : RW_READER);
		if (ISSET(flags, LK_NOWAIT)) {
			if (!rw_tryenter(&vip->vi_lock, op))
				return EBUSY;
		} else {
			rw_enter(&vip->vi_lock, op);
		}
	}
	VSTATE_ASSERT_UNLOCKED(vp, VS_ACTIVE);
	return 0;
}

/*
 * Unlock the node.
 */
int
genfs_unlock(void *v)
{
	struct vop_unlock_args /* {
		struct vnode *a_vp;
	} */ *ap = v;
	vnode_t *vp = ap->a_vp;
	vnode_impl_t *vip = VNODE_TO_VIMPL(vp);

	rw_exit(&vip->vi_lock);

	return 0;
}

/*
 * Return whether or not the node is locked.
 */
int
genfs_islocked(void *v)
{
	struct vop_islocked_args /* {
		struct vnode *a_vp;
	} */ *ap = v;
	vnode_t *vp = ap->a_vp;
	vnode_impl_t *vip = VNODE_TO_VIMPL(vp);

	if (rw_write_held(&vip->vi_lock))
		return LK_EXCLUSIVE;

	if (rw_read_held(&vip->vi_lock))
		return LK_SHARED;

	return 0;
}

int
genfs_mmap(void *v)
{

	return (0);
}

/*
 * VOP_PUTPAGES() for vnodes which never have pages.
 */

int
genfs_null_putpages(void *v)
{
	struct vop_putpages_args /* {
		struct vnode *a_vp;
		voff_t a_offlo;
		voff_t a_offhi;
		int a_flags;
	} */ *ap = v;
	struct vnode *vp = ap->a_vp;

	KASSERT(vp->v_uobj.uo_npages == 0);
	rw_exit(vp->v_uobj.vmobjlock);
	return (0);
}

void
genfs_node_init(struct vnode *vp, const struct genfs_ops *ops)
{
	struct genfs_node *gp = VTOG(vp);

	rw_init(&gp->g_glock);
	gp->g_op = ops;
}

void
genfs_node_destroy(struct vnode *vp)
{
	struct genfs_node *gp = VTOG(vp);

	rw_destroy(&gp->g_glock);
}

void
genfs_size(struct vnode *vp, off_t size, off_t *eobp, int flags)
{
	int bsize;

	bsize = 1 << vp->v_mount->mnt_fs_bshift;
	*eobp = (size + bsize - 1) & ~(bsize - 1);
}

static void
filt_genfsdetach(struct knote *kn)
{
	struct vnode *vp = (struct vnode *)kn->kn_hook;

	vn_knote_detach(vp, kn);
}

static int
filt_genfsread(struct knote *kn, long hint)
{
	struct vnode *vp = (struct vnode *)kn->kn_hook;
	int rv;

	/*
	 * filesystem is gone, so set the EOF flag and schedule
	 * the knote for deletion.
	 */
	switch (hint) {
	case NOTE_REVOKE:
		KASSERT(mutex_owned(vp->v_interlock));
		knote_set_eof(kn, EV_ONESHOT);
		return (1);
	case 0:
		mutex_enter(vp->v_interlock);
		kn->kn_data = vp->v_size - ((file_t *)kn->kn_obj)->f_offset;
		rv = (kn->kn_data != 0);
		mutex_exit(vp->v_interlock);
		return rv;
	default:
		KASSERT(mutex_owned(vp->v_interlock));
		kn->kn_data = vp->v_size - ((file_t *)kn->kn_obj)->f_offset;
		return (kn->kn_data != 0);
	}
}

static int
filt_genfswrite(struct knote *kn, long hint)
{
	struct vnode *vp = (struct vnode *)kn->kn_hook;

	/*
	 * filesystem is gone, so set the EOF flag and schedule
	 * the knote for deletion.
	 */
	switch (hint) {
	case NOTE_REVOKE:
		KASSERT(mutex_owned(vp->v_interlock));
		knote_set_eof(kn, EV_ONESHOT);
		return (1);
	case 0:
		mutex_enter(vp->v_interlock);
		kn->kn_data = 0;
		mutex_exit(vp->v_interlock);
		return 1;
	default:
		KASSERT(mutex_owned(vp->v_interlock));
		kn->kn_data = 0;
		return 1;
	}
}

static int
filt_genfsvnode(struct knote *kn, long hint)
{
	struct vnode *vp = (struct vnode *)kn->kn_hook;
	int fflags;

	switch (hint) {
	case NOTE_REVOKE:
		KASSERT(mutex_owned(vp->v_interlock));
		knote_set_eof(kn, 0);
		if ((kn->kn_sfflags & hint) != 0)
			kn->kn_fflags |= hint;
		return (1);
	case 0:
		mutex_enter(vp->v_interlock);
		fflags = kn->kn_fflags;
		mutex_exit(vp->v_interlock);
		break;
	default:
		KASSERT(mutex_owned(vp->v_interlock));
		if ((kn->kn_sfflags & hint) != 0)
			kn->kn_fflags |= hint;
		fflags = kn->kn_fflags;
		break;
	}

	return (fflags != 0);
}

static const struct filterops genfsread_filtops = {
	.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
	.f_attach = NULL,
	.f_detach = filt_genfsdetach,
	.f_event = filt_genfsread,
};

static const struct filterops genfswrite_filtops = {
	.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
	.f_attach = NULL,
	.f_detach = filt_genfsdetach,
	.f_event = filt_genfswrite,
};

static const struct filterops genfsvnode_filtops = {
	.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
	.f_attach = NULL,
	.f_detach = filt_genfsdetach,
	.f_event = filt_genfsvnode,
};

int
genfs_kqfilter(void *v)
{
	struct vop_kqfilter_args /* {
		struct vnode	*a_vp;
		struct knote	*a_kn;
	} */ *ap = v;
	struct vnode *vp;
	struct knote *kn;

	vp = ap->a_vp;
	kn = ap->a_kn;
	switch (kn->kn_filter) {
	case EVFILT_READ:
		kn->kn_fop = &genfsread_filtops;
		break;
	case EVFILT_WRITE:
		kn->kn_fop = &genfswrite_filtops;
		break;
	case EVFILT_VNODE:
		kn->kn_fop = &genfsvnode_filtops;
		break;
	default:
		return (EINVAL);
	}

	kn->kn_hook = vp;

	vn_knote_attach(vp, kn);

	return (0);
}

void
genfs_node_wrlock(struct vnode *vp)
{
	struct genfs_node *gp = VTOG(vp);

	rw_enter(&gp->g_glock, RW_WRITER);
}

void
genfs_node_rdlock(struct vnode *vp)
{
	struct genfs_node *gp = VTOG(vp);

	rw_enter(&gp->g_glock, RW_READER);
}

int
genfs_node_rdtrylock(struct vnode *vp)
{
	struct genfs_node *gp = VTOG(vp);

	return rw_tryenter(&gp->g_glock, RW_READER);
}

void
genfs_node_unlock(struct vnode *vp)
{
	struct genfs_node *gp = VTOG(vp);

	rw_exit(&gp->g_glock);
}

int
genfs_node_wrlocked(struct vnode *vp)
{
	struct genfs_node *gp = VTOG(vp);

	return rw_write_held(&gp->g_glock);
}

/*
 * Common filesystem object access control check routine.  Accepts a
 * vnode, cred, uid, gid, mode, acl, requested access mode.
 * Returns 0 on success, or an errno on failure.
 */
int
genfs_can_access(vnode_t *vp, kauth_cred_t cred, uid_t file_uid, gid_t file_gid,
    mode_t file_mode, struct acl *acl, accmode_t accmode)
{
	accmode_t dac_granted;
	int error;

	KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND)) == 0);
	KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE));

	/*
	 * Look for a normal, non-privileged way to access the file/directory
	 * as requested.  If it exists, go with that.
	 */

	dac_granted = 0;

	/* Check the owner. */
	if (kauth_cred_geteuid(cred) == file_uid) {
		dac_granted |= VADMIN;
		if (file_mode & S_IXUSR)
			dac_granted |= VEXEC;
		if (file_mode & S_IRUSR)
			dac_granted |= VREAD;
		if (file_mode & S_IWUSR)
			dac_granted |= (VWRITE | VAPPEND);

		goto privchk;
	}

	/* Otherwise, check the groups (first match) */
	/* Otherwise, check the groups. */
	error = kauth_cred_groupmember(cred, file_gid);
	if (error > 0)
		return error;
	if (error == 0) {
		if (file_mode & S_IXGRP)
			dac_granted |= VEXEC;
		if (file_mode & S_IRGRP)
			dac_granted |= VREAD;
		if (file_mode & S_IWGRP)
			dac_granted |= (VWRITE | VAPPEND);

		goto privchk;
	}

	/* Otherwise, check everyone else. */
	if (file_mode & S_IXOTH)
		dac_granted |= VEXEC;
	if (file_mode & S_IROTH)
		dac_granted |= VREAD;
	if (file_mode & S_IWOTH)
		dac_granted |= (VWRITE | VAPPEND);

privchk:
	if ((accmode & dac_granted) == accmode)
		return 0;

	return (accmode & VADMIN) ? EPERM : EACCES;
}

/*
 * Implement a version of genfs_can_access() that understands POSIX.1e ACL
 * semantics;
 * the access ACL has already been prepared for evaluation by the file system
 * and is passed via 'uid', 'gid', and 'acl'.  Return 0 on success, else an
 * errno value.
 */
int
genfs_can_access_acl_posix1e(vnode_t *vp, kauth_cred_t cred, uid_t file_uid,
    gid_t file_gid, mode_t file_mode, struct acl *acl, accmode_t accmode)
{
	struct acl_entry *acl_other, *acl_mask;
	accmode_t dac_granted;
	accmode_t acl_mask_granted;
	int group_matched, i;
	int error;

	KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND)) == 0);
	KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE));

	/*
	 * The owner matches if the effective uid associated with the
	 * credential matches that of the ACL_USER_OBJ entry.  While we're
	 * doing the first scan, also cache the location of the ACL_MASK and
	 * ACL_OTHER entries, preventing some future iterations.
	 */
	acl_mask = acl_other = NULL;
	for (i = 0; i < acl->acl_cnt; i++) {
		struct acl_entry *ae = &acl->acl_entry[i];
		switch (ae->ae_tag) {
		case ACL_USER_OBJ:
			if (kauth_cred_geteuid(cred) != file_uid)
				break;
			dac_granted = 0;
			dac_granted |= VADMIN;
			if (ae->ae_perm & ACL_EXECUTE)
				dac_granted |= VEXEC;
			if (ae->ae_perm & ACL_READ)
				dac_granted |= VREAD;
			if (ae->ae_perm & ACL_WRITE)
				dac_granted |= (VWRITE | VAPPEND);
			goto out;

		case ACL_MASK:
			acl_mask = ae;
			break;

		case ACL_OTHER:
			acl_other = ae;
			break;

		default:
			break;
		}
	}

	/*
	 * An ACL_OTHER entry should always exist in a valid access ACL.  If
	 * it doesn't, then generate a serious failure.	 For now, this means
	 * a debugging message and EPERM, but in the future should probably
	 * be a panic.
	 */
	if (acl_other == NULL) {
		/*
		 * XXX This should never happen
		 */
		printf("%s: ACL_OTHER missing\n", __func__);
		return EPERM;
	}

	/*
	 * Checks against ACL_USER, ACL_GROUP_OBJ, and ACL_GROUP fields are
	 * masked by an ACL_MASK entry, if any.	 As such, first identify the
	 * ACL_MASK field, then iterate through identifying potential user
	 * matches, then group matches.	 If there is no ACL_MASK, assume that
	 * the mask allows all requests to succeed.
	 */
	if (acl_mask != NULL) {
		acl_mask_granted = 0;
		if (acl_mask->ae_perm & ACL_EXECUTE)
			acl_mask_granted |= VEXEC;
		if (acl_mask->ae_perm & ACL_READ)
			acl_mask_granted |= VREAD;
		if (acl_mask->ae_perm & ACL_WRITE)
			acl_mask_granted |= (VWRITE | VAPPEND);
	} else
		acl_mask_granted = VEXEC | VREAD | VWRITE | VAPPEND;

	/*
	 * Check ACL_USER ACL entries.	There will either be one or no
	 * matches; if there is one, we accept or rejected based on the
	 * match; otherwise, we continue on to groups.
	 */
	for (i = 0; i < acl->acl_cnt; i++) {
		struct acl_entry *ae = &acl->acl_entry[i];
		switch (ae->ae_tag) {
		case ACL_USER:
			if (kauth_cred_geteuid(cred) != ae->ae_id)
				break;
			dac_granted = 0;
			if (ae->ae_perm & ACL_EXECUTE)
				dac_granted |= VEXEC;
			if (ae->ae_perm & ACL_READ)
				dac_granted |= VREAD;
			if (ae->ae_perm & ACL_WRITE)
				dac_granted |= (VWRITE | VAPPEND);
			dac_granted &= acl_mask_granted;
			goto out;
		}
	}

	/*
	 * Group match is best-match, not first-match, so find a "best"
	 * match.  Iterate across, testing each potential group match.	Make
	 * sure we keep track of whether we found a match or not, so that we
	 * know if we should try again with any available privilege, or if we
	 * should move on to ACL_OTHER.
	 */
	group_matched = 0;
	for (i = 0; i < acl->acl_cnt; i++) {
		struct acl_entry *ae = &acl->acl_entry[i];
		switch (ae->ae_tag) {
		case ACL_GROUP_OBJ:
			error = kauth_cred_groupmember(cred, file_gid);
			if (error > 0)
				return error;
			if (error)
				break;
			dac_granted = 0;
			if (ae->ae_perm & ACL_EXECUTE)
				dac_granted |= VEXEC;
			if (ae->ae_perm & ACL_READ)
				dac_granted |= VREAD;
			if (ae->ae_perm & ACL_WRITE)
				dac_granted |= (VWRITE | VAPPEND);
			dac_granted  &= acl_mask_granted;

			if ((accmode & dac_granted) == accmode)
				return 0;

			group_matched = 1;
			break;

		case ACL_GROUP:
			error = kauth_cred_groupmember(cred, ae->ae_id);
			if (error > 0)
				return error;
			if (error)
				break;
			dac_granted = 0;
			if (ae->ae_perm & ACL_EXECUTE)
				dac_granted |= VEXEC;
			if (ae->ae_perm & ACL_READ)
				dac_granted |= VREAD;
			if (ae->ae_perm & ACL_WRITE)
				dac_granted |= (VWRITE | VAPPEND);
			dac_granted  &= acl_mask_granted;

			if ((accmode & dac_granted) == accmode)
				return 0;

			group_matched = 1;
			break;

		default:
			break;
		}
	}

	if (group_matched == 1) {
		/*
		 * There was a match, but it did not grant rights via pure
		 * DAC.	 Try again, this time with privilege.
		 */
		for (i = 0; i < acl->acl_cnt; i++) {
			struct acl_entry *ae = &acl->acl_entry[i];
			switch (ae->ae_tag) {
			case ACL_GROUP_OBJ:
				error = kauth_cred_groupmember(cred, file_gid);
				if (error > 0)
					return error;
				if (error)
					break;
				dac_granted = 0;
				if (ae->ae_perm & ACL_EXECUTE)
					dac_granted |= VEXEC;
				if (ae->ae_perm & ACL_READ)
					dac_granted |= VREAD;
				if (ae->ae_perm & ACL_WRITE)
					dac_granted |= (VWRITE | VAPPEND);
				dac_granted &= acl_mask_granted;
				goto out;

			case ACL_GROUP:
				error = kauth_cred_groupmember(cred, ae->ae_id);
				if (error > 0)
					return error;
				if (error)
					break;
				dac_granted = 0;
				if (ae->ae_perm & ACL_EXECUTE)
				dac_granted |= VEXEC;
				if (ae->ae_perm & ACL_READ)
					dac_granted |= VREAD;
				if (ae->ae_perm & ACL_WRITE)
					dac_granted |= (VWRITE | VAPPEND);
				dac_granted &= acl_mask_granted;

				goto out;
			default:
				break;
			}
		}
		/*
		 * Even with privilege, group membership was not sufficient.
		 * Return failure.
		 */
		dac_granted = 0;
		goto out;
	}
		
	/*
	 * Fall back on ACL_OTHER.  ACL_MASK is not applied to ACL_OTHER.
	 */
	dac_granted = 0;
	if (acl_other->ae_perm & ACL_EXECUTE)
		dac_granted |= VEXEC;
	if (acl_other->ae_perm & ACL_READ)
		dac_granted |= VREAD;
	if (acl_other->ae_perm & ACL_WRITE)
		dac_granted |= (VWRITE | VAPPEND);

out:
	if ((accmode & dac_granted) == accmode)
		return 0;
	return (accmode & VADMIN) ? EPERM : EACCES;
}

static struct {
	accmode_t accmode;
	int mask;
} accmode2mask[] = {
	{ VREAD, ACL_READ_DATA },
	{ VWRITE, ACL_WRITE_DATA },
	{ VAPPEND, ACL_APPEND_DATA },
	{ VEXEC, ACL_EXECUTE },
	{ VREAD_NAMED_ATTRS, ACL_READ_NAMED_ATTRS },
	{ VWRITE_NAMED_ATTRS, ACL_WRITE_NAMED_ATTRS },
	{ VDELETE_CHILD, ACL_DELETE_CHILD },
	{ VREAD_ATTRIBUTES, ACL_READ_ATTRIBUTES },
	{ VWRITE_ATTRIBUTES, ACL_WRITE_ATTRIBUTES },
	{ VDELETE, ACL_DELETE },
	{ VREAD_ACL, ACL_READ_ACL },
	{ VWRITE_ACL, ACL_WRITE_ACL },
	{ VWRITE_OWNER, ACL_WRITE_OWNER },
	{ VSYNCHRONIZE, ACL_SYNCHRONIZE },
	{ 0, 0 },
};

static int
_access_mask_from_accmode(accmode_t accmode)
{
	int access_mask = 0, i;

	for (i = 0; accmode2mask[i].accmode != 0; i++) {
		if (accmode & accmode2mask[i].accmode)
			access_mask |= accmode2mask[i].mask;
	}

	/*
	 * VAPPEND is just a modifier for VWRITE; if the caller asked
	 * for 'VAPPEND | VWRITE', we want to check for ACL_APPEND_DATA only.
	 */
	if (access_mask & ACL_APPEND_DATA)
		access_mask &= ~ACL_WRITE_DATA;

	return (access_mask);
}

/*
 * Return 0, iff access is allowed, 1 otherwise.
 */
static int
_acl_denies(const struct acl *aclp, int access_mask, kauth_cred_t cred,
    int file_uid, int file_gid, int *denied_explicitly)
{
	int i, error;
	const struct acl_entry *ae;

	if (denied_explicitly != NULL)
		*denied_explicitly = 0;

	KASSERT(aclp->acl_cnt <= ACL_MAX_ENTRIES);

	for (i = 0; i < aclp->acl_cnt; i++) {
		ae = &(aclp->acl_entry[i]);

		if (ae->ae_entry_type != ACL_ENTRY_TYPE_ALLOW &&
		    ae->ae_entry_type != ACL_ENTRY_TYPE_DENY)
			continue;
		if (ae->ae_flags & ACL_ENTRY_INHERIT_ONLY)
			continue;
		switch (ae->ae_tag) {
		case ACL_USER_OBJ:
			if (kauth_cred_geteuid(cred) != file_uid)
				continue;
			break;
		case ACL_USER:
			if (kauth_cred_geteuid(cred) != ae->ae_id)
				continue;
			break;
		case ACL_GROUP_OBJ:
			error = kauth_cred_groupmember(cred, file_gid);
			if (error > 0)
				return error;
			if (error != 0)
				continue;
			break;
		case ACL_GROUP:
			error = kauth_cred_groupmember(cred, ae->ae_id);
			if (error > 0)
				return error;
			if (error != 0)
				continue;
			break;
		default:
			KASSERT(ae->ae_tag == ACL_EVERYONE);
		}

		if (ae->ae_entry_type == ACL_ENTRY_TYPE_DENY) {
			if (ae->ae_perm & access_mask) {
				if (denied_explicitly != NULL)
					*denied_explicitly = 1;
				return (1);
			}
		}

		access_mask &= ~(ae->ae_perm);
		if (access_mask == 0)
			return (0);
	}

	if (access_mask == 0)
		return (0);

	return (1);
}

int
genfs_can_access_acl_nfs4(vnode_t *vp, kauth_cred_t cred, uid_t file_uid,
    gid_t file_gid, mode_t file_mode, struct acl *aclp, accmode_t accmode)
{
	int denied, explicitly_denied, access_mask, is_directory,
	    must_be_owner = 0;
	file_mode = 0;

	KASSERT((accmode & ~(VEXEC | VWRITE | VREAD | VADMIN | VAPPEND |
	    VEXPLICIT_DENY | VREAD_NAMED_ATTRS | VWRITE_NAMED_ATTRS |
	    VDELETE_CHILD | VREAD_ATTRIBUTES | VWRITE_ATTRIBUTES | VDELETE |
	    VREAD_ACL | VWRITE_ACL | VWRITE_OWNER | VSYNCHRONIZE)) == 0);
	KASSERT((accmode & VAPPEND) == 0 || (accmode & VWRITE));

	if (accmode & VADMIN)
		must_be_owner = 1;

	/*
	 * Ignore VSYNCHRONIZE permission.
	 */
	accmode &= ~VSYNCHRONIZE;

	access_mask = _access_mask_from_accmode(accmode);

	if (vp && vp->v_type == VDIR)
		is_directory = 1;
	else
		is_directory = 0;

	/*
	 * File owner is always allowed to read and write the ACL
	 * and basic attributes.  This is to prevent a situation
	 * where user would change ACL in a way that prevents him
	 * from undoing the change.
	 */
	if (kauth_cred_geteuid(cred) == file_uid)
		access_mask &= ~(ACL_READ_ACL | ACL_WRITE_ACL |
		    ACL_READ_ATTRIBUTES | ACL_WRITE_ATTRIBUTES);

	/*
	 * Ignore append permission for regular files; use write
	 * permission instead.
	 */
	if (!is_directory && (access_mask & ACL_APPEND_DATA)) {
		access_mask &= ~ACL_APPEND_DATA;
		access_mask |= ACL_WRITE_DATA;
	}

	denied = _acl_denies(aclp, access_mask, cred, file_uid, file_gid,
	    &explicitly_denied);

	if (must_be_owner) {
		if (kauth_cred_geteuid(cred) != file_uid)
			denied = EPERM;
	}

	/*
	 * For VEXEC, ensure that at least one execute bit is set for
	 * non-directories. We have to check the mode here to stay
	 * consistent with execve(2). See the test in
	 * exec_check_permissions().
	 */
	__acl_nfs4_sync_mode_from_acl(&file_mode, aclp);
	if (!denied && !is_directory && (accmode & VEXEC) &&
	    (file_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0)
		denied = EACCES;

	if (!denied)
		return (0);

	/*
	 * Access failed.  Iff it was not denied explicitly and
	 * VEXPLICIT_DENY flag was specified, allow access.
	 */
	if ((accmode & VEXPLICIT_DENY) && explicitly_denied == 0)
		return (0);

	accmode &= ~VEXPLICIT_DENY;

	if (accmode & (VADMIN_PERMS | VDELETE_CHILD | VDELETE))
		denied = EPERM;
	else
		denied = EACCES;

	return (denied);
}

/*
 * Common routine to check if chmod() is allowed.
 *
 * Policy:
 *   - You must own the file, and
 *     - You must not set the "sticky" bit (meaningless, see chmod(2))
 *     - You must be a member of the group if you're trying to set the
 *	 SGIDf bit
 *
 * vp - vnode of the file-system object
 * cred - credentials of the invoker
 * cur_uid, cur_gid - current uid/gid of the file-system object
 * new_mode - new mode for the file-system object
 *
 * Returns 0 if the change is allowed, or an error value otherwise.
 */
int
genfs_can_chmod(vnode_t *vp, kauth_cred_t cred, uid_t cur_uid,
    gid_t cur_gid, mode_t new_mode)
{
	int error;

	/*
	 * To modify the permissions on a file, must possess VADMIN
	 * for that file.
	 */
	if ((error = VOP_ACCESSX(vp, VWRITE_ACL, cred)) != 0)
		return (error);

	/*
	 * Unprivileged users can't set the sticky bit on files.
	 */
	if ((vp->v_type != VDIR) && (new_mode & S_ISTXT))
		return (EFTYPE);

	/*
	 * If the invoker is trying to set the SGID bit on the file,
	 * check group membership.
	 */
	if (new_mode & S_ISGID) {
		int ismember;

		error = kauth_cred_ismember_gid(cred, cur_gid,
		    &ismember);
		if (error || !ismember)
			return (EPERM);
	}

	/*
	 * Deny setting setuid if we are not the file owner.
	 */
	if ((new_mode & S_ISUID) && cur_uid != kauth_cred_geteuid(cred))
		return (EPERM);

	return (0);
}

/*
 * Common routine to check if chown() is allowed.
 *
 * Policy:
 *   - You must own the file, and
 *     - You must not try to change ownership, and
 *     - You must be member of the new group
 *
 * vp - vnode
 * cred - credentials of the invoker
 * cur_uid, cur_gid - current uid/gid of the file-system object
 * new_uid, new_gid - target uid/gid of the file-system object
 *
 * Returns 0 if the change is allowed, or an error value otherwise.
 */
int	
genfs_can_chown(vnode_t *vp, kauth_cred_t cred, uid_t cur_uid,
    gid_t cur_gid, uid_t new_uid, gid_t new_gid)
{
	int error, ismember;

	/*
	 * To modify the ownership of a file, must possess VADMIN for that
	 * file.
	 */
	if ((error = VOP_ACCESSX(vp, VWRITE_OWNER, cred)) != 0)
		return (error);

	/*
	 * You can only change ownership of a file if:
	 * You own the file and...
	 */
	if (kauth_cred_geteuid(cred) == cur_uid) {
		/*
		 * You don't try to change ownership, and...
		 */
		if (new_uid != cur_uid)
			return (EPERM);

		/*
		 * You don't try to change group (no-op), or...
		 */
		if (new_gid == cur_gid)
			return (0);

		/*
		 * Your effective gid is the new gid, or...
		 */
		if (kauth_cred_getegid(cred) == new_gid)
			return (0);

		/*
		 * The new gid is one you're a member of.
		 */
		ismember = 0;
		error = kauth_cred_ismember_gid(cred, new_gid,
		    &ismember);
		if (!error && ismember)
			return (0);
	}

	return (EPERM);
}

int
genfs_can_chtimes(vnode_t *vp, kauth_cred_t cred, uid_t owner_uid,
    u_int vaflags)
{
	int error;
	/*
	 * Grant permission if the caller is the owner of the file, or
	 * the super-user, or has ACL_WRITE_ATTRIBUTES permission on
	 * on the file.	 If the time pointer is null, then write
	 * permission on the file is also sufficient.
	 *
	 * From NFSv4.1, draft 21, 6.2.1.3.1, Discussion of Mask Attributes: 
	 * A user having ACL_WRITE_DATA or ACL_WRITE_ATTRIBUTES
	 * will be allowed to set the times [..] to the current 
	 * server time.
	 */
	error = VOP_ACCESSX(vp, VWRITE_ATTRIBUTES, cred);
	if (error != 0 && (vaflags & VA_UTIMES_NULL) != 0)
		error = VOP_ACCESS(vp, VWRITE, cred);

	if (error)
		return (vaflags & VA_UTIMES_NULL) == 0 ? EPERM : EACCES;

	return 0;
}

/*
 * Common routine to check if chflags() is allowed.
 *
 * Policy:
 *   - You must own the file, and
 *   - You must not change system flags, and
 *   - You must not change flags on character/block devices.
 *
 * vp - vnode
 * cred - credentials of the invoker
 * owner_uid - uid of the file-system object
 * changing_sysflags - true if the invoker wants to change system flags
 */
int
genfs_can_chflags(vnode_t *vp, kauth_cred_t cred,
     uid_t owner_uid, bool changing_sysflags)
{

	/* The user must own the file. */
	if (kauth_cred_geteuid(cred) != owner_uid) {
		return EPERM;
	}

	if (changing_sysflags) {
		return EPERM;
	}

	/*
	 * Unprivileged users cannot change the flags on devices, even if they
	 * own them.
	 */
	if (vp->v_type == VCHR || vp->v_type == VBLK) {
		return EPERM;
	}

	return 0;
}

/*
 * Common "sticky" policy.
 *
 * When a directory is "sticky" (as determined by the caller), this
 * function may help implementing the following policy:
 * - Renaming a file in it is only possible if the user owns the directory
 *   or the file being renamed.
 * - Deleting a file from it is only possible if the user owns the
 *   directory or the file being deleted.
 */
int
genfs_can_sticky(vnode_t *vp, kauth_cred_t cred, uid_t dir_uid, uid_t file_uid)
{
	if (kauth_cred_geteuid(cred) != dir_uid &&
	    kauth_cred_geteuid(cred) != file_uid)
		return EPERM;

	return 0;
}

int
genfs_can_extattr(vnode_t *vp, kauth_cred_t cred, accmode_t accmode,
    int attrnamespace)
{
	/*
	 * Kernel-invoked always succeeds.
	 */
	if (cred == NOCRED)
		return 0;

	switch (attrnamespace) {
	case EXTATTR_NAMESPACE_SYSTEM:
		return kauth_authorize_system(cred, KAUTH_SYSTEM_FS_EXTATTR,
		    0, vp->v_mount, NULL, NULL);
	case EXTATTR_NAMESPACE_USER:
		return VOP_ACCESS(vp, accmode, cred);
	default:
		return EPERM;
	}
}

int
genfs_access(void *v)
{
	struct vop_access_args *ap = v;

	KASSERT((ap->a_accmode & ~(VEXEC | VWRITE | VREAD | VADMIN |
	    VAPPEND)) == 0);

	return VOP_ACCESSX(ap->a_vp, ap->a_accmode, ap->a_cred);
}

int
genfs_accessx(void *v)
{
	struct vop_accessx_args *ap = v;
	int error;
	accmode_t accmode = ap->a_accmode;
	error = vfs_unixify_accmode(&accmode);
	if (error != 0)
		return error;

	if (accmode == 0)
		return 0;

	return VOP_ACCESS(ap->a_vp, accmode, ap->a_cred);
}

/*
 * genfs_pathconf:
 *
 * Standard implementation of POSIX pathconf, to get information about limits
 * for a filesystem.
 * Override per filesystem for the case where the filesystem has smaller
 * limits.
 */
int
genfs_pathconf(void *v)
{
	struct vop_pathconf_args *ap = v;

	switch (ap->a_name) {
	case _PC_PATH_MAX:
		*ap->a_retval = PATH_MAX;
		return 0;
	case _PC_ACL_EXTENDED:
	case _PC_ACL_NFS4:
		*ap->a_retval = 0;
		return 0;
	default:
		return EINVAL;
	}
}
