From: Brian Foster <bfoster@redhat.com>
To: Lukas Czerner <lczerner@redhat.com>
Cc: Hugh Dickins <hughd@google.com>, Jan Kara <jack@suse.com>,
Eric Sandeen <sandeen@redhat.com>,
linux-mm@kvack.org, linux-fsdevel@vger.kernel.org,
djwong@kernel.org
Subject: Re: [PATCH v2 3/3] shmem: implement mount options for global quota limits
Date: Tue, 22 Nov 2022 16:03:25 -0500 [thread overview]
Message-ID: <Y305HR5ZModyqtLz@bfoster> (raw)
In-Reply-To: <20221121142854.91109-4-lczerner@redhat.com>
On Mon, Nov 21, 2022 at 03:28:54PM +0100, Lukas Czerner wrote:
> Implement a set of mount options for setting glopbal quota limits on
> tmpfs.
>
> usrquota_block_hardlimit - global user quota block hard limit
> usrquota_inode_hardlimit - global user quota inode hard limit
> grpquota_block_hardlimit - global group quota block hard limit
> grpquota_inode_hardlimit - global group quota inode hard limit
>
> 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 except root the first time the quota
> entry for user/group 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 it normally
> can.
>
> When any of the default quota limits are set, quota enforcement is enabled
> automatically as well.
>
> None of the quota related mount options can be set or changed on remount.
>
> Signed-off-by: Lukas Czerner <lczerner@redhat.com>
> ---
> v2: Rename mount option to something more sensible.
> Improve documentation.
> Check if the user provided limit isn't too large.
>
> Documentation/filesystems/tmpfs.rst | 36 +++++--
> include/linux/shmem_fs.h | 10 ++
> mm/shmem.c | 162 ++++++++++++++++++++++++++--
> 3 files changed, 190 insertions(+), 18 deletions(-)
>
...
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 26f2effd8f7c..a66a1e4cd0cb 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
...
> @@ -271,6 +273,57 @@ static DEFINE_MUTEX(shmem_swaplist_mutex);
>
> #define SHMEM_MAXQUOTAS 2
>
> +int shmem_dquot_acquire(struct dquot *dquot)
> +{
> + int type, ret = 0;
> + unsigned int memalloc;
> + struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
> + struct shmem_sb_info *sbinfo = SHMEM_SB(dquot->dq_sb);
> +
> +
> + mutex_lock(&dquot->dq_lock);
> + memalloc = memalloc_nofs_save();
> + if (test_bit(DQ_READ_B, &dquot->dq_flags)) {
> + smp_mb__before_atomic();
> + set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
> + goto out_iolock;
> + }
> +
> + type = dquot->dq_id.type;
> + ret = dqopt->ops[type]->read_dqblk(dquot);
So according to patch 1, this callback would alloc the quota id and set
DQ_FAKE_B and DQ_NO_SHRINK_B on the dquot. The shrinker will skip dquots
that are (noshrink && !fake). So as of this point the dquot would be
reclaimable if it were ultimately freed with no other changes, right?
> + if (ret < 0)
> + goto out_iolock;
> + /* Set the defaults */
> + if (type == USRQUOTA) {
> + dquot->dq_dqb.dqb_bhardlimit =
> + (sbinfo->usrquota_block_hardlimit << PAGE_SHIFT);
> + dquot->dq_dqb.dqb_ihardlimit = sbinfo->usrquota_inode_hardlimit;
> + } else if (type == GRPQUOTA) {
> + dquot->dq_dqb.dqb_bhardlimit =
> + (sbinfo->grpquota_block_hardlimit << PAGE_SHIFT);
> + dquot->dq_dqb.dqb_ihardlimit = sbinfo->grpquota_inode_hardlimit;
> + }
Then we set default limits from the mount option on the dquot. The dquot
is still has DQ_FAKE_B, so presumably the dquot would remain reclaimable
(once freed) even though it has a limit set (the mount default).
AFAICS the only place that clears DQ_FAKE_B is the setquota path, so I
take it that means the dquot becomes ultimately unreclaimable if/when
the user sets a non-zero quota limit, and then it only becomes
reclaimable again when quota limits are explicitly set to zero. Is that
the case?
If so, a couple questions:
1. Can a dquot ever be reclaimed if the user explicitly sets a quota
limit that matches the mount default?
2. How does enforcement of default limits actually work? For example, it
looks like dquot_add_inodes() skips enforcement when DQ_FAKE_B is set.
Have I missed somewhere where this flag should be cleared in this case?
Brian
> + /* Make sure flags update is visible after dquot has been filled */
> + smp_mb__before_atomic();
> + set_bit(DQ_READ_B, &dquot->dq_flags);
> + set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
> +out_iolock:
> + memalloc_nofs_restore(memalloc);
> + mutex_unlock(&dquot->dq_lock);
> + return ret;
> +}
> +
> +const struct dquot_operations shmem_dquot_operations = {
> + .write_dquot = dquot_commit,
> + .acquire_dquot = shmem_dquot_acquire,
> + .release_dquot = dquot_release,
> + .mark_dirty = dquot_mark_dquot_dirty,
> + .write_info = dquot_commit_info,
> + .alloc_dquot = dquot_alloc,
> + .destroy_dquot = dquot_destroy,
> + .get_next_id = dquot_get_next_id,
> +};
> +
> /*
> * We don't have any quota files to read, or write to/from, but quota code
> * requires .quota_read and .quota_write to exist.
> @@ -288,14 +341,14 @@ static ssize_t shmem_quota_read(struct super_block *sb, int type, char *data,
> }
>
>
> -static int shmem_enable_quotas(struct super_block *sb)
> +static int shmem_enable_quotas(struct super_block *sb, unsigned int dquot_flags)
> {
> int type, err = 0;
>
> sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
> for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
> err = dquot_load_quota_sb(sb, type, QFMT_MEM_ONLY,
> - DQUOT_USAGE_ENABLED);
> + dquot_flags);
> if (err)
> goto out_err;
> }
> @@ -3559,6 +3612,10 @@ enum shmem_param {
> Opt_inode32,
> Opt_inode64,
> Opt_quota,
> + 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[] = {
> @@ -3583,6 +3640,10 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
> fsparam_flag ("quota", Opt_quota),
> fsparam_flag ("usrquota", Opt_quota),
> fsparam_flag ("grpquota", Opt_quota),
> + 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),
> {}
> };
>
> @@ -3666,13 +3727,60 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
> ctx->full_inums = true;
> ctx->seen |= SHMEM_SEEN_INUMS;
> break;
> - case Opt_quota:
> #ifdef CONFIG_QUOTA
> + case Opt_quota:
> + ctx->seen |= SHMEM_SEEN_QUOTA;
> + break;
> + case Opt_usrquota_block_hardlimit:
> + size = memparse(param->string, &rest);
> + if (*rest || !size)
> + goto bad_value;
> + size = DIV_ROUND_UP(size, PAGE_SIZE);
> + if (size > ULONG_MAX)
> + return invalfc(fc,
> + "User quota block hardlimit too large.");
> + ctx->usrquota_block_hardlimit = size;
> + ctx->seen |= SHMEM_SEEN_QUOTA;
> + break;
> + case Opt_grpquota_block_hardlimit:
> + size = memparse(param->string, &rest);
> + if (*rest || !size)
> + goto bad_value;
> + size = DIV_ROUND_UP(size, PAGE_SIZE);
> + if (size > ULONG_MAX)
> + return invalfc(fc,
> + "Group quota block hardlimit too large.");
> + ctx->grpquota_block_hardlimit = size;
> + ctx->seen |= SHMEM_SEEN_QUOTA;
> + break;
> + case Opt_usrquota_inode_hardlimit:
> + size = memparse(param->string, &rest);
> + if (*rest || !size)
> + goto bad_value;
> + if (size > ULONG_MAX)
> + return invalfc(fc,
> + "User quota inode hardlimit too large.");
> + ctx->usrquota_inode_hardlimit = size;
> + ctx->seen |= SHMEM_SEEN_QUOTA;
> + break;
> + case Opt_grpquota_inode_hardlimit:
> + size = memparse(param->string, &rest);
> + if (*rest || !size)
> + goto bad_value;
> + if (size > ULONG_MAX)
> + return invalfc(fc,
> + "Group quota inode hardlimit too large.");
> + ctx->grpquota_inode_hardlimit = size;
> ctx->seen |= SHMEM_SEEN_QUOTA;
> + break;
> #else
> + case Opt_quota:
> + case Opt_usrquota_block_hardlimit:
> + case Opt_grpquota_block_hardlimit:
> + case Opt_usrquota_inode_hardlimit:
> + case Opt_grpquota_inode_hardlimit:
> goto unsupported_parameter;
> #endif
> - break;
> }
> return 0;
>
> @@ -3778,6 +3886,18 @@ static int shmem_reconfigure(struct fs_context *fc)
> goto out;
> }
>
> +#ifdef CONFIG_QUOTA
> +#define CHANGED_LIMIT(name) \
> + (ctx->name## _hardlimit && \
> + (ctx->name## _hardlimit != sbinfo->name## _hardlimit))
> +
> + if (CHANGED_LIMIT(usrquota_block) || CHANGED_LIMIT(usrquota_inode) ||
> + CHANGED_LIMIT(grpquota_block) || CHANGED_LIMIT(grpquota_inode)) {
> + err = "Cannot change global quota limit on remount";
> + goto out;
> + }
> +#endif /* CONFIG_QUOTA */
> +
> if (ctx->seen & SHMEM_SEEN_HUGE)
> sbinfo->huge = ctx->huge;
> if (ctx->seen & SHMEM_SEEN_INUMS)
> @@ -3942,11 +4062,22 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
>
> #ifdef SHMEM_QUOTA_TMPFS
> if (ctx->seen & SHMEM_SEEN_QUOTA) {
> - sb->dq_op = &dquot_operations;
> + unsigned int dquot_flags;
> +
> + sb->dq_op = &shmem_dquot_operations;
> sb->s_qcop = &dquot_quotactl_sysfile_ops;
> sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
>
> - if (shmem_enable_quotas(sb))
> + dquot_flags = DQUOT_USAGE_ENABLED;
> + /*
> + * If any of the global quota limits are set, enable
> + * quota enforcement
> + */
> + if (ctx->usrquota_block_hardlimit || ctx->usrquota_inode_hardlimit ||
> + ctx->grpquota_block_hardlimit || ctx->grpquota_inode_hardlimit)
> + dquot_flags |= DQUOT_LIMITS_ENABLED;
> +
> + if (shmem_enable_quotas(sb, dquot_flags))
> goto failed;
> }
> #endif /* SHMEM_QUOTA_TMPFS */
> @@ -3960,6 +4091,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
> if (!sb->s_root)
> goto failed;
>
> +#ifdef SHMEM_QUOTA_TMPFS
> + /*
> + * Set quota hard limits after shmem_get_inode() to avoid setting
> + * it for root
> + */
> + sbinfo->usrquota_block_hardlimit = ctx->usrquota_block_hardlimit;
> + sbinfo->usrquota_inode_hardlimit = ctx->usrquota_inode_hardlimit;
> + sbinfo->grpquota_block_hardlimit = ctx->grpquota_block_hardlimit;
> + sbinfo->grpquota_inode_hardlimit = ctx->grpquota_inode_hardlimit;
> +#endif /* SHMEM_QUOTA_TMPFS */
> +
> return 0;
>
> failed:
> --
> 2.38.1
>
>
next prev parent reply other threads:[~2022-11-22 21:03 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-11-21 14:28 [PATCH v2 0/3] [RFC] shmem: user and group quota support for tmpfs Lukas Czerner
2022-11-21 14:28 ` [PATCH v2 1/3] quota: add quota in-memory format support Lukas Czerner
2022-11-21 17:48 ` Darrick J. Wong
2022-11-22 9:04 ` Lukas Czerner
2022-11-22 15:23 ` Brian Foster
2022-11-23 9:52 ` Lukas Czerner
2022-11-23 12:32 ` Brian Foster
2022-11-22 12:59 ` Christoph Hellwig
2022-11-22 14:21 ` Lukas Czerner
2022-11-23 7:58 ` Christoph Hellwig
2022-11-23 8:36 ` Lukas Czerner
2022-11-23 12:37 ` Brian Foster
2022-11-23 18:09 ` Darrick J. Wong
2022-11-23 17:07 ` Jan Kara
2022-11-25 9:30 ` Lukas Czerner
2022-11-28 10:03 ` Jan Kara
2022-11-29 11:21 ` Christian Brauner
2022-11-29 13:11 ` Lukas Czerner
2022-11-21 14:28 ` [PATCH v2 2/3] shmem: implement user/group quota support for tmpfs Lukas Czerner
2022-11-22 15:21 ` kernel test robot
2022-11-22 20:57 ` Brian Foster
2022-11-23 9:01 ` Lukas Czerner
2022-11-23 12:35 ` Brian Foster
2022-11-23 16:37 ` Jan Kara
2022-11-25 8:59 ` Lukas Czerner
2022-11-25 9:14 ` Jan Kara
2022-11-25 9:49 ` Lukas Czerner
2022-11-21 14:28 ` [PATCH v2 3/3] shmem: implement mount options for global quota limits Lukas Czerner
2022-11-22 6:15 ` kernel test robot
2022-11-22 21:03 ` Brian Foster [this message]
2022-11-23 9:38 ` Lukas Czerner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Y305HR5ZModyqtLz@bfoster \
--to=bfoster@redhat.com \
--cc=djwong@kernel.org \
--cc=hughd@google.com \
--cc=jack@suse.com \
--cc=lczerner@redhat.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=sandeen@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).