diff --git a/module/os/freebsd/zfs/zfs_dir.c b/module/os/freebsd/zfs/zfs_dir.c index a0c9dd178..191df832d 100644 --- a/module/os/freebsd/zfs/zfs_dir.c +++ b/module/os/freebsd/zfs/zfs_dir.c @@ -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, &acl_ids, NULL)) != 0) 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); return (SET_ERROR(EDQUOT)); } diff --git a/module/os/freebsd/zfs/zfs_vnops_os.c b/module/os/freebsd/zfs/zfs_vnops_os.c index e183d1ec7..f53c94d3a 100644 --- a/module/os/freebsd/zfs/zfs_vnops_os.c +++ b/module/os/freebsd/zfs/zfs_vnops_os.c @@ -75,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -267,6 +268,71 @@ zfs_close(vnode_t *vp, int flag, int count, offset_t offset, cred_t *cr) 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 zfs_ioctl(vnode_t *vp, ulong_t com, intptr_t data, int flag, cred_t *cred, 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; 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: { zfs_rewrite_args_t *args = (zfs_rewrite_args_t *)data; if ((flag & FWRITE) == 0) @@ -2058,6 +2142,132 @@ zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr) 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 * 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; boolean_t skipaclchk = (flags & ATTR_NOACLCHECK) ? B_TRUE : B_FALSE; boolean_t fuid_dirtied = B_FALSE; + boolean_t handle_eadir = B_FALSE; sa_bulk_attr_t bulk[7], xattr_bulk[7]; 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; 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), &xattr_obj, sizeof (xattr_obj)); @@ -2783,6 +2995,10 @@ out: } else { err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); dmu_tx_commit(tx); + if (attrzp) { + if (err2 == 0 && handle_eadir) + err = zfs_setattr_dir(attrzp); + } } out2: @@ -3439,8 +3655,7 @@ zfs_symlink(znode_t *dzp, const char *name, vattr_t *vap, return (error); } - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, - 0 /* projid */)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, ZFS_DEFAULT_PROJID)) { zfs_acl_ids_free(&acl_ids); zfs_exit(zfsvfs, FTAG); return (SET_ERROR(EDQUOT)); diff --git a/module/os/freebsd/zfs/zfs_znode_os.c b/module/os/freebsd/zfs/zfs_znode_os.c index e97a0dd84..a720f53b0 100644 --- a/module/os/freebsd/zfs/zfs_znode_os.c +++ b/module/os/freebsd/zfs/zfs_znode_os.c @@ -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 mode, size, links, parent, pflags; uint64_t dzp_pflags = 0; + uint64_t projid = ZFS_DEFAULT_PROJID; uint64_t rdev = 0; zfsvfs_t *zfsvfs = dzp->z_zfsvfs; 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) 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. */ @@ -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) { SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_XATTR(zfsvfs), NULL, &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 || (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_mode = mode; (*zpp)->z_dnodesize = dnodesize; + (*zpp)->z_projid = projid; if (vap->va_mask & AT_XVATTR) 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); 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 @@ -1068,6 +1096,7 @@ zfs_rezget(znode_t *zp) int err; int count = 0; uint64_t gen; + uint64_t projid = ZFS_DEFAULT_PROJID; /* * 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)); } + 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; if (gen != zp->z_gen) { diff --git a/module/os/linux/zfs/zfs_znode_os.c b/module/os/linux/zfs/zfs_znode_os.c index 607b3995c..54e60b482 100644 --- a/module/os/linux/zfs/zfs_znode_os.c +++ b/module/os/linux/zfs/zfs_znode_os.c @@ -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)) { /* * 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 && dmu_objset_projectquota_enabled(zfsvfs->z_os)) diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 144b228bc..7969945a4 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -846,6 +846,15 @@ pre = post = 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 = ['poolversion_001_pos', 'poolversion_002_pos'] tags = ['functional', 'poolversion'] diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index b0c3364f6..617fe573c 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -175,17 +175,11 @@ tests = ['procfs_list_basic', 'procfs_list_concurrent_readers', tags = ['functional', 'procfs'] [tests/functional/projectquota:Linux] -tests = ['defaultprojectquota_001_pos', 'defaultprojectquota_002_pos', - 'defaultprojectquota_003_neg', 'defaultprojectquota_004_pos', - 'defaultprojectquota_005_pos', 'defaultprojectquota_006_pos', - 'defaultprojectquota_007_pos', +tests = ['defaultprojectquota_001_pos', 'defaultprojectquota_005_pos', 'projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos', - 'projectquota_001_pos', 'projectquota_002_pos', 'projectquota_003_pos', - 'projectquota_004_neg', 'projectquota_005_pos', 'projectquota_006_pos', - 'projectquota_007_pos', 'projectquota_008_pos', 'projectquota_009_pos', + 'projectquota_001_pos', 'projectquota_003_pos', 'projectquota_006_pos', 'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos', - 'projectspace_004_pos', 'projectspace_005_pos', - 'projecttree_001_pos', 'projecttree_002_pos', 'projecttree_003_neg'] + 'projectspace_004_pos', 'projectspace_005_pos', 'projecttree_001_pos'] tags = ['functional', 'projectquota'] [tests/functional/dos_attributes:Linux]