Re: [PATCH 2/2] shmem: implement mount options for global quota limits

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Nov 08, 2022 at 08:35:28AM -0800, Darrick J. Wong wrote:
> On Tue, Nov 08, 2022 at 02:30:10PM +0100, Lukas Czerner wrote:
> > Implement a set of mount options for setting glopbal quota limits on
> > tmpfs.
> > 
> > quota_ubh_limit - global user quota block hard limit
> 
> Is this the default per-user limit until the sysadmin runs setquota -u
> or something to change a per-user limit?  Or is this a global limit for
> all users and it's never possible to change it?

This is a global limit for all users, except root. The way it works is
that the limit is applied when dquot is first allocated for the
user/group - this is typically the first time user/group allocates
blocks, or creates an inode. It can be changed the same way as it would
be normally.

> 
> (Are there any plans for a soft limit?)

I have no plans for soft limit, but I also don't want to make the naming
confusing once someone decides they'd like to have soft limits in future.

> 
> > quota_uih_limit - global user quota inode hard limit
> > quota_gbh_limit - global group quota block hard limit
> > quota_gih_limit - global group quota inode hard limit
> 
> I don't really like these names, only 2/15 letters in the whole name
> actually distinguish them.

Yes, exactly my feeling as well. I am bad at naming things ;) I was
hoping I'd get some suggestions here, and that exactly what I got.
Thanks!

> 
> usrquota_block_hardlimit?
> usrquota_inode_hardlimit?  8/24...

I like it.

> 
> usrquota_block_limit?
> usrquota_inode_limit?  8/20...

I like it even more, but with hypothetical introduction of soft limits
in future it wouldn't be as obvious it is a hard limit. But maybe it's
not an issue at all.

Thanks!
-Lukas

> 
> (The mechanical changes below here looked ok to me...)
> 
> --D
> 
> > 
> > All of the above mount options will take an effect for any and all
> > users/groups except for root and can be changed using standard ways of
> > setting quota limits. Along with setting the limits, quota enforcement
> > will be enabled as well.
> > 
> > None of the mount options can be set or changed on remount.
> 
> > Signed-off-by: Lukas Czerner <lczerner@xxxxxxxxxx>
> > ---
> >  Documentation/filesystems/tmpfs.rst |  23 ++--
> >  include/linux/shmem_fs.h            |   4 +
> >  mm/shmem.c                          | 166 +++++++++++++++++++++++++---
> >  3 files changed, 171 insertions(+), 22 deletions(-)
> > 
> > diff --git a/Documentation/filesystems/tmpfs.rst b/Documentation/filesystems/tmpfs.rst
> > index 9c4f228ef4f3..be4aa964863d 100644
> > --- a/Documentation/filesystems/tmpfs.rst
> > +++ b/Documentation/filesystems/tmpfs.rst
> > @@ -88,14 +88,21 @@ 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.
> > +quota_ubh_limit  Set global user quota block hard limit.
> > +quota_uih_limit  Set global user quota inode hard limit.
> > +quota_gbh_limit  Set global group quota block hard limit.
> > +quota_gih_limit  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.
> >  
> >  
> >  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..eb5e2dc2dc4c 100644
> > --- a/include/linux/shmem_fs.h
> > +++ b/include/linux/shmem_fs.h
> > @@ -39,6 +39,10 @@ struct shmem_inode_info {
> >  
> >  struct shmem_sb_info {
> >  	unsigned long max_blocks;   /* How many blocks are allowed */
> > +	unsigned long quota_ubh_limit; /* Default user quota block hard limit */
> > +	unsigned long quota_uih_limit; /* Default user quota inode hard limit */
> > +	unsigned long quota_gbh_limit; /* Default group quota block hard limit */
> > +	unsigned long quota_gih_limit; /* Default group quota inode hard limit */
> >  	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 ec16659c2255..f1d6a3931b0a 100644
> > --- a/mm/shmem.c
> > +++ b/mm/shmem.c
> > @@ -99,6 +99,10 @@ static struct vfsmount *shm_mnt;
> >  /* Symlink up to this size is kmalloc'ed instead of using a swappable page */
> >  #define SHORT_SYMLINK_LEN 128
> >  
> > +#if defined(CONFIG_TMPFS) && defined(CONFIG_QUOTA)
> > +#define SHMEM_QUOTA_TMPFS
> > +#endif
> > +
> >  /*
> >   * shmem_fallocate communicates with shmem_fault or shmem_writepage via
> >   * inode->i_private (with i_rwsem making sure that it has only one user at
> > @@ -115,6 +119,12 @@ struct shmem_falloc {
> >  struct shmem_options {
> >  	unsigned long long blocks;
> >  	unsigned long long inodes;
> > +#ifdef SHMEM_QUOTA_TMPFS
> > +	unsigned long long quota_ubh_limit;
> > +	unsigned long long quota_uih_limit;
> > +	unsigned long long quota_gbh_limit;
> > +	unsigned long long quota_gih_limit;
> > +#endif
> >  	struct mempolicy *mpol;
> >  	kuid_t uid;
> >  	kgid_t gid;
> > @@ -147,10 +157,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,
> > @@ -285,6 +291,54 @@ static DEFINE_MUTEX(shmem_swaplist_mutex);
> >  #define QUOTABLOCK_BITS 10
> >  #define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS)
> >  
> > +struct kmem_cache *shmem_dquot_cachep;
> > +
> > +struct dquot *shmem_dquot_alloc(struct super_block *sb, int type)
> > +{
> > +	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
> > +	struct dquot *dquot = NULL;
> > +
> > +	dquot = kmem_cache_zalloc(shmem_dquot_cachep, GFP_NOFS);
> > +	if (!dquot)
> > +		return NULL;
> > +
> > +	if (type == USRQUOTA) {
> > +		dquot->dq_dqb.dqb_bhardlimit =
> > +			(sbinfo->quota_ubh_limit << PAGE_SHIFT);
> > +		dquot->dq_dqb.dqb_ihardlimit = sbinfo->quota_uih_limit;
> > +	} else if (type == GRPQUOTA) {
> > +		dquot->dq_dqb.dqb_bhardlimit =
> > +			(sbinfo->quota_gbh_limit << PAGE_SHIFT);
> > +		dquot->dq_dqb.dqb_ihardlimit = sbinfo->quota_gih_limit;
> > +	}
> > +	/*
> > +	 * This is a bit a of a hack to allow setting global default
> > +	 * limits on new files, by setting the limits here and preventing
> > +	 * quota from initializing everything to zero. It won't ever be
> > +	 * read from quota file because existing inodes in tmpfs are always
> > +	 * kept in memory (or swap) so we know we're getting dquot for a
> > +	 * new inode with no pre-existing dquot.
> > +	 */
> > +	set_bit(DQ_READ_B, &dquot->dq_flags);
> > +	return dquot;
> > +}
> > +
> > +static void shmem_dquot_destroy(struct dquot *dquot)
> > +{
> > +	kmem_cache_free(shmem_dquot_cachep, dquot);
> > +}
> > +
> > +const struct dquot_operations shmem_dquot_operations = {
> > +	.write_dquot	= dquot_commit,
> > +	.acquire_dquot	= dquot_acquire,
> > +	.release_dquot	= dquot_release,
> > +	.mark_dirty	= dquot_mark_dquot_dirty,
> > +	.write_info	= dquot_commit_info,
> > +	.alloc_dquot	= shmem_dquot_alloc,
> > +	.destroy_dquot	= shmem_dquot_destroy,
> > +	.get_next_id	= dquot_get_next_id,
> > +};
> > +
> >  static ssize_t shmem_quota_write_inode(struct inode *inode, int type,
> >  				       const char *data, size_t len, loff_t off)
> >  {
> > @@ -343,7 +397,7 @@ static ssize_t shmem_quota_write(struct super_block *sb, int type,
> >  	return shmem_quota_write_inode(inode, type, data, len, off);
> >  }
> >  
> > -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;
> >  	struct inode *inode;
> > @@ -389,7 +443,7 @@ static int shmem_enable_quotas(struct super_block *sb)
> >  		shmem_set_inode_flags(inode, FS_NOATIME_FL | FS_IMMUTABLE_FL);
> >  
> >  		err = dquot_load_quota_inode(inode, type, QFMT_VFS_V1,
> > -					     DQUOT_USAGE_ENABLED);
> > +					     dquot_flags);
> >  		iput(inode);
> >  		if (err)
> >  			goto out_err;
> > @@ -3720,6 +3774,10 @@ enum shmem_param {
> >  	Opt_inode32,
> >  	Opt_inode64,
> >  	Opt_quota,
> > +	Opt_quota_ubh_limit,
> > +	Opt_quota_uih_limit,
> > +	Opt_quota_gbh_limit,
> > +	Opt_quota_gih_limit,
> >  };
> >  
> >  static const struct constant_table shmem_param_enums_huge[] = {
> > @@ -3744,6 +3802,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("quota_ubh_limit",	Opt_quota_ubh_limit),
> > +	fsparam_string("quota_uih_limit",	Opt_quota_uih_limit),
> > +	fsparam_string("quota_gbh_limit",	Opt_quota_gbh_limit),
> > +	fsparam_string("quota_gih_limit",	Opt_quota_gih_limit),
> >  	{}
> >  };
> >  
> > @@ -3827,13 +3889,44 @@ 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_quota_ubh_limit:
> > +		size = memparse(param->string, &rest);
> > +		if (*rest || !size)
> > +			goto bad_value;
> > +		ctx->quota_ubh_limit = DIV_ROUND_UP(size, PAGE_SIZE);
> > +		ctx->seen |=  SHMEM_SEEN_QUOTA;
> > +		break;
> > +	case Opt_quota_gbh_limit:
> > +		size = memparse(param->string, &rest);
> > +		if (*rest || !size)
> > +			goto bad_value;
> > +		ctx->quota_gbh_limit = DIV_ROUND_UP(size, PAGE_SIZE);
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		break;
> > +	case Opt_quota_uih_limit:
> > +		ctx->quota_uih_limit = memparse(param->string, &rest);
> > +		if (*rest || !ctx->quota_uih_limit)
> > +			goto bad_value;
> > +		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		break;
> > +	case Opt_quota_gih_limit:
> > +		ctx->quota_gih_limit = memparse(param->string, &rest);
> > +		if (*rest || !ctx->quota_gih_limit)
> > +			goto bad_value;
> >  		ctx->seen |= SHMEM_SEEN_QUOTA;
> > +		break;
> >  #else
> > +	case Opt_quota:
> > +	case Opt_quota_ubh_limit:
> > +	case Opt_quota_gbh_limit:
> > +	case Opt_quota_uih_limit:
> > +	case Opt_quota_gih_limit:
> >  		goto unsupported_parameter;
> >  #endif
> > -		break;
> >  	}
> >  	return 0;
> >  
> > @@ -3933,12 +4026,24 @@ static int shmem_reconfigure(struct fs_context *fc)
> >  		goto out;
> >  	}
> >  
> > +#ifdef CONFIG_QUOTA
> >  	if (ctx->seen & SHMEM_SEEN_QUOTA &&
> >  	    !sb_any_quota_loaded(fc->root->d_sb)) {
> >  		err = "Cannot enable quota on remount";
> >  		goto out;
> >  	}
> >  
> > +#define CHANGED_LIMIT(name)						\
> > +	(ctx->quota_##name##_limit &&					\
> > +	(ctx->quota_##name##_limit != sbinfo->quota_ ##name##_limit))
> > +
> > +	if (CHANGED_LIMIT(ubh) || CHANGED_LIMIT(uih) ||
> > +	    CHANGED_LIMIT(gbh) || CHANGED_LIMIT(gih)) {
> > +		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)
> > @@ -4103,11 +4208,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->quota_ubh_limit || ctx->quota_uih_limit ||
> > +		    ctx->quota_gbh_limit || ctx->quota_gih_limit)
> > +			dquot_flags |= DQUOT_LIMITS_ENABLED;
> > +
> > +		if (shmem_enable_quotas(sb, dquot_flags))
> >  			goto failed;
> >  	}
> >  #endif  /* SHMEM_QUOTA_TMPFS */
> > @@ -4121,6 +4237,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->quota_ubh_limit = ctx->quota_ubh_limit;
> > +	sbinfo->quota_uih_limit = ctx->quota_uih_limit;
> > +	sbinfo->quota_gbh_limit = ctx->quota_gbh_limit;
> > +	sbinfo->quota_gih_limit = ctx->quota_gih_limit;
> > +#endif  /* SHMEM_QUOTA_TMPFS */
> > +
> >  	return 0;
> >  
> >  failed:
> > @@ -4183,16 +4310,27 @@ static void shmem_init_inode(void *foo)
> >  	inode_init_once(&info->vfs_inode);
> >  }
> >  
> > -static void shmem_init_inodecache(void)
> > +static void shmem_init_mem_caches(void)
> >  {
> >  	shmem_inode_cachep = kmem_cache_create("shmem_inode_cache",
> >  				sizeof(struct shmem_inode_info),
> >  				0, SLAB_PANIC|SLAB_ACCOUNT, shmem_init_inode);
> > +
> > +#ifdef SHMEM_QUOTA_TMPFS
> > +	shmem_dquot_cachep = kmem_cache_create("shmem_dquot",
> > +				sizeof(struct dquot), sizeof(unsigned long) * 4,
> > +				(SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT|
> > +					SLAB_MEM_SPREAD|SLAB_PANIC),
> > +				NULL);
> > +#endif
> >  }
> >  
> > -static void shmem_destroy_inodecache(void)
> > +static void shmem_destroy_mem_caches(void)
> >  {
> >  	kmem_cache_destroy(shmem_inode_cachep);
> > +#ifdef SHMEM_QUOTA_TMPFS
> > +	kmem_cache_destroy(shmem_dquot_cachep);
> > +#endif
> >  }
> >  
> >  /* Keep the page in page cache instead of truncating it */
> > @@ -4340,7 +4478,7 @@ void __init shmem_init(void)
> >  {
> >  	int error;
> >  
> > -	shmem_init_inodecache();
> > +	shmem_init_mem_caches();
> >  
> >  	error = register_filesystem(&shmem_fs_type);
> >  	if (error) {
> > @@ -4366,7 +4504,7 @@ void __init shmem_init(void)
> >  out1:
> >  	unregister_filesystem(&shmem_fs_type);
> >  out2:
> > -	shmem_destroy_inodecache();
> > +	shmem_destroy_mem_caches();
> >  	shm_mnt = ERR_PTR(error);
> >  }
> >  
> > -- 
> > 2.38.1
> > 
> 





[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Bugtraq]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux