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 2/3] shmem: implement user/group quota support for tmpfs
Date: Tue, 22 Nov 2022 15:57:57 -0500 [thread overview]
Message-ID: <Y3031WAOfomeW9tI@bfoster> (raw)
In-Reply-To: <20221121142854.91109-3-lczerner@redhat.com>
On Mon, Nov 21, 2022 at 03:28:53PM +0100, Lukas Czerner wrote:
> Implement user and group quota support for tmpfs using system quota file
> in vfsv0 quota format. Because everything in tmpfs is temporary and as a
> result is lost on umount, the quota files are initialized on every
> mount. This also goes for quota limits, that needs to be set up after
> every mount.
>
> The quota support in tmpfs is well separated from the rest of the
> filesystem and is only enabled using mount option -o quota (and
> usrquota and grpquota for compatibility reasons). Only quota accounting
> is enabled this way, enforcement needs to be enable by regular quota
> tools (using Q_QUOTAON ioctl).
>
FWIW, just from a first look through, it seems like this could be made a
little easier to review by splitting it up into a few smaller patches.
For example, could the accounting and enforcement support split into
separate patches?
A few more random notes/questions...
> Signed-off-by: Lukas Czerner <lczerner@redhat.com>
> ---
> v2: Use the newly introduced in-memory only quota foramt QFMT_MEM_ONLY
>
> Documentation/filesystems/tmpfs.rst | 12 ++
> fs/quota/dquot.c | 10 +-
> include/linux/shmem_fs.h | 3 +
> mm/shmem.c | 200 ++++++++++++++++++++++++----
> 4 files changed, 197 insertions(+), 28 deletions(-)
>
...
> diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
> index f1a7a03632a2..007604e7eb09 100644
> --- a/fs/quota/dquot.c
> +++ b/fs/quota/dquot.c
> @@ -716,11 +716,11 @@ int dquot_quota_sync(struct super_block *sb, int type)
> for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
> if (type != -1 && cnt != type)
> continue;
> - if (!sb_has_quota_active(sb, cnt))
> - continue;
> - inode_lock(dqopt->files[cnt]);
> - truncate_inode_pages(&dqopt->files[cnt]->i_data, 0);
> - inode_unlock(dqopt->files[cnt]);
> + if (sb_has_quota_active(sb, cnt) && dqopt->files[cnt]) {
> + inode_lock(dqopt->files[cnt]);
> + truncate_inode_pages(&dqopt->files[cnt]->i_data, 0);
> + inode_unlock(dqopt->files[cnt]);
> + }
Perhaps a separate patch with some context for the change in the commit
log? (Maybe it's obvious to others, I'm just not familiar with the core
quota code.)
> }
>
> return 0;
...
> diff --git a/mm/shmem.c b/mm/shmem.c
> index c1d8b8a1aa3b..26f2effd8f7c 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
...
> @@ -198,26 +208,34 @@ static inline void shmem_unacct_blocks(unsigned long flags, long pages)
> vm_unacct_memory(pages * VM_ACCT(PAGE_SIZE));
> }
>
> -static inline bool shmem_inode_acct_block(struct inode *inode, long pages)
> +static inline int shmem_inode_acct_block(struct inode *inode, long pages)
> {
It seems like the refactoring to make this helper return an error could
be a separate patch.
> struct shmem_inode_info *info = SHMEM_I(inode);
> struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
> + int err = -ENOSPC;
>
> if (shmem_acct_block(info->flags, pages))
> - return false;
> + return err;
>
> if (sbinfo->max_blocks) {
> if (percpu_counter_compare(&sbinfo->used_blocks,
> sbinfo->max_blocks - pages) > 0)
> goto unacct;
> + if (dquot_alloc_block_nodirty(inode, pages)) {
> + err = -EDQUOT;
> + goto unacct;
> + }
It looks like the dquot_alloc_*() helper already returns -EDQUOT, FWIW,
though it's not clear to me if you wanted to mask out other potential
errors.
> percpu_counter_add(&sbinfo->used_blocks, pages);
> + } else if (dquot_alloc_block_nodirty(inode, pages)) {
> + err = -EDQUOT;
> + goto unacct;
> }
>
> - return true;
> + return 0;
>
> unacct:
> shmem_unacct_blocks(info->flags, pages);
> - return false;
> + return err;
> }
>
> static inline void shmem_inode_unacct_blocks(struct inode *inode, long pages)
...
> @@ -247,6 +267,62 @@ bool vma_is_shmem(struct vm_area_struct *vma)
> static LIST_HEAD(shmem_swaplist);
> static DEFINE_MUTEX(shmem_swaplist_mutex);
>
> +#ifdef SHMEM_QUOTA_TMPFS
> +
> +#define SHMEM_MAXQUOTAS 2
> +
> +/*
> + * We don't have any quota files to read, or write to/from, but quota code
> + * requires .quota_read and .quota_write to exist.
> + */
> +static ssize_t shmem_quota_write(struct super_block *sb, int type,
> + const char *data, size_t len, loff_t off)
> +{
> + return len;
> +}
> +
> +static ssize_t shmem_quota_read(struct super_block *sb, int type, char *data,
> + size_t len, loff_t off)
> +{
> + return len;
> +}
> +
> +
> +static int shmem_enable_quotas(struct super_block *sb)
> +{
> + int type, err = 0;
> +
> + sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
A brief comment on the flags would be helpful.
> + for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
> + err = dquot_load_quota_sb(sb, type, QFMT_MEM_ONLY,
> + DQUOT_USAGE_ENABLED);
> + if (err)
> + goto out_err;
> + }
> + return 0;
> +
> +out_err:
> + pr_warn("tmpfs: failed to enable quota tracking (type=%d, err=%d)\n",
> + type, err);
> + for (type--; type >= 0; type--)
> + dquot_quota_off(sb, type);
> + return err;
> +}
> +
> +static void shmem_disable_quotas(struct super_block *sb)
> +{
> + int type;
> +
> + for (type = 0; type < SHMEM_MAXQUOTAS; type++)
> + dquot_quota_off(sb, type);
> +}
> +
> +static struct dquot **shmem_get_dquots(struct inode *inode)
> +{
> + return SHMEM_I(inode)->i_dquot;
> +}
> +#endif /* SHMEM_QUOTA_TMPFS */
> +
> /*
> * shmem_reserve_inode() performs bookkeeping to reserve a shmem inode, and
> * produces a novel ino for the newly allocated inode.
> @@ -353,7 +429,6 @@ static void shmem_recalc_inode(struct inode *inode)
> freed = info->alloced - info->swapped - inode->i_mapping->nrpages;
> if (freed > 0) {
> info->alloced -= freed;
> - inode->i_blocks -= freed * BLOCKS_PER_PAGE;
Did these various ->i_blocks updates get moved somewhere?
> shmem_inode_unacct_blocks(inode, freed);
> }
> }
...
> @@ -2384,6 +2467,35 @@ static struct inode *shmem_get_inode(struct super_block *sb, struct inode *dir,
> return inode;
> }
>
> +static struct inode *shmem_get_inode(struct super_block *sb, struct inode *dir,
> + umode_t mode, dev_t dev, unsigned long flags)
> +{
> + int err;
> + struct inode *inode;
> +
> + inode = shmem_get_inode_noquota(sb, dir, mode, dev, flags);
> + if (inode) {
> + err = dquot_initialize(inode);
> + if (err)
> + goto errout;
> +
> + err = dquot_alloc_inode(inode);
> + if (err) {
> + dquot_drop(inode);
> + goto errout;
> + }
> + }
> + return inode;
> +
> +errout:
> + inode->i_flags |= S_NOQUOTA;
I assume this is here so the free path won't unaccount an inode from the
quota that wasn't able to allocate, but is it needed with the
dquot_drop() above? If so, a comment might be helpful. :)
Brian
> + iput(inode);
> + shmem_free_inode(sb);
> + if (err)
> + return ERR_PTR(err);
> + return NULL;
> +}
> +
> #ifdef CONFIG_USERFAULTFD
> int shmem_mfill_atomic_pte(struct mm_struct *dst_mm,
> pmd_t *dst_pmd,
> @@ -2403,7 +2515,7 @@ int shmem_mfill_atomic_pte(struct mm_struct *dst_mm,
> int ret;
> pgoff_t max_off;
>
> - if (!shmem_inode_acct_block(inode, 1)) {
> + if (shmem_inode_acct_block(inode, 1)) {
> /*
> * We may have got a page, returned -ENOENT triggering a retry,
> * and now we find ourselves with -ENOMEM. Release the page, to
> @@ -2487,7 +2599,6 @@ int shmem_mfill_atomic_pte(struct mm_struct *dst_mm,
>
> spin_lock_irq(&info->lock);
> info->alloced++;
> - inode->i_blocks += BLOCKS_PER_PAGE;
> shmem_recalc_inode(inode);
> spin_unlock_irq(&info->lock);
>
> @@ -2908,7 +3019,7 @@ shmem_mknod(struct user_namespace *mnt_userns, struct inode *dir,
> int error = -ENOSPC;
>
> inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE);
> - if (inode) {
> + if (!IS_ERR_OR_NULL(inode)) {
> error = simple_acl_create(dir, inode);
> if (error)
> goto out_iput;
> @@ -2924,7 +3035,8 @@ shmem_mknod(struct user_namespace *mnt_userns, struct inode *dir,
> inode_inc_iversion(dir);
> d_instantiate(dentry, inode);
> dget(dentry); /* Extra count - pin the dentry in core */
> - }
> + } else if (IS_ERR(inode))
> + error = PTR_ERR(inode);
> return error;
> out_iput:
> iput(inode);
> @@ -2939,7 +3051,7 @@ shmem_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
> int error = -ENOSPC;
>
> inode = shmem_get_inode(dir->i_sb, dir, mode, 0, VM_NORESERVE);
> - if (inode) {
> + if (!IS_ERR_OR_NULL(inode)) {
> error = security_inode_init_security(inode, dir,
> NULL,
> shmem_initxattrs, NULL);
> @@ -2949,7 +3061,8 @@ shmem_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
> if (error)
> goto out_iput;
> d_tmpfile(file, inode);
> - }
> + } else if (IS_ERR(inode))
> + error = PTR_ERR(inode);
> return finish_open_simple(file, error);
> out_iput:
> iput(inode);
> @@ -3126,6 +3239,8 @@ static int shmem_symlink(struct user_namespace *mnt_userns, struct inode *dir,
> VM_NORESERVE);
> if (!inode)
> return -ENOSPC;
> + else if (IS_ERR(inode))
> + return PTR_ERR(inode);
>
> error = security_inode_init_security(inode, dir, &dentry->d_name,
> shmem_initxattrs, NULL);
> @@ -3443,6 +3558,7 @@ enum shmem_param {
> Opt_uid,
> Opt_inode32,
> Opt_inode64,
> + Opt_quota,
> };
>
> static const struct constant_table shmem_param_enums_huge[] = {
> @@ -3464,6 +3580,9 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
> fsparam_u32 ("uid", Opt_uid),
> fsparam_flag ("inode32", Opt_inode32),
> fsparam_flag ("inode64", Opt_inode64),
> + fsparam_flag ("quota", Opt_quota),
> + fsparam_flag ("usrquota", Opt_quota),
> + fsparam_flag ("grpquota", Opt_quota),
> {}
> };
>
> @@ -3547,6 +3666,13 @@ 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
> + ctx->seen |= SHMEM_SEEN_QUOTA;
> +#else
> + goto unsupported_parameter;
> +#endif
> + break;
> }
> return 0;
>
> @@ -3646,6 +3772,12 @@ static int shmem_reconfigure(struct fs_context *fc)
> goto out;
> }
>
> + if (ctx->seen & SHMEM_SEEN_QUOTA &&
> + !sb_any_quota_loaded(fc->root->d_sb)) {
> + err = "Cannot enable quota on remount";
> + goto out;
> + }
> +
> if (ctx->seen & SHMEM_SEEN_HUGE)
> sbinfo->huge = ctx->huge;
> if (ctx->seen & SHMEM_SEEN_INUMS)
> @@ -3728,6 +3860,9 @@ static void shmem_put_super(struct super_block *sb)
> {
> struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
>
> +#ifdef SHMEM_QUOTA_TMPFS
> + shmem_disable_quotas(sb);
> +#endif
> free_percpu(sbinfo->ino_batch);
> percpu_counter_destroy(&sbinfo->used_blocks);
> mpol_put(sbinfo->mpol);
> @@ -3805,14 +3940,26 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
> #endif
> uuid_gen(&sb->s_uuid);
>
> +#ifdef SHMEM_QUOTA_TMPFS
> + if (ctx->seen & SHMEM_SEEN_QUOTA) {
> + sb->dq_op = &dquot_operations;
> + sb->s_qcop = &dquot_quotactl_sysfile_ops;
> + sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
> +
> + if (shmem_enable_quotas(sb))
> + goto failed;
> + }
> +#endif /* SHMEM_QUOTA_TMPFS */
> +
> inode = shmem_get_inode(sb, NULL, S_IFDIR | sbinfo->mode, 0, VM_NORESERVE);
> - if (!inode)
> + if (IS_ERR_OR_NULL(inode))
> goto failed;
> inode->i_uid = sbinfo->uid;
> inode->i_gid = sbinfo->gid;
> sb->s_root = d_make_root(inode);
> if (!sb->s_root)
> goto failed;
> +
> return 0;
>
> failed:
> @@ -3976,7 +4123,12 @@ static const struct super_operations shmem_ops = {
> #ifdef CONFIG_TMPFS
> .statfs = shmem_statfs,
> .show_options = shmem_show_options,
> -#endif
> +#ifdef CONFIG_QUOTA
> + .quota_read = shmem_quota_read,
> + .quota_write = shmem_quota_write,
> + .get_dquots = shmem_get_dquots,
> +#endif /* CONFIG_QUOTA */
> +#endif /* CONFIG_TMPFS */
> .evict_inode = shmem_evict_inode,
> .drop_inode = generic_delete_inode,
> .put_super = shmem_put_super,
> @@ -4196,8 +4348,10 @@ static struct file *__shmem_file_setup(struct vfsmount *mnt, const char *name, l
>
> inode = shmem_get_inode(mnt->mnt_sb, NULL, S_IFREG | S_IRWXUGO, 0,
> flags);
> - if (unlikely(!inode)) {
> + if (IS_ERR_OR_NULL(inode)) {
> shmem_unacct_size(flags, size);
> + if (IS_ERR(inode))
> + return (struct file *)inode;
> return ERR_PTR(-ENOSPC);
> }
> inode->i_flags |= i_flags;
> --
> 2.38.1
>
>
next prev parent reply other threads:[~2022-11-22 20:57 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 [this message]
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
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=Y3031WAOfomeW9tI@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).