shmem: Add default quota limit mount options

Allow system administrator to set default global quota limits at tmpfs
mount time.

Signed-off-by: Lukas Czerner <lczerner@redhat.com>
Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Message-Id: <20230725144510.253763-7-cem@kernel.org>
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Lukas Czerner 2023-07-25 16:45:09 +02:00 committed by Christian Brauner
parent e09764cff4
commit de4c0e7ca8
4 changed files with 127 additions and 10 deletions

View File

@ -125,15 +125,31 @@ force huge pages on all tmpfs mounts for testing.
tmpfs also supports quota with the following mount options
======== =============================================================
quota User and group quota accounting and enforcement is enabled on
the mount. Tmpfs is using hidden system quota files that are
initialized on mount.
usrquota User quota accounting and enforcement is enabled on the
mount.
grpquota Group quota accounting and enforcement is enabled on the
mount.
======== =============================================================
======================== =================================================
quota User and group quota accounting and enforcement
is enabled on the mount. Tmpfs is using hidden
system quota files that are initialized on mount.
usrquota User quota accounting and enforcement is enabled
on the mount.
grpquota Group quota accounting and enforcement is enabled
on the mount.
usrquota_block_hardlimit Set global user quota block hard limit.
usrquota_inode_hardlimit Set global user quota inode hard limit.
grpquota_block_hardlimit Set global group quota block hard limit.
grpquota_inode_hardlimit Set global group quota inode hard limit.
======================== =================================================
None of the quota related mount options can be set or changed on remount.
Quota limit parameters accept a suffix k, m or g for kilo, mega and giga
and can't be changed on remount. Default global quota limits are taking
effect for any and all user/group/project except root the first time the
quota entry for user/group/project id is being accessed - typically the
first time an inode with a particular id ownership is being created after
the mount. In other words, instead of the limits being initialized to zero,
they are initialized with the particular value provided with these mount
options. The limits can be changed for any user/group id at any time as they
normally can be.
Note that tmpfs quotas do not support user namespaces so no uid/gid
translation is done if quotas are enabled inside user namespaces.

View File

@ -42,6 +42,13 @@ struct shmem_inode_info {
(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL | FS_NOATIME_FL)
#define SHMEM_FL_INHERITED (FS_NODUMP_FL | FS_NOATIME_FL)
struct shmem_quota_limits {
qsize_t usrquota_bhardlimit; /* Default user quota block hard limit */
qsize_t usrquota_ihardlimit; /* Default user quota inode hard limit */
qsize_t grpquota_bhardlimit; /* Default group quota block hard limit */
qsize_t grpquota_ihardlimit; /* Default group quota inode hard limit */
};
struct shmem_sb_info {
unsigned long max_blocks; /* How many blocks are allowed */
struct percpu_counter used_blocks; /* How many are allocated */
@ -60,6 +67,7 @@ struct shmem_sb_info {
spinlock_t shrinklist_lock; /* Protects shrinklist */
struct list_head shrinklist; /* List of shinkable inodes */
unsigned long shrinklist_len; /* Length of shrinklist */
struct shmem_quota_limits qlimits; /* Default quota limits */
};
static inline struct shmem_inode_info *SHMEM_I(struct inode *inode)

View File

@ -118,6 +118,7 @@ struct shmem_options {
int seen;
bool noswap;
unsigned short quota_types;
struct shmem_quota_limits qlimits;
#define SHMEM_SEEN_BLOCKS 1
#define SHMEM_SEEN_INODES 2
#define SHMEM_SEEN_HUGE 4
@ -3738,6 +3739,10 @@ enum shmem_param {
Opt_quota,
Opt_usrquota,
Opt_grpquota,
Opt_usrquota_block_hardlimit,
Opt_usrquota_inode_hardlimit,
Opt_grpquota_block_hardlimit,
Opt_grpquota_inode_hardlimit,
};
static const struct constant_table shmem_param_enums_huge[] = {
@ -3764,6 +3769,10 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
fsparam_flag ("quota", Opt_quota),
fsparam_flag ("usrquota", Opt_usrquota),
fsparam_flag ("grpquota", Opt_grpquota),
fsparam_string("usrquota_block_hardlimit", Opt_usrquota_block_hardlimit),
fsparam_string("usrquota_inode_hardlimit", Opt_usrquota_inode_hardlimit),
fsparam_string("grpquota_block_hardlimit", Opt_grpquota_block_hardlimit),
fsparam_string("grpquota_inode_hardlimit", Opt_grpquota_inode_hardlimit),
#endif
{}
};
@ -3874,6 +3883,42 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
ctx->seen |= SHMEM_SEEN_QUOTA;
ctx->quota_types |= QTYPE_MASK_GRP;
break;
case Opt_usrquota_block_hardlimit:
size = memparse(param->string, &rest);
if (*rest || !size)
goto bad_value;
if (size > SHMEM_QUOTA_MAX_SPC_LIMIT)
return invalfc(fc,
"User quota block hardlimit too large.");
ctx->qlimits.usrquota_bhardlimit = size;
break;
case Opt_grpquota_block_hardlimit:
size = memparse(param->string, &rest);
if (*rest || !size)
goto bad_value;
if (size > SHMEM_QUOTA_MAX_SPC_LIMIT)
return invalfc(fc,
"Group quota block hardlimit too large.");
ctx->qlimits.grpquota_bhardlimit = size;
break;
case Opt_usrquota_inode_hardlimit:
size = memparse(param->string, &rest);
if (*rest || !size)
goto bad_value;
if (size > SHMEM_QUOTA_MAX_INO_LIMIT)
return invalfc(fc,
"User quota inode hardlimit too large.");
ctx->qlimits.usrquota_ihardlimit = size;
break;
case Opt_grpquota_inode_hardlimit:
size = memparse(param->string, &rest);
if (*rest || !size)
goto bad_value;
if (size > SHMEM_QUOTA_MAX_INO_LIMIT)
return invalfc(fc,
"Group quota inode hardlimit too large.");
ctx->qlimits.grpquota_ihardlimit = size;
break;
}
return 0;
@ -3987,6 +4032,18 @@ static int shmem_reconfigure(struct fs_context *fc)
goto out;
}
#ifdef CONFIG_TMPFS_QUOTA
#define CHANGED_LIMIT(name) \
(ctx->qlimits.name## hardlimit && \
(ctx->qlimits.name## hardlimit != sbinfo->qlimits.name## hardlimit))
if (CHANGED_LIMIT(usrquota_b) || CHANGED_LIMIT(usrquota_i) ||
CHANGED_LIMIT(grpquota_b) || CHANGED_LIMIT(grpquota_i)) {
err = "Cannot change global quota limit on remount";
goto out;
}
#endif /* CONFIG_TMPFS_QUOTA */
if (ctx->seen & SHMEM_SEEN_HUGE)
sbinfo->huge = ctx->huge;
if (ctx->seen & SHMEM_SEEN_INUMS)
@ -4166,6 +4223,10 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
sb->s_qcop = &dquot_quotactl_sysfile_ops;
sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
/* Copy the default limits from ctx into sbinfo */
memcpy(&sbinfo->qlimits, &ctx->qlimits,
sizeof(struct shmem_quota_limits));
if (shmem_enable_quotas(sb, ctx->quota_types))
goto failed;
}

View File

@ -166,6 +166,7 @@ static int shmem_acquire_dquot(struct dquot *dquot)
{
struct mem_dqinfo *info = sb_dqinfo(dquot->dq_sb, dquot->dq_id.type);
struct rb_node **n = &((struct rb_root *)info->dqi_priv)->rb_node;
struct shmem_sb_info *sbinfo = dquot->dq_sb->s_fs_info;
struct rb_node *parent = NULL, *new_node = NULL;
struct quota_id *new_entry, *entry;
qid_t id = from_kqid(&init_user_ns, dquot->dq_id);
@ -195,6 +196,14 @@ static int shmem_acquire_dquot(struct dquot *dquot)
}
new_entry->id = id;
if (dquot->dq_id.type == USRQUOTA) {
new_entry->bhardlimit = sbinfo->qlimits.usrquota_bhardlimit;
new_entry->ihardlimit = sbinfo->qlimits.usrquota_ihardlimit;
} else if (dquot->dq_id.type == GRPQUOTA) {
new_entry->bhardlimit = sbinfo->qlimits.grpquota_bhardlimit;
new_entry->ihardlimit = sbinfo->qlimits.grpquota_ihardlimit;
}
new_node = &new_entry->node;
rb_link_node(new_node, parent, n);
rb_insert_color(new_node, (struct rb_root *)info->dqi_priv);
@ -224,6 +233,29 @@ static int shmem_acquire_dquot(struct dquot *dquot)
return ret;
}
static bool shmem_is_empty_dquot(struct dquot *dquot)
{
struct shmem_sb_info *sbinfo = dquot->dq_sb->s_fs_info;
qsize_t bhardlimit;
qsize_t ihardlimit;
if (dquot->dq_id.type == USRQUOTA) {
bhardlimit = sbinfo->qlimits.usrquota_bhardlimit;
ihardlimit = sbinfo->qlimits.usrquota_ihardlimit;
} else if (dquot->dq_id.type == GRPQUOTA) {
bhardlimit = sbinfo->qlimits.grpquota_bhardlimit;
ihardlimit = sbinfo->qlimits.grpquota_ihardlimit;
}
if (test_bit(DQ_FAKE_B, &dquot->dq_flags) ||
(dquot->dq_dqb.dqb_curspace == 0 &&
dquot->dq_dqb.dqb_curinodes == 0 &&
dquot->dq_dqb.dqb_bhardlimit == bhardlimit &&
dquot->dq_dqb.dqb_ihardlimit == ihardlimit))
return true;
return false;
}
/*
* Store limits from dquot in the tree unless it's fake. If it is fake
* remove the id from the tree since there is no useful information in
@ -261,7 +293,7 @@ static int shmem_release_dquot(struct dquot *dquot)
return -ENOENT;
found:
if (test_bit(DQ_FAKE_B, &dquot->dq_flags)) {
if (shmem_is_empty_dquot(dquot)) {
/* Remove entry from the tree */
rb_erase(&entry->node, info->dqi_priv);
kfree(entry);