Inherit the project ID for every object type

zfs_mknode() only assigned a project ID to regular files and
directories, so a symlink, device node, FIFO or socket created inside a
directory with ZFS_PROJINHERIT set was left at ZFS_DEFAULT_PROJID. The
cross-project checks in zfs_rename() and zfs_link() compare the object's
project ID against the directory's, so such an object is treated as
foreign to the very directory holding it, and cannot be renamed or
linked there at all -- "ln -sfn", which creates the new symlink under a
temporary name and renames it into place, fails with EXDEV.

ext4 and XFS store a project ID on every inode type and so do not have
this problem. Do the same, and quota-check new symlinks and rename
whiteouts against the inherited ID rather than the default one, so their
space is accounted to the project that owns them.

This changes accounting for newly created objects: symlinks, device
nodes and FIFOs now consume the project's quota where they previously
consumed none, so on a dataset already at its project quota, creating
one begins to fail with EDQUOT where it used to succeed. The space
involved is small, but the change in behavior is visible.

The ZFS_PROJINHERIT flag itself keeps propagating to regular files and
directories only, leaving lsattr(1) output unchanged.

Note that this only covers newly created objects; symlinks already on
disk keep the default project ID.

Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: Matt Turner <[email protected]>
Closes #18932
This commit is contained in:
Matt Turner authored and Brian Behlendorf committed 2026-08-21 13:58:04 -07:00
1 parent df3ff37fc8
commit e90badec11
4 files changed
+49 -43

No files matched your search

+2 -3
View File
@@ -1107,8 +1107,7 @@ zfs_create(znode_t *dzp, const char *name, vattr_t *vap, int excl, int mode,
cr, vsecp, &acl_ids)) != 0)
goto out;
if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode))
projid = zfs_inherit_projid(dzp);
projid = zfs_inherit_projid(dzp);
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) {
zfs_acl_ids_free(&acl_ids);
error = SET_ERROR(EDQUOT);
@@ -3651,7 +3650,7 @@ zfs_symlink(znode_t *dzp, const char *name, vattr_t *vap,
return (error);
}
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, ZFS_DEFAULT_PROJID)) {
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zfs_inherit_projid(dzp))) {
zfs_acl_ids_free(&acl_ids);
zfs_exit(zfsvfs, FTAG);
return (SET_ERROR(EDQUOT));
+21 -15
View File
@@ -672,22 +672,28 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
if (flag & IS_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;
/*
* 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;
}
/*
* Inherit project ID from parent if required. Every object type
* takes part, as ext4 and XFS do: an object that carried no project
* ID of its own would be treated as belonging to a different project
* than the directory holding it, so zfs_rename() and zfs_link()
* would refuse it with EXDEV even within its own project.
*
* The ZFS_PROJINHERIT flag itself keeps passing to regular files and
* directories only, as before, so that lsattr(1) output is unchanged.
*/
projid = zfs_inherit_projid(dzp);
if ((vap->va_type == VREG || vap->va_type == VDIR) &&
(dzp_pflags & ZFS_PROJINHERIT))
pflags |= ZFS_PROJINHERIT;
/*
* No execs denied will be determined when zfs_mode_compute() is called.
+5 -10
View File
@@ -725,8 +725,7 @@ top:
goto out;
have_acl = B_TRUE;
if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode))
projid = zfs_inherit_projid(dzp);
projid = zfs_inherit_projid(dzp);
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) {
zfs_acl_ids_free(&acl_ids);
error = SET_ERROR(EDQUOT);
@@ -925,8 +924,7 @@ top:
goto out;
have_acl = B_TRUE;
if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode))
projid = zfs_inherit_projid(dzp);
projid = zfs_inherit_projid(dzp);
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) {
zfs_acl_ids_free(&acl_ids);
error = SET_ERROR(EDQUOT);
@@ -3068,11 +3066,8 @@ top:
/* Set up inode creation for RENAME_WHITEOUT. */
if (rflags & RENAME_WHITEOUT) {
/*
* Whiteout files are not regular files or directories, so to
* match zfs_create() we do not inherit the project id.
*/
uint64_t wo_projid = ZFS_DEFAULT_PROJID;
/* Match zfs_create(): the whiteout joins its directory. */
uint64_t wo_projid = zfs_inherit_projid(sdzp);
error = zfs_zaccess_idmap(sdzp, ACE_ADD_FILE, 0, B_FALSE,
cr, idmap);
@@ -3409,7 +3404,7 @@ top:
return (error);
}
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, ZFS_DEFAULT_PROJID)) {
if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zfs_inherit_projid(dzp))) {
zfs_acl_ids_free(&acl_ids);
zfs_dirent_unlock(dl);
zfs_exit(zfsvfs, FTAG);
+21 -15
View File
@@ -782,22 +782,28 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
if (flag & IS_XATTR)
pflags |= ZFS_XATTR;
if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) {
/*
* 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;
/*
* 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->z_pflags & ZFS_PROJINHERIT)
pflags |= ZFS_PROJINHERIT;
}
/*
* Inherit project ID from parent if required. Every object type
* takes part, as ext4 and XFS do: an object that carried no project
* ID of its own would be treated as belonging to a different project
* than the directory holding it, so zfs_rename() and zfs_link()
* would refuse it with EXDEV even within its own project.
*
* The ZFS_PROJINHERIT flag itself keeps passing to regular files and
* directories only, as before, so that lsattr(1) output is unchanged.
*/
projid = zfs_inherit_projid(dzp);
if ((S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) &&
(dzp->z_pflags & ZFS_PROJINHERIT))
pflags |= ZFS_PROJINHERIT;
/*
* No execs denied will be determined when zfs_mode_compute() is called.