diff mbox series

[5/6] shmem: quota support

Message ID 20230403084759.884681-6-cem@kernel.org (mailing list archive)
State New
Headers show
Series shmem: Add user and group quota support for tmpfs | expand

Commit Message

Carlos Maiolino April 3, 2023, 8:47 a.m. UTC
From: Lukas Czerner <lczerner@redhat.com>

Now the basic infra-structure is in place, enable quota support for tmpfs.

Signed-off-by: Lukas Czerner <lczerner@redhat.com>
Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
---
 Documentation/filesystems/tmpfs.rst |  12 +++
 include/linux/shmem_fs.h            |   9 ++
 mm/shmem.c                          | 162 ++++++++++++++++++++++++++--
 3 files changed, 174 insertions(+), 9 deletions(-)

Comments

kernel test robot April 3, 2023, 2:31 p.m. UTC | #1
Hi,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on linus/master]
[also build test WARNING on v6.3-rc5]
[cannot apply to akpm-mm/mm-everything next-20230403]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/cem-kernel-org/shmem-make-shmem_inode_acct_block-return-error/20230403-165022
patch link:    https://lore.kernel.org/r/20230403084759.884681-6-cem%40kernel.org
patch subject: [PATCH 5/6] shmem: quota support
config: i386-allnoconfig (https://download.01.org/0day-ci/archive/20230403/202304032216.0SXl7l2X-lkp@intel.com/config)
compiler: gcc-11 (Debian 11.3.0-8) 11.3.0
reproduce (this is a W=1 build):
        # https://github.com/intel-lab-lkp/linux/commit/e060b9e86fd92d5e87f5b0c447e4bc610a3d3bbe
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review cem-kernel-org/shmem-make-shmem_inode_acct_block-return-error/20230403-165022
        git checkout e060b9e86fd92d5e87f5b0c447e4bc610a3d3bbe
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        make W=1 O=build_dir ARCH=i386 olddefconfig
        make W=1 O=build_dir ARCH=i386 SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
| Link: https://lore.kernel.org/oe-kbuild-all/202304032216.0SXl7l2X-lkp@intel.com/

All warnings (new ones prefixed by >>):

   mm/shmem.c: In function 'shmem_init':
>> mm/shmem.c:4252:1: warning: label 'out3' defined but not used [-Wunused-label]
    4252 | out3:
         | ^~~~


vim +/out3 +4252 mm/shmem.c

  4224	
  4225		error = register_filesystem(&shmem_fs_type);
  4226		if (error) {
  4227			pr_err("Could not register tmpfs\n");
  4228			goto out2;
  4229		}
  4230	
  4231		shm_mnt = kern_mount(&shmem_fs_type);
  4232		if (IS_ERR(shm_mnt)) {
  4233			error = PTR_ERR(shm_mnt);
  4234			pr_err("Could not kern_mount tmpfs\n");
  4235			goto out1;
  4236		}
  4237	
  4238	#ifdef CONFIG_TRANSPARENT_HUGEPAGE
  4239		if (has_transparent_hugepage() && shmem_huge > SHMEM_HUGE_DENY)
  4240			SHMEM_SB(shm_mnt->mnt_sb)->huge = shmem_huge;
  4241		else
  4242			shmem_huge = SHMEM_HUGE_NEVER; /* just in case it was patched */
  4243	#endif
  4244		return;
  4245	
  4246	out1:
  4247		unregister_filesystem(&shmem_fs_type);
  4248	out2:
  4249	#ifdef CONFIG_TMPFS_QUOTA
  4250		unregister_quota_format(&shmem_quota_format);
  4251	#endif
> 4252	out3:
  4253		shmem_destroy_inodecache();
  4254		shm_mnt = ERR_PTR(error);
  4255	}
  4256
Darrick J. Wong April 3, 2023, 6:46 p.m. UTC | #2
On Mon, Apr 03, 2023 at 10:47:58AM +0200, cem@kernel.org wrote:
> From: Lukas Czerner <lczerner@redhat.com>
> 
> Now the basic infra-structure is in place, enable quota support for tmpfs.
> 
> Signed-off-by: Lukas Czerner <lczerner@redhat.com>
> Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
> ---
>  Documentation/filesystems/tmpfs.rst |  12 +++
>  include/linux/shmem_fs.h            |   9 ++
>  mm/shmem.c                          | 162 ++++++++++++++++++++++++++--
>  3 files changed, 174 insertions(+), 9 deletions(-)
> 
> diff --git a/Documentation/filesystems/tmpfs.rst b/Documentation/filesystems/tmpfs.rst
> index 0408c245785e3..3f8d89bb7e1a5 100644
> --- a/Documentation/filesystems/tmpfs.rst
> +++ b/Documentation/filesystems/tmpfs.rst
> @@ -86,6 +86,18 @@ use up all the memory on the machine; but enhances the scalability of
>  that instance in a system with many CPUs making intensive use of it.
>  
>  
> +tmpfs also supports quota with the following mount options
> +
> +========  =============================================================
> +quota     User and group quota accounting and enforcement is enabled on
> +          the mount. Tmpfs is using hidden system quota files that are
> +          initialized on mount.
> +usrquota  User quota accounting and enforcement is enabled on the
> +          mount.
> +grpquota  Group quota accounting and enforcement is enabled on the
> +          mount.
> +========  =============================================================
> +
>  tmpfs has a mount option to set the NUMA memory allocation policy for
>  all files in that instance (if CONFIG_NUMA is enabled) - which can be
>  adjusted on the fly via 'mount -o remount ...'
> diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
> index cf38381bdb4c1..3e7e18726feb5 100644
> --- a/include/linux/shmem_fs.h
> +++ b/include/linux/shmem_fs.h
> @@ -26,6 +26,9 @@ struct shmem_inode_info {
>  	atomic_t		stop_eviction;	/* hold when working on inode */
>  	struct timespec64	i_crtime;	/* file creation time */
>  	unsigned int		fsflags;	/* flags for FS_IOC_[SG]ETFLAGS */
> +#ifdef CONFIG_TMPFS_QUOTA
> +	struct dquot		*i_dquot[MAXQUOTAS];

Why allocate three dquot pointers here...

> +#endif
>  	struct inode		vfs_inode;
>  };
>  
> @@ -171,4 +174,10 @@ extern int shmem_mfill_atomic_pte(struct mm_struct *dst_mm, pmd_t *dst_pmd,
>  #define SHMEM_QUOTA_MAX_SPC_LIMIT 0x7fffffffffffffffLL /* 2^63-1 */
>  #define SHMEM_QUOTA_MAX_INO_LIMIT 0x7fffffffffffffffLL
>  
> +#ifdef CONFIG_TMPFS_QUOTA
> +#define SHMEM_MAXQUOTAS 2

...when you're only allowing user and group quotas?

(Or: Why not allow project quotas?  But that's outside the scope you
defined.)

--D

> +extern const struct dquot_operations shmem_quota_operations;
> +extern struct quota_format_type shmem_quota_format;
> +#endif /* CONFIG_TMPFS_QUOTA */
> +
>  #endif
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 88e13930fc013..d7529c883eaf5 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -79,6 +79,7 @@ static struct vfsmount *shm_mnt;
>  #include <linux/userfaultfd_k.h>
>  #include <linux/rmap.h>
>  #include <linux/uuid.h>
> +#include <linux/quotaops.h>
>  
>  #include <linux/uaccess.h>
>  
> @@ -116,10 +117,12 @@ struct shmem_options {
>  	bool full_inums;
>  	int huge;
>  	int seen;
> +	unsigned short quota_types;
>  #define SHMEM_SEEN_BLOCKS 1
>  #define SHMEM_SEEN_INODES 2
>  #define SHMEM_SEEN_HUGE 4
>  #define SHMEM_SEEN_INUMS 8
> +#define SHMEM_SEEN_QUOTA 16
>  };
>  
>  #ifdef CONFIG_TMPFS
> @@ -211,8 +214,11 @@ static inline int shmem_inode_acct_block(struct inode *inode, long pages)
>  		if (percpu_counter_compare(&sbinfo->used_blocks,
>  					   sbinfo->max_blocks - pages) > 0)
>  			goto unacct;
> +		if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> +			goto unacct;
>  		percpu_counter_add(&sbinfo->used_blocks, pages);
> -	}
> +	} else if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> +		goto unacct;
>  
>  	return 0;
>  
> @@ -226,6 +232,8 @@ static inline void shmem_inode_unacct_blocks(struct inode *inode, long pages)
>  	struct shmem_inode_info *info = SHMEM_I(inode);
>  	struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
>  
> +	dquot_free_block_nodirty(inode, pages);
> +
>  	if (sbinfo->max_blocks)
>  		percpu_counter_sub(&sbinfo->used_blocks, pages);
>  	shmem_unacct_blocks(info->flags, pages);
> @@ -254,6 +262,47 @@ bool vma_is_shmem(struct vm_area_struct *vma)
>  static LIST_HEAD(shmem_swaplist);
>  static DEFINE_MUTEX(shmem_swaplist_mutex);
>  
> +#ifdef CONFIG_TMPFS_QUOTA
> +
> +static int shmem_enable_quotas(struct super_block *sb,
> +			       unsigned short quota_types)
> +{
> +	int type, err = 0;
> +
> +	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
> +	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
> +		if (!(quota_types & (1 << type)))
> +			continue;
> +		err = dquot_load_quota_sb(sb, type, QFMT_SHMEM,
> +					  DQUOT_USAGE_ENABLED |
> +					  DQUOT_LIMITS_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 /* CONFIG_TMPFS_QUOTA */
> +
>  /*
>   * shmem_reserve_inode() performs bookkeeping to reserve a shmem inode, and
>   * produces a novel ino for the newly allocated inode.
> @@ -360,7 +409,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;
>  		shmem_inode_unacct_blocks(inode, freed);
>  	}
>  }
> @@ -378,7 +426,6 @@ bool shmem_charge(struct inode *inode, long pages)
>  
>  	spin_lock_irqsave(&info->lock, flags);
>  	info->alloced += pages;
> -	inode->i_blocks += pages * BLOCKS_PER_PAGE;
>  	shmem_recalc_inode(inode);
>  	spin_unlock_irqrestore(&info->lock, flags);
>  
> @@ -394,7 +441,6 @@ void shmem_uncharge(struct inode *inode, long pages)
>  
>  	spin_lock_irqsave(&info->lock, flags);
>  	info->alloced -= pages;
> -	inode->i_blocks -= pages * BLOCKS_PER_PAGE;
>  	shmem_recalc_inode(inode);
>  	spin_unlock_irqrestore(&info->lock, flags);
>  
> @@ -1133,6 +1179,15 @@ static int shmem_setattr(struct mnt_idmap *idmap,
>  		}
>  	}
>  
> +	/* Transfer quota accounting */
> +	if (i_uid_needs_update(idmap, attr, inode) ||
> +	    i_gid_needs_update(idmap, attr,inode)) {
> +		error = dquot_transfer(idmap, inode, attr);
> +
> +		if (error)
> +			return error;
> +	}
> +
>  	setattr_copy(idmap, inode, attr);
>  	if (attr->ia_valid & ATTR_MODE)
>  		error = posix_acl_chmod(idmap, dentry, inode->i_mode);
> @@ -1178,7 +1233,9 @@ static void shmem_evict_inode(struct inode *inode)
>  	simple_xattrs_free(&info->xattrs);
>  	WARN_ON(inode->i_blocks);
>  	shmem_free_inode(inode->i_sb);
> +	dquot_free_inode(inode);
>  	clear_inode(inode);
> +	dquot_drop(inode);
>  }
>  
>  static int shmem_find_swap_entries(struct address_space *mapping,
> @@ -1975,7 +2032,6 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
>  
>  	spin_lock_irq(&info->lock);
>  	info->alloced += folio_nr_pages(folio);
> -	inode->i_blocks += (blkcnt_t)BLOCKS_PER_PAGE << folio_order(folio);
>  	shmem_recalc_inode(inode);
>  	spin_unlock_irq(&info->lock);
>  	alloced = true;
> @@ -2346,9 +2402,10 @@ static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
>  #define shmem_initxattrs NULL
>  #endif
>  
> -static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block *sb,
> -				     struct inode *dir, umode_t mode, dev_t dev,
> -				     unsigned long flags)
> +static struct inode *shmem_get_inode_noquota(struct mnt_idmap *idmap,
> +					     struct super_block *sb,
> +					     struct inode *dir, umode_t mode,
> +					     dev_t dev, unsigned long flags)
>  {
>  	struct inode *inode;
>  	struct shmem_inode_info *info;
> @@ -2422,6 +2479,37 @@ static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block
>  	return inode;
>  }
>  
> +static struct inode *shmem_get_inode(struct mnt_idmap *idmap,
> +				     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(idmap, sb, dir, mode, dev, flags);
> +	if (IS_ERR(inode))
> +		return 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;
> +	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,
> @@ -2525,7 +2613,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);
>  
> @@ -3372,6 +3459,7 @@ static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size)
>  
>  static const struct inode_operations shmem_short_symlink_operations = {
>  	.getattr	= shmem_getattr,
> +	.setattr	= shmem_setattr,
>  	.get_link	= simple_get_link,
>  #ifdef CONFIG_TMPFS_XATTR
>  	.listxattr	= shmem_listxattr,
> @@ -3380,6 +3468,7 @@ static const struct inode_operations shmem_short_symlink_operations = {
>  
>  static const struct inode_operations shmem_symlink_inode_operations = {
>  	.getattr	= shmem_getattr,
> +	.setattr	= shmem_setattr,
>  	.get_link	= shmem_get_link,
>  #ifdef CONFIG_TMPFS_XATTR
>  	.listxattr	= shmem_listxattr,
> @@ -3478,6 +3567,9 @@ enum shmem_param {
>  	Opt_uid,
>  	Opt_inode32,
>  	Opt_inode64,
> +	Opt_quota,
> +	Opt_usrquota,
> +	Opt_grpquota,
>  };
>  
>  static const struct constant_table shmem_param_enums_huge[] = {
> @@ -3499,6 +3591,11 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
>  	fsparam_u32   ("uid",		Opt_uid),
>  	fsparam_flag  ("inode32",	Opt_inode32),
>  	fsparam_flag  ("inode64",	Opt_inode64),
> +#ifdef CONFIG_TMPFS_QUOTA
> +	fsparam_flag  ("quota",		Opt_quota),
> +	fsparam_flag  ("usrquota",	Opt_usrquota),
> +	fsparam_flag  ("grpquota",	Opt_grpquota),
> +#endif
>  	{}
>  };
>  
> @@ -3582,6 +3679,18 @@ 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:
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		ctx->quota_types |= (QTYPE_MASK_USR | QTYPE_MASK_GRP);
> +		break;
> +	case Opt_usrquota:
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		ctx->quota_types |= QTYPE_MASK_USR;
> +		break;
> +	case Opt_grpquota:
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		ctx->quota_types |= QTYPE_MASK_GRP;
> +		break;
>  	}
>  	return 0;
>  
> @@ -3681,6 +3790,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)
> @@ -3763,6 +3878,9 @@ static void shmem_put_super(struct super_block *sb)
>  {
>  	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
>  
> +#ifdef CONFIG_TMPFS_QUOTA
> +	shmem_disable_quotas(sb);
> +#endif
>  	free_percpu(sbinfo->ino_batch);
>  	percpu_counter_destroy(&sbinfo->used_blocks);
>  	mpol_put(sbinfo->mpol);
> @@ -3841,6 +3959,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
>  #endif
>  	uuid_gen(&sb->s_uuid);
>  
> +#ifdef CONFIG_TMPFS_QUOTA
> +	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> +		sb->dq_op = &shmem_quota_operations;
> +		sb->s_qcop = &dquot_quotactl_sysfile_ops;
> +		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
> +
> +		if (shmem_enable_quotas(sb, ctx->quota_types))
> +			goto failed;
> +	}
> +#endif /* CONFIG_TMPFS_QUOTA */
> +
>  	inode = shmem_get_inode(&nop_mnt_idmap, sb, NULL, S_IFDIR | sbinfo->mode, 0,
>  				VM_NORESERVE);
>  	if (IS_ERR(inode)) {
> @@ -4016,6 +4145,9 @@ static const struct super_operations shmem_ops = {
>  #ifdef CONFIG_TMPFS
>  	.statfs		= shmem_statfs,
>  	.show_options	= shmem_show_options,
> +#endif
> +#ifdef CONFIG_TMPFS_QUOTA
> +	.get_dquots	= shmem_get_dquots,
>  #endif
>  	.evict_inode	= shmem_evict_inode,
>  	.drop_inode	= generic_delete_inode,
> @@ -4082,6 +4214,14 @@ void __init shmem_init(void)
>  
>  	shmem_init_inodecache();
>  
> +#ifdef CONFIG_TMPFS_QUOTA
> +	error = register_quota_format(&shmem_quota_format);
> +	if (error < 0) {
> +		pr_err("Could not register quota format\n");
> +		goto out3;
> +	}
> +#endif
> +
>  	error = register_filesystem(&shmem_fs_type);
>  	if (error) {
>  		pr_err("Could not register tmpfs\n");
> @@ -4106,6 +4246,10 @@ void __init shmem_init(void)
>  out1:
>  	unregister_filesystem(&shmem_fs_type);
>  out2:
> +#ifdef CONFIG_TMPFS_QUOTA
> +	unregister_quota_format(&shmem_quota_format);
> +#endif
> +out3:
>  	shmem_destroy_inodecache();
>  	shm_mnt = ERR_PTR(error);
>  }
> -- 
> 2.30.2
>
kernel test robot April 3, 2023, 10:03 p.m. UTC | #3
Hi,

kernel test robot noticed the following build warnings:

[auto build test WARNING on linus/master]
[also build test WARNING on v6.3-rc5]
[cannot apply to akpm-mm/mm-everything next-20230403]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/cem-kernel-org/shmem-make-shmem_inode_acct_block-return-error/20230403-165022
patch link:    https://lore.kernel.org/r/20230403084759.884681-6-cem%40kernel.org
patch subject: [PATCH 5/6] shmem: quota support
config: hexagon-randconfig-r045-20230403 (https://download.01.org/0day-ci/archive/20230404/202304040523.Z5vLqfkr-lkp@intel.com/config)
compiler: clang version 17.0.0 (https://github.com/llvm/llvm-project 67409911353323ca5edf2049ef0df54132fa1ca7)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/e060b9e86fd92d5e87f5b0c447e4bc610a3d3bbe
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review cem-kernel-org/shmem-make-shmem_inode_acct_block-return-error/20230403-165022
        git checkout e060b9e86fd92d5e87f5b0c447e4bc610a3d3bbe
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=hexagon olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=hexagon SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
| Link: https://lore.kernel.org/oe-kbuild-all/202304040523.Z5vLqfkr-lkp@intel.com/

All warnings (new ones prefixed by >>):

   In file included from mm/shmem.c:29:
   In file included from include/linux/pagemap.h:11:
   In file included from include/linux/highmem.h:12:
   In file included from include/linux/hardirq.h:11:
   In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
   In file included from include/asm-generic/hardirq.h:17:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/hexagon/include/asm/io.h:334:
   include/asm-generic/io.h:547:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __raw_readb(PCI_IOBASE + addr);
                             ~~~~~~~~~~ ^
   include/asm-generic/io.h:560:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr));
                                                           ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/little_endian.h:37:51: note: expanded from macro '__le16_to_cpu'
   #define __le16_to_cpu(x) ((__force __u16)(__le16)(x))
                                                     ^
   In file included from mm/shmem.c:29:
   In file included from include/linux/pagemap.h:11:
   In file included from include/linux/highmem.h:12:
   In file included from include/linux/hardirq.h:11:
   In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
   In file included from include/asm-generic/hardirq.h:17:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/hexagon/include/asm/io.h:334:
   include/asm-generic/io.h:573:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr));
                                                           ~~~~~~~~~~ ^
   include/uapi/linux/byteorder/little_endian.h:35:51: note: expanded from macro '__le32_to_cpu'
   #define __le32_to_cpu(x) ((__force __u32)(__le32)(x))
                                                     ^
   In file included from mm/shmem.c:29:
   In file included from include/linux/pagemap.h:11:
   In file included from include/linux/highmem.h:12:
   In file included from include/linux/hardirq.h:11:
   In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1:
   In file included from include/asm-generic/hardirq.h:17:
   In file included from include/linux/irq.h:20:
   In file included from include/linux/io.h:13:
   In file included from arch/hexagon/include/asm/io.h:334:
   include/asm-generic/io.h:584:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writeb(value, PCI_IOBASE + addr);
                               ~~~~~~~~~~ ^
   include/asm-generic/io.h:594:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr);
                                                         ~~~~~~~~~~ ^
   include/asm-generic/io.h:604:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
           __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr);
                                                         ~~~~~~~~~~ ^
>> mm/shmem.c:4252:1: warning: unused label 'out3' [-Wunused-label]
   out3:
   ^~~~~
   mm/shmem.c:1534:20: warning: unused function 'shmem_show_mpol' [-Wunused-function]
   static inline void shmem_show_mpol(struct seq_file *seq, struct mempolicy *mpol)
                      ^
   8 warnings generated.


vim +/out3 +4252 mm/shmem.c

  4224	
  4225		error = register_filesystem(&shmem_fs_type);
  4226		if (error) {
  4227			pr_err("Could not register tmpfs\n");
  4228			goto out2;
  4229		}
  4230	
  4231		shm_mnt = kern_mount(&shmem_fs_type);
  4232		if (IS_ERR(shm_mnt)) {
  4233			error = PTR_ERR(shm_mnt);
  4234			pr_err("Could not kern_mount tmpfs\n");
  4235			goto out1;
  4236		}
  4237	
  4238	#ifdef CONFIG_TRANSPARENT_HUGEPAGE
  4239		if (has_transparent_hugepage() && shmem_huge > SHMEM_HUGE_DENY)
  4240			SHMEM_SB(shm_mnt->mnt_sb)->huge = shmem_huge;
  4241		else
  4242			shmem_huge = SHMEM_HUGE_NEVER; /* just in case it was patched */
  4243	#endif
  4244		return;
  4245	
  4246	out1:
  4247		unregister_filesystem(&shmem_fs_type);
  4248	out2:
  4249	#ifdef CONFIG_TMPFS_QUOTA
  4250		unregister_quota_format(&shmem_quota_format);
  4251	#endif
> 4252	out3:
  4253		shmem_destroy_inodecache();
  4254		shm_mnt = ERR_PTR(error);
  4255	}
  4256
Yujie Liu April 4, 2023, 6:22 a.m. UTC | #4
Hello,

kernel test robot noticed "BUG:kernel_NULL_pointer_dereference,address" on:

commit: e060b9e86fd92d5e87f5b0c447e4bc610a3d3bbe ("[PATCH 5/6] shmem: quota support")
url: https://github.com/intel-lab-lkp/linux/commits/cem-kernel-org/shmem-make-shmem_inode_acct_block-return-error/20230403-165022
base: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git 7e364e56293bb98cae1b55fd835f5991c4e96e7d
patch link: https://lore.kernel.org/all/20230403084759.884681-6-cem@kernel.org/
patch subject: [PATCH 5/6] shmem: quota support

in testcase: boot

compiler: gcc-11
test machine: qemu-system-x86_64 -enable-kvm -cpu SandyBridge -smp 2 -m 16G

(please refer to attached dmesg/kmsg for entire log/backtrace)


+---------------------------------------------+------------+------------+
|                                             | 2dc93cb54d | e060b9e86f |
+---------------------------------------------+------------+------------+
| boot_successes                              | 14         | 0          |
| boot_failures                               | 0          | 12         |
| BUG:kernel_NULL_pointer_dereference,address | 0          | 12         |
| Oops:#[##]                                  | 0          | 12         |
| Kernel_panic-not_syncing:Fatal_exception    | 0          | 12         |
+---------------------------------------------+------------+------------+


If you fix the issue, kindly add following tag
| Reported-by: kernel test robot <yujie.liu@intel.com>
| Link: https://lore.kernel.org/oe-lkp/202304041417.1199f918-yujie.liu@intel.com


[    8.196316][   T58] BUG: kernel NULL pointer dereference, address: 00000000
[    8.196987][   T58] #PF: supervisor read access in kernel mode
[    8.197478][   T58] #PF: error_code(0x0000) - not-present page
[    8.197962][   T58] *pde = 00000000
[    8.198265][   T58] Oops: 0000 [#1]
[    8.198562][   T58] CPU: 0 PID: 58 Comm: rm Not tainted 6.3.0-rc5-00005-ge060b9e86fd9 #1 b33e4695914080c2d2a6f223361e2009396ebd64
[    8.199506][   T58] EIP: 0x0
[ 8.199759][ T58] Code: Unable to access opcode bytes at 0xffffffd6.

Code starting with the faulting instruction
===========================================
[    8.200289][   T58] EAX: ee6a6388 EBX: ee6a6388 ECX: 00000007 EDX: c162e7c0
[    8.200854][   T58] ESI: ee5b89c0 EDI: ee6a6348 EBP: ee6adecc ESP: ee6adec8
[    8.201416][   T58] DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 0068 EFLAGS: 00010246
[    8.202029][   T58] CR0: 80050033 CR2: ffffffd6 CR3: 2e5d5000 CR4: 00040690
[    8.207818][   T58] DR0: 00000000 DR1: 00000000 DR2: 00000000 DR3: 00000000
[    8.208397][   T58] DR6: fffe0ff0 DR7: 00000400
[    8.208781][   T58] Call Trace:
[ 8.209049][ T58] i_dquot (fs/quota/dquot.c:940) 
[ 8.209367][ T58] dquot_drop (fs/quota/dquot.c:1610 fs/quota/dquot.c:1593) 
[ 8.209709][ T58] shmem_evict_inode (mm/shmem.c:1239 (discriminator 3)) 
[ 8.210105][ T58] ? _raw_spin_unlock (kernel/locking/spinlock.c:187) 
[ 8.210498][ T58] evict (fs/inode.c:665) 
[ 8.210806][ T58] iput (fs/inode.c:1776) 
[ 8.211107][ T58] do_unlinkat (fs/namei.c:4325) 
[ 8.211481][ T58] __ia32_sys_unlink (fs/namei.c:4362) 
[ 8.211879][ T58] __do_fast_syscall_32 (arch/x86/entry/common.c:112 arch/x86/entry/common.c:178) 
[ 8.212299][ T58] do_fast_syscall_32 (arch/x86/entry/common.c:203) 
[ 8.212698][ T58] do_SYSENTER_32 (arch/x86/entry/common.c:247) 
[ 8.213076][ T58] entry_SYSENTER_32 (arch/x86/entry/entry_32.S:867) 
[    8.213473][   T58] EIP: 0xb7ed356d
[ 8.213773][ T58] Code: c4 01 10 03 03 74 c0 01 10 05 03 74 b8 01 10 06 03 74 b4 01 10 07 03 74 b0 01 10 08 03 74 d8 01 00 51 52 55 89 e5 0f 34 cd 80 <5d> 5a 59 c3 90 90 90 90 8d 76 00 58 b8 77 00 00 00 cd 80 90 8d 76
All code
========
   0:	c4 01 10 03          	(bad)
   4:	03 74 c0 01          	add    0x1(%rax,%rax,8),%esi
   8:	10 05 03 74 b8 01    	adc    %al,0x1b87403(%rip)        # 0x1b87411
   e:	10 06                	adc    %al,(%rsi)
  10:	03 74 b4 01          	add    0x1(%rsp,%rsi,4),%esi
  14:	10 07                	adc    %al,(%rdi)
  16:	03 74 b0 01          	add    0x1(%rax,%rsi,4),%esi
  1a:	10 08                	adc    %cl,(%rax)
  1c:	03 74 d8 01          	add    0x1(%rax,%rbx,8),%esi
  20:	00 51 52             	add    %dl,0x52(%rcx)
  23:	55                   	push   %rbp
  24:	89 e5                	mov    %esp,%ebp
  26:	0f 34                	sysenter
  28:	cd 80                	int    $0x80
  2a:*	5d                   	pop    %rbp		<-- trapping instruction
  2b:	5a                   	pop    %rdx
  2c:	59                   	pop    %rcx
  2d:	c3                   	ret
  2e:	90                   	nop
  2f:	90                   	nop
  30:	90                   	nop
  31:	90                   	nop
  32:	8d 76 00             	lea    0x0(%rsi),%esi
  35:	58                   	pop    %rax
  36:	b8 77 00 00 00       	mov    $0x77,%eax
  3b:	cd 80                	int    $0x80
  3d:	90                   	nop
  3e:	8d                   	.byte 0x8d
  3f:	76                   	.byte 0x76

Code starting with the faulting instruction
===========================================
   0:	5d                   	pop    %rbp
   1:	5a                   	pop    %rdx
   2:	59                   	pop    %rcx
   3:	c3                   	ret
   4:	90                   	nop
   5:	90                   	nop
   6:	90                   	nop
   7:	90                   	nop
   8:	8d 76 00             	lea    0x0(%rsi),%esi
   b:	58                   	pop    %rax
   c:	b8 77 00 00 00       	mov    $0x77,%eax
  11:	cd 80                	int    $0x80
  13:	90                   	nop
  14:	8d                   	.byte 0x8d
  15:	76                   	.byte 0x76
[    8.215315][   T58] EAX: ffffffda EBX: bfb99c97 ECX: bfb97fe0 EDX: 00000000
[    8.215899][   T58] ESI: bfb9814c EDI: bfb99c91 EBP: bfb97fb8 ESP: bfb97f98
[    8.216451][   T58] DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 007b EFLAGS: 00000282
[    8.217056][   T58] Modules linked in:
[    8.217386][   T58] CR2: 0000000000000000
[    8.217833][   T58] ---[ end trace 0000000000000000 ]---
[    8.218341][   T58] EIP: 0x0
[ 8.218608][ T58] Code: Unable to access opcode bytes at 0xffffffd6.

Code starting with the faulting instruction
===========================================


To reproduce:

        # build kernel
	cd linux
	cp config-6.3.0-rc5-00005-ge060b9e86fd9 .config
	make HOSTCC=gcc-11 CC=gcc-11 ARCH=i386 olddefconfig prepare modules_prepare bzImage modules
	make HOSTCC=gcc-11 CC=gcc-11 ARCH=i386 INSTALL_MOD_PATH=<mod-install-dir> modules_install
	cd <mod-install-dir>
	find lib/ | cpio -o -H newc --quiet | gzip > modules.cgz


        git clone https://github.com/intel/lkp-tests.git
        cd lkp-tests
        bin/lkp qemu -k <bzImage> -m modules.cgz job-script # job-script is attached in this email

        # if come across any failure that blocks the test,
        # please remove ~/.lkp and /lkp dir to run from a clean state.
Carlos Maiolino April 4, 2023, 1:41 p.m. UTC | #5
Hi.

> >  	atomic_t		stop_eviction;	/* hold when working on inode */
> >  	struct timespec64	i_crtime;	/* file creation time */
> >  	unsigned int		fsflags;	/* flags for FS_IOC_[SG]ETFLAGS */
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	struct dquot		*i_dquot[MAXQUOTAS];
> 
> Why allocate three dquot pointers here...
> 
> > +#endif
> >  	struct inode		vfs_inode;
> >  };
> >
> > @@ -171,4 +174,10 @@ extern int shmem_mfill_atomic_pte(struct mm_struct *dst_mm, pmd_t *dst_pmd,
> >  #define SHMEM_QUOTA_MAX_SPC_LIMIT 0x7fffffffffffffffLL /* 2^63-1 */
> >  #define SHMEM_QUOTA_MAX_INO_LIMIT 0x7fffffffffffffffLL
> >
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +#define SHMEM_MAXQUOTAS 2
> 
> ...when you're only allowing user and group quotas?

My bad, I should have used SHMEM_MAXQUOTAS to define the i_dquot

> 
> (Or: Why not allow project quotas?  But that's outside the scope you
> defined.)

This is indeed on my plan, which I want to do later, I want to deal with the
'avoid users to consume all memory' issue, then I want to add prjquotas here. I
want to limit the scope of this series by now to avoid it snowballing with more
and more features.

> 
> --D
> 
> > +extern const struct dquot_operations shmem_quota_operations;
> > +extern struct quota_format_type shmem_quota_format;
> > +#endif /* CONFIG_TMPFS_QUOTA */
> > +
> >  #endif
> > diff --git a/mm/shmem.c b/mm/shmem.c
> > index 88e13930fc013..d7529c883eaf5 100644
> > --- a/mm/shmem.c
> > +++ b/mm/shmem.c
> > @@ -79,6 +79,7 @@ static struct vfsmount *shm_mnt;
> >  #include <linux/userfaultfd_k.h>
> >  #include <linux/rmap.h>
> >  #include <linux/uuid.h>
> > +#include <linux/quotaops.h>
> >
> >  #include <linux/uaccess.h>
> >
> > @@ -116,10 +117,12 @@ struct shmem_options {
> >  	bool full_inums;
> >  	int huge;
> >  	int seen;
> > +	unsigned short quota_types;
> >  #define SHMEM_SEEN_BLOCKS 1
> >  #define SHMEM_SEEN_INODES 2
> >  #define SHMEM_SEEN_HUGE 4
> >  #define SHMEM_SEEN_INUMS 8
> > +#define SHMEM_SEEN_QUOTA 16
> >  };
> >
> >  #ifdef CONFIG_TMPFS
> > @@ -211,8 +214,11 @@ static inline int shmem_inode_acct_block(struct inode *inode, long pages)
> >  		if (percpu_counter_compare(&sbinfo->used_blocks,
> >  					   sbinfo->max_blocks - pages) > 0)
> >  			goto unacct;
> > +		if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> > +			goto unacct;
> >  		percpu_counter_add(&sbinfo->used_blocks, pages);
> > -	}
> > +	} else if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> > +		goto unacct;
> >
> >  	return 0;
> >
> > @@ -226,6 +232,8 @@ static inline void shmem_inode_unacct_blocks(struct inode *inode, long pages)
> >  	struct shmem_inode_info *info = SHMEM_I(inode);
> >  	struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
> >
> > +	dquot_free_block_nodirty(inode, pages);
> > +
> >  	if (sbinfo->max_blocks)
> >  		percpu_counter_sub(&sbinfo->used_blocks, pages);
> >  	shmem_unacct_blocks(info->flags, pages);
> > @@ -254,6 +262,47 @@ bool vma_is_shmem(struct vm_area_struct *vma)
> >  static LIST_HEAD(shmem_swaplist);
> >  static DEFINE_MUTEX(shmem_swaplist_mutex);
> >
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +
> > +static int shmem_enable_quotas(struct super_block *sb,
> > +			       unsigned short quota_types)
> > +{
> > +	int type, err = 0;
> > +
> > +	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
> > +	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
> > +		if (!(quota_types & (1 << type)))
> > +			continue;
> > +		err = dquot_load_quota_sb(sb, type, QFMT_SHMEM,
> > +					  DQUOT_USAGE_ENABLED |
> > +					  DQUOT_LIMITS_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 /* CONFIG_TMPFS_QUOTA */
> > +
> >  /*
> >   * shmem_reserve_inode() performs bookkeeping to reserve a shmem inode, and
> >   * produces a novel ino for the newly allocated inode.
> > @@ -360,7 +409,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;
> >  		shmem_inode_unacct_blocks(inode, freed);
> >  	}
> >  }
> > @@ -378,7 +426,6 @@ bool shmem_charge(struct inode *inode, long pages)
> >
> >  	spin_lock_irqsave(&info->lock, flags);
> >  	info->alloced += pages;
> > -	inode->i_blocks += pages * BLOCKS_PER_PAGE;
> >  	shmem_recalc_inode(inode);
> >  	spin_unlock_irqrestore(&info->lock, flags);
> >
> > @@ -394,7 +441,6 @@ void shmem_uncharge(struct inode *inode, long pages)
> >
> >  	spin_lock_irqsave(&info->lock, flags);
> >  	info->alloced -= pages;
> > -	inode->i_blocks -= pages * BLOCKS_PER_PAGE;
> >  	shmem_recalc_inode(inode);
> >  	spin_unlock_irqrestore(&info->lock, flags);
> >
> > @@ -1133,6 +1179,15 @@ static int shmem_setattr(struct mnt_idmap *idmap,
> >  		}
> >  	}
> >
> > +	/* Transfer quota accounting */
> > +	if (i_uid_needs_update(idmap, attr, inode) ||
> > +	    i_gid_needs_update(idmap, attr,inode)) {
> > +		error = dquot_transfer(idmap, inode, attr);
> > +
> > +		if (error)
> > +			return error;
> > +	}
> > +
> >  	setattr_copy(idmap, inode, attr);
> >  	if (attr->ia_valid & ATTR_MODE)
> >  		error = posix_acl_chmod(idmap, dentry, inode->i_mode);
> > @@ -1178,7 +1233,9 @@ static void shmem_evict_inode(struct inode *inode)
> >  	simple_xattrs_free(&info->xattrs);
> >  	WARN_ON(inode->i_blocks);
> >  	shmem_free_inode(inode->i_sb);
> > +	dquot_free_inode(inode);
> >  	clear_inode(inode);
> > +	dquot_drop(inode);
> >  }
> >
> >  static int shmem_find_swap_entries(struct address_space *mapping,
> > @@ -1975,7 +2032,6 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
> >
> >  	spin_lock_irq(&info->lock);
> >  	info->alloced += folio_nr_pages(folio);
> > -	inode->i_blocks += (blkcnt_t)BLOCKS_PER_PAGE << folio_order(folio);
> >  	shmem_recalc_inode(inode);
> >  	spin_unlock_irq(&info->lock);
> >  	alloced = true;
> > @@ -2346,9 +2402,10 @@ static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
> >  #define shmem_initxattrs NULL
> >  #endif
> >
> > -static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block *sb,
> > -				     struct inode *dir, umode_t mode, dev_t dev,
> > -				     unsigned long flags)
> > +static struct inode *shmem_get_inode_noquota(struct mnt_idmap *idmap,
> > +					     struct super_block *sb,
> > +					     struct inode *dir, umode_t mode,
> > +					     dev_t dev, unsigned long flags)
> >  {
> >  	struct inode *inode;
> >  	struct shmem_inode_info *info;
> > @@ -2422,6 +2479,37 @@ static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block
> >  	return inode;
> >  }
> >
> > +static struct inode *shmem_get_inode(struct mnt_idmap *idmap,
> > +				     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(idmap, sb, dir, mode, dev, flags);
> > +	if (IS_ERR(inode))
> > +		return 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;
> > +	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,
> > @@ -2525,7 +2613,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);
> >
> > @@ -3372,6 +3459,7 @@ static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size)
> >
> >  static const struct inode_operations shmem_short_symlink_operations = {
> >  	.getattr	= shmem_getattr,
> > +	.setattr	= shmem_setattr,
> >  	.get_link	= simple_get_link,
> >  #ifdef CONFIG_TMPFS_XATTR
> >  	.listxattr	= shmem_listxattr,
> > @@ -3380,6 +3468,7 @@ static const struct inode_operations shmem_short_symlink_operations = {
> >
> >  static const struct inode_operations shmem_symlink_inode_operations = {
> >  	.getattr	= shmem_getattr,
> > +	.setattr	= shmem_setattr,
> >  	.get_link	= shmem_get_link,
> >  #ifdef CONFIG_TMPFS_XATTR
> >  	.listxattr	= shmem_listxattr,
> > @@ -3478,6 +3567,9 @@ enum shmem_param {
> >  	Opt_uid,
> >  	Opt_inode32,
> >  	Opt_inode64,
> > +	Opt_quota,
> > +	Opt_usrquota,
> > +	Opt_grpquota,
> >  };
> >
> >  static const struct constant_table shmem_param_enums_huge[] = {
> > @@ -3499,6 +3591,11 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
> >  	fsparam_u32   ("uid",		Opt_uid),
> >  	fsparam_flag  ("inode32",	Opt_inode32),
> >  	fsparam_flag  ("inode64",	Opt_inode64),
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	fsparam_flag  ("quota",		Opt_quota),
> > +	fsparam_flag  ("usrquota",	Opt_usrquota),
> > +	fsparam_flag  ("grpquota",	Opt_grpquota),
> > +#endif
> >  	{}
> >  };
> >
> > @@ -3582,6 +3679,18 @@ 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:
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		ctx->quota_types |= (QTYPE_MASK_USR | QTYPE_MASK_GRP);
> > +		break;
> > +	case Opt_usrquota:
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		ctx->quota_types |= QTYPE_MASK_USR;
> > +		break;
> > +	case Opt_grpquota:
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		ctx->quota_types |= QTYPE_MASK_GRP;
> > +		break;
> >  	}
> >  	return 0;
> >
> > @@ -3681,6 +3790,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)
> > @@ -3763,6 +3878,9 @@ static void shmem_put_super(struct super_block *sb)
> >  {
> >  	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
> >
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	shmem_disable_quotas(sb);
> > +#endif
> >  	free_percpu(sbinfo->ino_batch);
> >  	percpu_counter_destroy(&sbinfo->used_blocks);
> >  	mpol_put(sbinfo->mpol);
> > @@ -3841,6 +3959,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
> >  #endif
> >  	uuid_gen(&sb->s_uuid);
> >
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> > +		sb->dq_op = &shmem_quota_operations;
> > +		sb->s_qcop = &dquot_quotactl_sysfile_ops;
> > +		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
> > +
> > +		if (shmem_enable_quotas(sb, ctx->quota_types))
> > +			goto failed;
> > +	}
> > +#endif /* CONFIG_TMPFS_QUOTA */
> > +
> >  	inode = shmem_get_inode(&nop_mnt_idmap, sb, NULL, S_IFDIR | sbinfo->mode, 0,
> >  				VM_NORESERVE);
> >  	if (IS_ERR(inode)) {
> > @@ -4016,6 +4145,9 @@ static const struct super_operations shmem_ops = {
> >  #ifdef CONFIG_TMPFS
> >  	.statfs		= shmem_statfs,
> >  	.show_options	= shmem_show_options,
> > +#endif
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	.get_dquots	= shmem_get_dquots,
> >  #endif
> >  	.evict_inode	= shmem_evict_inode,
> >  	.drop_inode	= generic_delete_inode,
> > @@ -4082,6 +4214,14 @@ void __init shmem_init(void)
> >
> >  	shmem_init_inodecache();
> >
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	error = register_quota_format(&shmem_quota_format);
> > +	if (error < 0) {
> > +		pr_err("Could not register quota format\n");
> > +		goto out3;
> > +	}
> > +#endif
> > +
> >  	error = register_filesystem(&shmem_fs_type);
> >  	if (error) {
> >  		pr_err("Could not register tmpfs\n");
> > @@ -4106,6 +4246,10 @@ void __init shmem_init(void)
> >  out1:
> >  	unregister_filesystem(&shmem_fs_type);
> >  out2:
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	unregister_quota_format(&shmem_quota_format);
> > +#endif
> > +out3:
> >  	shmem_destroy_inodecache();
> >  	shm_mnt = ERR_PTR(error);
> >  }
> > --
> > 2.30.2
> >
Darrick J. Wong April 4, 2023, 4:45 p.m. UTC | #6
On Tue, Apr 04, 2023 at 03:41:19PM +0200, Carlos Maiolino wrote:
> Hi.
> 
> > >  	atomic_t		stop_eviction;	/* hold when working on inode */
> > >  	struct timespec64	i_crtime;	/* file creation time */
> > >  	unsigned int		fsflags;	/* flags for FS_IOC_[SG]ETFLAGS */
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	struct dquot		*i_dquot[MAXQUOTAS];
> > 
> > Why allocate three dquot pointers here...
> > 
> > > +#endif
> > >  	struct inode		vfs_inode;
> > >  };
> > >
> > > @@ -171,4 +174,10 @@ extern int shmem_mfill_atomic_pte(struct mm_struct *dst_mm, pmd_t *dst_pmd,
> > >  #define SHMEM_QUOTA_MAX_SPC_LIMIT 0x7fffffffffffffffLL /* 2^63-1 */
> > >  #define SHMEM_QUOTA_MAX_INO_LIMIT 0x7fffffffffffffffLL
> > >
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +#define SHMEM_MAXQUOTAS 2
> > 
> > ...when you're only allowing user and group quotas?
> 
> My bad, I should have used SHMEM_MAXQUOTAS to define the i_dquot
> 
> > 
> > (Or: Why not allow project quotas?  But that's outside the scope you
> > defined.)
> 
> This is indeed on my plan, which I want to do later, I want to deal with the
> 'avoid users to consume all memory' issue, then I want to add prjquotas here. I
> want to limit the scope of this series by now to avoid it snowballing with more
> and more features.

Ok.  This all mostly looks fine to me ... to the extent that I know
anything about tmpfs. ;)

--D

> > 
> > --D
> > 
> > > +extern const struct dquot_operations shmem_quota_operations;
> > > +extern struct quota_format_type shmem_quota_format;
> > > +#endif /* CONFIG_TMPFS_QUOTA */
> > > +
> > >  #endif
> > > diff --git a/mm/shmem.c b/mm/shmem.c
> > > index 88e13930fc013..d7529c883eaf5 100644
> > > --- a/mm/shmem.c
> > > +++ b/mm/shmem.c
> > > @@ -79,6 +79,7 @@ static struct vfsmount *shm_mnt;
> > >  #include <linux/userfaultfd_k.h>
> > >  #include <linux/rmap.h>
> > >  #include <linux/uuid.h>
> > > +#include <linux/quotaops.h>
> > >
> > >  #include <linux/uaccess.h>
> > >
> > > @@ -116,10 +117,12 @@ struct shmem_options {
> > >  	bool full_inums;
> > >  	int huge;
> > >  	int seen;
> > > +	unsigned short quota_types;
> > >  #define SHMEM_SEEN_BLOCKS 1
> > >  #define SHMEM_SEEN_INODES 2
> > >  #define SHMEM_SEEN_HUGE 4
> > >  #define SHMEM_SEEN_INUMS 8
> > > +#define SHMEM_SEEN_QUOTA 16
> > >  };
> > >
> > >  #ifdef CONFIG_TMPFS
> > > @@ -211,8 +214,11 @@ static inline int shmem_inode_acct_block(struct inode *inode, long pages)
> > >  		if (percpu_counter_compare(&sbinfo->used_blocks,
> > >  					   sbinfo->max_blocks - pages) > 0)
> > >  			goto unacct;
> > > +		if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> > > +			goto unacct;
> > >  		percpu_counter_add(&sbinfo->used_blocks, pages);
> > > -	}
> > > +	} else if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> > > +		goto unacct;
> > >
> > >  	return 0;
> > >
> > > @@ -226,6 +232,8 @@ static inline void shmem_inode_unacct_blocks(struct inode *inode, long pages)
> > >  	struct shmem_inode_info *info = SHMEM_I(inode);
> > >  	struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
> > >
> > > +	dquot_free_block_nodirty(inode, pages);
> > > +
> > >  	if (sbinfo->max_blocks)
> > >  		percpu_counter_sub(&sbinfo->used_blocks, pages);
> > >  	shmem_unacct_blocks(info->flags, pages);
> > > @@ -254,6 +262,47 @@ bool vma_is_shmem(struct vm_area_struct *vma)
> > >  static LIST_HEAD(shmem_swaplist);
> > >  static DEFINE_MUTEX(shmem_swaplist_mutex);
> > >
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +
> > > +static int shmem_enable_quotas(struct super_block *sb,
> > > +			       unsigned short quota_types)
> > > +{
> > > +	int type, err = 0;
> > > +
> > > +	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
> > > +	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
> > > +		if (!(quota_types & (1 << type)))
> > > +			continue;
> > > +		err = dquot_load_quota_sb(sb, type, QFMT_SHMEM,
> > > +					  DQUOT_USAGE_ENABLED |
> > > +					  DQUOT_LIMITS_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 /* CONFIG_TMPFS_QUOTA */
> > > +
> > >  /*
> > >   * shmem_reserve_inode() performs bookkeeping to reserve a shmem inode, and
> > >   * produces a novel ino for the newly allocated inode.
> > > @@ -360,7 +409,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;
> > >  		shmem_inode_unacct_blocks(inode, freed);
> > >  	}
> > >  }
> > > @@ -378,7 +426,6 @@ bool shmem_charge(struct inode *inode, long pages)
> > >
> > >  	spin_lock_irqsave(&info->lock, flags);
> > >  	info->alloced += pages;
> > > -	inode->i_blocks += pages * BLOCKS_PER_PAGE;
> > >  	shmem_recalc_inode(inode);
> > >  	spin_unlock_irqrestore(&info->lock, flags);
> > >
> > > @@ -394,7 +441,6 @@ void shmem_uncharge(struct inode *inode, long pages)
> > >
> > >  	spin_lock_irqsave(&info->lock, flags);
> > >  	info->alloced -= pages;
> > > -	inode->i_blocks -= pages * BLOCKS_PER_PAGE;
> > >  	shmem_recalc_inode(inode);
> > >  	spin_unlock_irqrestore(&info->lock, flags);
> > >
> > > @@ -1133,6 +1179,15 @@ static int shmem_setattr(struct mnt_idmap *idmap,
> > >  		}
> > >  	}
> > >
> > > +	/* Transfer quota accounting */
> > > +	if (i_uid_needs_update(idmap, attr, inode) ||
> > > +	    i_gid_needs_update(idmap, attr,inode)) {
> > > +		error = dquot_transfer(idmap, inode, attr);
> > > +
> > > +		if (error)
> > > +			return error;
> > > +	}
> > > +
> > >  	setattr_copy(idmap, inode, attr);
> > >  	if (attr->ia_valid & ATTR_MODE)
> > >  		error = posix_acl_chmod(idmap, dentry, inode->i_mode);
> > > @@ -1178,7 +1233,9 @@ static void shmem_evict_inode(struct inode *inode)
> > >  	simple_xattrs_free(&info->xattrs);
> > >  	WARN_ON(inode->i_blocks);
> > >  	shmem_free_inode(inode->i_sb);
> > > +	dquot_free_inode(inode);
> > >  	clear_inode(inode);
> > > +	dquot_drop(inode);
> > >  }
> > >
> > >  static int shmem_find_swap_entries(struct address_space *mapping,
> > > @@ -1975,7 +2032,6 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
> > >
> > >  	spin_lock_irq(&info->lock);
> > >  	info->alloced += folio_nr_pages(folio);
> > > -	inode->i_blocks += (blkcnt_t)BLOCKS_PER_PAGE << folio_order(folio);
> > >  	shmem_recalc_inode(inode);
> > >  	spin_unlock_irq(&info->lock);
> > >  	alloced = true;
> > > @@ -2346,9 +2402,10 @@ static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
> > >  #define shmem_initxattrs NULL
> > >  #endif
> > >
> > > -static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block *sb,
> > > -				     struct inode *dir, umode_t mode, dev_t dev,
> > > -				     unsigned long flags)
> > > +static struct inode *shmem_get_inode_noquota(struct mnt_idmap *idmap,
> > > +					     struct super_block *sb,
> > > +					     struct inode *dir, umode_t mode,
> > > +					     dev_t dev, unsigned long flags)
> > >  {
> > >  	struct inode *inode;
> > >  	struct shmem_inode_info *info;
> > > @@ -2422,6 +2479,37 @@ static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block
> > >  	return inode;
> > >  }
> > >
> > > +static struct inode *shmem_get_inode(struct mnt_idmap *idmap,
> > > +				     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(idmap, sb, dir, mode, dev, flags);
> > > +	if (IS_ERR(inode))
> > > +		return 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;
> > > +	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,
> > > @@ -2525,7 +2613,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);
> > >
> > > @@ -3372,6 +3459,7 @@ static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size)
> > >
> > >  static const struct inode_operations shmem_short_symlink_operations = {
> > >  	.getattr	= shmem_getattr,
> > > +	.setattr	= shmem_setattr,
> > >  	.get_link	= simple_get_link,
> > >  #ifdef CONFIG_TMPFS_XATTR
> > >  	.listxattr	= shmem_listxattr,
> > > @@ -3380,6 +3468,7 @@ static const struct inode_operations shmem_short_symlink_operations = {
> > >
> > >  static const struct inode_operations shmem_symlink_inode_operations = {
> > >  	.getattr	= shmem_getattr,
> > > +	.setattr	= shmem_setattr,
> > >  	.get_link	= shmem_get_link,
> > >  #ifdef CONFIG_TMPFS_XATTR
> > >  	.listxattr	= shmem_listxattr,
> > > @@ -3478,6 +3567,9 @@ enum shmem_param {
> > >  	Opt_uid,
> > >  	Opt_inode32,
> > >  	Opt_inode64,
> > > +	Opt_quota,
> > > +	Opt_usrquota,
> > > +	Opt_grpquota,
> > >  };
> > >
> > >  static const struct constant_table shmem_param_enums_huge[] = {
> > > @@ -3499,6 +3591,11 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
> > >  	fsparam_u32   ("uid",		Opt_uid),
> > >  	fsparam_flag  ("inode32",	Opt_inode32),
> > >  	fsparam_flag  ("inode64",	Opt_inode64),
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	fsparam_flag  ("quota",		Opt_quota),
> > > +	fsparam_flag  ("usrquota",	Opt_usrquota),
> > > +	fsparam_flag  ("grpquota",	Opt_grpquota),
> > > +#endif
> > >  	{}
> > >  };
> > >
> > > @@ -3582,6 +3679,18 @@ 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:
> > > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > > +		ctx->quota_types |= (QTYPE_MASK_USR | QTYPE_MASK_GRP);
> > > +		break;
> > > +	case Opt_usrquota:
> > > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > > +		ctx->quota_types |= QTYPE_MASK_USR;
> > > +		break;
> > > +	case Opt_grpquota:
> > > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > > +		ctx->quota_types |= QTYPE_MASK_GRP;
> > > +		break;
> > >  	}
> > >  	return 0;
> > >
> > > @@ -3681,6 +3790,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)
> > > @@ -3763,6 +3878,9 @@ static void shmem_put_super(struct super_block *sb)
> > >  {
> > >  	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
> > >
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	shmem_disable_quotas(sb);
> > > +#endif
> > >  	free_percpu(sbinfo->ino_batch);
> > >  	percpu_counter_destroy(&sbinfo->used_blocks);
> > >  	mpol_put(sbinfo->mpol);
> > > @@ -3841,6 +3959,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
> > >  #endif
> > >  	uuid_gen(&sb->s_uuid);
> > >
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> > > +		sb->dq_op = &shmem_quota_operations;
> > > +		sb->s_qcop = &dquot_quotactl_sysfile_ops;
> > > +		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
> > > +
> > > +		if (shmem_enable_quotas(sb, ctx->quota_types))
> > > +			goto failed;
> > > +	}
> > > +#endif /* CONFIG_TMPFS_QUOTA */
> > > +
> > >  	inode = shmem_get_inode(&nop_mnt_idmap, sb, NULL, S_IFDIR | sbinfo->mode, 0,
> > >  				VM_NORESERVE);
> > >  	if (IS_ERR(inode)) {
> > > @@ -4016,6 +4145,9 @@ static const struct super_operations shmem_ops = {
> > >  #ifdef CONFIG_TMPFS
> > >  	.statfs		= shmem_statfs,
> > >  	.show_options	= shmem_show_options,
> > > +#endif
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	.get_dquots	= shmem_get_dquots,
> > >  #endif
> > >  	.evict_inode	= shmem_evict_inode,
> > >  	.drop_inode	= generic_delete_inode,
> > > @@ -4082,6 +4214,14 @@ void __init shmem_init(void)
> > >
> > >  	shmem_init_inodecache();
> > >
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	error = register_quota_format(&shmem_quota_format);
> > > +	if (error < 0) {
> > > +		pr_err("Could not register quota format\n");
> > > +		goto out3;
> > > +	}
> > > +#endif
> > > +
> > >  	error = register_filesystem(&shmem_fs_type);
> > >  	if (error) {
> > >  		pr_err("Could not register tmpfs\n");
> > > @@ -4106,6 +4246,10 @@ void __init shmem_init(void)
> > >  out1:
> > >  	unregister_filesystem(&shmem_fs_type);
> > >  out2:
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	unregister_quota_format(&shmem_quota_format);
> > > +#endif
> > > +out3:
> > >  	shmem_destroy_inodecache();
> > >  	shm_mnt = ERR_PTR(error);
> > >  }
> > > --
> > > 2.30.2
> > >
> 
> -- 
> Carlos Maiolino
Jan Kara April 5, 2023, 11:42 a.m. UTC | #7
On Mon 03-04-23 10:47:58, cem@kernel.org wrote:
> From: Lukas Czerner <lczerner@redhat.com>
> 
> Now the basic infra-structure is in place, enable quota support for tmpfs.
> 
> Signed-off-by: Lukas Czerner <lczerner@redhat.com>
> Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>

Some comments below...

> diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
> index cf38381bdb4c1..3e7e18726feb5 100644
> --- a/include/linux/shmem_fs.h
> +++ b/include/linux/shmem_fs.h
> @@ -26,6 +26,9 @@ struct shmem_inode_info {
>  	atomic_t		stop_eviction;	/* hold when working on inode */
>  	struct timespec64	i_crtime;	/* file creation time */
>  	unsigned int		fsflags;	/* flags for FS_IOC_[SG]ETFLAGS */
> +#ifdef CONFIG_TMPFS_QUOTA
> +	struct dquot		*i_dquot[MAXQUOTAS];
> +#endif
>  	struct inode		vfs_inode;
>  };
>  
> @@ -171,4 +174,10 @@ extern int shmem_mfill_atomic_pte(struct mm_struct *dst_mm, pmd_t *dst_pmd,
>  #define SHMEM_QUOTA_MAX_SPC_LIMIT 0x7fffffffffffffffLL /* 2^63-1 */
>  #define SHMEM_QUOTA_MAX_INO_LIMIT 0x7fffffffffffffffLL
>  
> +#ifdef CONFIG_TMPFS_QUOTA
> +#define SHMEM_MAXQUOTAS 2

You have this definition already in mm/shmem_quota.c.

> +extern const struct dquot_operations shmem_quota_operations;
> +extern struct quota_format_type shmem_quota_format;
> +#endif /* CONFIG_TMPFS_QUOTA */
> +
>  #endif
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 88e13930fc013..d7529c883eaf5 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -79,6 +79,7 @@ static struct vfsmount *shm_mnt;
>  #include <linux/userfaultfd_k.h>
>  #include <linux/rmap.h>
>  #include <linux/uuid.h>
> +#include <linux/quotaops.h>
>  
>  #include <linux/uaccess.h>
>  
> @@ -116,10 +117,12 @@ struct shmem_options {
>  	bool full_inums;
>  	int huge;
>  	int seen;
> +	unsigned short quota_types;
>  #define SHMEM_SEEN_BLOCKS 1
>  #define SHMEM_SEEN_INODES 2
>  #define SHMEM_SEEN_HUGE 4
>  #define SHMEM_SEEN_INUMS 8
> +#define SHMEM_SEEN_QUOTA 16
>  };
>  
>  #ifdef CONFIG_TMPFS
> @@ -211,8 +214,11 @@ static inline int shmem_inode_acct_block(struct inode *inode, long pages)
>  		if (percpu_counter_compare(&sbinfo->used_blocks,
>  					   sbinfo->max_blocks - pages) > 0)
>  			goto unacct;
> +		if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> +			goto unacct;

We generally try to avoid assignments in conditions so I'd do:

		err = dquot_alloc_block_nodirty(inode, pages);
		if (err)
			goto unacct;

>  		percpu_counter_add(&sbinfo->used_blocks, pages);
> -	}
> +	} else if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> +		goto unacct;
>  

The same here...

> @@ -1133,6 +1179,15 @@ static int shmem_setattr(struct mnt_idmap *idmap,
>  		}
>  	}
>  
> +	/* Transfer quota accounting */
> +	if (i_uid_needs_update(idmap, attr, inode) ||
> +	    i_gid_needs_update(idmap, attr,inode)) {
> +		error = dquot_transfer(idmap, inode, attr);
> +
> +		if (error)
> +			return error;
> +	}
> +

I think you also need to add:

        if (is_quota_modification(idmap, inode, attr)) {
                error = dquot_initialize(inode);
                if (error)
                        return error;
        }

to shmem_setattr().

>  	setattr_copy(idmap, inode, attr);
>  	if (attr->ia_valid & ATTR_MODE)
>  		error = posix_acl_chmod(idmap, dentry, inode->i_mode);
> @@ -1178,7 +1233,9 @@ static void shmem_evict_inode(struct inode *inode)
>  	simple_xattrs_free(&info->xattrs);
>  	WARN_ON(inode->i_blocks);
>  	shmem_free_inode(inode->i_sb);
> +	dquot_free_inode(inode);
>  	clear_inode(inode);
> +	dquot_drop(inode);
>  }
>  
>  static int shmem_find_swap_entries(struct address_space *mapping,
> @@ -1975,7 +2032,6 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
>  
>  	spin_lock_irq(&info->lock);
>  	info->alloced += folio_nr_pages(folio);
> -	inode->i_blocks += (blkcnt_t)BLOCKS_PER_PAGE << folio_order(folio);
>  	shmem_recalc_inode(inode);
>  	spin_unlock_irq(&info->lock);
>  	alloced = true;
> @@ -2346,9 +2402,10 @@ static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
>  #define shmem_initxattrs NULL
>  #endif
>  
> -static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block *sb,
> -				     struct inode *dir, umode_t mode, dev_t dev,
> -				     unsigned long flags)
> +static struct inode *shmem_get_inode_noquota(struct mnt_idmap *idmap,
> +					     struct super_block *sb,
> +					     struct inode *dir, umode_t mode,
> +					     dev_t dev, unsigned long flags)
>  {
>  	struct inode *inode;
>  	struct shmem_inode_info *info;
> @@ -2422,6 +2479,37 @@ static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block
>  	return inode;
>  }
>  
> +static struct inode *shmem_get_inode(struct mnt_idmap *idmap,
> +				     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(idmap, sb, dir, mode, dev, flags);
> +	if (IS_ERR(inode))
> +		return 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;
> +	iput(inode);
> +	shmem_free_inode(sb);

I think shmem_free_inode() is superfluous here. iput() above should already
unaccount the inode...

> +	if (err)

How could err be possibly unset here?

> +		return ERR_PTR(err);
> +	return NULL;
> +}
> +

> @@ -3582,6 +3679,18 @@ 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:
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		ctx->quota_types |= (QTYPE_MASK_USR | QTYPE_MASK_GRP);
> +		break;
> +	case Opt_usrquota:
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		ctx->quota_types |= QTYPE_MASK_USR;
> +		break;
> +	case Opt_grpquota:
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		ctx->quota_types |= QTYPE_MASK_GRP;
> +		break;
>  	}
>  	return 0;
>  
> @@ -3681,6 +3790,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)
> @@ -3763,6 +3878,9 @@ static void shmem_put_super(struct super_block *sb)
>  {
>  	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
>  
> +#ifdef CONFIG_TMPFS_QUOTA
> +	shmem_disable_quotas(sb);
> +#endif
>  	free_percpu(sbinfo->ino_batch);
>  	percpu_counter_destroy(&sbinfo->used_blocks);
>  	mpol_put(sbinfo->mpol);
> @@ -3841,6 +3959,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
>  #endif
>  	uuid_gen(&sb->s_uuid);
>  
> +#ifdef CONFIG_TMPFS_QUOTA
> +	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> +		sb->dq_op = &shmem_quota_operations;
> +		sb->s_qcop = &dquot_quotactl_sysfile_ops;
> +		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;

s_quota_types should rather be copied from ctx, shouldn't it? Or why is
s_quota_types inconsistent with ctx->quota_types?

								Honza
Carlos Maiolino April 11, 2023, 9:37 a.m. UTC | #8
On Wed, Apr 05, 2023 at 01:42:45PM +0200, Jan Kara wrote:
> On Mon 03-04-23 10:47:58, cem@kernel.org wrote:
> > From: Lukas Czerner <lczerner@redhat.com>
> >
> > Now the basic infra-structure is in place, enable quota support for tmpfs.
> >
> > Signed-off-by: Lukas Czerner <lczerner@redhat.com>
> > Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
> 
> Some comments below...
> 
> > diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
> > index cf38381bdb4c1..3e7e18726feb5 100644
> > --- a/include/linux/shmem_fs.h
> > +++ b/include/linux/shmem_fs.h
> > @@ -26,6 +26,9 @@ struct shmem_inode_info {
> >  	atomic_t		stop_eviction;	/* hold when working on inode */
> >  	struct timespec64	i_crtime;	/* file creation time */
> >  	unsigned int		fsflags;	/* flags for FS_IOC_[SG]ETFLAGS */
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	struct dquot		*i_dquot[MAXQUOTAS];
> > +#endif
> >  	struct inode		vfs_inode;
> >  };
> >
> > @@ -171,4 +174,10 @@ extern int shmem_mfill_atomic_pte(struct mm_struct *dst_mm, pmd_t *dst_pmd,
> >  #define SHMEM_QUOTA_MAX_SPC_LIMIT 0x7fffffffffffffffLL /* 2^63-1 */
> >  #define SHMEM_QUOTA_MAX_INO_LIMIT 0x7fffffffffffffffLL
> >
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +#define SHMEM_MAXQUOTAS 2
> 
> You have this definition already in mm/shmem_quota.c.
> 

True, that define is not visible from here though, I'll simply remove the one
from mm/shmem_quota.c and keep it here in shmem_fs.h.


> > +extern const struct dquot_operations shmem_quota_operations;
> > +extern struct quota_format_type shmem_quota_format;
> > +#endif /* CONFIG_TMPFS_QUOTA */
> > +
> >  #endif
> > diff --git a/mm/shmem.c b/mm/shmem.c
> > index 88e13930fc013..d7529c883eaf5 100644
> > --- a/mm/shmem.c
> > +++ b/mm/shmem.c
> > @@ -79,6 +79,7 @@ static struct vfsmount *shm_mnt;
> >  #include <linux/userfaultfd_k.h>
> >  #include <linux/rmap.h>
> >  #include <linux/uuid.h>
> > +#include <linux/quotaops.h>
> >
> >  #include <linux/uaccess.h>
> >
> > @@ -116,10 +117,12 @@ struct shmem_options {
> >  	bool full_inums;
> >  	int huge;
> >  	int seen;
> > +	unsigned short quota_types;
> >  #define SHMEM_SEEN_BLOCKS 1
> >  #define SHMEM_SEEN_INODES 2
> >  #define SHMEM_SEEN_HUGE 4
> >  #define SHMEM_SEEN_INUMS 8
> > +#define SHMEM_SEEN_QUOTA 16
> >  };
> >
> >  #ifdef CONFIG_TMPFS
> > @@ -211,8 +214,11 @@ static inline int shmem_inode_acct_block(struct inode *inode, long pages)
> >  		if (percpu_counter_compare(&sbinfo->used_blocks,
> >  					   sbinfo->max_blocks - pages) > 0)
> >  			goto unacct;
> > +		if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> > +			goto unacct;
> 
> We generally try to avoid assignments in conditions so I'd do:
> 
> 		err = dquot_alloc_block_nodirty(inode, pages);
> 		if (err)
> 			goto unacct;

Fair enough. Will update it for the new version
> 
> >  		percpu_counter_add(&sbinfo->used_blocks, pages);
> > -	}
> > +	} else if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
> > +		goto unacct;
> >
> 
> The same here...
> 
> > @@ -1133,6 +1179,15 @@ static int shmem_setattr(struct mnt_idmap *idmap,
> >  		}
> >  	}
> >
> > +	/* Transfer quota accounting */
> > +	if (i_uid_needs_update(idmap, attr, inode) ||
> > +	    i_gid_needs_update(idmap, attr,inode)) {
> > +		error = dquot_transfer(idmap, inode, attr);
> > +
> > +		if (error)
> > +			return error;
> > +	}
> > +
> 
> I think you also need to add:
> 
>         if (is_quota_modification(idmap, inode, attr)) {
>                 error = dquot_initialize(inode);
>                 if (error)
>                         return error;
>         }
> 
> to shmem_setattr().

Ok.

> 
> >  	setattr_copy(idmap, inode, attr);
> >  	if (attr->ia_valid & ATTR_MODE)
> >  		error = posix_acl_chmod(idmap, dentry, inode->i_mode);
> > @@ -1178,7 +1233,9 @@ static void shmem_evict_inode(struct inode *inode)
> >  	simple_xattrs_free(&info->xattrs);
> >  	WARN_ON(inode->i_blocks);
> >  	shmem_free_inode(inode->i_sb);
> > +	dquot_free_inode(inode);
> >  	clear_inode(inode);
> > +	dquot_drop(inode);
> >  }
> >
> >  static int shmem_find_swap_entries(struct address_space *mapping,
> > @@ -1975,7 +2032,6 @@ static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
> >
> >  	spin_lock_irq(&info->lock);
> >  	info->alloced += folio_nr_pages(folio);
> > -	inode->i_blocks += (blkcnt_t)BLOCKS_PER_PAGE << folio_order(folio);
> >  	shmem_recalc_inode(inode);
> >  	spin_unlock_irq(&info->lock);
> >  	alloced = true;
> > @@ -2346,9 +2402,10 @@ static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
> >  #define shmem_initxattrs NULL
> >  #endif
> >
> > -static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block *sb,
> > -				     struct inode *dir, umode_t mode, dev_t dev,
> > -				     unsigned long flags)
> > +static struct inode *shmem_get_inode_noquota(struct mnt_idmap *idmap,
> > +					     struct super_block *sb,
> > +					     struct inode *dir, umode_t mode,
> > +					     dev_t dev, unsigned long flags)
> >  {
> >  	struct inode *inode;
> >  	struct shmem_inode_info *info;
> > @@ -2422,6 +2479,37 @@ static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block
> >  	return inode;
> >  }
> >
> > +static struct inode *shmem_get_inode(struct mnt_idmap *idmap,
> > +				     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(idmap, sb, dir, mode, dev, flags);
> > +	if (IS_ERR(inode))
> > +		return 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;
> > +	iput(inode);
> > +	shmem_free_inode(sb);
> 
> I think shmem_free_inode() is superfluous here. iput() above should already
> unaccount the inode...

Right, I see it can be called from .evict_inode during iput_final(). Thanks for
spotting it.

> 
> > +	if (err)
> 
> How could err be possibly unset here?

I don't think it can, I'll update it.

> 
> > +		return ERR_PTR(err);
> > +	return NULL;
> > +}
> > +
> 
> > @@ -3582,6 +3679,18 @@ 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:
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		ctx->quota_types |= (QTYPE_MASK_USR | QTYPE_MASK_GRP);
> > +		break;
> > +	case Opt_usrquota:
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		ctx->quota_types |= QTYPE_MASK_USR;
> > +		break;
> > +	case Opt_grpquota:
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		ctx->quota_types |= QTYPE_MASK_GRP;
> > +		break;
> >  	}
> >  	return 0;
> >
> > @@ -3681,6 +3790,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)
> > @@ -3763,6 +3878,9 @@ static void shmem_put_super(struct super_block *sb)
> >  {
> >  	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
> >
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	shmem_disable_quotas(sb);
> > +#endif
> >  	free_percpu(sbinfo->ino_batch);
> >  	percpu_counter_destroy(&sbinfo->used_blocks);
> >  	mpol_put(sbinfo->mpol);
> > @@ -3841,6 +3959,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
> >  #endif
> >  	uuid_gen(&sb->s_uuid);
> >
> > +#ifdef CONFIG_TMPFS_QUOTA
> > +	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> > +		sb->dq_op = &shmem_quota_operations;
> > +		sb->s_qcop = &dquot_quotactl_sysfile_ops;
> > +		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
> 
> s_quota_types should rather be copied from ctx, shouldn't it? Or why is
> s_quota_types inconsistent with ctx->quota_types?

I believe s_qupta_types here is a bitmask of supported quota types, while
ctx->quota_types refers to the mount options being passed from the user.

So we should enable in sb->s_quota_types which quota types the filesystem
supports, not which were enabled by the user.

Cheers.
Jan Kara April 11, 2023, 1:03 p.m. UTC | #9
On Tue 11-04-23 11:37:26, Carlos Maiolino wrote:
> On Wed, Apr 05, 2023 at 01:42:45PM +0200, Jan Kara wrote:
> > > @@ -3763,6 +3878,9 @@ static void shmem_put_super(struct super_block *sb)
> > >  {
> > >  	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
> > >
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	shmem_disable_quotas(sb);
> > > +#endif
> > >  	free_percpu(sbinfo->ino_batch);
> > >  	percpu_counter_destroy(&sbinfo->used_blocks);
> > >  	mpol_put(sbinfo->mpol);
> > > @@ -3841,6 +3959,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
> > >  #endif
> > >  	uuid_gen(&sb->s_uuid);
> > >
> > > +#ifdef CONFIG_TMPFS_QUOTA
> > > +	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> > > +		sb->dq_op = &shmem_quota_operations;
> > > +		sb->s_qcop = &dquot_quotactl_sysfile_ops;
> > > +		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
> > 
> > s_quota_types should rather be copied from ctx, shouldn't it? Or why is
> > s_quota_types inconsistent with ctx->quota_types?
> 
> I believe s_qupta_types here is a bitmask of supported quota types, while
> ctx->quota_types refers to the mount options being passed from the user.
> 
> So we should enable in sb->s_quota_types which quota types the filesystem
> supports, not which were enabled by the user.

Oh, right.

								Honza
diff mbox series

Patch

diff --git a/Documentation/filesystems/tmpfs.rst b/Documentation/filesystems/tmpfs.rst
index 0408c245785e3..3f8d89bb7e1a5 100644
--- a/Documentation/filesystems/tmpfs.rst
+++ b/Documentation/filesystems/tmpfs.rst
@@ -86,6 +86,18 @@  use up all the memory on the machine; but enhances the scalability of
 that instance in a system with many CPUs making intensive use of it.
 
 
+tmpfs also supports quota with the following mount options
+
+========  =============================================================
+quota     User and group quota accounting and enforcement is enabled on
+          the mount. Tmpfs is using hidden system quota files that are
+          initialized on mount.
+usrquota  User quota accounting and enforcement is enabled on the
+          mount.
+grpquota  Group quota accounting and enforcement is enabled on the
+          mount.
+========  =============================================================
+
 tmpfs has a mount option to set the NUMA memory allocation policy for
 all files in that instance (if CONFIG_NUMA is enabled) - which can be
 adjusted on the fly via 'mount -o remount ...'
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index cf38381bdb4c1..3e7e18726feb5 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -26,6 +26,9 @@  struct shmem_inode_info {
 	atomic_t		stop_eviction;	/* hold when working on inode */
 	struct timespec64	i_crtime;	/* file creation time */
 	unsigned int		fsflags;	/* flags for FS_IOC_[SG]ETFLAGS */
+#ifdef CONFIG_TMPFS_QUOTA
+	struct dquot		*i_dquot[MAXQUOTAS];
+#endif
 	struct inode		vfs_inode;
 };
 
@@ -171,4 +174,10 @@  extern int shmem_mfill_atomic_pte(struct mm_struct *dst_mm, pmd_t *dst_pmd,
 #define SHMEM_QUOTA_MAX_SPC_LIMIT 0x7fffffffffffffffLL /* 2^63-1 */
 #define SHMEM_QUOTA_MAX_INO_LIMIT 0x7fffffffffffffffLL
 
+#ifdef CONFIG_TMPFS_QUOTA
+#define SHMEM_MAXQUOTAS 2
+extern const struct dquot_operations shmem_quota_operations;
+extern struct quota_format_type shmem_quota_format;
+#endif /* CONFIG_TMPFS_QUOTA */
+
 #endif
diff --git a/mm/shmem.c b/mm/shmem.c
index 88e13930fc013..d7529c883eaf5 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -79,6 +79,7 @@  static struct vfsmount *shm_mnt;
 #include <linux/userfaultfd_k.h>
 #include <linux/rmap.h>
 #include <linux/uuid.h>
+#include <linux/quotaops.h>
 
 #include <linux/uaccess.h>
 
@@ -116,10 +117,12 @@  struct shmem_options {
 	bool full_inums;
 	int huge;
 	int seen;
+	unsigned short quota_types;
 #define SHMEM_SEEN_BLOCKS 1
 #define SHMEM_SEEN_INODES 2
 #define SHMEM_SEEN_HUGE 4
 #define SHMEM_SEEN_INUMS 8
+#define SHMEM_SEEN_QUOTA 16
 };
 
 #ifdef CONFIG_TMPFS
@@ -211,8 +214,11 @@  static inline int shmem_inode_acct_block(struct inode *inode, long pages)
 		if (percpu_counter_compare(&sbinfo->used_blocks,
 					   sbinfo->max_blocks - pages) > 0)
 			goto unacct;
+		if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
+			goto unacct;
 		percpu_counter_add(&sbinfo->used_blocks, pages);
-	}
+	} else if ((err = dquot_alloc_block_nodirty(inode, pages)) != 0)
+		goto unacct;
 
 	return 0;
 
@@ -226,6 +232,8 @@  static inline void shmem_inode_unacct_blocks(struct inode *inode, long pages)
 	struct shmem_inode_info *info = SHMEM_I(inode);
 	struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
 
+	dquot_free_block_nodirty(inode, pages);
+
 	if (sbinfo->max_blocks)
 		percpu_counter_sub(&sbinfo->used_blocks, pages);
 	shmem_unacct_blocks(info->flags, pages);
@@ -254,6 +262,47 @@  bool vma_is_shmem(struct vm_area_struct *vma)
 static LIST_HEAD(shmem_swaplist);
 static DEFINE_MUTEX(shmem_swaplist_mutex);
 
+#ifdef CONFIG_TMPFS_QUOTA
+
+static int shmem_enable_quotas(struct super_block *sb,
+			       unsigned short quota_types)
+{
+	int type, err = 0;
+
+	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
+	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
+		if (!(quota_types & (1 << type)))
+			continue;
+		err = dquot_load_quota_sb(sb, type, QFMT_SHMEM,
+					  DQUOT_USAGE_ENABLED |
+					  DQUOT_LIMITS_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 /* CONFIG_TMPFS_QUOTA */
+
 /*
  * shmem_reserve_inode() performs bookkeeping to reserve a shmem inode, and
  * produces a novel ino for the newly allocated inode.
@@ -360,7 +409,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;
 		shmem_inode_unacct_blocks(inode, freed);
 	}
 }
@@ -378,7 +426,6 @@  bool shmem_charge(struct inode *inode, long pages)
 
 	spin_lock_irqsave(&info->lock, flags);
 	info->alloced += pages;
-	inode->i_blocks += pages * BLOCKS_PER_PAGE;
 	shmem_recalc_inode(inode);
 	spin_unlock_irqrestore(&info->lock, flags);
 
@@ -394,7 +441,6 @@  void shmem_uncharge(struct inode *inode, long pages)
 
 	spin_lock_irqsave(&info->lock, flags);
 	info->alloced -= pages;
-	inode->i_blocks -= pages * BLOCKS_PER_PAGE;
 	shmem_recalc_inode(inode);
 	spin_unlock_irqrestore(&info->lock, flags);
 
@@ -1133,6 +1179,15 @@  static int shmem_setattr(struct mnt_idmap *idmap,
 		}
 	}
 
+	/* Transfer quota accounting */
+	if (i_uid_needs_update(idmap, attr, inode) ||
+	    i_gid_needs_update(idmap, attr,inode)) {
+		error = dquot_transfer(idmap, inode, attr);
+
+		if (error)
+			return error;
+	}
+
 	setattr_copy(idmap, inode, attr);
 	if (attr->ia_valid & ATTR_MODE)
 		error = posix_acl_chmod(idmap, dentry, inode->i_mode);
@@ -1178,7 +1233,9 @@  static void shmem_evict_inode(struct inode *inode)
 	simple_xattrs_free(&info->xattrs);
 	WARN_ON(inode->i_blocks);
 	shmem_free_inode(inode->i_sb);
+	dquot_free_inode(inode);
 	clear_inode(inode);
+	dquot_drop(inode);
 }
 
 static int shmem_find_swap_entries(struct address_space *mapping,
@@ -1975,7 +2032,6 @@  static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,
 
 	spin_lock_irq(&info->lock);
 	info->alloced += folio_nr_pages(folio);
-	inode->i_blocks += (blkcnt_t)BLOCKS_PER_PAGE << folio_order(folio);
 	shmem_recalc_inode(inode);
 	spin_unlock_irq(&info->lock);
 	alloced = true;
@@ -2346,9 +2402,10 @@  static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
 #define shmem_initxattrs NULL
 #endif
 
-static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block *sb,
-				     struct inode *dir, umode_t mode, dev_t dev,
-				     unsigned long flags)
+static struct inode *shmem_get_inode_noquota(struct mnt_idmap *idmap,
+					     struct super_block *sb,
+					     struct inode *dir, umode_t mode,
+					     dev_t dev, unsigned long flags)
 {
 	struct inode *inode;
 	struct shmem_inode_info *info;
@@ -2422,6 +2479,37 @@  static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block
 	return inode;
 }
 
+static struct inode *shmem_get_inode(struct mnt_idmap *idmap,
+				     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(idmap, sb, dir, mode, dev, flags);
+	if (IS_ERR(inode))
+		return 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;
+	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,
@@ -2525,7 +2613,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);
 
@@ -3372,6 +3459,7 @@  static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size)
 
 static const struct inode_operations shmem_short_symlink_operations = {
 	.getattr	= shmem_getattr,
+	.setattr	= shmem_setattr,
 	.get_link	= simple_get_link,
 #ifdef CONFIG_TMPFS_XATTR
 	.listxattr	= shmem_listxattr,
@@ -3380,6 +3468,7 @@  static const struct inode_operations shmem_short_symlink_operations = {
 
 static const struct inode_operations shmem_symlink_inode_operations = {
 	.getattr	= shmem_getattr,
+	.setattr	= shmem_setattr,
 	.get_link	= shmem_get_link,
 #ifdef CONFIG_TMPFS_XATTR
 	.listxattr	= shmem_listxattr,
@@ -3478,6 +3567,9 @@  enum shmem_param {
 	Opt_uid,
 	Opt_inode32,
 	Opt_inode64,
+	Opt_quota,
+	Opt_usrquota,
+	Opt_grpquota,
 };
 
 static const struct constant_table shmem_param_enums_huge[] = {
@@ -3499,6 +3591,11 @@  const struct fs_parameter_spec shmem_fs_parameters[] = {
 	fsparam_u32   ("uid",		Opt_uid),
 	fsparam_flag  ("inode32",	Opt_inode32),
 	fsparam_flag  ("inode64",	Opt_inode64),
+#ifdef CONFIG_TMPFS_QUOTA
+	fsparam_flag  ("quota",		Opt_quota),
+	fsparam_flag  ("usrquota",	Opt_usrquota),
+	fsparam_flag  ("grpquota",	Opt_grpquota),
+#endif
 	{}
 };
 
@@ -3582,6 +3679,18 @@  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:
+		ctx->seen |= SHMEM_SEEN_QUOTA;
+		ctx->quota_types |= (QTYPE_MASK_USR | QTYPE_MASK_GRP);
+		break;
+	case Opt_usrquota:
+		ctx->seen |= SHMEM_SEEN_QUOTA;
+		ctx->quota_types |= QTYPE_MASK_USR;
+		break;
+	case Opt_grpquota:
+		ctx->seen |= SHMEM_SEEN_QUOTA;
+		ctx->quota_types |= QTYPE_MASK_GRP;
+		break;
 	}
 	return 0;
 
@@ -3681,6 +3790,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)
@@ -3763,6 +3878,9 @@  static void shmem_put_super(struct super_block *sb)
 {
 	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
 
+#ifdef CONFIG_TMPFS_QUOTA
+	shmem_disable_quotas(sb);
+#endif
 	free_percpu(sbinfo->ino_batch);
 	percpu_counter_destroy(&sbinfo->used_blocks);
 	mpol_put(sbinfo->mpol);
@@ -3841,6 +3959,17 @@  static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
 #endif
 	uuid_gen(&sb->s_uuid);
 
+#ifdef CONFIG_TMPFS_QUOTA
+	if (ctx->seen & SHMEM_SEEN_QUOTA) {
+		sb->dq_op = &shmem_quota_operations;
+		sb->s_qcop = &dquot_quotactl_sysfile_ops;
+		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
+
+		if (shmem_enable_quotas(sb, ctx->quota_types))
+			goto failed;
+	}
+#endif /* CONFIG_TMPFS_QUOTA */
+
 	inode = shmem_get_inode(&nop_mnt_idmap, sb, NULL, S_IFDIR | sbinfo->mode, 0,
 				VM_NORESERVE);
 	if (IS_ERR(inode)) {
@@ -4016,6 +4145,9 @@  static const struct super_operations shmem_ops = {
 #ifdef CONFIG_TMPFS
 	.statfs		= shmem_statfs,
 	.show_options	= shmem_show_options,
+#endif
+#ifdef CONFIG_TMPFS_QUOTA
+	.get_dquots	= shmem_get_dquots,
 #endif
 	.evict_inode	= shmem_evict_inode,
 	.drop_inode	= generic_delete_inode,
@@ -4082,6 +4214,14 @@  void __init shmem_init(void)
 
 	shmem_init_inodecache();
 
+#ifdef CONFIG_TMPFS_QUOTA
+	error = register_quota_format(&shmem_quota_format);
+	if (error < 0) {
+		pr_err("Could not register quota format\n");
+		goto out3;
+	}
+#endif
+
 	error = register_filesystem(&shmem_fs_type);
 	if (error) {
 		pr_err("Could not register tmpfs\n");
@@ -4106,6 +4246,10 @@  void __init shmem_init(void)
 out1:
 	unregister_filesystem(&shmem_fs_type);
 out2:
+#ifdef CONFIG_TMPFS_QUOTA
+	unregister_quota_format(&shmem_quota_format);
+#endif
+out3:
 	shmem_destroy_inodecache();
 	shm_mnt = ERR_PTR(error);
 }