FreeBSD: Wire projects support

While FreeBSD itself does not support projects, there is no reason
why it can't be controlled via `zfs project` and other subcommands.
Most of the code is actually already there and just needs some
revival and sync with Linux, plus enabling some tests not depending
on the OS support.

Reviewed-by: Ameer Hamza <ahamza@ixsystems.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by:	Alexander Motin <mav@FreeBSD.org>
Sponsored by:	iXsystems, Inc.
Closes #17423
This commit is contained in:
Alexander Motin 2025-06-19 17:39:20 -04:00 committed by GitHub
parent 717213d431
commit 5e5253be84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 271 additions and 13 deletions

View File

@ -833,7 +833,7 @@ zfs_make_xattrdir(znode_t *zp, vattr_t *vap, znode_t **xvpp, cred_t *cr)
if ((error = zfs_acl_ids_create(zp, IS_XATTR, vap, cr, NULL, if ((error = zfs_acl_ids_create(zp, IS_XATTR, vap, cr, NULL,
&acl_ids, NULL)) != 0) &acl_ids, NULL)) != 0)
return (error); return (error);
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, 0)) { if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zp->z_projid)) {
zfs_acl_ids_free(&acl_ids); zfs_acl_ids_free(&acl_ids);
return (SET_ERROR(EDQUOT)); return (SET_ERROR(EDQUOT));
} }

View File

@ -75,6 +75,7 @@
#include <sys/zfs_quota.h> #include <sys/zfs_quota.h>
#include <sys/zfs_sa.h> #include <sys/zfs_sa.h>
#include <sys/zfs_rlock.h> #include <sys/zfs_rlock.h>
#include <sys/zfs_project.h>
#include <sys/bio.h> #include <sys/bio.h>
#include <sys/buf.h> #include <sys/buf.h>
#include <sys/sched.h> #include <sys/sched.h>
@ -267,6 +268,71 @@ zfs_close(vnode_t *vp, int flag, int count, offset_t offset, cred_t *cr)
return (0); return (0);
} }
static int
zfs_ioctl_getxattr(vnode_t *vp, zfsxattr_t *fsx)
{
znode_t *zp = VTOZ(vp);
memset(fsx, 0, sizeof (*fsx));
fsx->fsx_xflags = (zp->z_pflags & ZFS_PROJINHERIT) ?
ZFS_PROJINHERIT_FL : 0;
fsx->fsx_projid = zp->z_projid;
return (0);
}
static int
zfs_ioctl_setflags(vnode_t *vp, uint32_t ioctl_flags, xvattr_t *xva)
{
uint64_t zfs_flags = VTOZ(vp)->z_pflags;
xoptattr_t *xoap;
if (ioctl_flags & ~(ZFS_PROJINHERIT_FL))
return (SET_ERROR(EOPNOTSUPP));
xva_init(xva);
xoap = xva_getxoptattr(xva);
#define FLAG_CHANGE(iflag, zflag, xflag, xfield) do { \
if (((ioctl_flags & (iflag)) && !(zfs_flags & (zflag))) || \
((zfs_flags & (zflag)) && !(ioctl_flags & (iflag)))) { \
XVA_SET_REQ(xva, (xflag)); \
(xfield) = ((ioctl_flags & (iflag)) != 0); \
} \
} while (0)
FLAG_CHANGE(ZFS_PROJINHERIT_FL, ZFS_PROJINHERIT, XAT_PROJINHERIT,
xoap->xoa_projinherit);
#undef FLAG_CHANGE
return (0);
}
static int
zfs_ioctl_setxattr(vnode_t *vp, zfsxattr_t *fsx, cred_t *cr)
{
znode_t *zp = VTOZ(vp);
xvattr_t xva;
xoptattr_t *xoap;
int err;
if (!zpl_is_valid_projid(fsx->fsx_projid))
return (SET_ERROR(EINVAL));
err = zfs_ioctl_setflags(vp, fsx->fsx_xflags, &xva);
if (err)
return (err);
xoap = xva_getxoptattr(&xva);
XVA_SET_REQ(&xva, XAT_PROJID);
xoap->xoa_projid = fsx->fsx_projid;
err = zfs_setattr(zp, (vattr_t *)&xva, 0, cr, NULL);
return (err);
}
static int static int
zfs_ioctl(vnode_t *vp, ulong_t com, intptr_t data, int flag, cred_t *cred, zfs_ioctl(vnode_t *vp, ulong_t com, intptr_t data, int flag, cred_t *cred,
int *rvalp) int *rvalp)
@ -306,6 +372,24 @@ zfs_ioctl(vnode_t *vp, ulong_t com, intptr_t data, int flag, cred_t *cred,
*(offset_t *)data = off; *(offset_t *)data = off;
return (0); return (0);
} }
case ZFS_IOC_FSGETXATTR: {
zfsxattr_t *fsx = (zfsxattr_t *)data;
error = vn_lock(vp, LK_SHARED);
if (error)
return (error);
error = zfs_ioctl_getxattr(vp, fsx);
VOP_UNLOCK(vp);
return (error);
}
case ZFS_IOC_FSSETXATTR: {
zfsxattr_t *fsx = (zfsxattr_t *)data;
error = vn_lock(vp, LK_EXCLUSIVE);
if (error)
return (error);
error = zfs_ioctl_setxattr(vp, fsx, cred);
VOP_UNLOCK(vp);
return (error);
}
case ZFS_IOC_REWRITE: { case ZFS_IOC_REWRITE: {
zfs_rewrite_args_t *args = (zfs_rewrite_args_t *)data; zfs_rewrite_args_t *args = (zfs_rewrite_args_t *)data;
if ((flag & FWRITE) == 0) if ((flag & FWRITE) == 0)
@ -2058,6 +2142,132 @@ zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr)
return (0); return (0);
} }
/*
* For the operation of changing file's user/group/project, we need to
* handle not only the main object that is assigned to the file directly,
* but also the ones that are used by the file via hidden xattr directory.
*
* Because the xattr directory may contains many EA entries, as to it may
* be impossible to change all of them via the transaction of changing the
* main object's user/group/project attributes. Then we have to change them
* via other multiple independent transactions one by one. It may be not good
* solution, but we have no better idea yet.
*/
static int
zfs_setattr_dir(znode_t *dzp)
{
zfsvfs_t *zfsvfs = dzp->z_zfsvfs;
objset_t *os = zfsvfs->z_os;
zap_cursor_t zc;
zap_attribute_t *zap;
znode_t *zp = NULL;
dmu_tx_t *tx = NULL;
uint64_t uid, gid;
sa_bulk_attr_t bulk[4];
int count;
int err;
zap = zap_attribute_alloc();
zap_cursor_init(&zc, os, dzp->z_id);
while ((err = zap_cursor_retrieve(&zc, zap)) == 0) {
count = 0;
if (zap->za_integer_length != 8 || zap->za_num_integers != 1) {
err = ENXIO;
break;
}
err = zfs_dirent_lookup(dzp, zap->za_name, &zp, ZEXISTS);
if (err == ENOENT)
goto next;
if (err)
break;
if (zp->z_uid == dzp->z_uid &&
zp->z_gid == dzp->z_gid &&
zp->z_projid == dzp->z_projid)
goto next;
tx = dmu_tx_create(os);
if (!(zp->z_pflags & ZFS_PROJID))
dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE);
else
dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE);
err = dmu_tx_assign(tx, DMU_TX_WAIT);
if (err)
break;
mutex_enter(&dzp->z_lock);
if (zp->z_uid != dzp->z_uid) {
uid = dzp->z_uid;
SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL,
&uid, sizeof (uid));
zp->z_uid = uid;
}
if (zp->z_gid != dzp->z_gid) {
gid = dzp->z_gid;
SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL,
&gid, sizeof (gid));
zp->z_gid = gid;
}
uint64_t projid = dzp->z_projid;
if (zp->z_projid != projid) {
if (!(zp->z_pflags & ZFS_PROJID)) {
err = sa_add_projid(zp->z_sa_hdl, tx, projid);
if (unlikely(err == EEXIST)) {
err = 0;
} else if (err != 0) {
goto sa_add_projid_err;
} else {
projid = ZFS_INVALID_PROJID;
}
}
if (projid != ZFS_INVALID_PROJID) {
zp->z_projid = projid;
SA_ADD_BULK_ATTR(bulk, count,
SA_ZPL_PROJID(zfsvfs), NULL, &zp->z_projid,
sizeof (zp->z_projid));
}
}
sa_add_projid_err:
mutex_exit(&dzp->z_lock);
if (likely(count > 0)) {
err = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx);
dmu_tx_commit(tx);
} else if (projid == ZFS_INVALID_PROJID) {
dmu_tx_commit(tx);
} else {
dmu_tx_abort(tx);
}
tx = NULL;
if (err != 0 && err != ENOENT)
break;
next:
if (zp) {
zrele(zp);
zp = NULL;
}
zap_cursor_advance(&zc);
}
if (tx)
dmu_tx_abort(tx);
if (zp) {
zrele(zp);
}
zap_cursor_fini(&zc);
zap_attribute_free(zap);
return (err == ENOENT ? 0 : err);
}
/* /*
* Set the file attributes to the values contained in the * Set the file attributes to the values contained in the
* vattr structure. * vattr structure.
@ -2103,6 +2313,7 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns)
zfs_acl_t *aclp; zfs_acl_t *aclp;
boolean_t skipaclchk = (flags & ATTR_NOACLCHECK) ? B_TRUE : B_FALSE; boolean_t skipaclchk = (flags & ATTR_NOACLCHECK) ? B_TRUE : B_FALSE;
boolean_t fuid_dirtied = B_FALSE; boolean_t fuid_dirtied = B_FALSE;
boolean_t handle_eadir = B_FALSE;
sa_bulk_attr_t bulk[7], xattr_bulk[7]; sa_bulk_attr_t bulk[7], xattr_bulk[7];
int count = 0, xattr_count = 0; int count = 0, xattr_count = 0;
@ -2454,6 +2665,7 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns)
mask = vap->va_mask; mask = vap->va_mask;
if ((mask & (AT_UID | AT_GID)) || projid != ZFS_INVALID_PROJID) { if ((mask & (AT_UID | AT_GID)) || projid != ZFS_INVALID_PROJID) {
handle_eadir = B_TRUE;
err = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), err = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs),
&xattr_obj, sizeof (xattr_obj)); &xattr_obj, sizeof (xattr_obj));
@ -2783,6 +2995,10 @@ out:
} else { } else {
err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx);
dmu_tx_commit(tx); dmu_tx_commit(tx);
if (attrzp) {
if (err2 == 0 && handle_eadir)
err = zfs_setattr_dir(attrzp);
}
} }
out2: out2:
@ -3439,8 +3655,7 @@ zfs_symlink(znode_t *dzp, const char *name, vattr_t *vap,
return (error); return (error);
} }
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, ZFS_DEFAULT_PROJID)) {
0 /* projid */)) {
zfs_acl_ids_free(&acl_ids); zfs_acl_ids_free(&acl_ids);
zfs_exit(zfsvfs, FTAG); zfs_exit(zfsvfs, FTAG);
return (SET_ERROR(EDQUOT)); return (SET_ERROR(EDQUOT));

View File

@ -564,6 +564,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
uint64_t crtime[2], atime[2], mtime[2], ctime[2]; uint64_t crtime[2], atime[2], mtime[2], ctime[2];
uint64_t mode, size, links, parent, pflags; uint64_t mode, size, links, parent, pflags;
uint64_t dzp_pflags = 0; uint64_t dzp_pflags = 0;
uint64_t projid = ZFS_DEFAULT_PROJID;
uint64_t rdev = 0; uint64_t rdev = 0;
zfsvfs_t *zfsvfs = dzp->z_zfsvfs; zfsvfs_t *zfsvfs = dzp->z_zfsvfs;
dmu_buf_t *db; dmu_buf_t *db;
@ -671,6 +672,23 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
if (flag & IS_XATTR) if (flag & IS_XATTR)
pflags |= ZFS_XATTR; pflags |= ZFS_XATTR;
if (vap->va_type == VREG || vap->va_type == VDIR) {
/*
* With ZFS_PROJID flag, we can easily know whether there is
* project ID stored on disk or not. See zpl_get_file_info().
*/
if (obj_type != DMU_OT_ZNODE &&
dmu_objset_projectquota_enabled(zfsvfs->z_os))
pflags |= ZFS_PROJID;
/*
* Inherit project ID from parent if required.
*/
projid = zfs_inherit_projid(dzp);
if (dzp_pflags & ZFS_PROJINHERIT)
pflags |= ZFS_PROJINHERIT;
}
/* /*
* No execs denied will be determined when zfs_mode_compute() is called. * No execs denied will be determined when zfs_mode_compute() is called.
*/ */
@ -752,6 +770,10 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
if (obj_type == DMU_OT_ZNODE) { if (obj_type == DMU_OT_ZNODE) {
SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_XATTR(zfsvfs), NULL, SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_XATTR(zfsvfs), NULL,
&empty_xattr, 8); &empty_xattr, 8);
} else if (dmu_objset_projectquota_enabled(zfsvfs->z_os) &&
pflags & ZFS_PROJID) {
SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PROJID(zfsvfs),
NULL, &projid, 8);
} }
if (obj_type == DMU_OT_ZNODE || if (obj_type == DMU_OT_ZNODE ||
(vap->va_type == VBLK || vap->va_type == VCHR)) { (vap->va_type == VBLK || vap->va_type == VCHR)) {
@ -799,6 +821,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
(*zpp)->z_pflags = pflags; (*zpp)->z_pflags = pflags;
(*zpp)->z_mode = mode; (*zpp)->z_mode = mode;
(*zpp)->z_dnodesize = dnodesize; (*zpp)->z_dnodesize = dnodesize;
(*zpp)->z_projid = projid;
if (vap->va_mask & AT_XVATTR) if (vap->va_mask & AT_XVATTR)
zfs_xvattr_set(*zpp, (xvattr_t *)vap, tx); zfs_xvattr_set(*zpp, (xvattr_t *)vap, tx);
@ -916,6 +939,11 @@ zfs_xvattr_set(znode_t *zp, xvattr_t *xvap, dmu_tx_t *tx)
zp->z_pflags, tx); zp->z_pflags, tx);
XVA_SET_RTN(xvap, XAT_SPARSE); XVA_SET_RTN(xvap, XAT_SPARSE);
} }
if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) {
ZFS_ATTR_SET(zp, ZFS_PROJINHERIT, xoap->xoa_projinherit,
zp->z_pflags, tx);
XVA_SET_RTN(xvap, XAT_PROJINHERIT);
}
} }
int int
@ -1068,6 +1096,7 @@ zfs_rezget(znode_t *zp)
int err; int err;
int count = 0; int count = 0;
uint64_t gen; uint64_t gen;
uint64_t projid = ZFS_DEFAULT_PROJID;
/* /*
* Remove cached pages before reloading the znode, so that they are not * Remove cached pages before reloading the znode, so that they are not
@ -1148,6 +1177,17 @@ zfs_rezget(znode_t *zp)
return (SET_ERROR(EIO)); return (SET_ERROR(EIO));
} }
if (dmu_objset_projectquota_enabled(zfsvfs->z_os)) {
err = sa_lookup(zp->z_sa_hdl, SA_ZPL_PROJID(zfsvfs),
&projid, 8);
if (err != 0 && err != ENOENT) {
zfs_znode_dmu_fini(zp);
ZFS_OBJ_HOLD_EXIT(zfsvfs, obj_num);
return (err);
}
}
zp->z_projid = projid;
zp->z_mode = mode; zp->z_mode = mode;
if (gen != zp->z_gen) { if (gen != zp->z_gen) {

View File

@ -768,7 +768,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) { if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) {
/* /*
* With ZFS_PROJID flag, we can easily know whether there is * With ZFS_PROJID flag, we can easily know whether there is
* project ID stored on disk or not. See zfs_space_delta_cb(). * project ID stored on disk or not. See zpl_get_file_info().
*/ */
if (obj_type != DMU_OT_ZNODE && if (obj_type != DMU_OT_ZNODE &&
dmu_objset_projectquota_enabled(zfsvfs->z_os)) dmu_objset_projectquota_enabled(zfsvfs->z_os))

View File

@ -846,6 +846,15 @@ pre =
post = post =
tags = ['functional', 'pool_names'] tags = ['functional', 'pool_names']
[tests/functional/projectquota]
tests = ['defaultprojectquota_002_pos', 'defaultprojectquota_003_neg',
'defaultprojectquota_004_pos', 'defaultprojectquota_006_pos',
'defaultprojectquota_007_pos', 'projectquota_002_pos',
'projectquota_004_neg', 'projectquota_005_pos', 'projectquota_007_pos',
'projectquota_008_pos', 'projectquota_009_pos', 'projecttree_002_pos',
'projecttree_003_neg']
tags = ['functional', 'projectquota']
[tests/functional/poolversion] [tests/functional/poolversion]
tests = ['poolversion_001_pos', 'poolversion_002_pos'] tests = ['poolversion_001_pos', 'poolversion_002_pos']
tags = ['functional', 'poolversion'] tags = ['functional', 'poolversion']

View File

@ -175,17 +175,11 @@ tests = ['procfs_list_basic', 'procfs_list_concurrent_readers',
tags = ['functional', 'procfs'] tags = ['functional', 'procfs']
[tests/functional/projectquota:Linux] [tests/functional/projectquota:Linux]
tests = ['defaultprojectquota_001_pos', 'defaultprojectquota_002_pos', tests = ['defaultprojectquota_001_pos', 'defaultprojectquota_005_pos',
'defaultprojectquota_003_neg', 'defaultprojectquota_004_pos',
'defaultprojectquota_005_pos', 'defaultprojectquota_006_pos',
'defaultprojectquota_007_pos',
'projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos', 'projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos',
'projectquota_001_pos', 'projectquota_002_pos', 'projectquota_003_pos', 'projectquota_001_pos', 'projectquota_003_pos', 'projectquota_006_pos',
'projectquota_004_neg', 'projectquota_005_pos', 'projectquota_006_pos',
'projectquota_007_pos', 'projectquota_008_pos', 'projectquota_009_pos',
'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos', 'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos',
'projectspace_004_pos', 'projectspace_005_pos', 'projectspace_004_pos', 'projectspace_005_pos', 'projecttree_001_pos']
'projecttree_001_pos', 'projecttree_002_pos', 'projecttree_003_neg']
tags = ['functional', 'projectquota'] tags = ['functional', 'projectquota']
[tests/functional/dos_attributes:Linux] [tests/functional/dos_attributes:Linux]