diff mbox series

[v2,3/3] shmem: implement mount options for global quota limits

Message ID 20221121142854.91109-4-lczerner@redhat.com (mailing list archive)
State New
Headers show
Series shmem: user and group quota support for tmpfs | expand

Commit Message

Lukas Czerner Nov. 21, 2022, 2:28 p.m. UTC
Implement a set of mount options for setting glopbal quota limits on
tmpfs.

usrquota_block_hardlimit - global user quota block hard limit
usrquota_inode_hardlimit - global user quota inode hard limit
grpquota_block_hardlimit - global group quota block hard limit
grpquota_inode_hardlimit - global group quota inode hard limit

Quota limit parameters accept a suffix k, m or g for kilo, mega and giga
and can't be changed on remount. Default global quota limits are taking
effect for any and all user/group except root the first time the quota
entry for user/group id is being accessed - typically the first time an
inode with a particular id ownership is being created after the mount. In
other words, instead of the limits being initialized to zero, they are
initialized with the particular value provided with these mount options.
The limits can be changed for any user/group id at any time as it normally
can.

When any of the default quota limits are set, quota enforcement is enabled
automatically as well.

None of the quota related mount options can be set or changed on remount.

Signed-off-by: Lukas Czerner <lczerner@redhat.com>
---
v2: Rename mount option to something more sensible.
    Improve documentation.
    Check if the user provided limit isn't too large.

 Documentation/filesystems/tmpfs.rst |  36 +++++--
 include/linux/shmem_fs.h            |  10 ++
 mm/shmem.c                          | 162 ++++++++++++++++++++++++++--
 3 files changed, 190 insertions(+), 18 deletions(-)

Comments

kernel test robot Nov. 22, 2022, 6:15 a.m. UTC | #1
Hi Lukas,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on jack-fs/for_next]
[also build test WARNING on linus/master v6.1-rc6 next-20221121]
[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/Lukas-Czerner/shmem-user-and-group-quota-support-for-tmpfs/20221121-223006
base:   https://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs.git for_next
patch link:    https://lore.kernel.org/r/20221121142854.91109-4-lczerner%40redhat.com
patch subject: [PATCH v2 3/3] shmem: implement mount options for global quota limits
config: hexagon-randconfig-r045-20221121
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project af8c49dc1ec44339d915d988ffe0f38da68ca0e7)
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/e5478d3304138f156d65c72620adfb05ce079aa1
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Lukas-Czerner/shmem-user-and-group-quota-support-for-tmpfs/20221121-223006
        git checkout e5478d3304138f156d65c72620adfb05ce079aa1
        # 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 SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <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:276:5: warning: no previous prototype for function 'shmem_dquot_acquire' [-Wmissing-prototypes]
   int shmem_dquot_acquire(struct dquot *dquot)
       ^
   mm/shmem.c:276:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   int shmem_dquot_acquire(struct dquot *dquot)
   ^
   static 
   7 warnings generated.


vim +/shmem_dquot_acquire +276 mm/shmem.c

   275	
 > 276	int shmem_dquot_acquire(struct dquot *dquot)
   277	{
   278		int type, ret = 0;
   279		unsigned int memalloc;
   280		struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
   281		struct shmem_sb_info *sbinfo = SHMEM_SB(dquot->dq_sb);
   282	
   283	
   284		mutex_lock(&dquot->dq_lock);
   285		memalloc = memalloc_nofs_save();
   286		if (test_bit(DQ_READ_B, &dquot->dq_flags)) {
   287			smp_mb__before_atomic();
   288			set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
   289			goto out_iolock;
   290		}
   291	
   292		type = dquot->dq_id.type;
   293		ret = dqopt->ops[type]->read_dqblk(dquot);
   294		if (ret < 0)
   295			goto out_iolock;
   296		/* Set the defaults */
   297		if (type == USRQUOTA) {
   298			dquot->dq_dqb.dqb_bhardlimit =
   299				(sbinfo->usrquota_block_hardlimit << PAGE_SHIFT);
   300			dquot->dq_dqb.dqb_ihardlimit = sbinfo->usrquota_inode_hardlimit;
   301		} else if (type == GRPQUOTA) {
   302			dquot->dq_dqb.dqb_bhardlimit =
   303				(sbinfo->grpquota_block_hardlimit << PAGE_SHIFT);
   304			dquot->dq_dqb.dqb_ihardlimit = sbinfo->grpquota_inode_hardlimit;
   305		}
   306		/* Make sure flags update is visible after dquot has been filled */
   307		smp_mb__before_atomic();
   308		set_bit(DQ_READ_B, &dquot->dq_flags);
   309		set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
   310	out_iolock:
   311		memalloc_nofs_restore(memalloc);
   312		mutex_unlock(&dquot->dq_lock);
   313		return ret;
   314	}
   315
Brian Foster Nov. 22, 2022, 9:03 p.m. UTC | #2
On Mon, Nov 21, 2022 at 03:28:54PM +0100, Lukas Czerner wrote:
> Implement a set of mount options for setting glopbal quota limits on
> tmpfs.
> 
> usrquota_block_hardlimit - global user quota block hard limit
> usrquota_inode_hardlimit - global user quota inode hard limit
> grpquota_block_hardlimit - global group quota block hard limit
> grpquota_inode_hardlimit - global group quota inode hard limit
> 
> Quota limit parameters accept a suffix k, m or g for kilo, mega and giga
> and can't be changed on remount. Default global quota limits are taking
> effect for any and all user/group except root the first time the quota
> entry for user/group id is being accessed - typically the first time an
> inode with a particular id ownership is being created after the mount. In
> other words, instead of the limits being initialized to zero, they are
> initialized with the particular value provided with these mount options.
> The limits can be changed for any user/group id at any time as it normally
> can.
> 
> When any of the default quota limits are set, quota enforcement is enabled
> automatically as well.
> 
> None of the quota related mount options can be set or changed on remount.
> 
> Signed-off-by: Lukas Czerner <lczerner@redhat.com>
> ---
> v2: Rename mount option to something more sensible.
>     Improve documentation.
>     Check if the user provided limit isn't too large.
> 
>  Documentation/filesystems/tmpfs.rst |  36 +++++--
>  include/linux/shmem_fs.h            |  10 ++
>  mm/shmem.c                          | 162 ++++++++++++++++++++++++++--
>  3 files changed, 190 insertions(+), 18 deletions(-)
> 
...
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 26f2effd8f7c..a66a1e4cd0cb 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
...
> @@ -271,6 +273,57 @@ static DEFINE_MUTEX(shmem_swaplist_mutex);
>  
>  #define SHMEM_MAXQUOTAS 2
>  
> +int shmem_dquot_acquire(struct dquot *dquot)
> +{
> +	int type, ret = 0;
> +	unsigned int memalloc;
> +	struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
> +	struct shmem_sb_info *sbinfo = SHMEM_SB(dquot->dq_sb);
> +
> +
> +	mutex_lock(&dquot->dq_lock);
> +	memalloc = memalloc_nofs_save();
> +	if (test_bit(DQ_READ_B, &dquot->dq_flags)) {
> +		smp_mb__before_atomic();
> +		set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
> +		goto out_iolock;
> +	}
> +
> +	type = dquot->dq_id.type;
> +	ret = dqopt->ops[type]->read_dqblk(dquot);

So according to patch 1, this callback would alloc the quota id and set
DQ_FAKE_B and DQ_NO_SHRINK_B on the dquot. The shrinker will skip dquots
that are (noshrink && !fake). So as of this point the dquot would be
reclaimable if it were ultimately freed with no other changes, right?

> +	if (ret < 0)
> +		goto out_iolock;
> +	/* Set the defaults */
> +	if (type == USRQUOTA) {
> +		dquot->dq_dqb.dqb_bhardlimit =
> +			(sbinfo->usrquota_block_hardlimit << PAGE_SHIFT);
> +		dquot->dq_dqb.dqb_ihardlimit = sbinfo->usrquota_inode_hardlimit;
> +	} else if (type == GRPQUOTA) {
> +		dquot->dq_dqb.dqb_bhardlimit =
> +			(sbinfo->grpquota_block_hardlimit << PAGE_SHIFT);
> +		dquot->dq_dqb.dqb_ihardlimit = sbinfo->grpquota_inode_hardlimit;
> +	}

Then we set default limits from the mount option on the dquot. The dquot
is still has DQ_FAKE_B, so presumably the dquot would remain reclaimable
(once freed) even though it has a limit set (the mount default).

AFAICS the only place that clears DQ_FAKE_B is the setquota path, so I
take it that means the dquot becomes ultimately unreclaimable if/when
the user sets a non-zero quota limit, and then it only becomes
reclaimable again when quota limits are explicitly set to zero. Is that
the case?

If so, a couple questions:

1. Can a dquot ever be reclaimed if the user explicitly sets a quota
limit that matches the mount default?

2. How does enforcement of default limits actually work? For example, it
looks like dquot_add_inodes() skips enforcement when DQ_FAKE_B is set.
Have I missed somewhere where this flag should be cleared in this case?

Brian

> +	/* Make sure flags update is visible after dquot has been filled */
> +	smp_mb__before_atomic();
> +	set_bit(DQ_READ_B, &dquot->dq_flags);
> +	set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
> +out_iolock:
> +	memalloc_nofs_restore(memalloc);
> +	mutex_unlock(&dquot->dq_lock);
> +	return ret;
> +}
> +
> +const struct dquot_operations shmem_dquot_operations = {
> +	.write_dquot	= dquot_commit,
> +	.acquire_dquot	= shmem_dquot_acquire,
> +	.release_dquot	= dquot_release,
> +	.mark_dirty	= dquot_mark_dquot_dirty,
> +	.write_info	= dquot_commit_info,
> +	.alloc_dquot	= dquot_alloc,
> +	.destroy_dquot	= dquot_destroy,
> +	.get_next_id	= dquot_get_next_id,
> +};
> +
>  /*
>   * We don't have any quota files to read, or write to/from, but quota code
>   * requires .quota_read and .quota_write to exist.
> @@ -288,14 +341,14 @@ static ssize_t shmem_quota_read(struct super_block *sb, int type, char *data,
>  }
>  
>  
> -static int shmem_enable_quotas(struct super_block *sb)
> +static int shmem_enable_quotas(struct super_block *sb, unsigned int dquot_flags)
>  {
>  	int type, err = 0;
>  
>  	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
>  	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
>  		err = dquot_load_quota_sb(sb, type, QFMT_MEM_ONLY,
> -					  DQUOT_USAGE_ENABLED);
> +					  dquot_flags);
>  		if (err)
>  			goto out_err;
>  	}
> @@ -3559,6 +3612,10 @@ enum shmem_param {
>  	Opt_inode32,
>  	Opt_inode64,
>  	Opt_quota,
> +	Opt_usrquota_block_hardlimit,
> +	Opt_usrquota_inode_hardlimit,
> +	Opt_grpquota_block_hardlimit,
> +	Opt_grpquota_inode_hardlimit,
>  };
>  
>  static const struct constant_table shmem_param_enums_huge[] = {
> @@ -3583,6 +3640,10 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
>  	fsparam_flag  ("quota",		Opt_quota),
>  	fsparam_flag  ("usrquota",	Opt_quota),
>  	fsparam_flag  ("grpquota",	Opt_quota),
> +	fsparam_string("usrquota_block_hardlimit",	Opt_usrquota_block_hardlimit),
> +	fsparam_string("usrquota_inode_hardlimit",	Opt_usrquota_inode_hardlimit),
> +	fsparam_string("grpquota_block_hardlimit",	Opt_grpquota_block_hardlimit),
> +	fsparam_string("grpquota_inode_hardlimit",	Opt_grpquota_inode_hardlimit),
>  	{}
>  };
>  
> @@ -3666,13 +3727,60 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
>  		ctx->full_inums = true;
>  		ctx->seen |= SHMEM_SEEN_INUMS;
>  		break;
> -	case Opt_quota:
>  #ifdef CONFIG_QUOTA
> +	case Opt_quota:
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		break;
> +	case Opt_usrquota_block_hardlimit:
> +		size = memparse(param->string, &rest);
> +		if (*rest || !size)
> +			goto bad_value;
> +		size = DIV_ROUND_UP(size, PAGE_SIZE);
> +		if (size > ULONG_MAX)
> +			return invalfc(fc,
> +				       "User quota block hardlimit too large.");
> +		ctx->usrquota_block_hardlimit = size;
> +		ctx->seen |=  SHMEM_SEEN_QUOTA;
> +		break;
> +	case Opt_grpquota_block_hardlimit:
> +		size = memparse(param->string, &rest);
> +		if (*rest || !size)
> +			goto bad_value;
> +		size = DIV_ROUND_UP(size, PAGE_SIZE);
> +		if (size > ULONG_MAX)
> +			return invalfc(fc,
> +				       "Group quota block hardlimit too large.");
> +		ctx->grpquota_block_hardlimit = size;
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		break;
> +	case Opt_usrquota_inode_hardlimit:
> +		size = memparse(param->string, &rest);
> +		if (*rest || !size)
> +			goto bad_value;
> +		if (size > ULONG_MAX)
> +			return invalfc(fc,
> +				       "User quota inode hardlimit too large.");
> +		ctx->usrquota_inode_hardlimit = size;
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		break;
> +	case Opt_grpquota_inode_hardlimit:
> +		size = memparse(param->string, &rest);
> +		if (*rest || !size)
> +			goto bad_value;
> +		if (size > ULONG_MAX)
> +			return invalfc(fc,
> +				       "Group quota inode hardlimit too large.");
> +		ctx->grpquota_inode_hardlimit = size;
>  		ctx->seen |= SHMEM_SEEN_QUOTA;
> +		break;
>  #else
> +	case Opt_quota:
> +	case Opt_usrquota_block_hardlimit:
> +	case Opt_grpquota_block_hardlimit:
> +	case Opt_usrquota_inode_hardlimit:
> +	case Opt_grpquota_inode_hardlimit:
>  		goto unsupported_parameter;
>  #endif
> -		break;
>  	}
>  	return 0;
>  
> @@ -3778,6 +3886,18 @@ static int shmem_reconfigure(struct fs_context *fc)
>  		goto out;
>  	}
>  
> +#ifdef CONFIG_QUOTA
> +#define CHANGED_LIMIT(name)						\
> +	(ctx->name## _hardlimit &&					\
> +	(ctx->name## _hardlimit != sbinfo->name## _hardlimit))
> +
> +	if (CHANGED_LIMIT(usrquota_block) || CHANGED_LIMIT(usrquota_inode) ||
> +	    CHANGED_LIMIT(grpquota_block) || CHANGED_LIMIT(grpquota_inode)) {
> +		err = "Cannot change global quota limit on remount";
> +		goto out;
> +	}
> +#endif /* CONFIG_QUOTA */
> +
>  	if (ctx->seen & SHMEM_SEEN_HUGE)
>  		sbinfo->huge = ctx->huge;
>  	if (ctx->seen & SHMEM_SEEN_INUMS)
> @@ -3942,11 +4062,22 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
>  
>  #ifdef SHMEM_QUOTA_TMPFS
>  	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> -		sb->dq_op = &dquot_operations;
> +		unsigned int dquot_flags;
> +
> +		sb->dq_op = &shmem_dquot_operations;
>  		sb->s_qcop = &dquot_quotactl_sysfile_ops;
>  		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
>  
> -		if (shmem_enable_quotas(sb))
> +		dquot_flags = DQUOT_USAGE_ENABLED;
> +		/*
> +		 * If any of the global quota limits are set, enable
> +		 * quota enforcement
> +		 */
> +		if (ctx->usrquota_block_hardlimit || ctx->usrquota_inode_hardlimit ||
> +		    ctx->grpquota_block_hardlimit || ctx->grpquota_inode_hardlimit)
> +			dquot_flags |= DQUOT_LIMITS_ENABLED;
> +
> +		if (shmem_enable_quotas(sb, dquot_flags))
>  			goto failed;
>  	}
>  #endif  /* SHMEM_QUOTA_TMPFS */
> @@ -3960,6 +4091,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
>  	if (!sb->s_root)
>  		goto failed;
>  
> +#ifdef SHMEM_QUOTA_TMPFS
> +	/*
> +	 * Set quota hard limits after shmem_get_inode() to avoid setting
> +	 * it for root
> +	 */
> +	sbinfo->usrquota_block_hardlimit = ctx->usrquota_block_hardlimit;
> +	sbinfo->usrquota_inode_hardlimit = ctx->usrquota_inode_hardlimit;
> +	sbinfo->grpquota_block_hardlimit = ctx->grpquota_block_hardlimit;
> +	sbinfo->grpquota_inode_hardlimit = ctx->grpquota_inode_hardlimit;
> +#endif  /* SHMEM_QUOTA_TMPFS */
> +
>  	return 0;
>  
>  failed:
> -- 
> 2.38.1
> 
>
Lukas Czerner Nov. 23, 2022, 9:38 a.m. UTC | #3
On Tue, Nov 22, 2022 at 04:03:25PM -0500, Brian Foster wrote:
> On Mon, Nov 21, 2022 at 03:28:54PM +0100, Lukas Czerner wrote:
> > Implement a set of mount options for setting glopbal quota limits on
> > tmpfs.
> > 
> > usrquota_block_hardlimit - global user quota block hard limit
> > usrquota_inode_hardlimit - global user quota inode hard limit
> > grpquota_block_hardlimit - global group quota block hard limit
> > grpquota_inode_hardlimit - global group quota inode hard limit
> > 
> > Quota limit parameters accept a suffix k, m or g for kilo, mega and giga
> > and can't be changed on remount. Default global quota limits are taking
> > effect for any and all user/group except root the first time the quota
> > entry for user/group id is being accessed - typically the first time an
> > inode with a particular id ownership is being created after the mount. In
> > other words, instead of the limits being initialized to zero, they are
> > initialized with the particular value provided with these mount options.
> > The limits can be changed for any user/group id at any time as it normally
> > can.
> > 
> > When any of the default quota limits are set, quota enforcement is enabled
> > automatically as well.
> > 
> > None of the quota related mount options can be set or changed on remount.
> > 
> > Signed-off-by: Lukas Czerner <lczerner@redhat.com>
> > ---
> > v2: Rename mount option to something more sensible.
> >     Improve documentation.
> >     Check if the user provided limit isn't too large.
> > 
> >  Documentation/filesystems/tmpfs.rst |  36 +++++--
> >  include/linux/shmem_fs.h            |  10 ++
> >  mm/shmem.c                          | 162 ++++++++++++++++++++++++++--
> >  3 files changed, 190 insertions(+), 18 deletions(-)
> > 
> ...
> > diff --git a/mm/shmem.c b/mm/shmem.c
> > index 26f2effd8f7c..a66a1e4cd0cb 100644
> > --- a/mm/shmem.c
> > +++ b/mm/shmem.c
> ...
> > @@ -271,6 +273,57 @@ static DEFINE_MUTEX(shmem_swaplist_mutex);
> >  
> >  #define SHMEM_MAXQUOTAS 2
> >  
> > +int shmem_dquot_acquire(struct dquot *dquot)
> > +{
> > +	int type, ret = 0;
> > +	unsigned int memalloc;
> > +	struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
> > +	struct shmem_sb_info *sbinfo = SHMEM_SB(dquot->dq_sb);
> > +
> > +
> > +	mutex_lock(&dquot->dq_lock);
> > +	memalloc = memalloc_nofs_save();
> > +	if (test_bit(DQ_READ_B, &dquot->dq_flags)) {
> > +		smp_mb__before_atomic();
> > +		set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
> > +		goto out_iolock;
> > +	}
> > +
> > +	type = dquot->dq_id.type;
> > +	ret = dqopt->ops[type]->read_dqblk(dquot);
> 
> So according to patch 1, this callback would alloc the quota id and set
> DQ_FAKE_B and DQ_NO_SHRINK_B on the dquot. The shrinker will skip dquots
> that are (noshrink && !fake). So as of this point the dquot would be
> reclaimable if it were ultimately freed with no other changes, right?

Right, the flags are set in read_dqblk() of quota-mem; which is
mem_read_dquot()

> 
> > +	if (ret < 0)
> > +		goto out_iolock;
> > +	/* Set the defaults */
> > +	if (type == USRQUOTA) {
> > +		dquot->dq_dqb.dqb_bhardlimit =
> > +			(sbinfo->usrquota_block_hardlimit << PAGE_SHIFT);
> > +		dquot->dq_dqb.dqb_ihardlimit = sbinfo->usrquota_inode_hardlimit;
> > +	} else if (type == GRPQUOTA) {
> > +		dquot->dq_dqb.dqb_bhardlimit =
> > +			(sbinfo->grpquota_block_hardlimit << PAGE_SHIFT);
> > +		dquot->dq_dqb.dqb_ihardlimit = sbinfo->grpquota_inode_hardlimit;
> > +	}
> 
> Then we set default limits from the mount option on the dquot. The dquot
> is still has DQ_FAKE_B, so presumably the dquot would remain reclaimable
> (once freed) even though it has a limit set (the mount default).
> 
> AFAICS the only place that clears DQ_FAKE_B is the setquota path, so I
> take it that means the dquot becomes ultimately unreclaimable if/when
> the user sets a non-zero quota limit, and then it only becomes
> reclaimable again when quota limits are explicitly set to zero. Is that
> the case?
> 
> If so, a couple questions:
> 
> 1. Can a dquot ever be reclaimed if the user explicitly sets a quota
> limit that matches the mount default?
> 
> 2. How does enforcement of default limits actually work? For example, it
> looks like dquot_add_inodes() skips enforcement when DQ_FAKE_B is set.
> Have I missed somewhere where this flag should be cleared in this case?

Sigh, you're righ. This won't work. My test script didn't catch this,
but setting DQ_FAKE_B will make the quota infrastructure think there are
no limits and just bail out of testing them.

Better solution might be to make a custom ->set_dqblk() and set
DQ_NO_SHRINK_B only if it differs from the defaults in shmem. This would
solve your question 1. as well.

I'll think about it some more and resend.

Thanks!
-Lukas

> 
> Brian
> 
> > +	/* Make sure flags update is visible after dquot has been filled */
> > +	smp_mb__before_atomic();
> > +	set_bit(DQ_READ_B, &dquot->dq_flags);
> > +	set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
> > +out_iolock:
> > +	memalloc_nofs_restore(memalloc);
> > +	mutex_unlock(&dquot->dq_lock);
> > +	return ret;
> > +}
> > +
> > +const struct dquot_operations shmem_dquot_operations = {
> > +	.write_dquot	= dquot_commit,
> > +	.acquire_dquot	= shmem_dquot_acquire,
> > +	.release_dquot	= dquot_release,
> > +	.mark_dirty	= dquot_mark_dquot_dirty,
> > +	.write_info	= dquot_commit_info,
> > +	.alloc_dquot	= dquot_alloc,
> > +	.destroy_dquot	= dquot_destroy,
> > +	.get_next_id	= dquot_get_next_id,
> > +};
> > +
> >  /*
> >   * We don't have any quota files to read, or write to/from, but quota code
> >   * requires .quota_read and .quota_write to exist.
> > @@ -288,14 +341,14 @@ static ssize_t shmem_quota_read(struct super_block *sb, int type, char *data,
> >  }
> >  
> >  
> > -static int shmem_enable_quotas(struct super_block *sb)
> > +static int shmem_enable_quotas(struct super_block *sb, unsigned int dquot_flags)
> >  {
> >  	int type, err = 0;
> >  
> >  	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
> >  	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
> >  		err = dquot_load_quota_sb(sb, type, QFMT_MEM_ONLY,
> > -					  DQUOT_USAGE_ENABLED);
> > +					  dquot_flags);
> >  		if (err)
> >  			goto out_err;
> >  	}
> > @@ -3559,6 +3612,10 @@ enum shmem_param {
> >  	Opt_inode32,
> >  	Opt_inode64,
> >  	Opt_quota,
> > +	Opt_usrquota_block_hardlimit,
> > +	Opt_usrquota_inode_hardlimit,
> > +	Opt_grpquota_block_hardlimit,
> > +	Opt_grpquota_inode_hardlimit,
> >  };
> >  
> >  static const struct constant_table shmem_param_enums_huge[] = {
> > @@ -3583,6 +3640,10 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
> >  	fsparam_flag  ("quota",		Opt_quota),
> >  	fsparam_flag  ("usrquota",	Opt_quota),
> >  	fsparam_flag  ("grpquota",	Opt_quota),
> > +	fsparam_string("usrquota_block_hardlimit",	Opt_usrquota_block_hardlimit),
> > +	fsparam_string("usrquota_inode_hardlimit",	Opt_usrquota_inode_hardlimit),
> > +	fsparam_string("grpquota_block_hardlimit",	Opt_grpquota_block_hardlimit),
> > +	fsparam_string("grpquota_inode_hardlimit",	Opt_grpquota_inode_hardlimit),
> >  	{}
> >  };
> >  
> > @@ -3666,13 +3727,60 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
> >  		ctx->full_inums = true;
> >  		ctx->seen |= SHMEM_SEEN_INUMS;
> >  		break;
> > -	case Opt_quota:
> >  #ifdef CONFIG_QUOTA
> > +	case Opt_quota:
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		break;
> > +	case Opt_usrquota_block_hardlimit:
> > +		size = memparse(param->string, &rest);
> > +		if (*rest || !size)
> > +			goto bad_value;
> > +		size = DIV_ROUND_UP(size, PAGE_SIZE);
> > +		if (size > ULONG_MAX)
> > +			return invalfc(fc,
> > +				       "User quota block hardlimit too large.");
> > +		ctx->usrquota_block_hardlimit = size;
> > +		ctx->seen |=  SHMEM_SEEN_QUOTA;
> > +		break;
> > +	case Opt_grpquota_block_hardlimit:
> > +		size = memparse(param->string, &rest);
> > +		if (*rest || !size)
> > +			goto bad_value;
> > +		size = DIV_ROUND_UP(size, PAGE_SIZE);
> > +		if (size > ULONG_MAX)
> > +			return invalfc(fc,
> > +				       "Group quota block hardlimit too large.");
> > +		ctx->grpquota_block_hardlimit = size;
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		break;
> > +	case Opt_usrquota_inode_hardlimit:
> > +		size = memparse(param->string, &rest);
> > +		if (*rest || !size)
> > +			goto bad_value;
> > +		if (size > ULONG_MAX)
> > +			return invalfc(fc,
> > +				       "User quota inode hardlimit too large.");
> > +		ctx->usrquota_inode_hardlimit = size;
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		break;
> > +	case Opt_grpquota_inode_hardlimit:
> > +		size = memparse(param->string, &rest);
> > +		if (*rest || !size)
> > +			goto bad_value;
> > +		if (size > ULONG_MAX)
> > +			return invalfc(fc,
> > +				       "Group quota inode hardlimit too large.");
> > +		ctx->grpquota_inode_hardlimit = size;
> >  		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		break;
> >  #else
> > +	case Opt_quota:
> > +	case Opt_usrquota_block_hardlimit:
> > +	case Opt_grpquota_block_hardlimit:
> > +	case Opt_usrquota_inode_hardlimit:
> > +	case Opt_grpquota_inode_hardlimit:
> >  		goto unsupported_parameter;
> >  #endif
> > -		break;
> >  	}
> >  	return 0;
> >  
> > @@ -3778,6 +3886,18 @@ static int shmem_reconfigure(struct fs_context *fc)
> >  		goto out;
> >  	}
> >  
> > +#ifdef CONFIG_QUOTA
> > +#define CHANGED_LIMIT(name)						\
> > +	(ctx->name## _hardlimit &&					\
> > +	(ctx->name## _hardlimit != sbinfo->name## _hardlimit))
> > +
> > +	if (CHANGED_LIMIT(usrquota_block) || CHANGED_LIMIT(usrquota_inode) ||
> > +	    CHANGED_LIMIT(grpquota_block) || CHANGED_LIMIT(grpquota_inode)) {
> > +		err = "Cannot change global quota limit on remount";
> > +		goto out;
> > +	}
> > +#endif /* CONFIG_QUOTA */
> > +
> >  	if (ctx->seen & SHMEM_SEEN_HUGE)
> >  		sbinfo->huge = ctx->huge;
> >  	if (ctx->seen & SHMEM_SEEN_INUMS)
> > @@ -3942,11 +4062,22 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
> >  
> >  #ifdef SHMEM_QUOTA_TMPFS
> >  	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> > -		sb->dq_op = &dquot_operations;
> > +		unsigned int dquot_flags;
> > +
> > +		sb->dq_op = &shmem_dquot_operations;
> >  		sb->s_qcop = &dquot_quotactl_sysfile_ops;
> >  		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
> >  
> > -		if (shmem_enable_quotas(sb))
> > +		dquot_flags = DQUOT_USAGE_ENABLED;
> > +		/*
> > +		 * If any of the global quota limits are set, enable
> > +		 * quota enforcement
> > +		 */
> > +		if (ctx->usrquota_block_hardlimit || ctx->usrquota_inode_hardlimit ||
> > +		    ctx->grpquota_block_hardlimit || ctx->grpquota_inode_hardlimit)
> > +			dquot_flags |= DQUOT_LIMITS_ENABLED;
> > +
> > +		if (shmem_enable_quotas(sb, dquot_flags))
> >  			goto failed;
> >  	}
> >  #endif  /* SHMEM_QUOTA_TMPFS */
> > @@ -3960,6 +4091,17 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
> >  	if (!sb->s_root)
> >  		goto failed;
> >  
> > +#ifdef SHMEM_QUOTA_TMPFS
> > +	/*
> > +	 * Set quota hard limits after shmem_get_inode() to avoid setting
> > +	 * it for root
> > +	 */
> > +	sbinfo->usrquota_block_hardlimit = ctx->usrquota_block_hardlimit;
> > +	sbinfo->usrquota_inode_hardlimit = ctx->usrquota_inode_hardlimit;
> > +	sbinfo->grpquota_block_hardlimit = ctx->grpquota_block_hardlimit;
> > +	sbinfo->grpquota_inode_hardlimit = ctx->grpquota_inode_hardlimit;
> > +#endif  /* SHMEM_QUOTA_TMPFS */
> > +
> >  	return 0;
> >  
> >  failed:
> > -- 
> > 2.38.1
> > 
> > 
>
diff mbox series

Patch

diff --git a/Documentation/filesystems/tmpfs.rst b/Documentation/filesystems/tmpfs.rst
index 9c4f228ef4f3..7150aeb3e546 100644
--- a/Documentation/filesystems/tmpfs.rst
+++ b/Documentation/filesystems/tmpfs.rst
@@ -88,14 +88,34 @@  that instance in a system with many CPUs making intensive use of it.
 
 tmpfs also supports quota with the following mount options
 
-========  =============================================================
-quota     Quota accounting is enabled on the mount. Tmpfs is using
-          hidden system quota files that are initialized on mount.
-          Quota limits can quota enforcement can be enabled using
-          standard quota tools.
-usrquota  Same as quota option. Exists for compatibility reasons.
-grpquota  Same as quota option. Exists for compatibility reasons.
-========  =============================================================
+======================== =================================================
+quota                    Quota accounting is enabled on the mount. Tmpfs
+                         is using hidden system quota files that are
+                         initialized on mount. Quota limits can quota
+                         enforcement can be enabled using standard quota
+                         tools.
+usrquota                 Same as quota option. Exists for compatibility.
+grpquota                 Same as quota option. Exists for compatibility.
+usrquota_block_hardlimit Set global user quota block hard limit.
+usrquota_inode_hardlimit Set global user quota inode hard limit.
+usrquota_block_hardlimit Set global group quota block hard limit.
+usrquota_inode_hardlimit Set global group quota inode hard limit.
+======================== =================================================
+
+Quota limit parameters accept a suffix k, m or g for kilo, mega and giga
+and can't be changed on remount. Default global quota limits are taking
+effect for any and all user/group except root the first time the quota
+entry for user/group id is being accessed - typically the first time an
+inode with a particular id ownership is being created after the mount. In
+other words, instead of the limits being initialized to zero, they are
+initialized with the particular value provided with these mount options.
+The limits can be changed for any user/group id at any time as it normally
+can.
+
+When any of the default quota limits are set, quota enforcement is enabled
+automatically as well.
+
+None of the quota related mount options can be set or changed on remount.
 
 
 tmpfs has a mount option to set the NUMA memory allocation policy for
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index 02a328c98d3a..174daeb5d554 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -10,6 +10,10 @@ 
 #include <linux/xattr.h>
 #include <linux/fs_parser.h>
 
+#if defined(CONFIG_TMPFS) && defined(CONFIG_QUOTA)
+#define SHMEM_QUOTA_TMPFS
+#endif
+
 /* inode in-kernel data */
 
 struct shmem_inode_info {
@@ -39,6 +43,12 @@  struct shmem_inode_info {
 
 struct shmem_sb_info {
 	unsigned long max_blocks;   /* How many blocks are allowed */
+#ifdef SHMEM_QUOTA_TMPFS
+	unsigned long usrquota_block_hardlimit; /* Default user quota block hard limit */
+	unsigned long usrquota_inode_hardlimit; /* Default user quota inode hard limit */
+	unsigned long grpquota_block_hardlimit; /* Default group quota block hard limit */
+	unsigned long grpquota_inode_hardlimit; /* Default group quota inode hard limit */
+#endif
 	struct percpu_counter used_blocks;  /* How many are allocated */
 	unsigned long max_inodes;   /* How many inodes are allowed */
 	unsigned long free_inodes;  /* How many are left for allocation */
diff --git a/mm/shmem.c b/mm/shmem.c
index 26f2effd8f7c..a66a1e4cd0cb 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -110,6 +110,12 @@  struct shmem_falloc {
 struct shmem_options {
 	unsigned long long blocks;
 	unsigned long long inodes;
+#ifdef SHMEM_QUOTA_TMPFS
+	unsigned long usrquota_block_hardlimit;
+	unsigned long usrquota_inode_hardlimit;
+	unsigned long grpquota_block_hardlimit;
+	unsigned long grpquota_inode_hardlimit;
+#endif
 	struct mempolicy *mpol;
 	kuid_t uid;
 	kgid_t gid;
@@ -142,10 +148,6 @@  static unsigned long shmem_default_max_inodes(void)
 }
 #endif
 
-#if defined(CONFIG_TMPFS) && defined(CONFIG_QUOTA)
-#define SHMEM_QUOTA_TMPFS
-#endif
-
 static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
 			     struct folio **foliop, enum sgp_type sgp,
 			     gfp_t gfp, struct vm_area_struct *vma,
@@ -271,6 +273,57 @@  static DEFINE_MUTEX(shmem_swaplist_mutex);
 
 #define SHMEM_MAXQUOTAS 2
 
+int shmem_dquot_acquire(struct dquot *dquot)
+{
+	int type, ret = 0;
+	unsigned int memalloc;
+	struct quota_info *dqopt = sb_dqopt(dquot->dq_sb);
+	struct shmem_sb_info *sbinfo = SHMEM_SB(dquot->dq_sb);
+
+
+	mutex_lock(&dquot->dq_lock);
+	memalloc = memalloc_nofs_save();
+	if (test_bit(DQ_READ_B, &dquot->dq_flags)) {
+		smp_mb__before_atomic();
+		set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
+		goto out_iolock;
+	}
+
+	type = dquot->dq_id.type;
+	ret = dqopt->ops[type]->read_dqblk(dquot);
+	if (ret < 0)
+		goto out_iolock;
+	/* Set the defaults */
+	if (type == USRQUOTA) {
+		dquot->dq_dqb.dqb_bhardlimit =
+			(sbinfo->usrquota_block_hardlimit << PAGE_SHIFT);
+		dquot->dq_dqb.dqb_ihardlimit = sbinfo->usrquota_inode_hardlimit;
+	} else if (type == GRPQUOTA) {
+		dquot->dq_dqb.dqb_bhardlimit =
+			(sbinfo->grpquota_block_hardlimit << PAGE_SHIFT);
+		dquot->dq_dqb.dqb_ihardlimit = sbinfo->grpquota_inode_hardlimit;
+	}
+	/* Make sure flags update is visible after dquot has been filled */
+	smp_mb__before_atomic();
+	set_bit(DQ_READ_B, &dquot->dq_flags);
+	set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
+out_iolock:
+	memalloc_nofs_restore(memalloc);
+	mutex_unlock(&dquot->dq_lock);
+	return ret;
+}
+
+const struct dquot_operations shmem_dquot_operations = {
+	.write_dquot	= dquot_commit,
+	.acquire_dquot	= shmem_dquot_acquire,
+	.release_dquot	= dquot_release,
+	.mark_dirty	= dquot_mark_dquot_dirty,
+	.write_info	= dquot_commit_info,
+	.alloc_dquot	= dquot_alloc,
+	.destroy_dquot	= dquot_destroy,
+	.get_next_id	= dquot_get_next_id,
+};
+
 /*
  * We don't have any quota files to read, or write to/from, but quota code
  * requires .quota_read and .quota_write to exist.
@@ -288,14 +341,14 @@  static ssize_t shmem_quota_read(struct super_block *sb, int type, char *data,
 }
 
 
-static int shmem_enable_quotas(struct super_block *sb)
+static int shmem_enable_quotas(struct super_block *sb, unsigned int dquot_flags)
 {
 	int type, err = 0;
 
 	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
 	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
 		err = dquot_load_quota_sb(sb, type, QFMT_MEM_ONLY,
-					  DQUOT_USAGE_ENABLED);
+					  dquot_flags);
 		if (err)
 			goto out_err;
 	}
@@ -3559,6 +3612,10 @@  enum shmem_param {
 	Opt_inode32,
 	Opt_inode64,
 	Opt_quota,
+	Opt_usrquota_block_hardlimit,
+	Opt_usrquota_inode_hardlimit,
+	Opt_grpquota_block_hardlimit,
+	Opt_grpquota_inode_hardlimit,
 };
 
 static const struct constant_table shmem_param_enums_huge[] = {
@@ -3583,6 +3640,10 @@  const struct fs_parameter_spec shmem_fs_parameters[] = {
 	fsparam_flag  ("quota",		Opt_quota),
 	fsparam_flag  ("usrquota",	Opt_quota),
 	fsparam_flag  ("grpquota",	Opt_quota),
+	fsparam_string("usrquota_block_hardlimit",	Opt_usrquota_block_hardlimit),
+	fsparam_string("usrquota_inode_hardlimit",	Opt_usrquota_inode_hardlimit),
+	fsparam_string("grpquota_block_hardlimit",	Opt_grpquota_block_hardlimit),
+	fsparam_string("grpquota_inode_hardlimit",	Opt_grpquota_inode_hardlimit),
 	{}
 };
 
@@ -3666,13 +3727,60 @@  static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
 		ctx->full_inums = true;
 		ctx->seen |= SHMEM_SEEN_INUMS;
 		break;
-	case Opt_quota:
 #ifdef CONFIG_QUOTA
+	case Opt_quota:
+		ctx->seen |= SHMEM_SEEN_QUOTA;
+		break;
+	case Opt_usrquota_block_hardlimit:
+		size = memparse(param->string, &rest);
+		if (*rest || !size)
+			goto bad_value;
+		size = DIV_ROUND_UP(size, PAGE_SIZE);
+		if (size > ULONG_MAX)
+			return invalfc(fc,
+				       "User quota block hardlimit too large.");
+		ctx->usrquota_block_hardlimit = size;
+		ctx->seen |=  SHMEM_SEEN_QUOTA;
+		break;
+	case Opt_grpquota_block_hardlimit:
+		size = memparse(param->string, &rest);
+		if (*rest || !size)
+			goto bad_value;
+		size = DIV_ROUND_UP(size, PAGE_SIZE);
+		if (size > ULONG_MAX)
+			return invalfc(fc,
+				       "Group quota block hardlimit too large.");
+		ctx->grpquota_block_hardlimit = size;
+		ctx->seen |= SHMEM_SEEN_QUOTA;
+		break;
+	case Opt_usrquota_inode_hardlimit:
+		size = memparse(param->string, &rest);
+		if (*rest || !size)
+			goto bad_value;
+		if (size > ULONG_MAX)
+			return invalfc(fc,
+				       "User quota inode hardlimit too large.");
+		ctx->usrquota_inode_hardlimit = size;
+		ctx->seen |= SHMEM_SEEN_QUOTA;
+		break;
+	case Opt_grpquota_inode_hardlimit:
+		size = memparse(param->string, &rest);
+		if (*rest || !size)
+			goto bad_value;
+		if (size > ULONG_MAX)
+			return invalfc(fc,
+				       "Group quota inode hardlimit too large.");
+		ctx->grpquota_inode_hardlimit = size;
 		ctx->seen |= SHMEM_SEEN_QUOTA;
+		break;
 #else
+	case Opt_quota:
+	case Opt_usrquota_block_hardlimit:
+	case Opt_grpquota_block_hardlimit:
+	case Opt_usrquota_inode_hardlimit:
+	case Opt_grpquota_inode_hardlimit:
 		goto unsupported_parameter;
 #endif
-		break;
 	}
 	return 0;
 
@@ -3778,6 +3886,18 @@  static int shmem_reconfigure(struct fs_context *fc)
 		goto out;
 	}
 
+#ifdef CONFIG_QUOTA
+#define CHANGED_LIMIT(name)						\
+	(ctx->name## _hardlimit &&					\
+	(ctx->name## _hardlimit != sbinfo->name## _hardlimit))
+
+	if (CHANGED_LIMIT(usrquota_block) || CHANGED_LIMIT(usrquota_inode) ||
+	    CHANGED_LIMIT(grpquota_block) || CHANGED_LIMIT(grpquota_inode)) {
+		err = "Cannot change global quota limit on remount";
+		goto out;
+	}
+#endif /* CONFIG_QUOTA */
+
 	if (ctx->seen & SHMEM_SEEN_HUGE)
 		sbinfo->huge = ctx->huge;
 	if (ctx->seen & SHMEM_SEEN_INUMS)
@@ -3942,11 +4062,22 @@  static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
 
 #ifdef SHMEM_QUOTA_TMPFS
 	if (ctx->seen & SHMEM_SEEN_QUOTA) {
-		sb->dq_op = &dquot_operations;
+		unsigned int dquot_flags;
+
+		sb->dq_op = &shmem_dquot_operations;
 		sb->s_qcop = &dquot_quotactl_sysfile_ops;
 		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
 
-		if (shmem_enable_quotas(sb))
+		dquot_flags = DQUOT_USAGE_ENABLED;
+		/*
+		 * If any of the global quota limits are set, enable
+		 * quota enforcement
+		 */
+		if (ctx->usrquota_block_hardlimit || ctx->usrquota_inode_hardlimit ||
+		    ctx->grpquota_block_hardlimit || ctx->grpquota_inode_hardlimit)
+			dquot_flags |= DQUOT_LIMITS_ENABLED;
+
+		if (shmem_enable_quotas(sb, dquot_flags))
 			goto failed;
 	}
 #endif  /* SHMEM_QUOTA_TMPFS */
@@ -3960,6 +4091,17 @@  static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
 	if (!sb->s_root)
 		goto failed;
 
+#ifdef SHMEM_QUOTA_TMPFS
+	/*
+	 * Set quota hard limits after shmem_get_inode() to avoid setting
+	 * it for root
+	 */
+	sbinfo->usrquota_block_hardlimit = ctx->usrquota_block_hardlimit;
+	sbinfo->usrquota_inode_hardlimit = ctx->usrquota_inode_hardlimit;
+	sbinfo->grpquota_block_hardlimit = ctx->grpquota_block_hardlimit;
+	sbinfo->grpquota_inode_hardlimit = ctx->grpquota_inode_hardlimit;
+#endif  /* SHMEM_QUOTA_TMPFS */
+
 	return 0;
 
 failed: