diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 665e183485f0..cd26d877888d 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -8863,13 +8863,14 @@ zfs_do_project(int argc, char **argv) .zpc_newline = B_TRUE, .zpc_recursive = B_FALSE, .zpc_set_flag = B_FALSE, + .zpc_add_hierarchy = B_FALSE, }; int ret = 0, c; if (argc < 2) usage(B_FALSE); - while ((c = getopt(argc, argv, "0Ccdkp:rs")) != -1) { + while ((c = getopt(argc, argv, "0Ccdkp:rsY")) != -1) { switch (c) { case '0': zpc.zpc_newline = B_FALSE; @@ -8933,6 +8934,12 @@ zfs_do_project(int argc, char **argv) zpc.zpc_set_flag = B_TRUE; zpc.zpc_op = ZFS_PROJECT_OP_SET; break; + case 'Y': + zpc.zpc_add_hierarchy = B_TRUE; + zpc.zpc_set_flag = B_TRUE; + zpc.zpc_recursive = B_TRUE; + zpc.zpc_dironly = B_FALSE; + break; default: (void) fprintf(stderr, gettext("invalid option '%c'\n"), optopt); @@ -8959,6 +8966,12 @@ zfs_do_project(int argc, char **argv) gettext("'-0' is only valid together with '-c'\n")); usage(B_FALSE); } + if (zpc.zpc_add_hierarchy) { + (void) fprintf(stderr, + gettext("'-Y' is only valid with set project " + "id\n")); + usage(B_FALSE); + } break; case ZFS_PROJECT_OP_CHECK: if (zpc.zpc_keep_projid) { @@ -8966,6 +8979,12 @@ zfs_do_project(int argc, char **argv) gettext("'-k' is only valid together with '-C'\n")); usage(B_FALSE); } + if (zpc.zpc_add_hierarchy) { + (void) fprintf(stderr, + gettext("'-Y' is only valid with set project " + "id\n")); + usage(B_FALSE); + } break; case ZFS_PROJECT_OP_CLEAR: if (zpc.zpc_dironly) { @@ -8983,6 +9002,12 @@ zfs_do_project(int argc, char **argv) gettext("'-p' is useless together with '-C'\n")); usage(B_FALSE); } + if (zpc.zpc_add_hierarchy) { + (void) fprintf(stderr, + gettext("'-Y' is only valid with set project " + "id\n")); + usage(B_FALSE); + } break; case ZFS_PROJECT_OP_SET: if (zpc.zpc_dironly) { @@ -9001,6 +9026,13 @@ zfs_do_project(int argc, char **argv) gettext("'-0' is only valid together with '-c'\n")); usage(B_FALSE); } + if (zpc.zpc_add_hierarchy && zpc.zpc_expected_projid == + ZFS_INVALID_PROJID) { + (void) fprintf(stderr, + gettext("'-p' is needed for add hierarchy " + "option '-Y'\n")); + usage(B_FALSE); + } break; default: ASSERT(0); diff --git a/cmd/zfs/zfs_project.c b/cmd/zfs/zfs_project.c index fbf5e6cbdc68..13073b4386b7 100644 --- a/cmd/zfs/zfs_project.c +++ b/cmd/zfs/zfs_project.c @@ -88,6 +88,11 @@ zfs_project_sanity_check(const char *name, zfs_project_control_t *zpc, "'-r' option on non-dir target %s\n"), name); return (-1); } + if (zpc->zpc_add_hierarchy) { + (void) fprintf(stderr, gettext( + "'-Y' option on non-dir target %s\n"), name); + return (-1); + } } return (0); @@ -118,6 +123,30 @@ zfs_project_load_projid(const char *name, zfs_project_control_t *zpc) return (ret); } +static int +zfs_project_add_hierarchy(const char *name, zfs_project_control_t *zpc) +{ + project_hierarchy_arg_t pharg; + int ret, fd; + + fd = open(name, O_RDONLY | O_NOCTTY); + if (fd < 0) { + (void) fprintf(stderr, gettext("failed to open %s: %s\n"), + name, strerror(errno)); + return (fd); + } + pharg.pha_projid = zpc->zpc_expected_projid; + + ret = ioctl(fd, FS_IOC_ADD_PROJECT_HIERARCHY, &pharg); + if (ret) + (void) fprintf(stderr, + gettext("failed to add project hierarchy for %s: %s\n"), + name, strerror(errno)); + + close(fd); + return (ret); +} + static int zfs_project_handle_one(const char *name, zfs_project_control_t *zpc) { @@ -194,10 +223,12 @@ zfs_project_handle_one(const char *name, zfs_project_control_t *zpc) } ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx); - if (ret) + if (ret && errno != EXDEV) { (void) fprintf(stderr, gettext("failed to set xattr for %s: %s\n"), name, strerror(errno)); + } + out: close(fd); @@ -247,11 +278,13 @@ zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc, ret = zfs_project_handle_one(fullname, zpc); if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR) zfs_project_item_alloc(head, fullname); + if (ret && errno == EXDEV) + ret = 0; free(fullname); } - if (errno && !ret) { + if (errno && !ret && errno != EXDEV) { ret = -errno; (void) fprintf(stderr, gettext("failed to readdir %s: %s\n"), name, strerror(errno)); @@ -273,6 +306,11 @@ zfs_project_handle(const char *name, zfs_project_control_t *zpc) if (ret) return (ret); + if (zpc->zpc_op == ZFS_PROJECT_OP_SET && zpc->zpc_add_hierarchy) { + ret = zfs_project_add_hierarchy(name, zpc); + if (ret) + return (ret); + } if ((zpc->zpc_op == ZFS_PROJECT_OP_SET || zpc->zpc_op == ZFS_PROJECT_OP_CHECK) && zpc->zpc_expected_projid == ZFS_INVALID_PROJID) { @@ -286,8 +324,11 @@ zfs_project_handle(const char *name, zfs_project_control_t *zpc) if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly || (!zpc->zpc_recursive && zpc->zpc_op != ZFS_PROJECT_OP_LIST && - zpc->zpc_op != ZFS_PROJECT_OP_CHECK)) + zpc->zpc_op != ZFS_PROJECT_OP_CHECK)) { + if (ret && errno == EXDEV) + ret = 0; return (ret); + } list_create(&head, sizeof (zfs_project_item_t), offsetof(zfs_project_item_t, zpi_list)); @@ -295,6 +336,8 @@ zfs_project_handle(const char *name, zfs_project_control_t *zpc) while ((zpi = list_remove_head(&head)) != NULL) { if (!ret) ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head); + if (ret && errno == EXDEV) + ret = 0; free(zpi); } diff --git a/cmd/zfs/zfs_projectutil.h b/cmd/zfs/zfs_projectutil.h index c61d2ea9b92b..82ee4099b5cd 100644 --- a/cmd/zfs/zfs_projectutil.h +++ b/cmd/zfs/zfs_projectutil.h @@ -43,6 +43,7 @@ typedef struct zfs_project_control { boolean_t zpc_newline; boolean_t zpc_recursive; boolean_t zpc_set_flag; + boolean_t zpc_add_hierarchy; } zfs_project_control_t; int zfs_project_handle(const char *name, zfs_project_control_t *zpc); diff --git a/include/os/freebsd/zfs/sys/zfs_vfsops_os.h b/include/os/freebsd/zfs/sys/zfs_vfsops_os.h index 3ed311d49cc6..b0bccce25baf 100644 --- a/include/os/freebsd/zfs/sys/zfs_vfsops_os.h +++ b/include/os/freebsd/zfs/sys/zfs_vfsops_os.h @@ -95,6 +95,7 @@ struct zfsvfs { uint64_t z_userobjquota_obj; uint64_t z_groupobjquota_obj; uint64_t z_projectquota_obj; + uint64_t z_projecthierarchy_obj; uint64_t z_projectobjquota_obj; uint64_t z_defaultuserquota; uint64_t z_defaultgroupquota; diff --git a/include/os/linux/zfs/sys/zfs_vfsops_os.h b/include/os/linux/zfs/sys/zfs_vfsops_os.h index ab46d5f8ca08..80309fc33f5a 100644 --- a/include/os/linux/zfs/sys/zfs_vfsops_os.h +++ b/include/os/linux/zfs/sys/zfs_vfsops_os.h @@ -130,6 +130,7 @@ struct zfsvfs { uint64_t z_userobjquota_obj; uint64_t z_groupobjquota_obj; uint64_t z_projectquota_obj; + uint64_t z_projecthierarchy_obj; uint64_t z_projectobjquota_obj; uint64_t z_defaultuserquota; uint64_t z_defaultgroupquota; diff --git a/include/os/linux/zfs/sys/zfs_vnops_os.h b/include/os/linux/zfs/sys/zfs_vnops_os.h index b41ffcc178ca..a22a6743b286 100644 --- a/include/os/linux/zfs/sys/zfs_vnops_os.h +++ b/include/os/linux/zfs/sys/zfs_vnops_os.h @@ -66,6 +66,7 @@ extern int zfs_getattr_fast(zidmap_t *, struct inode *ip, struct kstat *sp); #endif extern int zfs_setattr(znode_t *zp, vattr_t *vap, int flag, cred_t *cr, zidmap_t *mnt_ns); +extern int zfs_setattr_xattr_dir(znode_t *zp); extern int zfs_rename(znode_t *sdzp, char *snm, znode_t *tdzp, char *tnm, cred_t *cr, int flags, uint64_t rflags, vattr_t *wo_vap, zidmap_t *mnt_ns); diff --git a/include/sys/dmu_objset.h b/include/sys/dmu_objset.h index 288ad30166df..976f6e6836db 100644 --- a/include/sys/dmu_objset.h +++ b/include/sys/dmu_objset.h @@ -200,6 +200,12 @@ struct objset { dmu_objset_upgrade_cb_t os_upgrade_cb; boolean_t os_upgrade_exit; int os_upgrade_status; + + kmutex_t os_projecthierarchyused_lock; + kmutex_t os_projecthierarchyop_lock; + + avl_tree_t os_project_deltas[TXG_SIZE]; + uint64_t os_projecthierarchy_obj; }; #define DMU_META_OBJSET 0 @@ -268,6 +274,8 @@ int dmu_fsname(const char *snapname, char *buf); void dmu_objset_evict_done(objset_t *os); void dmu_objset_willuse_space(objset_t *os, int64_t space, dmu_tx_t *tx); +void do_projectusage_update(objset_t *os, uint64_t projid, int64_t used, + dmu_tx_t *tx); void dmu_objset_init(void); void dmu_objset_fini(void); diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index c8deb5be419e..733d4356fcba 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -1993,6 +1993,11 @@ enum zio_encrypt { ZFS_XA_NS_PREFIX_MATCH(LINUX_TRUSTED, name) || \ ZFS_XA_NS_PREFIX_MATCH(LINUX_USER, name)) +typedef struct project_hierarchy_arg { + uint64_t pha_projid; +} project_hierarchy_arg_t; +#define FS_IOC_ADD_PROJECT_HIERARCHY _IOW('t', 9, project_hierarchy_arg_t) + #ifdef __cplusplus } #endif diff --git a/include/sys/zfs_quota.h b/include/sys/zfs_quota.h index 62389cd2f3b2..0e4fdaf0759b 100644 --- a/include/sys/zfs_quota.h +++ b/include/sys/zfs_quota.h @@ -38,6 +38,11 @@ extern int zfs_userspace_many(struct zfsvfs *, zfs_userquota_prop_t, uint64_t *, void *, uint64_t *, uint64_t *); extern int zfs_set_userquota(struct zfsvfs *, zfs_userquota_prop_t, const char *, uint64_t, uint64_t); +extern int zfs_project_hierarchy_add(struct zfsvfs *, uint64_t, uint64_t); +extern int zfs_project_hierarchy_remove(struct zfsvfs *, uint64_t, uint64_t, + boolean_t); +extern int zfs_projects_are_hierarchical(struct zfsvfs *, uint64_t, uint64_t, + boolean_t *); extern boolean_t zfs_id_overobjquota(struct zfsvfs *, uint64_t, uint64_t); extern boolean_t zfs_id_overblockquota(struct zfsvfs *, uint64_t, uint64_t); diff --git a/include/sys/zfs_znode.h b/include/sys/zfs_znode.h index b3a267e16f3e..2f935436e920 100644 --- a/include/sys/zfs_znode.h +++ b/include/sys/zfs_znode.h @@ -140,6 +140,7 @@ extern "C" { #define ZFS_FUID_TABLES "FUID" #define ZFS_SHARES_DIR "SHARES" #define ZFS_SA_ATTRS "SA_ATTRS" +#define ZFS_PROJECT_HIERARCHY "PROJECT_HIERARCHY" /* * Convert mode bits (zp_mode) to BSD-style DT_* values for storing in diff --git a/module/os/freebsd/zfs/zfs_vfsops.c b/module/os/freebsd/zfs/zfs_vfsops.c index 493ac9f69ad4..137767d928ca 100644 --- a/module/os/freebsd/zfs/zfs_vfsops.c +++ b/module/os/freebsd/zfs/zfs_vfsops.c @@ -938,6 +938,13 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os) else if (error != 0) return (error); + error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_PROJECT_HIERARCHY, + 8, 1, &zfsvfs->z_projecthierarchy_obj); + if (error == ENOENT) + zfsvfs->z_projecthierarchy_obj = 0; + else if (error != 0) + return (error); + error = zap_lookup(os, MASTER_NODE_OBJ, zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA], 8, 1, &zfsvfs->z_userobjquota_obj); diff --git a/module/os/linux/zfs/zfs_vfsops.c b/module/os/linux/zfs/zfs_vfsops.c index 3682c7500c7d..a442feca77a6 100644 --- a/module/os/linux/zfs/zfs_vfsops.c +++ b/module/os/linux/zfs/zfs_vfsops.c @@ -762,6 +762,14 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os) else if (error != 0) return (error); + error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_PROJECT_HIERARCHY, + 8, 1, &zfsvfs->z_projecthierarchy_obj); + if (error == ENOENT) + zfsvfs->z_projecthierarchy_obj = 0; + else if (error != 0) + return (error); + zfsvfs->z_os->os_projecthierarchy_obj = zfsvfs->z_projecthierarchy_obj; + error = zap_lookup(os, MASTER_NODE_OBJ, zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA], 8, 1, &zfsvfs->z_userobjquota_obj); diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index 0138afb14f95..facc70e539f3 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -2640,6 +2640,50 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns) return (err); } +/* + * This function sets projid of zp on its xattr dir and each xattr file + */ +int +zfs_setattr_xattr_dir(znode_t *zp) +{ + zfsvfs_t *zfsvfs = ZTOZSB(zp); + znode_t *xzp = NULL; + uint64_t xattr_obj = 0; + xoptattr_t *xoap; + xvattr_t xva; + int err; + + err = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), + &xattr_obj, sizeof (xattr_obj)); + if (err || !xattr_obj) + return (err); + + err = zfs_zget(zfsvfs, xattr_obj, &xzp); + ASSERT(err == 0); + xva_init(&xva); + xoap = xva_getxoptattr(&xva); + XVA_SET_REQ(&xva, XAT_PROJID); + xoap->xoa_projid = zp->z_projid; + XVA_SET_REQ(&xva, XAT_PROJINHERIT); + xoap->xoa_projinherit = 1; + + cred_t *cr = CRED(); + crhold(cr); + err = zfs_setattr(xzp, (vattr_t *)&xva, 0, cr, zfs_init_idmap); + crfree(cr); + if (err) { + zrele(xzp); + return (err); + } + err = zfs_setattr_dir(xzp); + if (err) { + zrele(xzp); + return (err); + } + zrele(xzp); + return (0); +} + typedef struct zfs_zlock { krwlock_t *zl_rwlock; /* lock we acquired */ znode_t *zl_znode; /* znode we held */ diff --git a/module/os/linux/zfs/zpl_file.c b/module/os/linux/zfs/zpl_file.c index 5e7b6403f374..3a112d618a0a 100644 --- a/module/os/linux/zfs/zpl_file.c +++ b/module/os/linux/zfs/zpl_file.c @@ -44,6 +44,9 @@ #ifdef HAVE_VFS_FILEMAP_DIRTY_FOLIO #include #endif +#include +#include + /* * When using fallocate(2) to preallocate space, inflate the requested @@ -873,6 +876,7 @@ zpl_ioctl_setxattr(struct file *filp, void __user *arg) xoptattr_t *xoap; int err; fstrans_cookie_t cookie; + zfsvfs_t *zfsvfs = ITOZSB(ip); if (copy_from_user(&fsx, arg, sizeof (fsx))) return (-EFAULT); @@ -880,6 +884,34 @@ zpl_ioctl_setxattr(struct file *filp, void __user *arg) if (!zpl_is_valid_projid(fsx.fsx_projid)) return (-EINVAL); + if (ITOZ(ip)->z_projid == fsx.fsx_projid && + (!!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL)) == + (!!(ITOZ(ip)->z_pflags & ZFS_PROJINHERIT))) { + return (0); + } + if (fsx.fsx_projid != ZFS_DEFAULT_PROJID && + ITOZ(ip)->z_projid != ZFS_DEFAULT_PROJID && + fsx.fsx_projid != ITOZ(ip)->z_projid) { + + /* + * Skip if fsx.fsx_projid is hierarchical parent of z_projid. + * Return special error code EXDEV for caller to know that + * object has projid set, whose parent project is project id + * being set. Caller can skip setting project id futher on EXDEV + * assuming its successfully done setting the projid. + * Usage of child project is already accounted in parent project + * while associating project to inode hierarchy. + */ + boolean_t hierarchical = B_FALSE; + err = -zfs_projects_are_hierarchical(zfsvfs, ITOZ(ip)->z_projid, + fsx.fsx_projid, &hierarchical); + if (err) + return (err); + if (hierarchical) { + return (-EXDEV); + } + } + err = __zpl_ioctl_setflags(ip, fsx.fsx_xflags, &xva); if (err) return (err); @@ -1006,6 +1038,73 @@ zpl_ioctl_rewrite(struct file *filp, void __user *arg) return (err); } +static int +zpl_ioctl_add_project_hierarchy(struct file *filp, void *arg) +{ + project_hierarchy_arg_t pharg; + zfsvfs_t *zfsvfs; + uint64_t projid; + uint64_t ino; + int error = 0; + + if (copy_from_user(&pharg, arg, sizeof (project_hierarchy_arg_t))) { + cmn_err(CE_NOTE, "%s:%d arg copyin error ", __func__, + __LINE__); + return (SET_ERROR(-EFAULT)); + } + + zfsvfs = ITOZSB(file_inode(filp)); + if ((error = zpl_enter(zfsvfs, FTAG)) != 0) + return (error); + + ino = file_inode(filp)->i_ino; + projid = pharg.pha_projid; + + zfs_dbgmsg("projid=%lld ino=%lld", projid, ino); + if (!dmu_objset_projectquota_enabled(zfsvfs->z_os)) { + cmn_err(CE_NOTE, "%s:%d projquota not enabled", __func__, + __LINE__); + error = (SET_ERROR(-ENOTSUP)); + goto out; + } + if (!zpl_is_valid_projid(projid)) { + cmn_err(CE_NOTE, "%s:%d projid=%lld ino=%lld projid not valid", + __func__, __LINE__, projid, ino); + error = (SET_ERROR(-EINVAL)); + goto out; + } + uint64_t quotaobj = zfsvfs->z_projectquota_obj; + uint64_t quota; + char buf[32]; + (void) snprintf(buf, sizeof (buf), "%llx", (longlong_t)projid); + if ((quotaobj == 0) || (zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, + "a) != 0)) { + cmn_err(CE_NOTE, "%s:%d projid=%lld ino=%lld quota not set", + __func__, __LINE__, projid, ino); + error = (SET_ERROR(-EINVAL)); + goto out; + } + + cred_t *cr = CRED(); + crhold(cr); + fstrans_cookie_t cookie = spl_fstrans_mark(); + error = -zfs_project_hierarchy_add(zfsvfs, projid, ino); + spl_fstrans_unmark(cookie); + crfree(cr); + +out: + if (error) { + char dsname[ZFS_MAX_DATASET_NAME_LEN]; + dsl_dataset_name(zfsvfs->z_os->os_dsl_dataset, dsname); + cmn_err(CE_NOTE, "%s:%d ds=:%s hobj=%lld project=%lld ino=%lld " + "error=%d on hierarchy add", __func__, __LINE__, dsname, + zfsvfs->z_projecthierarchy_obj, projid, ino, -error); + } + zpl_exit(zfsvfs, FTAG); + + return (error); +} + static long zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { @@ -1026,6 +1125,8 @@ zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return (zpl_ioctl_setdosflags(filp, (void *)arg)); case ZFS_IOC_REWRITE: return (zpl_ioctl_rewrite(filp, (void *)arg)); + case FS_IOC_ADD_PROJECT_HIERARCHY: + return (zpl_ioctl_add_project_hierarchy(filp, (void *)arg)); default: return (-ENOTTY); } diff --git a/module/zfs/dmu_objset.c b/module/zfs/dmu_objset.c index c6ec607217d4..6cb8f32be223 100644 --- a/module/zfs/dmu_objset.c +++ b/module/zfs/dmu_objset.c @@ -98,6 +98,8 @@ static void dmu_objset_find_dp_cb(void *arg); static void dmu_objset_upgrade(objset_t *os, dmu_objset_upgrade_cb_t cb); static void dmu_objset_upgrade_stop(objset_t *os); +static void projectusage_os_avl_create(objset_t *os); +static void projectusage_os_avl_destroy(objset_t *os); void dmu_objset_init(void) @@ -703,6 +705,10 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, } mutex_init(&os->os_upgrade_lock, NULL, MUTEX_DEFAULT, NULL); + mutex_init(&os->os_projecthierarchyused_lock, NULL, MUTEX_DEFAULT, + NULL); + mutex_init(&os->os_projecthierarchyop_lock, NULL, MUTEX_DEFAULT, NULL); + projectusage_os_avl_create(os); *osp = os; return (0); @@ -1064,6 +1070,9 @@ dmu_objset_evict_done(objset_t *os) mutex_destroy(&os->os_obj_lock); mutex_destroy(&os->os_user_ptr_lock); mutex_destroy(&os->os_upgrade_lock); + mutex_destroy(&os->os_projecthierarchyused_lock); + mutex_destroy(&os->os_projecthierarchyop_lock); + projectusage_os_avl_destroy(os); for (int i = 0; i < TXG_SIZE; i++) multilist_destroy(&os->os_dirty_dnodes[i]); spa_evicting_os_deregister(os->os_spa, os); @@ -1957,6 +1966,34 @@ userquota_compare(const void *l, const void *r) return (TREE_ISIGN(rv)); } +static void +projectusage_os_avl_create(objset_t *os) +{ + for (int i = 0; i < TXG_SIZE; i++) { + avl_create(&os->os_project_deltas[i], userquota_compare, + sizeof (userquota_node_t), offsetof(userquota_node_t, + uqn_node)); + } +} + +static void +projectusage_os_avl_destroy(objset_t *os) +{ + for (int i = 0; i < TXG_SIZE; i++) { + avl_destroy(&os->os_project_deltas[i]); + } +} + +/* + * Return TRUE if the given record is for counting objects and not used-bytes. + */ +static boolean_t +is_obj_record(userquota_node_t *uqn) +{ + return (strncmp(uqn->uqn_id, DMU_OBJACCT_PREFIX, + DMU_OBJACCT_PREFIX_LEN) == 0); +} + static void do_userquota_cacheflush(objset_t *os, userquota_cache_t *cache, dmu_tx_t *tx) { @@ -1993,6 +2030,10 @@ do_userquota_cacheflush(objset_t *os, userquota_cache_t *cache, dmu_tx_t *tx) avl_destroy(&cache->uqc_group_deltas); if (dmu_objset_projectquota_enabled(os)) { + uint64_t parentproj_ino[2] = {0, 0}; + uint64_t parent_projid = 0; + char pbuf[32]; + cookie = NULL; while ((uqn = avl_destroy_nodes(&cache->uqc_project_deltas, &cookie)) != NULL) { @@ -2000,10 +2041,67 @@ do_userquota_cacheflush(objset_t *os, userquota_cache_t *cache, dmu_tx_t *tx) VERIFY0(zap_increment(os, DMU_PROJECTUSED_OBJECT, uqn->uqn_id, uqn->uqn_delta, tx)); mutex_exit(&os->os_userused_lock); + if (!is_obj_record(uqn) && + os->os_projecthierarchy_obj) { + if (zap_lookup(os, os->os_projecthierarchy_obj, + uqn->uqn_id, 8, 2, parentproj_ino) == 0) { + parent_projid = parentproj_ino[0]; + } else { + parent_projid = 0; + } + while (parent_projid != 0) { + (void) snprintf(pbuf, sizeof (pbuf), + "%llx", (longlong_t)parent_projid); + mutex_enter(&os->os_userused_lock); + zap_increment(os, + DMU_PROJECTUSED_OBJECT, + pbuf, uqn->uqn_delta, tx); + mutex_exit(&os->os_userused_lock); + parentproj_ino[0] = 0; + if (zap_lookup(os, + os->os_projecthierarchy_obj, pbuf, + 8, 2, parentproj_ino) == 0) { + parent_projid = + parentproj_ino[0]; + } else { + parent_projid = 0; + } + } + } kmem_free(uqn, sizeof (*uqn)); } avl_destroy(&cache->uqc_project_deltas); } + + uint64_t txg = tx->tx_txg; + if (dmu_objset_projectquota_enabled(os) && + (avl_numnodes(&os->os_project_deltas[txg & TXG_MASK]) != 0)) { + mutex_enter(&os->os_projecthierarchyused_lock); + if (avl_numnodes(&os->os_project_deltas[txg & TXG_MASK]) != 0) { + avl_tree_t project_deltas = { 0 }; + + avl_create(&project_deltas, userquota_compare, + sizeof (userquota_node_t), + offsetof(userquota_node_t, uqn_node)); + avl_swap(&project_deltas, + &os->os_project_deltas[txg & TXG_MASK]); + mutex_exit(&os->os_projecthierarchyused_lock); + + cookie = NULL; + while ((uqn = avl_destroy_nodes(&project_deltas, + &cookie)) != NULL) { + mutex_enter(&os->os_userused_lock); + + zap_increment(os, DMU_PROJECTUSED_OBJECT, + uqn->uqn_id, uqn->uqn_delta, tx); + mutex_exit(&os->os_userused_lock); + kmem_free(uqn, sizeof (*uqn)); + } + avl_destroy(&project_deltas); + } else { + mutex_exit(&os->os_projecthierarchyused_lock); + } + } } static void @@ -2079,6 +2177,20 @@ do_userobjquota_update(objset_t *os, userquota_cache_t *cache, uint64_t flags, } } +void +do_projectusage_update(objset_t *os, uint64_t projid, int64_t used, + dmu_tx_t *tx) +{ + char name[20 + DMU_OBJACCT_PREFIX_LEN]; + int64_t delta = used; + + if (delta == 0) + return; + (void) snprintf(name, sizeof (name), "%llx", (longlong_t)projid); + userquota_update_cache(&os->os_project_deltas[tx->tx_txg & TXG_MASK], + name, delta); +} + typedef struct userquota_updates_arg { objset_t *uua_os; int uua_sublist_idx; diff --git a/module/zfs/zfs_quota.c b/module/zfs/zfs_quota.c index b8fe512d4f09..6f2fed0a43ae 100644 --- a/module/zfs/zfs_quota.c +++ b/module/zfs/zfs_quota.c @@ -37,6 +37,7 @@ #include #include #include +#include int zpl_get_file_info(dmu_object_type_t bonustype, const void *data, @@ -305,6 +306,930 @@ zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, return (err); } +/* + * This function returns true, if "expected_parent_projid" is hierarchical + * parent project of "projid", otherwise returns false. + * zfsvfs->z_projecthierarchy_obj 0 means, no hierarchical projects. + */ +int +zfs_projects_are_hierarchical(zfsvfs_t *zfsvfs, uint64_t projid, + uint64_t expected_parent_projid, boolean_t *hierarchical_p) { + + boolean_t hierarchical = B_FALSE; + int err = 0; + + if ((err = zfs_enter(zfsvfs, FTAG)) != 0) { + return (err); + } + + if (!zfsvfs->z_projecthierarchy_obj) { + goto out; + } + + while (projid != ZFS_DEFAULT_PROJID) { + char buf[20 + DMU_OBJACCT_PREFIX_LEN]; + + (void) snprintf(buf, sizeof (buf), "%llx", (longlong_t)projid); + + uint64_t parentproj_ino[2] = {0, 0}; + uint64_t projhierarchyobj; + uint64_t parent_projid; + + projhierarchyobj = zfsvfs->z_projecthierarchy_obj; + err = zap_lookup(zfsvfs->z_os, projhierarchyobj, buf, 8, 2, + &parentproj_ino); + if (err == ENOENT) { + err = 0; + break; + } + if (err) { + char dsname[ZFS_MAX_DATASET_NAME_LEN]; + dsl_dataset_name(zfsvfs->z_os->os_dsl_dataset, dsname); + cmn_err(CE_NOTE, "%s:%d ds=%s hobj=%lld project=%lld " + "error=%d on zap_lookup", __func__, __LINE__, + dsname, zfsvfs->z_projecthierarchy_obj, projid, + err); + break; + } + + parent_projid = parentproj_ino[0]; + if (parent_projid == expected_parent_projid) { + hierarchical = B_TRUE; + break; + } + projid = parent_projid; + } + +out: + zfs_exit(zfsvfs, FTAG); + *hierarchical_p = hierarchical; + return (err); +} + +/* + * This function gets the usage of given projid. + */ +static int +zfs_project_get_usage(zfsvfs_t *zfsvfs, uint64_t projid, uint64_t *usedp) +{ + int err; + + /* + * get project usage of projid. + */ + char buf[20 + DMU_OBJACCT_PREFIX_LEN]; + uint64_t used; + + (void) snprintf(buf, sizeof (buf), "%llx", + (longlong_t)projid); + used = 0; + err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT, buf, 8, + 1, &used); + if (err == ENOENT) + err = 0; + if (err != 0) { + cmn_err(CE_NOTE, "%s:%d error=%d on zap_lookup. projid=%lld", + __func__, __LINE__, err, projid); + *usedp = 0; + return (err); + } + + *usedp = used; + return (0); +} + +/* + * This function finds total usages of child projects of given parent project. + */ +static int +zfs_project_hierarchy_get_childrens_usage(zfsvfs_t *zfsvfs, uint64_t *objp, + uint64_t parent_projid, uint64_t *childrens_usedp) +{ + zap_cursor_t zc; + zap_attribute_t *za; + uint64_t parentproj_ino[2]; + uint64_t h_projid, h_parent_projid, h_ino; + int error = 0; + + zfs_dbgmsg(" Get usage of all child project of projid=%lld", + parent_projid); + za = zap_attribute_alloc(); + for (zap_cursor_init(&zc, zfsvfs->z_os, *objp); + zap_cursor_retrieve(&zc, za) == 0; zap_cursor_advance(&zc)) { + if (za->za_num_integers != 2) + continue; + error = zap_lookup(zfsvfs->z_os, *objp, + za->za_name, 8, 2, parentproj_ino); + if (error == ENOENT) { + error = 0; + continue; + } + if (error) { + cmn_err(CE_NOTE, "%s:%d error %d on zap lookup. projid=" + "%s", __func__, __LINE__, error, za->za_name); + break; + } + h_parent_projid = parentproj_ino[0]; + h_ino = parentproj_ino[1]; + h_projid = zfs_strtonum(za->za_name, NULL); + if (h_parent_projid == parent_projid) { + uint64_t child_used = 0; + error = zfs_project_get_usage(zfsvfs, h_projid, + &child_used); + if (error) { + cmn_err(CE_NOTE, "%s:%d error %d on projid=%s " + "get usage", __func__, __LINE__, error, + za->za_name); + break; + } + *childrens_usedp += child_used; + } + } + zap_cursor_fini(&zc); + zap_attribute_free(za); + + return (error); +} + +/* + * This function finds project id associated to given ino. + */ +static int +zfs_project_hierarchy_ino_to_project(zfsvfs_t *zfsvfs, uint64_t *objp, + uint64_t ino, uint64_t *projidp) +{ + zap_cursor_t zc; + zap_attribute_t *za; + uint64_t parentproj_ino[2]; + uint64_t h_projid, h_parent_projid, h_ino; + uint64_t projid; + int error = 0; + + projid = 0; + za = zap_attribute_alloc(); + for (zap_cursor_init(&zc, zfsvfs->z_os, *objp); + zap_cursor_retrieve(&zc, za) == 0; zap_cursor_advance(&zc)) { + if (za->za_num_integers != 2) + continue; + error = zap_lookup(zfsvfs->z_os, *objp, + za->za_name, 8, 2, parentproj_ino); + if (error == ENOENT) { + error = 0; + continue; + } + if (error) { + cmn_err(CE_NOTE, "%s:%d error %d on zap lookup. projid=" + "%s", __func__, __LINE__, error, za->za_name); + break; + } + h_parent_projid = parentproj_ino[0]; + h_ino = parentproj_ino[1]; + h_projid = zfs_strtonum(za->za_name, NULL); + zfs_dbgmsg("zap entry projid=%lld parent_projid=%llx ino=%lld", + h_projid, h_parent_projid, h_ino); + if (h_ino == ino) { + projid = h_projid; + break; + } + } + zap_cursor_fini(&zc); + zap_attribute_free(za); + zfs_dbgmsg("projid=%lld for ino=%lld. error=%d", projid, ino, error); + *projidp = projid; + + return (error); +} + +/* + * This functin sets the projid on znode. + */ +static int +zfs_projectino_set_projid(znode_t *zp, uint64_t projid, dmu_tx_t *tx) +{ + boolean_t projid_done = B_FALSE; + zfsvfs_t *zfsvfs = ZTOZSB(zp); + int error = 0; + + if (!(zp->z_pflags & ZFS_PROJID)) { + /* + * zp was created before project quota feature upgrade. + * It needs sa relayout to set projid. + */ + error = sa_add_projid(zp->z_sa_hdl, tx, projid); + if (unlikely(error == EEXIST)) { + error = 0; + } else if (error != 0) { + goto out; + } else { + projid_done = B_TRUE; + } + } + + if (!projid_done) { + zp->z_projid = projid; + error = sa_update(zp->z_sa_hdl, SA_ZPL_PROJID(zfsvfs), + (void *)&zp->z_projid, sizeof (uint64_t), tx); + if (error) + goto out; + } + zp->z_pflags |= ZFS_PROJINHERIT; + error = sa_update(zp->z_sa_hdl, SA_ZPL_FLAGS(zfsvfs), + (void *)&zp->z_pflags, sizeof (uint64_t), tx); + +out: + zfs_dbgmsg("ino=%lld projid=%lld error=%d", zp->z_id, projid, error); + if (error) { + cmn_err(CE_NOTE, "%s:%d error %d projid=%lld, ino=%lld", + __func__, __LINE__, error, projid, zp->z_id); + } + return (error); +} + +/* + * This function adds the given usage to all parent projects in hierarchy up. + */ +static int +zfs_project_hierarchy_parent_usage_update_all(zfsvfs_t *zfsvfs, + uint64_t parent_projid, dmu_tx_t *tx, uint64_t *objp, int64_t used) +{ + int err = 0; + + while (parent_projid != ZFS_DEFAULT_PROJID) { + mutex_enter(&zfsvfs->z_os->os_projecthierarchyused_lock); + do_projectusage_update(zfsvfs->z_os, + parent_projid, used, tx); + mutex_exit(&zfsvfs->z_os->os_projecthierarchyused_lock); + + char pprojbuf[32]; + (void) snprintf(pprojbuf, sizeof (pprojbuf), "%llx", + (longlong_t)parent_projid); + + uint64_t p_parentproj_ino[2]; + err = zap_lookup(zfsvfs->z_os, *objp, + pprojbuf, 8, 2, + &p_parentproj_ino); + if (err == 0) { + parent_projid = + p_parentproj_ino[0]; + } else { + cmn_err(CE_NOTE, "%s:%d error %d on zap lookup. projid=" + "%lld", __func__, __LINE__, err, parent_projid); + break; + } + } + + return (err); +} + +/* + * This function gets the usage of projid and add it to parent_projid. + */ +static int +zfs_project_hierarchy_parent_usage_update(zfsvfs_t *zfsvfs, uint64_t projid, + uint64_t parent_projid, dmu_tx_t *tx, boolean_t subtract) +{ + int err; + + if (projid != parent_projid && parent_projid != ZFS_DEFAULT_PROJID) { + + /* + * get project usage of projid. + * Update usage on parent_projid. + */ + char buf[20 + DMU_OBJACCT_PREFIX_LEN]; + uint64_t used; + int64_t delta; + + (void) snprintf(buf, sizeof (buf), "%llx", + (longlong_t)projid); + used = 0; + err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT, buf, 8, + 1, &used); + if (err == ENOENT) + err = 0; + if (err != 0) { + cmn_err(CE_NOTE, "%s:%d projid=%lld " + "parent_projid=%lld delta=%lld err=%d", __func__, + __LINE__, projid, parent_projid, delta, err); + return (err); + } + delta = used; + + if (subtract) { + delta = -delta; + } + + mutex_enter(&zfsvfs->z_os->os_projecthierarchyused_lock); + do_projectusage_update(zfsvfs->z_os, + parent_projid, delta, tx); + mutex_exit(&zfsvfs->z_os->os_projecthierarchyused_lock); + } + + return (0); +} + +/* + * This function updates zap entry in ZFS_PROJECT_HIERARCHY zap object. + * Zap entry is formed with name= and value=. + * projid and ino are function arguments. parent_projid + * is project id on parent inode of "ino" in hierarchy upword, which has + * entry in ZFS_PROJECT_HIERARCHY, means hierarchical parent project. + */ +static int +zfs_project_hierarchy_zap_update(zfsvfs_t *zfsvfs, uint64_t *objp, + uint64_t projid, uint64_t ino, char *buf, dmu_tx_t *tx, + boolean_t parent_usage_update) +{ + uint64_t parent = 0; + uint64_t parentproj_ino[2]; + uint64_t cur_parentproj_ino[2]; + uint64_t cur_parent_projid = ZFS_DEFAULT_PROJID; + znode_t *zp = NULL; + int err = 0; + + zfs_dbgmsg("Update parent_projid for projid=%llx", projid); + ASSERT(ino != 0); + + /* + * Get current parent_projid for existing project. + * No entry means that its new project being added, so consider "0" as + * current parent project id. + */ + err = zap_lookup(zfsvfs->z_os, *objp, buf, 8, 2, + &cur_parentproj_ino); + if (err == 0) { + cur_parent_projid = cur_parentproj_ino[0]; + } else if (err == ENOENT) { + cur_parent_projid = 0; + err = 0; + } else { + cmn_err(CE_NOTE, "%s:%d error %d on zap lookup. projid=%lld", + __func__, __LINE__, err, projid); + goto out; + } + + char pbuf[32]; + uint64_t p_parentproj_ino[2] = {0, 0}; + uint64_t parent_projid = ZFS_DEFAULT_PROJID; + + uint64_t zid = ino; + err = zfs_zget(zfsvfs, zid, &zp); + if (err == ENOENT) { + zfs_dbgmsg("ENOENT on ino=%lld associated to projid=%llx. " + "Remove from hierarchy", ino, projid); + err = 0; + goto update; + } + if (err) + goto out; + if (zp->z_projid != projid) { + zfs_dbgmsg("different projid=%lld on ino=%lld, its associated " + "to projid=%llx. Remove from hierarchy", zp->z_projid, ino, + projid); + zrele(zp); + zp = NULL; + goto update; + } + + /* + * Get parent inode of ino passed. if project id on parent inode is 0 or + * non-zero project id with no entry in zap, then check the next parent + * following till the root inode. + * When updating child project to associate with newly added parent + * project, immediate parent inode of inode corresponding to child + * project could have projid "0", if its not an inode corresponding to + * parent project. so follow up to find parent inode with non-zero + * projid and entry in zap. Inode corresponding to parent project has + * projid set. + */ + while (zid != zfsvfs->z_root) { + err = sa_lookup(zp->z_sa_hdl, + SA_ZPL_PARENT(zfsvfs), &parent, sizeof (parent)); + zrele(zp); + zp = NULL; + if (err) { + cmn_err(CE_NOTE, "%s:%d error %d on sa_lookup. ino=" + "%lld", __func__, __LINE__, err, zp->z_id); + break; + } + + err = zfs_zget(zfsvfs, parent, &zp); + if (err) { + cmn_err(CE_NOTE, "%s:%d error %d on zget. ino=%lld", + __func__, __LINE__, err, parent); + break; + } + parent_projid = zp->z_projid; + if (parent_projid != 0) { + (void) snprintf(pbuf, sizeof (pbuf), "%llx", + (longlong_t)parent_projid); + err = zap_lookup(zfsvfs->z_os, *objp, pbuf, 8, 2, + &p_parentproj_ino); + if (err == 0) + break; + } + zid = parent; + } + if (zp) { + zrele(zp); + zp = NULL; + } + + if (err) + goto out; + +update: + parentproj_ino[0] = parent_projid; + parentproj_ino[1] = ino; + + err = zap_update(zfsvfs->z_os, *objp, buf, 8, 2, &parentproj_ino, tx); + if (parent_usage_update && parent_projid != ZFS_DEFAULT_PROJID && + parent_projid != cur_parent_projid) + zfs_project_hierarchy_parent_usage_update(zfsvfs, projid, + parent_projid, tx, B_FALSE); + +out: + zfs_dbgmsg("updated parent_projid for projid=%llx, cur_parent_projid=" + "%llx. parent projid=%llx, ino=%lld err=%d", projid, + cur_parent_projid, parent_projid, ino, err); + return (err); +} + +/* + * This function updates each zap entry in PROJECT_HIERARCHY zap object, for the + * change in parent project due to project add or remove. + */ +static int +zfs_project_hierarchy_zap_update_all(zfsvfs_t *zfsvfs, uint64_t *objp, + uint64_t updated_projid, dmu_tx_t *tx, boolean_t project_removed) +{ + zap_cursor_t zc; + zap_attribute_t *za; + uint64_t parentproj_ino[2]; + uint64_t projid, parent_projid, ino; + int error = 0; + + zfs_dbgmsg("Update all projects. newly %s projid=%llx", + (project_removed ? "removed" : "added"), updated_projid); + za = zap_attribute_alloc(); + for (zap_cursor_init(&zc, zfsvfs->z_os, *objp); + zap_cursor_retrieve(&zc, za) == 0; zap_cursor_advance(&zc)) { + if (za->za_num_integers != 2) + continue; + error = zap_lookup(zfsvfs->z_os, *objp, + za->za_name, 8, 2, parentproj_ino); + if (error == ENOENT) { + error = 0; + continue; + } + if (error) { + cmn_err(CE_NOTE, "%s:%d error %d on zap lookup projid=" + "%s", __func__, __LINE__, error, za->za_name); + break; + } + parent_projid = parentproj_ino[0]; + ino = parentproj_ino[1]; + zfs_dbgmsg("zap entry projid=%s parent_projid=%llx ino=%lld", + za->za_name, parent_projid, ino); + if (project_removed && parent_projid != updated_projid) + continue; + if (!project_removed && parent_projid == updated_projid) + continue; + projid = zfs_strtonum(za->za_name, NULL); + + boolean_t parent_usage_update = B_TRUE; + if (project_removed) { + ASSERT(parent_projid == updated_projid); + /* + * "parent_projid" removed, so its association with + * "projid" would be removed. projid would be + * associated to new parent project in hierarchy up via + * zfs_project_hierarchy_zap_update. substract projid + * usage from current "parent_projid". + */ + error = zfs_project_hierarchy_parent_usage_update( + zfsvfs, projid, parent_projid, tx, B_TRUE); + if (error) { + cmn_err(CE_NOTE, "%s:%d error %d on parent " + "usage update. projid=%lld parent_projid=" + "%lld", __func__, __LINE__, error, projid, + parent_projid); + break; + } + /* + * all parent project in hierarchy up already accounts + * projid usage, so no update needed to new parent + * project via zfs_project_hierarchy_zap_update. + */ + parent_usage_update = B_FALSE; + } + + /* + * parent_usage_update would be true, when new project added and + * associating child project to it. + * zfs_project_hierarchy_zap_update would add "projid" usage to + * its new "parent_projid". + */ + error = zfs_project_hierarchy_zap_update(zfsvfs, objp, projid, + ino, za->za_name, tx, parent_usage_update); + if (error) { + cmn_err(CE_NOTE, "%s:%d error %d on hierarchy zap " + "update. projid=%lld ino=%lld", __func__, __LINE__, + error, projid, ino); + break; + } + } + zap_cursor_fini(&zc); + zap_attribute_free(za); + zfs_dbgmsg("Done Updating all projects. newly %s projid=%llx.error=%d", + (project_removed ? "removed" : "added"), updated_projid, error); + + return (error); +} + +/* + * This function removes a zap entry from project hierarchy zap. + * Its used to remove project entry when associated directory inode is deleted. + */ +static int +zfs_project_hierarchy_remove_entry(zfsvfs_t *zfsvfs, uint64_t projid) +{ + dmu_tx_t *tx; + int error; + uint64_t phobj = zfsvfs->z_projecthierarchy_obj; + if (phobj != 0 && projid != ZFS_DEFAULT_PROJID) { + uint64_t parentproj_ino[2] = {0, 0}; + char projbuf[32]; + (void) snprintf(projbuf, sizeof (projbuf), "%llx", + (longlong_t)projid); + error = zap_lookup(zfsvfs->z_os, phobj, projbuf, 8, 2, + &parentproj_ino); + if (error == 0) { + txg_wait_synced(dmu_objset_pool(zfsvfs->z_os), 0); + + mutex_enter(&zfsvfs->z_os->os_projecthierarchyop_lock); + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_zap(tx, phobj, B_TRUE, NULL); + error = dmu_tx_assign(tx, DMU_TX_WAIT); + if (error) { + dmu_tx_abort(tx); + } else { + error = zap_remove(zfsvfs->z_os, phobj, projbuf, + tx); + dmu_tx_commit(tx); + } + mutex_exit(&zfsvfs->z_os->os_projecthierarchyop_lock); + } + zfs_dbgmsg("removed entry projid=%lld. error=%d", projid, + error); + } + + return (0); +} + +/* + * This function removes association of given project id and ino. + */ +int +zfs_project_hierarchy_remove(zfsvfs_t *zfsvfs, uint64_t projid, uint64_t ino, + boolean_t toponly) +{ + uint64_t *objp; + dmu_tx_t *tx; + int err; + + zfs_dbgmsg("remove projid=%lld from ino=%lld", projid, ino); + objp = &zfsvfs->z_projecthierarchy_obj; + if (objp == 0) + return (0); + + uint64_t h_parentproj = 0, h_ino = 0; + char projbuf[32]; + + (void) snprintf(projbuf, sizeof (projbuf), "%llx", (longlong_t)projid); + + uint64_t parentproj_ino[2]; + err = zap_lookup(zfsvfs->z_os, *objp, projbuf, 8, 2, + &parentproj_ino); + if (err == 0) { + h_parentproj = parentproj_ino[0]; + h_ino = parentproj_ino[1]; + if (ino != h_ino) { + cmn_err(CE_NOTE, "%s:%d project %lld associated to ino=" + "%lld and not to %lld", __func__, __LINE__, projid, + h_ino, ino); + return (EINVAL); + } + if (toponly && h_parentproj != 0) { + cmn_err(CE_NOTE, "%s:%d project %lld assocated to ino=" + "%lld, is not top of hierarchy", __func__, __LINE__, + projid, ino); + return (EINVAL); + } + } else if (err != ENOENT) { + cmn_err(CE_NOTE, "%s:%d error %d on zap_lookup. projid=%lld " + "ino=%lld", __func__, __LINE__, err, projid, ino); + return (err); + } else { + cmn_err(CE_NOTE, "%s:%d projid=%lld not associated to ino=%lld" + " and not to any other ino", __func__, __LINE__, projid, + ino); + return (EINVAL); + } + + mutex_enter(&zfsvfs->z_os->os_projecthierarchyop_lock); + /* + * Get projid own usage to later reduce it from all its parents once its + * removed from hierarchy. + */ + uint64_t project_used = 0; + err = zfs_project_get_usage(zfsvfs, projid, &project_used); + if (err) { + mutex_exit(&zfsvfs->z_os->os_projecthierarchyop_lock); + return (err); + } + uint64_t childrens_used = 0; + err = zfs_project_hierarchy_get_childrens_usage(zfsvfs, objp, projid, + &childrens_used); + if (err) { + mutex_exit(&zfsvfs->z_os->os_projecthierarchyop_lock); + return (err); + } + + uint64_t project_own_used; + if (project_used > childrens_used) + project_own_used = project_used - childrens_used; + else + project_own_used = 0; + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_zap(tx, *objp, B_TRUE, NULL); + err = dmu_tx_assign(tx, DMU_TX_WAIT); + if (err) { + dmu_tx_abort(tx); + mutex_exit(&zfsvfs->z_os->os_projecthierarchyop_lock); + return (err); + } + + err = zap_remove(zfsvfs->z_os, *objp, projbuf, tx); + if (err == ENOENT) + err = 0; + ASSERT0(err); + + /* + * projid removed from hierarchy. Update childrens assocation in + * hierarchy. + */ + err = zfs_project_hierarchy_zap_update_all(zfsvfs, objp, projid, tx, + B_TRUE); + ASSERT0(err); + + /* + * Reduce projid own usage from its parents in hierarchy. + */ + err = zfs_project_hierarchy_parent_usage_update_all(zfsvfs, + h_parentproj, tx, objp, -project_own_used); + ASSERT0(err); + + dmu_tx_commit(tx); + mutex_exit(&zfsvfs->z_os->os_projecthierarchyop_lock); + zfs_dbgmsg("removed projid=%lld from ino=%lld", projid, ino); + + return (err); +} + +/* + * This function creats association of given projid with its parent project. + * Parent project is discovered by walking up in hierarchy from the given + * directory ino. It also updates the existing projects parent. Parent project + * change for existing projects is possible, when a new project is added in + * middle of the hierarchy of existing projects. + */ +int +zfs_project_hierarchy_add(zfsvfs_t *zfsvfs, uint64_t projid, uint64_t ino) +{ + uint64_t *objp; + dmu_tx_t *tx; + char buf[32]; + int err = 0; + + if ((err = zfs_enter(zfsvfs, FTAG)) != 0) + return (err); + + zfs_dbgmsg("add projid=%lld ino=%lld", projid, ino); + objp = &zfsvfs->z_projecthierarchy_obj; + + (void) snprintf(buf, sizeof (buf), "%llx", (longlong_t)projid); + + if (*objp != 0) { + uint64_t parentproj_ino[2]; + err = zap_lookup(zfsvfs->z_os, *objp, buf, 8, 2, + &parentproj_ino); + if (err == 0) { + uint64_t projino; + projino = parentproj_ino[1]; + if (ino == projino) { + cmn_err(CE_NOTE, "%s:%d projid=%lld already " + "added in given ino=%lld hierarchy", + __func__, __LINE__, projid, ino); + goto out_exit; + } else { + cmn_err(CE_NOTE, "%s:%d projid=%lld already " + "added in ino=%lld hierarchy. can't add for" + " another ino=%lld", __func__, __LINE__, + projid, projino, ino); + err = (SET_ERROR(EINVAL)); + goto out_exit; + } + } else if (err != ENOENT) { + cmn_err(CE_NOTE, "%s:%d error %d on zap_lookup. projid=" + "%lld ino=%lld", __func__, __LINE__, err, projid, + ino); + goto out_exit; + } + + /* + * If different project associated to given inode in hierarchy, + * then it needs to be removed first from hierarchy. + * Can't associate a new project to ino, if another project + * is associated and it has quota set. + */ + uint64_t h_projid = 0; + err = zfs_project_hierarchy_ino_to_project(zfsvfs, objp, ino, + &h_projid); + if (err) { + cmn_err(CE_NOTE, "%s:%d error %d on ino to project. " + "projid=%lld ino=%lld", __func__, __LINE__, err, + projid, ino); + goto out_exit; + } + if (h_projid != 0) { + uint64_t *quotaobjp; + uint64_t quota; + char hbuf[32]; + quotaobjp = &zfsvfs->z_projectquota_obj; + (void) snprintf(hbuf, sizeof (hbuf), "%llx", + (longlong_t)h_projid); + err = zap_lookup(zfsvfs->z_os, *quotaobjp, hbuf, 8, 1, + "a); + if (err == 0) { + cmn_err(CE_NOTE, "%s:%d ino=%lld already " + "associated to projid=%lld with quota set. " + "can't associate to another projid=%lld", + __func__, __LINE__, ino, h_projid, projid); + err = (SET_ERROR(EINVAL)); + goto out_exit; + } else if (err != ENOENT) { + cmn_err(CE_NOTE, "%s:%d error %d on quota " + "zap_lookup. projid=%lld ino=%lld hprojid=" + "%lld", __func__, __LINE__, err, projid, + ino, h_projid); + goto out_exit; + } + err = zfs_project_hierarchy_remove(zfsvfs, h_projid, + ino, B_FALSE); + if (err) { + cmn_err(CE_NOTE, "%s:%d error %d on hierarchy " + "remove. projid=%lld ino=%lld hprojid=%lld", + __func__, __LINE__, err, projid, ino, + h_projid); + goto out_exit; + } + } + } + + znode_t *qzp = NULL; + + err = zfs_zget(zfsvfs, ino, &qzp); + if (err) { + cmn_err(CE_NOTE, "%s:%d error %d on zget. projid=%lld " + "ino=%lld", __func__, __LINE__, err, projid, ino); + goto out_exit; + } + + mutex_enter(&zfsvfs->z_os->os_projecthierarchyop_lock); + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, qzp->z_sa_hdl, B_TRUE); + dmu_tx_hold_zap(tx, *objp ? *objp : DMU_NEW_OBJECT, B_TRUE, NULL); + if (*objp == 0) { + dmu_tx_hold_zap(tx, MASTER_NODE_OBJ, B_TRUE, + ZFS_PROJECT_HIERARCHY); + } + err = dmu_tx_assign(tx, DMU_TX_WAIT); + if (err) { + dmu_tx_abort(tx); + goto out_exit_rele_unlock; + } + + mutex_enter(&zfsvfs->z_lock); + if (*objp == 0) { + *objp = zap_create(zfsvfs->z_os, DMU_OT_USERGROUP_QUOTA, + DMU_OT_NONE, 0, tx); + VERIFY(0 == zap_add(zfsvfs->z_os, MASTER_NODE_OBJ, + ZFS_PROJECT_HIERARCHY, 8, + 1, objp, tx)); + zfsvfs->z_os->os_projecthierarchy_obj = + zfsvfs->z_projecthierarchy_obj; + } + mutex_exit(&zfsvfs->z_lock); + + /* + * Set the projid on corresponding inode, so that child project can see + * and associate with it. + */ + err = zfs_projectino_set_projid(qzp, projid, tx); + ASSERT0(err); + + err = zfs_project_hierarchy_zap_update(zfsvfs, objp, projid, ino, buf, + tx, B_FALSE); + ASSERT0(err); + err = zfs_project_hierarchy_zap_update_all(zfsvfs, objp, projid, tx, + B_FALSE); + ASSERT0(err); + + dmu_tx_commit(tx); + /* + * As we set the projid on inode, so also set projid on its xattr's. + */ + zfs_setattr_xattr_dir(qzp); + +out_exit_rele_unlock: + mutex_exit(&zfsvfs->z_os->os_projecthierarchyop_lock); + zrele(qzp); +out_exit: + zfs_exit(zfsvfs, FTAG); + zfs_dbgmsg("Done add projid=%lld ino=%lld err:%d", projid, ino, err); + return (err); +} + +/* + * This function removes projects from hierarchy, which doesn't + * have quota and parent project id 0. Project without quota and + * no parent project doesn't need hierarchical accounting. + * It also cleanup the entries corresponding to deleted directory + * inode. + */ +static int +zfs_project_hierarchy_cleanup(zfsvfs_t *zfsvfs) +{ + zap_cursor_t zc; + zap_attribute_t *za; + uint64_t parentproj_ino[2]; + uint64_t h_projid, h_parent_projid, h_ino; + uint64_t *hobjp, *quotaobjp; + + quotaobjp = &zfsvfs->z_projectquota_obj; + hobjp = &zfsvfs->z_projecthierarchy_obj; + if (hobjp == NULL) + return (0); + +more: + boolean_t remove_more = B_FALSE; + za = zap_attribute_alloc(); + for (zap_cursor_init(&zc, zfsvfs->z_os, *hobjp); + zap_cursor_retrieve(&zc, za) == 0; zap_cursor_advance(&zc)) { + if (za->za_num_integers != 2) + continue; + if (zap_lookup(zfsvfs->z_os, *hobjp, za->za_name, 8, 2, + parentproj_ino)) + continue; + h_parent_projid = parentproj_ino[0]; + h_ino = parentproj_ino[1]; + h_projid = zfs_strtonum(za->za_name, NULL); + zfs_dbgmsg("zap entry projid=%lld parent_projid=%llx ino=%lld", + h_projid, h_parent_projid, h_ino); + + znode_t *zp = NULL; + uint64_t z_projid = 0; + int err; + err = zfs_zget(zfsvfs, h_ino, &zp); + if (err == 0) { + z_projid = zp->z_projid; + zrele(zp); + } + if (err != 0 || z_projid != h_projid) { + zfs_project_hierarchy_remove_entry(zfsvfs, h_projid); + continue; + } + if (h_parent_projid != 0) + continue; + uint64_t quota; + if (!quotaobjp || (zap_lookup(zfsvfs->z_os, *quotaobjp, + za->za_name, 8, 1, "a) == ENOENT)) { + if (zfs_project_hierarchy_remove(zfsvfs, h_projid, + h_ino, B_FALSE) == 0) + remove_more = B_TRUE; + } + } + zap_cursor_fini(&zc); + zap_attribute_free(za); + if (remove_more) + goto more; + zfs_dbgmsg("Removed all projects with none quota and parent project 0"); + return (0); +} + int zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, const char *domain, uint64_t rid, uint64_t quota) @@ -379,8 +1304,11 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, } mutex_exit(&zfsvfs->z_lock); + boolean_t cleanup_hierarchy = B_FALSE; if (quota == 0) { err = zap_remove(zfsvfs->z_os, *objp, buf, tx); + if (!err) + cleanup_hierarchy = B_TRUE; if (err == ENOENT) err = 0; } else { @@ -390,6 +1318,8 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, if (fuid_dirtied) zfs_fuid_sync(zfsvfs, tx); dmu_tx_commit(tx); + if (cleanup_hierarchy) + zfs_project_hierarchy_cleanup(zfsvfs); return (err); } @@ -461,6 +1391,9 @@ zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) char buf[20]; uint64_t used, quota, quotaobj, default_quota = 0; int err; + uint64_t projecthierarchyobj = 0; + uint64_t parentproj_ino[2] = {0, 0}; + uint64_t parent_projid = ZFS_DEFAULT_PROJID; if (usedobj == DMU_PROJECTUSED_OBJECT) { if (!dmu_objset_projectquota_present(zfsvfs->z_os)) { @@ -475,6 +1408,7 @@ zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) } quotaobj = zfsvfs->z_projectquota_obj; default_quota = zfsvfs->z_defaultprojectquota; + projecthierarchyobj = zfsvfs->z_projecthierarchy_obj; } else if (usedobj == DMU_USERUSED_OBJECT) { quotaobj = zfsvfs->z_userquota_obj; default_quota = zfsvfs->z_defaultuserquota; @@ -487,6 +1421,7 @@ zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) if (zfsvfs->z_replay) return (B_FALSE); +checkquota: (void) snprintf(buf, sizeof (buf), "%llx", (longlong_t)id); if (quotaobj == 0) { if (default_quota == 0) @@ -501,7 +1436,21 @@ zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) err = zap_lookup(zfsvfs->z_os, usedobj, buf, 8, 1, &used); if (err != 0) return (B_FALSE); - return (used >= quota); + + if (used >= quota) + return (B_TRUE); + + if (projecthierarchyobj) { + err = zap_lookup(zfsvfs->z_os, projecthierarchyobj, buf, 8, 2, + &parentproj_ino); + parent_projid = parentproj_ino[0]; + if (parent_projid != ZFS_DEFAULT_PROJID) { + id = parent_projid; + goto checkquota; + } + } + + return (B_FALSE); } boolean_t @@ -515,6 +1464,8 @@ EXPORT_SYMBOL(zpl_get_file_info); EXPORT_SYMBOL(zfs_userspace_one); EXPORT_SYMBOL(zfs_userspace_many); EXPORT_SYMBOL(zfs_set_userquota); +EXPORT_SYMBOL(zfs_project_hierarchy_add); +EXPORT_SYMBOL(zfs_project_hierarchy_remove); EXPORT_SYMBOL(zfs_id_overblockquota); EXPORT_SYMBOL(zfs_id_overobjquota); EXPORT_SYMBOL(zfs_id_overquota); diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index 8d3cb06076a3..a99f0f211f00 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -189,7 +189,8 @@ tests = ['defaultprojectquota_001_pos', 'defaultprojectquota_002_pos', 'projectquota_007_pos', 'projectquota_008_pos', 'projectquota_009_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'] + 'projecttree_001_pos', 'projecttree_002_pos', 'projecttree_003_neg', + 'projecttree_hierarchy_001_pos'] tags = ['functional', 'projectquota'] [tests/functional/dos_attributes:Linux] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index acff5e57db93..a3e352c20a56 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -1783,6 +1783,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/projectquota/projecttree_001_pos.ksh \ functional/projectquota/projecttree_002_pos.ksh \ functional/projectquota/projecttree_003_neg.ksh \ + functional/projectquota/projecttree_hierarchy_001_pos.ksh \ functional/projectquota/setup.ksh \ functional/quota/cleanup.ksh \ functional/quota/quota_001_pos.ksh \ diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg b/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg index 6a7d5f902ed8..d8635bd549fc 100644 --- a/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg @@ -34,6 +34,8 @@ export PGROUP=pgroup export PRJID1=1001 export PRJID2=1002 +export PRJID3=1003 +export PRJID4=1004 export QFS=$TESTPOOL/$TESTFS export PRJFILE=$TESTDIR/tfile diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib b/tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib index 7776c8f40f7a..265259b0cbf9 100644 --- a/tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib @@ -44,6 +44,10 @@ function cleanup_projectquota log_must zfs set projectobjquota@$PRJID1=none $QFS log_must zfs set projectquota@$PRJID2=none $QFS log_must zfs set projectobjquota@$PRJID2=none $QFS + log_must zfs set projectquota@$PRJID3=none $QFS + log_must zfs set projectobjquota@$PRJID3=none $QFS + log_must zfs set projectquota@$PRJID4=none $QFS + log_must zfs set projectobjquota@$PRJID4=none $QFS log_must zfs set defaultprojectquota=none $QFS log_must zfs set defaultprojectobjquota=none $QFS log_must chmod 0755 $mntp diff --git a/tests/zfs-tests/tests/functional/projectquota/projecttree_hierarchy_001_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projecttree_hierarchy_001_pos.ksh new file mode 100755 index 000000000000..64625a9a3e0c --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projecttree_hierarchy_001_pos.ksh @@ -0,0 +1,296 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2025 by Nutanix. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Validate Hierarchical project quota +# +# +# STRATEGY: +# 1. Setup directory tree +# a (Directory) +# |__ f1 (100M file) +# |__ b (Directory) +# |__ f1 (100M file) +# |__ c (Directory) +# |__f1 (100M file) +# +# 2. set 400M projectquota on directory 'a', 'b' and 'c' +# 3. Set a PRJID1 project on on 'a' directory tree +# 4. Validate usage of PRJID1 is 300M +# 5. Set a PRJID2 project on on 'a/b' directory tree +# 6. Validate usage of PRJID1 is 300M +# 7. Validate usage of PRJID2 is 200M +# 8. Set a PRJID3 project on on 'a/b/c' directory tree +# 9. Validate usage of PRJID1 is 300M +# 10. Validate usage of PRJID2 is 200M +# 11. Validate usage of PRJID3 is 100M +# 12. Write 100M data file in 'c' +# 13. Validate usage of PRJID1 is 400M +# 14. Validate usage of PRJID2 is 300M +# 15. Validate usage of PRJID3 is 200M +# 16. Validate new data writes in 'c' fails, as 400M quota on PRJID1 reached +# 17. Validate new data writes in 'b' fails, as 400M quota on PRJID1 reached +# 18. Validate new data writes in 'a' fails, as 400M quota on PRJID1 reached +# 19. Validate same with hierarchy assocation sequence of /a/b/c, /a/b, /a +# 20. Validate same with hierarchy assocation sequence of /a, /a/b/c, /a/b +# 21. Validate removing project quota in middle of hierarchy and adding new project quota +# + +function cleanup +{ + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Validate Hierarchical project quota" + +mkmount_writable $QFS +log_note "Setup Directory tree" +log_must user_run $PUSER mkdir -p $PRJDIR/a/b/c +log_must user_run $PUSER mkfile 100m $PRJDIR/a/f1 +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/f1 +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f1 +sync_all_pools +zfs projectspace $QFS + +log_note "Validate project assocation sequence /a, /a/b, /a/b/c" +log_mustnot zfs project -p $PRJID1 -srY $PRJDIR/a +log_must zfs set projectquota@$PRJID1=400m $QFS +log_must zfs project -p $PRJID1 -srY $PRJDIR/a +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,quota $QFS | grep $PRJID1 | grep 400M" +log_must eval "zfs get -H -ovalue projectquota@$PRJID1 $QFS | grep 400M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" +log_mustnot zfs project -p $PRJID2 -srY $PRJDIR/a/b +log_must zfs set projectquota@$PRJID2=400m $QFS +log_must zfs project -p $PRJID2 -srY $PRJDIR/a/b +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,quota $QFS | grep $PRJID2 | grep 400M" +log_must eval "zfs get -H -ovalue projectquota@$PRJID2 $QFS | grep 400M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 200\\.\*M" +log_mustnot zfs project -p $PRJID3 -srY $PRJDIR/a/b/c +log_must zfs set projectquota@$PRJID3=400m $QFS +log_must zfs project -p $PRJID3 -srY $PRJDIR/a/b/c +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,quota $QFS | grep $PRJID3 | grep 400M" +log_must eval "zfs get -H -ovalue projectquota@$PRJID3 $QFS | grep 400M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 200\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 100\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 100\\.\*M" + + +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f2 +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 400\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 400\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 200\\.\*M" + +log_note "Validate can't write at any level as quota exceeded at /a" +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/f3 +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/b/f3 +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f3 + +log_note "Remove Directory tree" +log_must rm -fR $PRJDIR/a +sync_all_pools +zfs projectspace $QFS +log_must zfs set projectquota@$PRJID1=none projectquota@$PRJID2=none projectquota@$PRJID3=none $QFS + +log_note "Setup Directory tree again" +log_must user_run $PUSER mkdir -p $PRJDIR/a/b/c +log_must user_run $PUSER mkfile 100m $PRJDIR/a/f1 +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/f1 +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f1 +sync_all_pools +zfs projectspace $QFS + +log_note "Validate project assocation sequence /a/b/c, /a/b, /a" +log_must zfs set projectquota@$PRJID3=400m $QFS +log_must zfs project -p $PRJID3 -srY $PRJDIR/a/b/c +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 100\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 100\\.\*M" + +log_must zfs set projectquota@$PRJID2=400m $QFS +log_must zfs project -p $PRJID2 -srY $PRJDIR/a/b +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 200\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 100\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 100\\.\*M" + +log_must zfs set projectquota@$PRJID1=400m $QFS +log_must zfs project -p $PRJID1 -srY $PRJDIR/a +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 200\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 100\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 100\\.\*M" + +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f2 +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 400\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 400\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 200\\.\*M" + +log_note "Validate can't write at any level as quota exceeded at /a" +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/f3 +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/b/f3 +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f3 + +log_note "Remove Directory tree" +log_must rm -fR $PRJDIR/a +sync_all_pools +zfs projectspace $QFS +log_must zfs set projectquota@$PRJID1=none projectquota@$PRJID2=none projectquota@$PRJID3=none $QFS + +log_note "Setup Directory tree again" +log_must user_run $PUSER mkdir -p $PRJDIR/a/b/c +log_must user_run $PUSER mkfile 100m $PRJDIR/a/f1 +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/f1 +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f1 +sync_all_pools +zfs projectspace $QFS + +log_note "Validate project assocation sequence /a, /a/b/c, /a/b" +log_must zfs set projectquota@$PRJID1=400m $QFS +log_must zfs project -p $PRJID1 -srY $PRJDIR/a +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" + +log_must zfs set projectquota@$PRJID3=400m $QFS +log_must zfs project -p $PRJID3 -srY $PRJDIR/a/b/c +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 100\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 100\\.\*M" + +log_must zfs set projectquota@$PRJID2=400m $QFS +log_must zfs project -p $PRJID2 -srY $PRJDIR/a/b +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 200\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 100\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 100\\.\*M" + +log_must user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f2 +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 400\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 400\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 200\\.\*M" + +log_note "Validate can't write at any level as quota exceeded at /a" +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/f3 +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/b/f3 +log_mustnot user_run $PUSER mkfile 100m $PRJDIR/a/b/c/f3 + +log_note "Validate file remove reduces usage in hierarchy" +log_must rm $PRJDIR/a/b/c/f2 +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID2 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID2 $QFS | grep 200\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 100\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 100\\.\*M" + +log_note "Validate an existing project associated to directory, can't be associated to another directory" +log_must zfs project -p $PRJID1 -srY $PRJDIR/a +log_mustnot zfs project -p $PRJID1 -srY $PRJDIR/a/b +log_mustnot zfs project -p $PRJID1 -srY $PRJDIR/a/b/c +log_must zfs project -p $PRJID2 -srY $PRJDIR/a/b +log_mustnot zfs project -p $PRJID2 -srY $PRJDIR/a/ +log_mustnot zfs project -p $PRJID2 -srY $PRJDIR/a//b/c +log_must zfs project -p $PRJID3 -srY $PRJDIR/a/b/c +log_mustnot zfs project -p $PRJID3 -srY $PRJDIR/a/ +log_mustnot zfs project -p $PRJID3 -srY $PRJDIR/a/b + +#Validate a new project can be associcated to directory path, if its +#assocaited to existing project without quota. Basically, quota on project +#removed, but project was associated to path in middle of heirarchy, +#so its assocation with project still exist due to accounting in hierarchy. +#Now new project quota and project assoication with path should be possible. +log_note "Validate a new project can be associcated to directory path" +log_must zfs set projectquota@$PRJID4=400m $QFS +log_mustnot zfs project -p $PRJID4 -srY $PRJDIR/a/b +log_must zfs set projectquota@$PRJID2=none $QFS +log_must zfs project -p $PRJID4 -srY $PRJDIR/a/b +sync_all_pools +zfs projectspace $QFS +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID1 | grep 300\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID1 $QFS | grep 300\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID4 | grep 200\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID4 $QFS | grep 200\\.\*M" +log_must eval "zfs projectspace -H -oname,used $QFS | grep $PRJID3 | grep 100\\.\*M" +log_must eval "zfs get -H -ovalue projectused@$PRJID3 $QFS | grep 100\\.\*M" + +log_note "Remove Directory tree" +log_must rm -fR $PRJDIR/a +sync_all_pools +zfs projectspace $QFS +log_must zfs set projectquota@$PRJID1=none projectquota@$PRJID4=none projectquota@$PRJID3=none $QFS + +log_pass "Validate Hierarchical project quota"