#include "opt_quota.h"
#include "opt_suiddir.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/unistd.h>
#include <sys/vnode.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/jail.h>
#include <sys/sysctl.h>
#include <sys/sfbuf.h>
#include <vm/vm_extern.h>
#include <vm/vm_object.h>
#include <vm/vm_page2.h>
#ifdef LWBUF_IS_OPTIMAL
static int vm_read_shortcut_enable = 1;
SYSCTL_INT(_vm, OID_AUTO, read_shortcut_enable, CTLFLAG_RW,
&vm_read_shortcut_enable, 0, "Direct vm_object vop_read shortcut");
#endif
int
vop_helper_access(struct vop_access_args *ap, uid_t ino_uid, gid_t ino_gid,
mode_t ino_mode, u_int32_t ino_flags)
{
struct vnode *vp = ap->a_vp;
struct ucred *cred = ap->a_cred;
mode_t mask, mode = ap->a_mode;
gid_t *gp;
int i;
uid_t proc_uid;
gid_t proc_gid;
if (ap->a_flags & AT_EACCESS) {
proc_uid = cred->cr_uid;
proc_gid = cred->cr_gid;
} else {
proc_uid = cred->cr_ruid;
proc_gid = cred->cr_rgid;
}
if (mode & VWRITE) {
switch (vp->v_type) {
case VDIR:
case VLNK:
case VREG:
case VDATABASE:
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EROFS);
break;
default:
break;
}
}
if ((mode & VWRITE) && (ino_flags & IMMUTABLE))
return (EPERM);
if (proc_uid == 0)
return (0);
mask = 0;
if (proc_uid == ino_uid) {
if (mode & VEXEC)
mask |= S_IXUSR;
if (mode & VREAD)
mask |= S_IRUSR;
if (mode & VWRITE)
mask |= S_IWUSR;
return ((ino_mode & mask) == mask ? 0 : EACCES);
}
if (proc_gid == ino_gid) {
if (mode & VEXEC)
mask |= S_IXGRP;
if (mode & VREAD)
mask |= S_IRGRP;
if (mode & VWRITE)
mask |= S_IWGRP;
return ((ino_mode & mask) == mask ? 0 : EACCES);
}
for (i = 1, gp = &cred->cr_groups[1]; i < cred->cr_ngroups; i++, gp++)
if (ino_gid == *gp) {
if (mode & VEXEC)
mask |= S_IXGRP;
if (mode & VREAD)
mask |= S_IRGRP;
if (mode & VWRITE)
mask |= S_IWGRP;
return ((ino_mode & mask) == mask ? 0 : EACCES);
}
if (mode & VEXEC)
mask |= S_IXOTH;
if (mode & VREAD)
mask |= S_IROTH;
if (mode & VWRITE)
mask |= S_IWOTH;
return ((ino_mode & mask) == mask ? 0 : EACCES);
}
int
vop_helper_setattr_flags(u_int32_t *ino_flags, u_int32_t vaflags,
uid_t uid, struct ucred *cred)
{
int error;
if (cred->cr_uid != uid &&
(error = caps_priv_check(cred, SYSCAP_NOVFS_SYSFLAGS)))
{
return(error);
}
if (cred->cr_uid == 0 &&
(!jailed(cred) || PRISON_CAP_ISSET(cred->cr_prison->pr_caps,
PRISON_CAP_VFS_CHFLAGS))) {
if ((*ino_flags & (SF_NOUNLINK|SF_IMMUTABLE|SF_APPEND)) &&
securelevel > 0)
return (EPERM);
*ino_flags = vaflags;
} else {
if (*ino_flags & (SF_NOUNLINK|SF_IMMUTABLE|SF_APPEND) ||
(vaflags & UF_SETTABLE) != vaflags)
return (EPERM);
*ino_flags &= SF_SETTABLE;
*ino_flags |= vaflags & UF_SETTABLE;
}
return(0);
}
uid_t
vop_helper_create_uid(struct mount *mp, mode_t dmode, uid_t duid,
struct ucred *cred, mode_t *modep)
{
#ifdef SUIDDIR
if ((mp->mnt_flag & MNT_SUIDDIR) && (dmode & S_ISUID) &&
duid != cred->cr_uid && duid) {
*modep &= ~07111;
return(duid);
}
#endif
return(cred->cr_uid);
}
int
vop_helper_chmod(struct vnode *vp, mode_t new_mode, struct ucred *cred,
uid_t cur_uid, gid_t cur_gid, mode_t *cur_modep)
{
int error;
if (cred->cr_uid != cur_uid) {
error = caps_priv_check(cred, SYSCAP_NOVFS_CHMOD);
if (error)
return (error);
}
if (cred->cr_uid) {
if (vp->v_type != VDIR && (*cur_modep & S_ISTXT))
return (EFTYPE);
if (!groupmember(cur_gid, cred) && (*cur_modep & S_ISGID))
return (EPERM);
}
*cur_modep &= ~ALLPERMS;
*cur_modep |= new_mode & ALLPERMS;
return(0);
}
int
vop_helper_chown(struct vnode *vp, uid_t new_uid, gid_t new_gid,
struct ucred *cred,
uid_t *cur_uidp, gid_t *cur_gidp, mode_t *cur_modep)
{
gid_t ogid;
uid_t ouid;
int error;
if (new_uid == (uid_t)VNOVAL)
new_uid = *cur_uidp;
if (new_gid == (gid_t)VNOVAL)
new_gid = *cur_gidp;
if ((cred->cr_uid != *cur_uidp || new_uid != *cur_uidp ||
(new_gid != *cur_gidp && !(cred->cr_gid == new_gid ||
groupmember(new_gid, cred)))) &&
(error = caps_priv_check(cred, SYSCAP_NOVFS_CHOWN)))
{
return (error);
}
ogid = *cur_gidp;
ouid = *cur_uidp;
*cur_uidp = new_uid;
*cur_gidp = new_gid;
if (cred->cr_uid != 0 && (ouid != new_uid || ogid != new_gid))
*cur_modep &= ~(S_ISUID | S_ISGID);
return(0);
}
#ifdef LWBUF_IS_OPTIMAL
int
vop_helper_read_shortcut(struct vop_read_args *ap)
{
struct vnode *vp;
struct uio *uio;
struct lwbuf *lwb;
struct lwbuf lwb_cache;
vm_object_t obj;
vm_page_t m;
int offset;
int n;
int error;
vp = ap->a_vp;
uio = ap->a_uio;
if (vm_read_shortcut_enable == 0)
return(0);
if (vp->v_object == NULL || uio->uio_segflg == UIO_NOCOPY)
return(0);
if (vp->v_filesize == NOOFFSET)
return(0);
if (uio->uio_resid == 0)
return(0);
obj = vp->v_object;
vm_object_hold_shared(obj);
error = 0;
while (uio->uio_resid && error == 0) {
offset = (int)uio->uio_offset & PAGE_MASK;
n = PAGE_SIZE - offset;
if (n > uio->uio_resid)
n = uio->uio_resid;
if (vp->v_filesize < uio->uio_offset)
break;
if (uio->uio_offset + n > vp->v_filesize)
n = vp->v_filesize - uio->uio_offset;
if (n == 0)
break;
m = vm_page_lookup_sbusy_try(obj, OFF_TO_IDX(uio->uio_offset),
0, PAGE_SIZE);
if (error || m == NULL) {
error = 0;
break;
}
if ((m->valid & VM_PAGE_BITS_ALL) != VM_PAGE_BITS_ALL) {
vm_page_sbusy_drop(m);
break;
}
lwb = lwbuf_alloc(m, &lwb_cache);
error = uiomove_nofault((char *)lwbuf_kva(lwb) + offset,
n, uio);
vm_page_flag_set(m, PG_REFERENCED);
lwbuf_free(lwb);
vm_page_sbusy_drop(m);
}
vm_object_drop(obj);
if (error == EFAULT)
error = 0;
return (error);
}
#else
int
vop_helper_read_shortcut(struct vop_read_args *ap)
{
return(0);
}
#endif