Commit de4c0e7c authored by Lukas Czerner's avatar Lukas Czerner Committed by Christian Brauner

shmem: Add default quota limit mount options

Allow system administrator to set default global quota limits at tmpfs
mount time.
Signed-off-by: default avatarLukas Czerner <lczerner@redhat.com>
Signed-off-by: default avatarCarlos Maiolino <cmaiolino@redhat.com>
Reviewed-by: default avatarJan Kara <jack@suse.cz>
Message-Id: <20230725144510.253763-7-cem@kernel.org>
Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parent e09764cf
...@@ -125,15 +125,31 @@ force huge pages on all tmpfs mounts for testing. ...@@ -125,15 +125,31 @@ force huge pages on all tmpfs mounts for testing.
tmpfs also supports quota with the following mount options tmpfs also supports quota with the following mount options
======== ============================================================= ======================== =================================================
quota User and group quota accounting and enforcement is enabled on quota User and group quota accounting and enforcement
the mount. Tmpfs is using hidden system quota files that are is enabled on the mount. Tmpfs is using hidden
initialized on mount. system quota files that are initialized on mount.
usrquota User quota accounting and enforcement is enabled on the usrquota User quota accounting and enforcement is enabled
mount. on the mount.
grpquota Group quota accounting and enforcement is enabled on the grpquota Group quota accounting and enforcement is enabled
mount. 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 Note that tmpfs quotas do not support user namespaces so no uid/gid
translation is done if quotas are enabled inside user namespaces. translation is done if quotas are enabled inside user namespaces.
......
...@@ -42,6 +42,13 @@ struct shmem_inode_info { ...@@ -42,6 +42,13 @@ struct shmem_inode_info {
(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL | FS_NOATIME_FL) (FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL | FS_NOATIME_FL)
#define SHMEM_FL_INHERITED (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 { struct shmem_sb_info {
unsigned long max_blocks; /* How many blocks are allowed */ unsigned long max_blocks; /* How many blocks are allowed */
struct percpu_counter used_blocks; /* How many are allocated */ struct percpu_counter used_blocks; /* How many are allocated */
...@@ -60,6 +67,7 @@ struct shmem_sb_info { ...@@ -60,6 +67,7 @@ struct shmem_sb_info {
spinlock_t shrinklist_lock; /* Protects shrinklist */ spinlock_t shrinklist_lock; /* Protects shrinklist */
struct list_head shrinklist; /* List of shinkable inodes */ struct list_head shrinklist; /* List of shinkable inodes */
unsigned long shrinklist_len; /* Length of shrinklist */ 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) static inline struct shmem_inode_info *SHMEM_I(struct inode *inode)
......
...@@ -118,6 +118,7 @@ struct shmem_options { ...@@ -118,6 +118,7 @@ struct shmem_options {
int seen; int seen;
bool noswap; bool noswap;
unsigned short quota_types; unsigned short quota_types;
struct shmem_quota_limits qlimits;
#define SHMEM_SEEN_BLOCKS 1 #define SHMEM_SEEN_BLOCKS 1
#define SHMEM_SEEN_INODES 2 #define SHMEM_SEEN_INODES 2
#define SHMEM_SEEN_HUGE 4 #define SHMEM_SEEN_HUGE 4
...@@ -3738,6 +3739,10 @@ enum shmem_param { ...@@ -3738,6 +3739,10 @@ enum shmem_param {
Opt_quota, Opt_quota,
Opt_usrquota, Opt_usrquota,
Opt_grpquota, 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[] = { static const struct constant_table shmem_param_enums_huge[] = {
...@@ -3764,6 +3769,10 @@ const struct fs_parameter_spec shmem_fs_parameters[] = { ...@@ -3764,6 +3769,10 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
fsparam_flag ("quota", Opt_quota), fsparam_flag ("quota", Opt_quota),
fsparam_flag ("usrquota", Opt_usrquota), fsparam_flag ("usrquota", Opt_usrquota),
fsparam_flag ("grpquota", Opt_grpquota), 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 #endif
{} {}
}; };
...@@ -3874,6 +3883,42 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param) ...@@ -3874,6 +3883,42 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
ctx->seen |= SHMEM_SEEN_QUOTA; ctx->seen |= SHMEM_SEEN_QUOTA;
ctx->quota_types |= QTYPE_MASK_GRP; ctx->quota_types |= QTYPE_MASK_GRP;
break; 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; return 0;
...@@ -3987,6 +4032,18 @@ static int shmem_reconfigure(struct fs_context *fc) ...@@ -3987,6 +4032,18 @@ static int shmem_reconfigure(struct fs_context *fc)
goto out; 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) if (ctx->seen & SHMEM_SEEN_HUGE)
sbinfo->huge = ctx->huge; sbinfo->huge = ctx->huge;
if (ctx->seen & SHMEM_SEEN_INUMS) if (ctx->seen & SHMEM_SEEN_INUMS)
...@@ -4166,6 +4223,10 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc) ...@@ -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_qcop = &dquot_quotactl_sysfile_ops;
sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP; 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)) if (shmem_enable_quotas(sb, ctx->quota_types))
goto failed; goto failed;
} }
......
...@@ -166,6 +166,7 @@ static int shmem_acquire_dquot(struct dquot *dquot) ...@@ -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 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 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 rb_node *parent = NULL, *new_node = NULL;
struct quota_id *new_entry, *entry; struct quota_id *new_entry, *entry;
qid_t id = from_kqid(&init_user_ns, dquot->dq_id); qid_t id = from_kqid(&init_user_ns, dquot->dq_id);
...@@ -195,6 +196,14 @@ static int shmem_acquire_dquot(struct dquot *dquot) ...@@ -195,6 +196,14 @@ static int shmem_acquire_dquot(struct dquot *dquot)
} }
new_entry->id = id; 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; new_node = &new_entry->node;
rb_link_node(new_node, parent, n); rb_link_node(new_node, parent, n);
rb_insert_color(new_node, (struct rb_root *)info->dqi_priv); rb_insert_color(new_node, (struct rb_root *)info->dqi_priv);
...@@ -224,6 +233,29 @@ static int shmem_acquire_dquot(struct dquot *dquot) ...@@ -224,6 +233,29 @@ static int shmem_acquire_dquot(struct dquot *dquot)
return ret; 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 * 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 * 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) ...@@ -261,7 +293,7 @@ static int shmem_release_dquot(struct dquot *dquot)
return -ENOENT; return -ENOENT;
found: found:
if (test_bit(DQ_FAKE_B, &dquot->dq_flags)) { if (shmem_is_empty_dquot(dquot)) {
/* Remove entry from the tree */ /* Remove entry from the tree */
rb_erase(&entry->node, info->dqi_priv); rb_erase(&entry->node, info->dqi_priv);
kfree(entry); kfree(entry);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment