#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: genfs_vnops.c,v 1.220 2023/03/03 10:02:51 hannken 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);
int
genfs_parsepath(void *v)
{
struct vop_parsepath_args
*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
*ap = v;
return (ap->a_events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM));
}
int
genfs_seek(void *v)
{
struct vop_seek_args
*ap = v;
if (ap->a_newoff < 0)
return (EINVAL);
return (0);
}
int
genfs_abortop(void *v)
{
struct vop_abortop_args
*ap = v;
(void)ap;
return (0);
}
int
genfs_fcntl(void *v)
{
struct vop_fcntl_args
*ap = v;
if (ap->a_command == F_SETFL)
return (0);
else
return (EOPNOTSUPP);
}
int
genfs_badop(void *v)
{
panic("genfs: bad op");
}
int
genfs_nullop(void *v)
{
return (0);
}
int
genfs_einval(void *v)
{
return (EINVAL);
}
int
genfs_erofs_link(void *v)
{
struct vop_link_v2_args
*ap = v;
VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
return EROFS;
}
int
genfs_eopnotsupp(void *v)
{
struct vop_generic_args
*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);
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;
if ((j = flags & VDESC_VP0_WILLPUT)) {
vp = *VOPARG_OFFSETTO(struct vnode **, offset_vp, ap);
if (!vp)
continue;
switch (j) {
case VDESC_VP0_WILLPUT:
if (vp == vp_last)
vrele(vp);
else {
vput(vp);
vp_last = vp;
}
break;
case VDESC_VP0_WILLRELE:
vrele(vp);
break;
}
}
}
return (EOPNOTSUPP);
}
int
genfs_ebadf(void *v)
{
return (EBADF);
}
int
genfs_enoioctl(void *v)
{
return (EPASSTHROUGH);
}
int
genfs_revoke(void *v)
{
struct vop_revoke_args
*ap = v;
#ifdef DIAGNOSTIC
if ((ap->a_flags & REVOKEALL) == 0)
panic("genfs_revoke: not revokeall");
#endif
vrevoke(ap->a_vp);
return (0);
}
int
genfs_deadlock(void *v)
{
struct vop_lock_args
*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;
}
int
genfs_deadunlock(void *v)
{
struct vop_unlock_args
*ap = v;
vnode_t *vp = ap->a_vp;
vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
rw_exit(&vip->vi_lock);
return 0;
}
int
genfs_lock(void *v)
{
struct vop_lock_args
*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;
}
int
genfs_unlock(void *v)
{
struct vop_unlock_args
*ap = v;
vnode_t *vp = ap->a_vp;
vnode_impl_t *vip = VNODE_TO_VIMPL(vp);
rw_exit(&vip->vi_lock);
return 0;
}
int
genfs_islocked(void *v)
{
struct vop_islocked_args
*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);
}
int
genfs_null_putpages(void *v)
{
struct vop_putpages_args
*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;
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;
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
*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);
}
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));
dac_granted = 0;
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;
}
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;
}
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;
}
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));
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;
}
}
if (acl_other == NULL) {
printf("%s: ACL_OTHER missing\n", __func__);
return EPERM;
}
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;
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_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) {
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;
}
}
dac_granted = 0;
goto out;
}
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;
}
if (access_mask & ACL_APPEND_DATA)
access_mask &= ~ACL_WRITE_DATA;
return (access_mask);
}
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;
accmode &= ~VSYNCHRONIZE;
access_mask = _access_mask_from_accmode(accmode);
if (vp && vp->v_type == VDIR)
is_directory = 1;
else
is_directory = 0;
if (kauth_cred_geteuid(cred) == file_uid)
access_mask &= ~(ACL_READ_ACL | ACL_WRITE_ACL |
ACL_READ_ATTRIBUTES | ACL_WRITE_ATTRIBUTES);
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;
}
__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);
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);
}
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;
if ((error = VOP_ACCESSX(vp, VWRITE_ACL, cred)) != 0)
return (error);
if ((vp->v_type != VDIR) && (new_mode & S_ISTXT))
return (EFTYPE);
if (new_mode & S_ISGID) {
int ismember;
error = kauth_cred_ismember_gid(cred, cur_gid,
&ismember);
if (error || !ismember)
return (EPERM);
}
if ((new_mode & S_ISUID) && cur_uid != kauth_cred_geteuid(cred))
return (EPERM);
return (0);
}
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;
if ((error = VOP_ACCESSX(vp, VWRITE_OWNER, cred)) != 0)
return (error);
if (kauth_cred_geteuid(cred) == cur_uid) {
if (new_uid != cur_uid)
return (EPERM);
if (new_gid == cur_gid)
return (0);
if (kauth_cred_getegid(cred) == new_gid)
return (0);
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;
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;
}
int
genfs_can_chflags(vnode_t *vp, kauth_cred_t cred,
uid_t owner_uid, bool changing_sysflags)
{
if (kauth_cred_geteuid(cred) != owner_uid) {
return EPERM;
}
if (changing_sysflags) {
return EPERM;
}
if (vp->v_type == VCHR || vp->v_type == VBLK) {
return EPERM;
}
return 0;
}
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)
{
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);
}
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;
}
}