linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
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
> 
> 



  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).