#include <sys/cdefs.h>
#if 0
__FBSDID("$FreeBSD: head/sys/ufs/ufs/ufs_acl.c 356669 2020-01-13 02:31:51Z mjg $");
#endif
__KERNEL_RCSID(0, "$NetBSD: ufs_acl.c,v 1.7 2026/01/22 03:24:19 riastradh Exp $");
#if defined(_KERNEL_OPT)
#include "opt_ffs.h"
#include "opt_quota.h"
#include "opt_wapbl.h"
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/acl.h>
#include <sys/event.h>
#include <sys/extattr.h>
#include <sys/mount.h>
#include <sys/proc.h>
#include <sys/sdt.h>
#include <sys/stat.h>
#include <sys/systm.h>
#include <sys/vnode.h>
#include <sys/wapbl.h>
#include <ufs/ufs/acl.h>
#include <ufs/ufs/dir.h>
#include <ufs/ufs/extattr.h>
#include <ufs/ufs/inode.h>
#include <ufs/ufs/quota.h>
#include <ufs/ufs/ufs_extern.h>
#include <ufs/ufs/ufs_wapbl.h>
#include <ufs/ufs/ufsmount.h>
#include <ufs/ffs/fs.h>
#ifdef UFS_ACL
void
ufs_sync_acl_from_inode(struct inode *ip, struct acl *acl)
{
struct acl_entry *acl_mask, *acl_group_obj;
int i;
acl_mask = NULL;
acl_group_obj = NULL;
for (i = 0; i < acl->acl_cnt; i++) {
switch (acl->acl_entry[i].ae_tag) {
case ACL_USER_OBJ:
acl->acl_entry[i].ae_perm = acl_posix1e_mode_to_perm(
ACL_USER_OBJ, ip->i_mode);
acl->acl_entry[i].ae_id = ACL_UNDEFINED_ID;
break;
case ACL_GROUP_OBJ:
acl_group_obj = &acl->acl_entry[i];
acl->acl_entry[i].ae_id = ACL_UNDEFINED_ID;
break;
case ACL_OTHER:
acl->acl_entry[i].ae_perm = acl_posix1e_mode_to_perm(
ACL_OTHER, ip->i_mode);
acl->acl_entry[i].ae_id = ACL_UNDEFINED_ID;
break;
case ACL_MASK:
acl_mask = &acl->acl_entry[i];
acl->acl_entry[i].ae_id = ACL_UNDEFINED_ID;
break;
case ACL_USER:
case ACL_GROUP:
break;
default:
panic("ufs_sync_acl_from_inode(): bad ae_tag");
}
}
if (acl_group_obj == NULL)
panic("ufs_sync_acl_from_inode(): no ACL_GROUP_OBJ");
if (acl_mask == NULL) {
acl_group_obj->ae_perm = acl_posix1e_mode_to_perm(
ACL_GROUP_OBJ, ip->i_mode);
} else {
acl_mask->ae_perm = acl_posix1e_mode_to_perm(ACL_GROUP_OBJ,
ip->i_mode);
}
}
void
ufs_sync_inode_from_acl(struct acl *acl, struct inode *ip)
{
ip->i_mode &= ACL_PRESERVE_MASK;
ip->i_mode |= acl_posix1e_acl_to_mode(acl);
DIP_ASSIGN(ip, mode, ip->i_mode);
}
int
ufs_getacl_nfs4_internal(struct vnode *vp, struct acl *aclp, struct lwp *l)
{
int error;
size_t len;
struct inode *ip = VTOI(vp);
len = sizeof(*aclp);
bzero(aclp, len);
error = vn_extattr_get(vp, IO_NODELOCKED,
NFS4_ACL_EXTATTR_NAMESPACE, NFS4_ACL_EXTATTR_NAME, &len, aclp, l);
aclp->acl_maxcnt = ACL_MAX_ENTRIES;
if (error == ENOATTR) {
acl_nfs4_sync_acl_from_mode(aclp, ip->i_mode, ip->i_uid);
return 0;
}
if (error)
return error;
if (len != sizeof(*aclp)) {
printf("%s: Loaded invalid ACL ("
"%zu bytes), inumber %ju on %s\n", __func__, len,
(uintmax_t)ip->i_number, ip->i_fs->fs_fsmnt);
return SET_ERROR(EPERM);
}
error = acl_nfs4_check(aclp, vp->v_type == VDIR);
if (error) {
printf("%s: Loaded invalid ACL "
"(failed acl_nfs4_check), inumber %ju on %s\n", __func__,
(uintmax_t)ip->i_number, ip->i_fs->fs_fsmnt);
return SET_ERROR(EPERM);
}
return 0;
}
static int
ufs_getacl_nfs4(struct vop_getacl_args *ap, struct lwp *l)
{
int error;
if ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) == 0)
return SET_ERROR(EINVAL);
error = VOP_ACCESSX(ap->a_vp, VREAD_ACL, ap->a_cred);
if (error)
return error;
error = ufs_getacl_nfs4_internal(ap->a_vp, ap->a_aclp, l);
return error;
}
static int
ufs_get_oldacl(acl_type_t type, struct oldacl *old, struct vnode *vp,
struct lwp *l)
{
int error;
size_t len;
struct inode *ip = VTOI(vp);
len = sizeof(*old);
switch (type) {
case ACL_TYPE_ACCESS:
error = vn_extattr_get(vp, IO_NODELOCKED,
POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE,
POSIX1E_ACL_ACCESS_EXTATTR_NAME, &len, old, l);
break;
case ACL_TYPE_DEFAULT:
if (vp->v_type != VDIR)
return SET_ERROR(EINVAL);
error = vn_extattr_get(vp, IO_NODELOCKED,
POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE,
POSIX1E_ACL_DEFAULT_EXTATTR_NAME, &len, old, l);
break;
default:
return SET_ERROR(EINVAL);
}
if (error != 0)
return (error);
if (len != sizeof(*old)) {
printf("%s: Loaded invalid ACL "
"(len = %zu), inumber %ju on %s\n", __func__, len,
(uintmax_t)ip->i_number, ip->i_fs->fs_fsmnt);
return SET_ERROR(EPERM);
}
return 0;
}
static int
ufs_getacl_posix1e(struct vop_getacl_args *ap, struct lwp *l)
{
struct inode *ip = VTOI(ap->a_vp);
int error;
struct oldacl *old;
if ((ap->a_vp->v_mount->mnt_flag & MNT_POSIX1EACLS) == 0)
return SET_ERROR(EINVAL);
old = kmem_zalloc(sizeof(*old), KM_SLEEP);
error = ufs_get_oldacl(ap->a_type, old, ap->a_vp, l);
switch (error) {
case ENOATTR:
switch (ap->a_type) {
case ACL_TYPE_ACCESS:
old->acl_cnt = 3;
old->acl_entry[0].ae_tag = ACL_USER_OBJ;
old->acl_entry[0].ae_id = ACL_UNDEFINED_ID;
old->acl_entry[0].ae_perm = ACL_PERM_NONE;
old->acl_entry[1].ae_tag = ACL_GROUP_OBJ;
old->acl_entry[1].ae_id = ACL_UNDEFINED_ID;
old->acl_entry[1].ae_perm = ACL_PERM_NONE;
old->acl_entry[2].ae_tag = ACL_OTHER;
old->acl_entry[2].ae_id = ACL_UNDEFINED_ID;
old->acl_entry[2].ae_perm = ACL_PERM_NONE;
break;
case ACL_TYPE_DEFAULT:
old->acl_cnt = 0;
break;
}
case 0:
error = acl_copy_oldacl_into_acl(old, ap->a_aclp);
if (error != 0)
break;
if (ap->a_type == ACL_TYPE_ACCESS)
ufs_sync_acl_from_inode(ip, ap->a_aclp);
default:
break;
}
kmem_free(old, sizeof(*old));
return error;
}
int
ufs_getacl(void *v)
{
struct vop_getacl_args *ap = v;
if ((ap->a_vp->v_mount->mnt_flag & (MNT_POSIX1EACLS | MNT_NFS4ACLS)) == 0)
return SET_ERROR(EOPNOTSUPP);
if (ap->a_type == ACL_TYPE_NFS4)
return ufs_getacl_nfs4(ap, curlwp);
return ufs_getacl_posix1e(ap, curlwp);
}
int
ufs_setacl_nfs4_internal(struct vnode *vp, struct acl *aclp,
struct lwp *l, bool lock)
{
int error;
mode_t mode;
struct inode *ip = VTOI(vp);
KASSERT(acl_nfs4_check(aclp, vp->v_type == VDIR) == 0);
if (acl_nfs4_is_trivial(aclp, ip->i_uid)) {
error = vn_extattr_rm(vp, IO_NODELOCKED,
NFS4_ACL_EXTATTR_NAMESPACE, NFS4_ACL_EXTATTR_NAME, l);
if (error == ENOATTR)
error = 0;
} else {
error = vn_extattr_set(vp, IO_NODELOCKED,
NFS4_ACL_EXTATTR_NAMESPACE, NFS4_ACL_EXTATTR_NAME,
sizeof(*aclp), aclp, l);
}
if (error == ENOATTR)
return SET_ERROR(EOPNOTSUPP);
if (error)
return error;
mode = ip->i_mode;
__acl_nfs4_sync_mode_from_acl(&mode, aclp);
if (lock && (error = UFS_WAPBL_BEGIN(vp->v_mount)) != 0)
return error;
ip->i_mode &= ACL_PRESERVE_MASK;
ip->i_mode |= mode;
DIP_ASSIGN(ip, mode, ip->i_mode);
ip->i_flag |= IN_CHANGE;
error = UFS_UPDATE(vp, NULL, NULL, 0);
if (lock)
UFS_WAPBL_END(vp->v_mount);
return error;
}
static int
ufs_setacl_nfs4(struct vop_setacl_args *ap, struct lwp *l)
{
int error;
struct inode *ip = VTOI(ap->a_vp);
if ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) == 0)
return SET_ERROR(EINVAL);
if (ap->a_vp->v_mount->mnt_flag & MNT_RDONLY)
return SET_ERROR(EROFS);
if (ap->a_aclp == NULL)
return SET_ERROR(EINVAL);
error = VOP_ACLCHECK(ap->a_vp, ap->a_type, ap->a_aclp, ap->a_cred);
if (error)
return error;
if (ip->i_flags & (IMMUTABLE | APPEND))
return SET_ERROR(EPERM);
if ((error = VOP_ACCESSX(ap->a_vp, VWRITE_ACL, l->l_cred)))
return error;
if (ap->a_aclp->acl_cnt > (ACL_MAX_ENTRIES - 6) / 2)
return SET_ERROR(ENOSPC);
error = ufs_setacl_nfs4_internal(ap->a_vp, ap->a_aclp, l, true);
return error;
}
int
ufs_setacl_posix1e(struct vnode *vp, int type, struct acl *aclp,
kauth_cred_t cred, struct lwp *l)
{
struct inode *ip = VTOI(vp);
int error;
struct oldacl *old;
if ((vp->v_mount->mnt_flag & MNT_POSIX1EACLS) == 0)
return SET_ERROR(EINVAL);
if (aclp != NULL) {
error = VOP_ACLCHECK(vp, type, aclp, cred);
if (error != 0)
return error;
} else {
if (type != ACL_TYPE_DEFAULT)
return SET_ERROR(EINVAL);
if (vp->v_type != VDIR)
return SET_ERROR(ENOTDIR);
}
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return SET_ERROR(EROFS);
if (ip->i_flags & (IMMUTABLE | APPEND))
return SET_ERROR(EPERM);
if ((error = VOP_ACCESS(vp, VADMIN, cred)))
return error;
switch(type) {
case ACL_TYPE_ACCESS:
old = kmem_zalloc(sizeof(*old), KM_SLEEP);
error = acl_copy_acl_into_oldacl(aclp, old);
if (error == 0) {
error = vn_extattr_set(vp, IO_NODELOCKED,
POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE,
POSIX1E_ACL_ACCESS_EXTATTR_NAME, sizeof(*old),
old, l);
}
kmem_free(old, sizeof(*old));
break;
case ACL_TYPE_DEFAULT:
if (aclp == NULL) {
error = vn_extattr_rm(vp, IO_NODELOCKED,
POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE,
POSIX1E_ACL_DEFAULT_EXTATTR_NAME, l);
if (error == ENOATTR)
error = 0;
} else {
old = kmem_zalloc(sizeof(*old), KM_SLEEP);
error = acl_copy_acl_into_oldacl(aclp, old);
if (error == 0) {
error = vn_extattr_set(vp, IO_NODELOCKED,
POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE,
POSIX1E_ACL_DEFAULT_EXTATTR_NAME,
sizeof(*old), (char *) old, l);
}
kmem_free(old, sizeof(*old));
}
break;
default:
error = SET_ERROR(EINVAL);
}
if (error == ENOATTR)
return SET_ERROR(EOPNOTSUPP);
if (error != 0)
return error;
if (type == ACL_TYPE_ACCESS) {
if ((error = UFS_WAPBL_BEGIN(vp->v_mount)) != 0)
return error;
ufs_sync_inode_from_acl(aclp, ip);
ip->i_flag |= IN_CHANGE;
error = UFS_UPDATE(vp, NULL, NULL, 0);
UFS_WAPBL_END(vp->v_mount);
}
return error;
}
int
ufs_setacl(void *v)
{
struct vop_setacl_args *ap = v;
if ((ap->a_vp->v_mount->mnt_flag & (MNT_POSIX1EACLS | MNT_NFS4ACLS)) == 0)
return SET_ERROR(EOPNOTSUPP);
if (ap->a_type == ACL_TYPE_NFS4)
return ufs_setacl_nfs4(ap, curlwp);
return ufs_setacl_posix1e(ap->a_vp, ap->a_type, ap->a_aclp, ap->a_cred,
curlwp);
}
static int
ufs_aclcheck_nfs4(struct vop_aclcheck_args *ap, struct lwp *l)
{
int is_directory = 0;
if ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) == 0)
return SET_ERROR(EINVAL);
if (ap->a_aclp->acl_cnt > (ACL_MAX_ENTRIES - 6) / 2)
return SET_ERROR(ENOSPC);
if (ap->a_vp->v_type == VDIR)
is_directory = 1;
return acl_nfs4_check(ap->a_aclp, is_directory);
}
static int
ufs_aclcheck_posix1e(struct vop_aclcheck_args *ap, struct lwp *l)
{
if ((ap->a_vp->v_mount->mnt_flag & MNT_POSIX1EACLS) == 0)
return SET_ERROR(EINVAL);
switch(ap->a_type) {
case ACL_TYPE_ACCESS:
break;
case ACL_TYPE_DEFAULT:
if (ap->a_vp->v_type != VDIR)
return SET_ERROR(EINVAL);
break;
default:
return SET_ERROR(EINVAL);
}
if (ap->a_aclp->acl_cnt > OLDACL_MAX_ENTRIES)
return SET_ERROR(EINVAL);
return acl_posix1e_check(ap->a_aclp);
}
int
ufs_aclcheck(void *v)
{
struct vop_aclcheck_args *ap = v;
if ((ap->a_vp->v_mount->mnt_flag & (MNT_POSIX1EACLS | MNT_NFS4ACLS)) == 0)
return SET_ERROR(EOPNOTSUPP);
if (ap->a_type == ACL_TYPE_NFS4)
return ufs_aclcheck_nfs4(ap, curlwp);
return ufs_aclcheck_posix1e(ap, curlwp);
}
#endif