Skip to content

Commit 1ed2a49

Browse files
committed
Add hierarchical project quota support
ZFS has a project quota concept, where project is defined with unique id in dataset, called as project id (aka projid) and files/dirs inside dataset are assigned to project by setting projid on them. By associating a project (projid) with specific directory tree in dataset and setting quota on the projid enforces a directory quota. When needs to enforce different quota's in directory hierarchy, say want to limit usage of /d1/d2 to 10G and /d1/d3 also to 10G, this can be achieved by associating different project with /d1/d2 and /d1/d3 and setting quota on them. Now if want to limit the overall usage of /d1 to 15G. So need to associate another project with /d1, which should account the all usage under it. This would enforce a hierarchical directory quota. So, to provide hierarchical directory quota, need of supporting multiple project in same hierarchy, where objects under child project are accounted in all projects in hierarchy upword. Project quota associated to directory should be enforced on usage of all of the object inside it regardless of either it has child quota in hierarchy or not. This means, if you have projet quota P1 (projid=11) associated to directory /d1 and child project quota P2 (projid=22) associated to /d1/d2, then usage of objects (files/dirs/..) under /d1/d2 would be accounted in both projid=11 and projid=22. Objects specific to /d1 would be accounted in projid=11. To account object under child projects in all project in hierarchy up, each project needs to associated to immediate parent project. To discover a immediate parent project in hierarchy, project needs to be associated to root/top inode of project tree. When associating a project to root/top inode of directory tree, parent project is found by walking up-word in hierarchy from the inode. Parent project is found, when it finds a parent inode, on which project id is set and this project id has inode association. Project to top/root inode and parent project association is maintained in the new ZAP object on disk. Once a new project gets associated with its parent project, also correct the parent project of all existing projects. There could be a change in the parent project due to the new project quota. E.g. first associates a project 11 with /d1, so its parent project is none. Then associates a project 22 with /d1/d2/d3, so the project of /d1 becomes the parent of the project associated with /d1/d2/d3. Means, project 11 becomes the parent project of project 22. And finally associate a project 33 with /d1/d2, so now 11 becomes the parent project of 33 and 33 becomes the parent project of 22. When associating a project to its new parent project (change in parent project), propagate current usage of project to its new parent project. Once a hierarchical relationship is established between projects, any further changes/writes on objects would account usage in objects project id as well as in its hierarchical parent project ids. To account existing objects usage under a new project, set project id on directory recursive. While Setting project id on objects under a project hierarchy, set the project id on objects if current project id is 0 or it has a project id where new project id being set is not the hierarchical parent of current project id on object. If project id being set is the hierarchical parent of current project id on the object, then usage of the object is already account-ed in project id being set via hierarchical association. E.g. First associated project-id 22 with /d1/d2/d3, So, all objects inside /d1/d2/d3 are set with project id 22 and their usage gets account-ed in project 22. Now, when associating a new project 11 with /d1, project 11 would become a parent project of project 22 and as part of this association, current usage of project 22 would be propagated to project 11. After this, setting project id 11 on /d1 recursive would set project id 11 on all objects until it reaches the /d1/d2/d3 which has a project id 22 and it would break here as project id 11 being set is parent project of 22. This effectively would account for the remain-ing project 11 specific objects usage in it. In another case, where the project 11 first associated with /d1 and so usage of all objects under /d1 gets account-ed under project 11. Now, later associate project 22 with /d1/d2/d3, so the usage of objects within /d1/d2/d3 would be account-ed in project id 22, while setting project id 22 on /d1/d2/d3 and underneath objects. As project 11 is already associated as parent project of 22, so by setting project id 22 on objects from earlier project id 11, it would account the objects usage in project 22. For project 11, usage would decrement with respect to changing id from 11 to 22 and increment back with respect to 11 is hierarchical parent of 22, so effectively no change/delta in usage of project 11 would happen as expected. Projects once associated in the hierarchy, its association won’t be removed until it has an active parent project (quota is set) associated. Association is needed for hierarchical accounting purposes. Projects without active parent projects in hierarchy would be dis-associated from hierarchy. So, when a project is removed (quota is set to none), ZFS would disassociate non-active projects (quota none), which don't have parent projects. Hierarchical quota is enforced by checking project quota on project of object as well as parent projects in hierarchy. Signed-off-by: Jitendra Patidar <[email protected]>
1 parent b6916f9 commit 1ed2a49

File tree

21 files changed

+1631
-6
lines changed

21 files changed

+1631
-6
lines changed

cmd/zfs/zfs_main.c

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8863,13 +8863,14 @@ zfs_do_project(int argc, char **argv)
88638863
.zpc_newline = B_TRUE,
88648864
.zpc_recursive = B_FALSE,
88658865
.zpc_set_flag = B_FALSE,
8866+
.zpc_add_hierarchy = B_FALSE,
88668867
};
88678868
int ret = 0, c;
88688869

88698870
if (argc < 2)
88708871
usage(B_FALSE);
88718872

8872-
while ((c = getopt(argc, argv, "0Ccdkp:rs")) != -1) {
8873+
while ((c = getopt(argc, argv, "0Ccdkp:rsY")) != -1) {
88738874
switch (c) {
88748875
case '0':
88758876
zpc.zpc_newline = B_FALSE;
@@ -8933,6 +8934,12 @@ zfs_do_project(int argc, char **argv)
89338934
zpc.zpc_set_flag = B_TRUE;
89348935
zpc.zpc_op = ZFS_PROJECT_OP_SET;
89358936
break;
8937+
case 'Y':
8938+
zpc.zpc_add_hierarchy = B_TRUE;
8939+
zpc.zpc_set_flag = B_TRUE;
8940+
zpc.zpc_recursive = B_TRUE;
8941+
zpc.zpc_dironly = B_FALSE;
8942+
break;
89368943
default:
89378944
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
89388945
optopt);
@@ -8959,13 +8966,25 @@ zfs_do_project(int argc, char **argv)
89598966
gettext("'-0' is only valid together with '-c'\n"));
89608967
usage(B_FALSE);
89618968
}
8969+
if (zpc.zpc_add_hierarchy) {
8970+
(void) fprintf(stderr,
8971+
gettext("'-Y' is only valid with set project "
8972+
"id\n"));
8973+
usage(B_FALSE);
8974+
}
89628975
break;
89638976
case ZFS_PROJECT_OP_CHECK:
89648977
if (zpc.zpc_keep_projid) {
89658978
(void) fprintf(stderr,
89668979
gettext("'-k' is only valid together with '-C'\n"));
89678980
usage(B_FALSE);
89688981
}
8982+
if (zpc.zpc_add_hierarchy) {
8983+
(void) fprintf(stderr,
8984+
gettext("'-Y' is only valid with set project "
8985+
"id\n"));
8986+
usage(B_FALSE);
8987+
}
89698988
break;
89708989
case ZFS_PROJECT_OP_CLEAR:
89718990
if (zpc.zpc_dironly) {
@@ -8983,6 +9002,12 @@ zfs_do_project(int argc, char **argv)
89839002
gettext("'-p' is useless together with '-C'\n"));
89849003
usage(B_FALSE);
89859004
}
9005+
if (zpc.zpc_add_hierarchy) {
9006+
(void) fprintf(stderr,
9007+
gettext("'-Y' is only valid with set project "
9008+
"id\n"));
9009+
usage(B_FALSE);
9010+
}
89869011
break;
89879012
case ZFS_PROJECT_OP_SET:
89889013
if (zpc.zpc_dironly) {
@@ -9001,6 +9026,13 @@ zfs_do_project(int argc, char **argv)
90019026
gettext("'-0' is only valid together with '-c'\n"));
90029027
usage(B_FALSE);
90039028
}
9029+
if (zpc.zpc_add_hierarchy && zpc.zpc_expected_projid ==
9030+
ZFS_INVALID_PROJID) {
9031+
(void) fprintf(stderr,
9032+
gettext("'-p' is needed for add hierarchy "
9033+
"option '-Y'\n"));
9034+
usage(B_FALSE);
9035+
}
90049036
break;
90059037
default:
90069038
ASSERT(0);

cmd/zfs/zfs_project.c

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ zfs_project_sanity_check(const char *name, zfs_project_control_t *zpc,
8888
"'-r' option on non-dir target %s\n"), name);
8989
return (-1);
9090
}
91+
if (zpc->zpc_add_hierarchy) {
92+
(void) fprintf(stderr, gettext(
93+
"'-Y' option on non-dir target %s\n"), name);
94+
return (-1);
95+
}
9196
}
9297

9398
return (0);
@@ -118,6 +123,30 @@ zfs_project_load_projid(const char *name, zfs_project_control_t *zpc)
118123
return (ret);
119124
}
120125

126+
static int
127+
zfs_project_add_hierarchy(const char *name, zfs_project_control_t *zpc)
128+
{
129+
project_hierarchy_arg_t pharg;
130+
int ret, fd;
131+
132+
fd = open(name, O_RDONLY | O_NOCTTY);
133+
if (fd < 0) {
134+
(void) fprintf(stderr, gettext("failed to open %s: %s\n"),
135+
name, strerror(errno));
136+
return (fd);
137+
}
138+
pharg.pha_projid = zpc->zpc_expected_projid;
139+
140+
ret = ioctl(fd, FS_IOC_ADD_PROJECT_HIERARCHY, &pharg);
141+
if (ret)
142+
(void) fprintf(stderr,
143+
gettext("failed to add project hierarchy for %s: %s\n"),
144+
name, strerror(errno));
145+
146+
close(fd);
147+
return (ret);
148+
}
149+
121150
static int
122151
zfs_project_handle_one(const char *name, zfs_project_control_t *zpc)
123152
{
@@ -194,10 +223,12 @@ zfs_project_handle_one(const char *name, zfs_project_control_t *zpc)
194223
}
195224

196225
ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx);
197-
if (ret)
226+
if (ret && errno != EXDEV) {
198227
(void) fprintf(stderr,
199228
gettext("failed to set xattr for %s: %s\n"),
200229
name, strerror(errno));
230+
}
231+
201232

202233
out:
203234
close(fd);
@@ -247,11 +278,13 @@ zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc,
247278
ret = zfs_project_handle_one(fullname, zpc);
248279
if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR)
249280
zfs_project_item_alloc(head, fullname);
281+
if (ret && errno == EXDEV)
282+
ret = 0;
250283

251284
free(fullname);
252285
}
253286

254-
if (errno && !ret) {
287+
if (errno && !ret && errno != EXDEV) {
255288
ret = -errno;
256289
(void) fprintf(stderr, gettext("failed to readdir %s: %s\n"),
257290
name, strerror(errno));
@@ -273,6 +306,11 @@ zfs_project_handle(const char *name, zfs_project_control_t *zpc)
273306
if (ret)
274307
return (ret);
275308

309+
if (zpc->zpc_op == ZFS_PROJECT_OP_SET && zpc->zpc_add_hierarchy) {
310+
ret = zfs_project_add_hierarchy(name, zpc);
311+
if (ret)
312+
return (ret);
313+
}
276314
if ((zpc->zpc_op == ZFS_PROJECT_OP_SET ||
277315
zpc->zpc_op == ZFS_PROJECT_OP_CHECK) &&
278316
zpc->zpc_expected_projid == ZFS_INVALID_PROJID) {
@@ -286,15 +324,20 @@ zfs_project_handle(const char *name, zfs_project_control_t *zpc)
286324
if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly ||
287325
(!zpc->zpc_recursive &&
288326
zpc->zpc_op != ZFS_PROJECT_OP_LIST &&
289-
zpc->zpc_op != ZFS_PROJECT_OP_CHECK))
327+
zpc->zpc_op != ZFS_PROJECT_OP_CHECK)) {
328+
if (ret && errno == EXDEV)
329+
ret = 0;
290330
return (ret);
331+
}
291332

292333
list_create(&head, sizeof (zfs_project_item_t),
293334
offsetof(zfs_project_item_t, zpi_list));
294335
zfs_project_item_alloc(&head, name);
295336
while ((zpi = list_remove_head(&head)) != NULL) {
296337
if (!ret)
297338
ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head);
339+
if (ret && errno == EXDEV)
340+
ret = 0;
298341
free(zpi);
299342
}
300343

cmd/zfs/zfs_projectutil.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ typedef struct zfs_project_control {
4343
boolean_t zpc_newline;
4444
boolean_t zpc_recursive;
4545
boolean_t zpc_set_flag;
46+
boolean_t zpc_add_hierarchy;
4647
} zfs_project_control_t;
4748

4849
int zfs_project_handle(const char *name, zfs_project_control_t *zpc);

include/os/freebsd/zfs/sys/zfs_vfsops_os.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct zfsvfs {
9595
uint64_t z_userobjquota_obj;
9696
uint64_t z_groupobjquota_obj;
9797
uint64_t z_projectquota_obj;
98+
uint64_t z_projecthierarchy_obj;
9899
uint64_t z_projectobjquota_obj;
99100
uint64_t z_defaultuserquota;
100101
uint64_t z_defaultgroupquota;

include/os/linux/zfs/sys/zfs_vfsops_os.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ struct zfsvfs {
130130
uint64_t z_userobjquota_obj;
131131
uint64_t z_groupobjquota_obj;
132132
uint64_t z_projectquota_obj;
133+
uint64_t z_projecthierarchy_obj;
133134
uint64_t z_projectobjquota_obj;
134135
uint64_t z_defaultuserquota;
135136
uint64_t z_defaultgroupquota;

include/os/linux/zfs/sys/zfs_vnops_os.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ extern int zfs_getattr_fast(zidmap_t *, struct inode *ip, struct kstat *sp);
6666
#endif
6767
extern int zfs_setattr(znode_t *zp, vattr_t *vap, int flag, cred_t *cr,
6868
zidmap_t *mnt_ns);
69+
extern int zfs_setattr_xattr_dir(znode_t *zp);
6970
extern int zfs_rename(znode_t *sdzp, char *snm, znode_t *tdzp,
7071
char *tnm, cred_t *cr, int flags, uint64_t rflags, vattr_t *wo_vap,
7172
zidmap_t *mnt_ns);

include/sys/dmu_objset.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ struct objset {
200200
dmu_objset_upgrade_cb_t os_upgrade_cb;
201201
boolean_t os_upgrade_exit;
202202
int os_upgrade_status;
203+
204+
kmutex_t os_projecthierarchyused_lock;
205+
kmutex_t os_projecthierarchyop_lock;
206+
207+
avl_tree_t os_project_deltas[TXG_SIZE];
208+
uint64_t os_projecthierarchy_obj;
203209
};
204210

205211
#define DMU_META_OBJSET 0
@@ -268,6 +274,8 @@ int dmu_fsname(const char *snapname, char *buf);
268274

269275
void dmu_objset_evict_done(objset_t *os);
270276
void dmu_objset_willuse_space(objset_t *os, int64_t space, dmu_tx_t *tx);
277+
void do_projectusage_update(objset_t *os, uint64_t projid, int64_t used,
278+
dmu_tx_t *tx);
271279

272280
void dmu_objset_init(void);
273281
void dmu_objset_fini(void);

include/sys/fs/zfs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,6 +1993,11 @@ enum zio_encrypt {
19931993
ZFS_XA_NS_PREFIX_MATCH(LINUX_TRUSTED, name) || \
19941994
ZFS_XA_NS_PREFIX_MATCH(LINUX_USER, name))
19951995

1996+
typedef struct project_hierarchy_arg {
1997+
uint64_t pha_projid;
1998+
} project_hierarchy_arg_t;
1999+
#define FS_IOC_ADD_PROJECT_HIERARCHY _IOW('t', 9, project_hierarchy_arg_t)
2000+
19962001
#ifdef __cplusplus
19972002
}
19982003
#endif

include/sys/zfs_quota.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ extern int zfs_userspace_many(struct zfsvfs *, zfs_userquota_prop_t,
3838
uint64_t *, void *, uint64_t *, uint64_t *);
3939
extern int zfs_set_userquota(struct zfsvfs *, zfs_userquota_prop_t,
4040
const char *, uint64_t, uint64_t);
41+
extern int zfs_project_hierarchy_add(struct zfsvfs *, uint64_t, uint64_t);
42+
extern int zfs_project_hierarchy_remove(struct zfsvfs *, uint64_t, uint64_t,
43+
boolean_t);
44+
extern int zfs_projects_are_hierarchical(struct zfsvfs *, uint64_t, uint64_t,
45+
boolean_t *);
4146

4247
extern boolean_t zfs_id_overobjquota(struct zfsvfs *, uint64_t, uint64_t);
4348
extern boolean_t zfs_id_overblockquota(struct zfsvfs *, uint64_t, uint64_t);

include/sys/zfs_znode.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ extern "C" {
140140
#define ZFS_FUID_TABLES "FUID"
141141
#define ZFS_SHARES_DIR "SHARES"
142142
#define ZFS_SA_ATTRS "SA_ATTRS"
143+
#define ZFS_PROJECT_HIERARCHY "PROJECT_HIERARCHY"
143144

144145
/*
145146
* Convert mode bits (zp_mode) to BSD-style DT_* values for storing in

module/os/freebsd/zfs/zfs_vfsops.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,13 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
938938
else if (error != 0)
939939
return (error);
940940

941+
error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_PROJECT_HIERARCHY,
942+
8, 1, &zfsvfs->z_projecthierarchy_obj);
943+
if (error == ENOENT)
944+
zfsvfs->z_projecthierarchy_obj = 0;
945+
else if (error != 0)
946+
return (error);
947+
941948
error = zap_lookup(os, MASTER_NODE_OBJ,
942949
zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA],
943950
8, 1, &zfsvfs->z_userobjquota_obj);

module/os/linux/zfs/zfs_vfsops.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,14 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
762762
else if (error != 0)
763763
return (error);
764764

765+
error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_PROJECT_HIERARCHY,
766+
8, 1, &zfsvfs->z_projecthierarchy_obj);
767+
if (error == ENOENT)
768+
zfsvfs->z_projecthierarchy_obj = 0;
769+
else if (error != 0)
770+
return (error);
771+
zfsvfs->z_os->os_projecthierarchy_obj = zfsvfs->z_projecthierarchy_obj;
772+
765773
error = zap_lookup(os, MASTER_NODE_OBJ,
766774
zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA],
767775
8, 1, &zfsvfs->z_userobjquota_obj);

module/os/linux/zfs/zfs_vnops_os.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2640,6 +2640,50 @@ zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, zidmap_t *mnt_ns)
26402640
return (err);
26412641
}
26422642

2643+
/*
2644+
* This function sets projid of zp on its xattr dir and each xattr file
2645+
*/
2646+
int
2647+
zfs_setattr_xattr_dir(znode_t *zp)
2648+
{
2649+
zfsvfs_t *zfsvfs = ZTOZSB(zp);
2650+
znode_t *xzp = NULL;
2651+
uint64_t xattr_obj = 0;
2652+
xoptattr_t *xoap;
2653+
xvattr_t xva;
2654+
int err;
2655+
2656+
err = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs),
2657+
&xattr_obj, sizeof (xattr_obj));
2658+
if (err || !xattr_obj)
2659+
return (err);
2660+
2661+
err = zfs_zget(zfsvfs, xattr_obj, &xzp);
2662+
ASSERT(err == 0);
2663+
xva_init(&xva);
2664+
xoap = xva_getxoptattr(&xva);
2665+
XVA_SET_REQ(&xva, XAT_PROJID);
2666+
xoap->xoa_projid = zp->z_projid;
2667+
XVA_SET_REQ(&xva, XAT_PROJINHERIT);
2668+
xoap->xoa_projinherit = 1;
2669+
2670+
cred_t *cr = CRED();
2671+
crhold(cr);
2672+
err = zfs_setattr(xzp, (vattr_t *)&xva, 0, cr, zfs_init_idmap);
2673+
crfree(cr);
2674+
if (err) {
2675+
zrele(xzp);
2676+
return (err);
2677+
}
2678+
err = zfs_setattr_dir(xzp);
2679+
if (err) {
2680+
zrele(xzp);
2681+
return (err);
2682+
}
2683+
zrele(xzp);
2684+
return (0);
2685+
}
2686+
26432687
typedef struct zfs_zlock {
26442688
krwlock_t *zl_rwlock; /* lock we acquired */
26452689
znode_t *zl_znode; /* znode we held */

0 commit comments

Comments
 (0)