Implement default user and group quotas
This change adds the 'defaultuserquota' and 'defaultgroupquota' properties to ZFS datasets to apply a quota to users and groups that do not have a specific quota assigned. The default quota checking mechanism works alongside the existing 'userquota' and 'groupquota' checks, only taking effect if no quota is assigned for a particular user/group. This means that it's possible to exceed a default quota by quite a lot before the user/groupused property is updated and further writes are denied, which was already the case for user/groupquota. Default quotas are implemented similarly to 'normal' user quotas, but instead of being user properties that are preserved across snapshots, they default back to none. NB: this is different from the observed Solaris behavior, which is to preserve default quotas across snapshot/clone/promote. For instance, Solaris has: # zfs set defaultuserquota=100M tank/fs # zfs snap tank/fs@snap # zfs clone tank/fs@snap tank/fs-clone # zfs get -H defaultuserquota tank/fs-clone tank/fs-clone defaultuserquota 100M - Whereas this commit does: # zfs set defaultuserquota=100M tank/fs # zfs snap tank/fs@snap # zfs clone tank/fs@snap tank/fs-clone # zfs get -H defaultuserquota tank/fs-clone tank/fs-clone defaultuserquota none default It should also be possible to implement a default project quota using an analogous process, if doing so makes sense. Signed-off-by: Todd Seidelmann <seidelma@wharton.upenn.edu>
This commit is contained in:
parent
c8184d714b
commit
03006174b7
|
@ -585,6 +585,10 @@ usage(boolean_t requested)
|
||||||
(void) fprintf(fp, "YES NO <size> | none\n");
|
(void) fprintf(fp, "YES NO <size> | none\n");
|
||||||
(void) fprintf(fp, "\t%-15s ", "projectobjquota@...");
|
(void) fprintf(fp, "\t%-15s ", "projectobjquota@...");
|
||||||
(void) fprintf(fp, "YES NO <size> | none\n");
|
(void) fprintf(fp, "YES NO <size> | none\n");
|
||||||
|
(void) fprintf(fp, "\t%-15s ", "defaultuserquota");
|
||||||
|
(void) fprintf(fp, "YES NO <size> | none\n");
|
||||||
|
(void) fprintf(fp, "\t%-15s ", "defaultgroupquota");
|
||||||
|
(void) fprintf(fp, "YES NO <size> | none\n");
|
||||||
(void) fprintf(fp, "\t%-15s ", "written@<snap>");
|
(void) fprintf(fp, "\t%-15s ", "written@<snap>");
|
||||||
(void) fprintf(fp, " NO NO <size>\n");
|
(void) fprintf(fp, " NO NO <size>\n");
|
||||||
(void) fprintf(fp, "\t%-15s ", "written#<bookmark>");
|
(void) fprintf(fp, "\t%-15s ", "written#<bookmark>");
|
||||||
|
|
|
@ -128,6 +128,8 @@ struct zfsvfs {
|
||||||
uint64_t z_groupobjquota_obj;
|
uint64_t z_groupobjquota_obj;
|
||||||
uint64_t z_projectquota_obj;
|
uint64_t z_projectquota_obj;
|
||||||
uint64_t z_projectobjquota_obj;
|
uint64_t z_projectobjquota_obj;
|
||||||
|
uint64_t z_defaultuserquota_obj;
|
||||||
|
uint64_t z_defaultgroupquota_obj;
|
||||||
uint64_t z_replay_eof; /* New end of file - replay only */
|
uint64_t z_replay_eof; /* New end of file - replay only */
|
||||||
sa_attr_type_t *z_attr_table; /* SA attr mapping->id */
|
sa_attr_type_t *z_attr_table; /* SA attr mapping->id */
|
||||||
uint64_t z_hold_size; /* znode hold array size */
|
uint64_t z_hold_size; /* znode hold array size */
|
||||||
|
|
|
@ -193,6 +193,8 @@ typedef enum {
|
||||||
ZFS_PROP_SNAPSHOTS_CHANGED,
|
ZFS_PROP_SNAPSHOTS_CHANGED,
|
||||||
ZFS_PROP_PREFETCH,
|
ZFS_PROP_PREFETCH,
|
||||||
ZFS_PROP_VOLTHREADING,
|
ZFS_PROP_VOLTHREADING,
|
||||||
|
ZFS_PROP_DEFAULTUSERQUOTA,
|
||||||
|
ZFS_PROP_DEFAULTGROUPQUOTA,
|
||||||
ZFS_NUM_PROPS
|
ZFS_NUM_PROPS
|
||||||
} zfs_prop_t;
|
} zfs_prop_t;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ extern int zfs_userspace_many(struct zfsvfs *, zfs_userquota_prop_t,
|
||||||
uint64_t *, void *, uint64_t *);
|
uint64_t *, void *, uint64_t *);
|
||||||
extern int zfs_set_userquota(struct zfsvfs *, zfs_userquota_prop_t,
|
extern int zfs_set_userquota(struct zfsvfs *, zfs_userquota_prop_t,
|
||||||
const char *, uint64_t, uint64_t);
|
const char *, uint64_t, uint64_t);
|
||||||
|
extern int zfs_set_defaultquota(struct zfsvfs *, int, uint64_t);
|
||||||
|
|
||||||
extern boolean_t zfs_id_overobjquota(struct zfsvfs *, uint64_t, uint64_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);
|
extern boolean_t zfs_id_overblockquota(struct zfsvfs *, uint64_t, uint64_t);
|
||||||
|
|
|
@ -2811,6 +2811,8 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
|
||||||
case ZFS_PROP_REFQUOTA:
|
case ZFS_PROP_REFQUOTA:
|
||||||
case ZFS_PROP_RESERVATION:
|
case ZFS_PROP_RESERVATION:
|
||||||
case ZFS_PROP_REFRESERVATION:
|
case ZFS_PROP_REFRESERVATION:
|
||||||
|
case ZFS_PROP_DEFAULTUSERQUOTA:
|
||||||
|
case ZFS_PROP_DEFAULTGROUPQUOTA:
|
||||||
|
|
||||||
if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
|
if (get_numeric_property(zhp, prop, src, &source, &val) != 0)
|
||||||
return (-1);
|
return (-1);
|
||||||
|
|
|
@ -743,6 +743,22 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
|
||||||
else if (error != 0)
|
else if (error != 0)
|
||||||
return (error);
|
return (error);
|
||||||
|
|
||||||
|
error = zap_lookup(os, MASTER_NODE_OBJ,
|
||||||
|
zfs_prop_to_name(ZFS_PROP_DEFAULTUSERQUOTA),
|
||||||
|
8, 1, &zfsvfs->z_defaultuserquota_obj);
|
||||||
|
if (error == ENOENT)
|
||||||
|
zfsvfs->z_defaultuserquota_obj = 0;
|
||||||
|
else if (error != 0)
|
||||||
|
return (error);
|
||||||
|
|
||||||
|
error = zap_lookup(os, MASTER_NODE_OBJ,
|
||||||
|
zfs_prop_to_name(ZFS_PROP_DEFAULTGROUPQUOTA),
|
||||||
|
8, 1, &zfsvfs->z_defaultgroupquota_obj);
|
||||||
|
if (error == ENOENT)
|
||||||
|
zfsvfs->z_defaultgroupquota_obj = 0;
|
||||||
|
else if (error != 0)
|
||||||
|
return (error);
|
||||||
|
|
||||||
error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_FUID_TABLES, 8, 1,
|
error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_FUID_TABLES, 8, 1,
|
||||||
&zfsvfs->z_fuid_obj);
|
&zfsvfs->z_fuid_obj);
|
||||||
if (error == ENOENT)
|
if (error == ENOENT)
|
||||||
|
|
|
@ -694,6 +694,12 @@ zfs_prop_init(void)
|
||||||
zprop_register_number(ZFS_PROP_SNAPSHOT_LIMIT, "snapshot_limit",
|
zprop_register_number(ZFS_PROP_SNAPSHOT_LIMIT, "snapshot_limit",
|
||||||
UINT64_MAX, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
|
UINT64_MAX, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME,
|
||||||
"<count> | none", "SSLIMIT", B_FALSE, sfeatures);
|
"<count> | none", "SSLIMIT", B_FALSE, sfeatures);
|
||||||
|
zprop_register_number(ZFS_PROP_DEFAULTUSERQUOTA, "defaultuserquota", 0,
|
||||||
|
PROP_DEFAULT, ZFS_TYPE_FILESYSTEM, "<size> | none",
|
||||||
|
"DEFAULTUSERQUOTA", B_FALSE, sfeatures);
|
||||||
|
zprop_register_number(ZFS_PROP_DEFAULTGROUPQUOTA, "defaultgroupquota",
|
||||||
|
0, PROP_DEFAULT, ZFS_TYPE_FILESYSTEM, "<size> | none",
|
||||||
|
"DEFAULTGROUPQUOTA", B_FALSE, sfeatures);
|
||||||
|
|
||||||
/* inherit number properties */
|
/* inherit number properties */
|
||||||
zprop_register_number(ZFS_PROP_RECORDSIZE, "recordsize",
|
zprop_register_number(ZFS_PROP_RECORDSIZE, "recordsize",
|
||||||
|
|
|
@ -2567,6 +2567,25 @@ zfs_prop_set_special(const char *dsname, zprop_source_t source,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ZFS_PROP_DEFAULTUSERQUOTA:
|
||||||
|
case ZFS_PROP_DEFAULTGROUPQUOTA:
|
||||||
|
{
|
||||||
|
zfsvfs_t *zfsvfs;
|
||||||
|
|
||||||
|
if ((err = zfsvfs_hold(dsname, FTAG, &zfsvfs, B_TRUE)) != 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
err = zfs_set_defaultquota(zfsvfs, prop, intval);
|
||||||
|
zfsvfs_rele(zfsvfs, FTAG);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set err to -1 to force the zfs_set_prop_nvlist code down the
|
||||||
|
* default path to set the value in the nvlist.
|
||||||
|
*/
|
||||||
|
if (err == 0)
|
||||||
|
err = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = -1;
|
err = -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,6 +366,59 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
|
||||||
return (err);
|
return (err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
zfs_set_defaultquota(zfsvfs_t *zfsvfs, int type, uint64_t quota)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
dmu_tx_t *tx;
|
||||||
|
uint64_t *objp;
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
if (type == ZFS_PROP_DEFAULTUSERQUOTA) {
|
||||||
|
objp = &zfsvfs->z_defaultuserquota_obj;
|
||||||
|
name = zfs_prop_to_name(ZFS_PROP_DEFAULTUSERQUOTA);
|
||||||
|
} else if (type == ZFS_PROP_DEFAULTGROUPQUOTA) {
|
||||||
|
objp = &zfsvfs->z_defaultgroupquota_obj;
|
||||||
|
name = zfs_prop_to_name(ZFS_PROP_DEFAULTGROUPQUOTA);
|
||||||
|
} else {
|
||||||
|
/* defaultprojectquota NYI (does it make sense to do so?) */
|
||||||
|
return (SET_ERROR(EINVAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
tx = dmu_tx_create(zfsvfs->z_os);
|
||||||
|
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, name);
|
||||||
|
}
|
||||||
|
err = dmu_tx_assign(tx, TXG_WAIT);
|
||||||
|
if (err) {
|
||||||
|
dmu_tx_abort(tx);
|
||||||
|
return (err);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, name,
|
||||||
|
8, 1, objp, tx));
|
||||||
|
}
|
||||||
|
mutex_exit(&zfsvfs->z_lock);
|
||||||
|
|
||||||
|
if (quota == 0) {
|
||||||
|
err = zap_remove(zfsvfs->z_os, *objp, name, tx);
|
||||||
|
if (err == ENOENT)
|
||||||
|
err = 0;
|
||||||
|
} else {
|
||||||
|
err = zap_update(zfsvfs->z_os, *objp, name,
|
||||||
|
8, 1, "a, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(err == 0);
|
||||||
|
dmu_tx_commit(tx);
|
||||||
|
return (err);
|
||||||
|
}
|
||||||
|
|
||||||
boolean_t
|
boolean_t
|
||||||
zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
|
zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
|
||||||
{
|
{
|
||||||
|
@ -423,8 +476,9 @@ boolean_t
|
||||||
zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
|
zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
|
||||||
{
|
{
|
||||||
char buf[20];
|
char buf[20];
|
||||||
uint64_t used, quota, quotaobj;
|
uint64_t used, quota, quotaobj, defquota, defquotaobj;
|
||||||
int err;
|
int err, uerr, derr;
|
||||||
|
const char *name;
|
||||||
|
|
||||||
if (usedobj == DMU_PROJECTUSED_OBJECT) {
|
if (usedobj == DMU_PROJECTUSED_OBJECT) {
|
||||||
if (!dmu_objset_projectquota_present(zfsvfs->z_os)) {
|
if (!dmu_objset_projectquota_present(zfsvfs->z_os)) {
|
||||||
|
@ -440,22 +494,46 @@ zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
|
||||||
quotaobj = zfsvfs->z_projectquota_obj;
|
quotaobj = zfsvfs->z_projectquota_obj;
|
||||||
} else if (usedobj == DMU_USERUSED_OBJECT) {
|
} else if (usedobj == DMU_USERUSED_OBJECT) {
|
||||||
quotaobj = zfsvfs->z_userquota_obj;
|
quotaobj = zfsvfs->z_userquota_obj;
|
||||||
|
defquotaobj = zfsvfs->z_defaultuserquota_obj;
|
||||||
|
name = zfs_prop_to_name(ZFS_PROP_DEFAULTUSERQUOTA);
|
||||||
} else if (usedobj == DMU_GROUPUSED_OBJECT) {
|
} else if (usedobj == DMU_GROUPUSED_OBJECT) {
|
||||||
quotaobj = zfsvfs->z_groupquota_obj;
|
quotaobj = zfsvfs->z_groupquota_obj;
|
||||||
|
defquotaobj = zfsvfs->z_defaultgroupquota_obj;
|
||||||
|
name = zfs_prop_to_name(ZFS_PROP_DEFAULTGROUPQUOTA);
|
||||||
} else {
|
} else {
|
||||||
return (B_FALSE);
|
return (B_FALSE);
|
||||||
}
|
}
|
||||||
if (quotaobj == 0 || zfsvfs->z_replay)
|
|
||||||
|
/* no quota assigned */
|
||||||
|
if (quotaobj == 0 && defquotaobj == 0) {
|
||||||
return (B_FALSE);
|
return (B_FALSE);
|
||||||
|
}
|
||||||
|
if (zfsvfs->z_replay) {
|
||||||
|
return (B_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
(void) snprintf(buf, sizeof (buf), "%llx", (longlong_t)id);
|
(void) snprintf(buf, sizeof (buf), "%llx", (longlong_t)id);
|
||||||
err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, "a);
|
derr = zap_lookup(zfsvfs->z_os, defquotaobj, name,
|
||||||
if (err != 0)
|
8, 1, &defquota);
|
||||||
|
uerr = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, "a);
|
||||||
|
|
||||||
|
/* bail if both id-specific && default lookups failed */
|
||||||
|
if (uerr != 0 && derr != 0)
|
||||||
return (B_FALSE);
|
return (B_FALSE);
|
||||||
|
|
||||||
err = zap_lookup(zfsvfs->z_os, usedobj, buf, 8, 1, &used);
|
err = zap_lookup(zfsvfs->z_os, usedobj, buf, 8, 1, &used);
|
||||||
if (err != 0)
|
if (err != 0)
|
||||||
return (B_FALSE);
|
return (B_FALSE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if a user/group has a specific quota assigned, use that.
|
||||||
|
* if neither quota...we've already returned false (hopefully).
|
||||||
|
* if a default quota is set, but no user quota is,
|
||||||
|
* use the default.
|
||||||
|
*/
|
||||||
|
if (uerr != 0 && derr == 0 && defquota)
|
||||||
|
quota = defquota;
|
||||||
|
|
||||||
return (used >= quota);
|
return (used >= quota);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,6 +548,7 @@ EXPORT_SYMBOL(zpl_get_file_info);
|
||||||
EXPORT_SYMBOL(zfs_userspace_one);
|
EXPORT_SYMBOL(zfs_userspace_one);
|
||||||
EXPORT_SYMBOL(zfs_userspace_many);
|
EXPORT_SYMBOL(zfs_userspace_many);
|
||||||
EXPORT_SYMBOL(zfs_set_userquota);
|
EXPORT_SYMBOL(zfs_set_userquota);
|
||||||
|
EXPORT_SYMBOL(zfs_set_defaultquota);
|
||||||
EXPORT_SYMBOL(zfs_id_overblockquota);
|
EXPORT_SYMBOL(zfs_id_overblockquota);
|
||||||
EXPORT_SYMBOL(zfs_id_overobjquota);
|
EXPORT_SYMBOL(zfs_id_overobjquota);
|
||||||
EXPORT_SYMBOL(zfs_id_overquota);
|
EXPORT_SYMBOL(zfs_id_overquota);
|
||||||
|
|
Loading…
Reference in New Issue