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:04 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.