Re: [PATCH 1/2] shmem: implement user/group quota support for tmpfs

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

 



On Tue, Nov 08, 2022 at 02:30:09PM +0100, Lukas Czerner wrote:
> Implement user and group quota support for tmpfs using system quota file
> in vfsv0 quota format. Because everything in tmpfs is temporary and as a
> result is lost on umount, the quota files are initialized on every
> mount. This also goes for quota limits, that needs to be set up after
> every mount.
> 
> The quota support in tmpfs is well separated from the rest of the
> filesystem and is only enabled using mount option -o quota (and
> usrquota and grpquota for compatibility reasons). Only quota accounting
> is enabled this way, enforcement needs to be enable by regular quota
> tools (using Q_QUOTAON ioctl).
> 
> Signed-off-by: Lukas Czerner <lczerner@xxxxxxxxxx>
> ---
>  Documentation/filesystems/tmpfs.rst |  12 +
>  include/linux/shmem_fs.h            |   3 +
>  mm/shmem.c                          | 361 ++++++++++++++++++++++++++--
>  3 files changed, 353 insertions(+), 23 deletions(-)
> 
> diff --git a/Documentation/filesystems/tmpfs.rst b/Documentation/filesystems/tmpfs.rst
> index 0408c245785e..9c4f228ef4f3 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     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.

I guess this is following ext* where group and user quota cannot be
enabled individually, nor is there a 'noenforce' mode?

Not complaining here, since it /does/ cut down on the number of mount
options. :)

> +========  =============================================================
> +
> +
>  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 d500ea967dc7..02a328c98d3a 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_QUOTA
> +	struct dquot		*i_dquot[MAXQUOTAS];
> +#endif
>  	struct inode		vfs_inode;
>  };
>  
> diff --git a/mm/shmem.c b/mm/shmem.c
> index c1d8b8a1aa3b..ec16659c2255 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -79,6 +79,12 @@ static struct vfsmount *shm_mnt;
>  #include <linux/userfaultfd_k.h>
>  #include <linux/rmap.h>
>  #include <linux/uuid.h>
> +#include <linux/quotaops.h>
> +/*
> + * Required for structures definitions and macros for quote file
> + * initialization.
> + */
> +#include <../fs/quota/quotaio_v2.h>
>  
>  #include <linux/uaccess.h>
>  
> @@ -120,8 +126,13 @@ struct shmem_options {
>  #define SHMEM_SEEN_INODES 2
>  #define SHMEM_SEEN_HUGE 4
>  #define SHMEM_SEEN_INUMS 8
> +#define SHMEM_SEEN_QUOTA 16
>  };
>  
> +static void shmem_set_inode_flags(struct inode *, unsigned int);
> +static struct inode *shmem_get_inode_noquota(struct super_block *,
> +			struct inode *, umode_t, dev_t, unsigned long);
> +
>  #ifdef CONFIG_TMPFS
>  static unsigned long shmem_default_max_blocks(void)
>  {
> @@ -136,6 +147,10 @@ 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,
> @@ -198,26 +213,34 @@ static inline void shmem_unacct_blocks(unsigned long flags, long pages)
>  		vm_unacct_memory(pages * VM_ACCT(PAGE_SIZE));
>  }
>  
> -static inline bool shmem_inode_acct_block(struct inode *inode, long pages)
> +static inline int shmem_inode_acct_block(struct inode *inode, long pages)
>  {
>  	struct shmem_inode_info *info = SHMEM_I(inode);
>  	struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
> +	int err = -ENOSPC;
>  
>  	if (shmem_acct_block(info->flags, pages))
> -		return false;
> +		return err;
>  
>  	if (sbinfo->max_blocks) {
>  		if (percpu_counter_compare(&sbinfo->used_blocks,
>  					   sbinfo->max_blocks - pages) > 0)
>  			goto unacct;
> +		if (dquot_alloc_block_nodirty(inode, pages)) {
> +			err = -EDQUOT;
> +			goto unacct;
> +		}
>  		percpu_counter_add(&sbinfo->used_blocks, pages);
> +	} else if (dquot_alloc_block_nodirty(inode, pages)) {
> +		err = -EDQUOT;
> +		goto unacct;
>  	}
>  
> -	return true;
> +	return 0;
>  
>  unacct:
>  	shmem_unacct_blocks(info->flags, pages);
> -	return false;
> +	return err;
>  }
>  
>  static inline void shmem_inode_unacct_blocks(struct inode *inode, long pages)
> @@ -225,6 +248,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);
> @@ -247,6 +272,218 @@ bool vma_is_shmem(struct vm_area_struct *vma)
>  static LIST_HEAD(shmem_swaplist);
>  static DEFINE_MUTEX(shmem_swaplist_mutex);
>  
> +#ifdef SHMEM_QUOTA_TMPFS
> +
> +#define SHMEM_MAXQUOTAS 2
> +
> +/*
> + * Define macros needed for quota file initialization.
> + */
> +#define MAX_IQ_TIME  604800	/* (7*24*60*60) 1 week */
> +#define MAX_DQ_TIME  604800	/* (7*24*60*60) 1 week */
> +#define QT_TREEOFF	1	/* Offset of tree in file in blocks */
> +#define QUOTABLOCK_BITS 10
> +#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS)
> +
> +static ssize_t shmem_quota_write_inode(struct inode *inode, int type,
> +				       const char *data, size_t len, loff_t off)
> +{
> +	loff_t i_size = i_size_read(inode);
> +	struct page *page = NULL;
> +	unsigned long offset;
> +	struct folio *folio;
> +	int err = 0;
> +	pgoff_t index;
> +
> +	index = off >> PAGE_SHIFT;
> +	offset = off & ~PAGE_MASK;
> +
> +	/*
> +	 * We expect the write to fit into a single page
> +	 */
> +	if (PAGE_SIZE - offset < len) {
> +		pr_warn("tmpfs: quota write (off=%llu, len=%llu) doesn't fit into a single page\n",
> +			(unsigned long long)off, (unsigned long long)len);
> +		return -EIO;
> +	}
> +
> +	err = shmem_get_folio(inode, index, &folio, SGP_WRITE);
> +	if (err)
> +		return err;
> +
> +	page = folio_file_page(folio, index);
> +	if (PageHWPoison(page)) {
> +		folio_unlock(folio);
> +		folio_put(folio);
> +		return -EIO;
> +	}
> +
> +	/* Write data, or zeroout the portion of the page */
> +	if (data)
> +		memcpy(page_address(page) + offset, data, len);
> +	else
> +		memset(page_address(page) + offset, 0, len);
> +
> +	SetPageUptodate(page);

Don't we need to mark the /folio/ uptodate here?  Or at least the head
page if the quota file got a THP?

Hm.  shmem_write_end will SetPageUptodate on the head page if it's a
compound page, but it'll set_page_dirty to mark the whole folio dirty.
Now I'm confused completely. :(

OTOH I guess these are quota files, perhaps they simply don't need THP
anyway?

> +	flush_dcache_folio(folio);
> +	folio_mark_dirty(folio);
> +	folio_unlock(folio);
> +	folio_put(folio);
> +
> +	if (i_size < off + len)
> +		i_size_write(inode, off + len);
> +	return err ? err : len;
> +}
> +
> +static ssize_t shmem_quota_write(struct super_block *sb, int type,
> +				const char *data, size_t len, loff_t off)
> +{
> +	struct inode *inode = sb_dqopt(sb)->files[type];
> +
> +	return shmem_quota_write_inode(inode, type, data, len, off);
> +}
> +
> +static int shmem_enable_quotas(struct super_block *sb)
> +{
> +	int type, err = 0;
> +	struct inode *inode;
> +	struct v2_disk_dqheader qheader;
> +	struct v2_disk_dqinfo qinfo;
> +	static const uint quota_magics[] = V2_INITQMAGICS;
> +	static const uint quota_versions[] = V2_INITQVERSIONS;
> +
> +	sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE | DQUOT_NOLIST_DIRTY;
> +	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
> +		inode = shmem_get_inode_noquota(sb, NULL, S_IFREG | 0777, 0,
> +						VM_NORESERVE);
> +		if (IS_ERR_OR_NULL(inode)) {
> +			err = PTR_ERR(inode);
> +			goto out_err;
> +		}
> +		inode->i_flags |= S_NOQUOTA;
> +
> +		/* Initialize generic quota file header */
> +		qheader.dqh_magic = cpu_to_le32(quota_magics[type]);
> +		qheader.dqh_version = cpu_to_le32(quota_versions[type]);
> +
> +		/* Initialize the quota file info structure */
> +		qinfo.dqi_bgrace = cpu_to_le32(MAX_DQ_TIME);
> +		qinfo.dqi_igrace = cpu_to_le32(MAX_IQ_TIME);
> +		qinfo.dqi_flags = cpu_to_le32(0);
> +		qinfo.dqi_blocks = cpu_to_le32(QT_TREEOFF + 1);
> +		qinfo.dqi_free_blk = cpu_to_le32(0);
> +		qinfo.dqi_free_entry = cpu_to_le32(0);
> +
> +		/*
> +		 * Write out generic quota header, quota info structure and
> +		 * zeroout first tree block.
> +		 */
> +		shmem_quota_write_inode(inode, type, (const char *)&qheader,
> +					sizeof(qheader), 0);

Why is it not important to check error returns here?

> +		shmem_quota_write_inode(inode, type, (const char *)&qinfo,
> +					sizeof(qinfo), sizeof(qheader));
> +		shmem_quota_write_inode(inode, type, 0,
> +					QT_TREEOFF * QUOTABLOCK_SIZE,
> +					QUOTABLOCK_SIZE);
> +
> +		shmem_set_inode_flags(inode, FS_NOATIME_FL | FS_IMMUTABLE_FL);
> +
> +		err = dquot_load_quota_inode(inode, type, QFMT_VFS_V1,

I wonder, what's the difference between quota v1 and v2?  Wouldn't we
want the newer version?

> +					     DQUOT_USAGE_ENABLED);
> +		iput(inode);
> +		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--) {
> +		inode = sb_dqopt(sb)->files[type];
> +		if (inode)
> +			inode = igrab(inode);
> +		dquot_quota_off(sb, type);
> +		if (inode)
> +			iput(inode);
> +	}
> +	return err;
> +}
> +
> +static void shmem_disable_quotas(struct super_block *sb)
> +{
> +	struct inode *inode = NULL;
> +	int type;
> +
> +	for (type = 0; type < SHMEM_MAXQUOTAS; type++) {
> +		inode = sb_dqopt(sb)->files[type];
> +		if (inode)
> +			inode = igrab(inode);
> +		dquot_quota_off(sb, type);
> +		if (inode)
> +			iput(inode);
> +	}
> +}
> +
> +static ssize_t shmem_quota_read(struct super_block *sb, int type, char *data,
> +			       size_t len, loff_t off)
> +{
> +	struct inode *inode = sb_dqopt(sb)->files[type];
> +	loff_t i_size = i_size_read(inode);
> +	struct folio *folio = NULL;
> +	struct page *page = NULL;
> +	unsigned long offset;
> +	int tocopy, err = 0;
> +	pgoff_t index;
> +	size_t toread;
> +
> +	index = off >> PAGE_SHIFT;
> +	offset = off & ~PAGE_MASK;
> +
> +	if (off > i_size)
> +		return 0;
> +	if (off+len > i_size)
> +		len = i_size - off;
> +	toread = len;
> +	while (toread > 0) {
> +		tocopy = PAGE_SIZE - offset < toread ?
> +			 PAGE_SIZE - offset : toread;
> +
> +		err = shmem_get_folio(inode, index, &folio, SGP_READ);
> +		if (err) {
> +			if (err == -EINVAL)
> +				err = 0;
> +			return err;
> +		}
> +
> +		if (folio) {

folio && folio_test_uptodate() ?

> +			folio_unlock(folio);
> +			page = folio_file_page(folio, index);
> +			if (PageHWPoison(page)) {
> +				folio_put(folio);
> +				return -EIO;
> +			}
> +			memcpy(data, page_address(page) + offset, tocopy);
> +			folio_put(folio);

Also, I'm a little surprised that it's safe to read the contents of a
folio after unlocking it.

> +		} else { /* A hole */
> +			memset(data, 0, tocopy);
> +		}
> +
> +		offset = 0;
> +		toread -= tocopy;
> +		data += tocopy;
> +		index++;
> +		cond_resched();
> +	}
> +	return len;
> +}
> +
> +static struct dquot **shmem_get_dquots(struct inode *inode)
> +{
> +	return SHMEM_I(inode)->i_dquot;
> +}
> +#endif /* SHMEM_QUOTA_TMPFS */
> +
>  /*
>   * shmem_reserve_inode() performs bookkeeping to reserve a shmem inode, and
>   * produces a novel ino for the newly allocated inode.
> @@ -353,7 +590,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;

I guess the quotaops helpers take care of the i_blocks updates now?

--D

>  		shmem_inode_unacct_blocks(inode, freed);
>  	}
>  }
> @@ -363,7 +599,7 @@ bool shmem_charge(struct inode *inode, long pages)
>  	struct shmem_inode_info *info = SHMEM_I(inode);
>  	unsigned long flags;
>  
> -	if (!shmem_inode_acct_block(inode, pages))
> +	if (shmem_inode_acct_block(inode, pages))
>  		return false;
>  
>  	/* nrpages adjustment first, then shmem_recalc_inode() when balanced */
> @@ -371,7 +607,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);
>  
> @@ -387,7 +622,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);
>  
> @@ -1119,6 +1353,13 @@ static int shmem_setattr(struct user_namespace *mnt_userns,
>  		}
>  	}
>  
> +	 /* Transfer quota accounting */
> +	if (i_uid_needs_update(mnt_userns, attr, inode) ||
> +	    i_gid_needs_update(mnt_userns, attr, inode))
> +		error = dquot_transfer(mnt_userns, inode, attr);
> +	if (error)
> +		return error;
> +
>  	setattr_copy(&init_user_ns, inode, attr);
>  	if (attr->ia_valid & ATTR_MODE)
>  		error = posix_acl_chmod(&init_user_ns, inode, inode->i_mode);
> @@ -1164,7 +1405,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,
> @@ -1569,14 +1812,14 @@ static struct folio *shmem_alloc_and_acct_folio(gfp_t gfp, struct inode *inode,
>  {
>  	struct shmem_inode_info *info = SHMEM_I(inode);
>  	struct folio *folio;
> -	int nr;
> -	int err = -ENOSPC;
> +	int nr, err;
>  
>  	if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
>  		huge = false;
>  	nr = huge ? HPAGE_PMD_NR : 1;
>  
> -	if (!shmem_inode_acct_block(inode, nr))
> +	err = shmem_inode_acct_block(inode, nr);
> +	if (err)
>  		goto failed;
>  
>  	if (huge)
> @@ -1949,7 +2192,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;
> @@ -2315,8 +2557,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 super_block *sb, struct inode *dir,
> -				     umode_t mode, dev_t dev, unsigned long flags)
> +static struct inode *shmem_get_inode_noquota(struct super_block *sb,
> +					     struct inode *dir,
> +					     umode_t mode, dev_t dev,
> +					     unsigned long flags)
>  {
>  	struct inode *inode;
>  	struct shmem_inode_info *info;
> @@ -2384,6 +2628,35 @@ static struct inode *shmem_get_inode(struct super_block *sb, struct inode *dir,
>  	return inode;
>  }
>  
> +static struct inode *shmem_get_inode(struct super_block *sb, struct inode *dir,
> +				     umode_t mode, dev_t dev, unsigned long flags)
> +{
> +	int err;
> +	struct inode *inode;
> +
> +	inode = shmem_get_inode_noquota(sb, dir, mode, dev, flags);
> +	if (inode) {
> +		err = dquot_initialize(inode);
> +		if (err)
> +			goto errout;
> +
> +		err = dquot_alloc_inode(inode);
> +		if (err) {
> +			dquot_drop(inode);
> +			goto errout;
> +		}
> +	}
> +	return inode;
> +
> +errout:
> +	inode->i_flags |= S_NOQUOTA;
> +	iput(inode);
> +	shmem_free_inode(sb);
> +	if (err)
> +		return ERR_PTR(err);
> +	return NULL;
> +}
> +
>  #ifdef CONFIG_USERFAULTFD
>  int shmem_mfill_atomic_pte(struct mm_struct *dst_mm,
>  			   pmd_t *dst_pmd,
> @@ -2403,7 +2676,7 @@ int shmem_mfill_atomic_pte(struct mm_struct *dst_mm,
>  	int ret;
>  	pgoff_t max_off;
>  
> -	if (!shmem_inode_acct_block(inode, 1)) {
> +	if (shmem_inode_acct_block(inode, 1)) {
>  		/*
>  		 * We may have got a page, returned -ENOENT triggering a retry,
>  		 * and now we find ourselves with -ENOMEM. Release the page, to
> @@ -2487,7 +2760,6 @@ int shmem_mfill_atomic_pte(struct mm_struct *dst_mm,
>  
>  	spin_lock_irq(&info->lock);
>  	info->alloced++;
> -	inode->i_blocks += BLOCKS_PER_PAGE;
>  	shmem_recalc_inode(inode);
>  	spin_unlock_irq(&info->lock);
>  
> @@ -2908,7 +3180,7 @@ shmem_mknod(struct user_namespace *mnt_userns, struct inode *dir,
>  	int error = -ENOSPC;
>  
>  	inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE);
> -	if (inode) {
> +	if (!IS_ERR_OR_NULL(inode)) {
>  		error = simple_acl_create(dir, inode);
>  		if (error)
>  			goto out_iput;
> @@ -2924,7 +3196,8 @@ shmem_mknod(struct user_namespace *mnt_userns, struct inode *dir,
>  		inode_inc_iversion(dir);
>  		d_instantiate(dentry, inode);
>  		dget(dentry); /* Extra count - pin the dentry in core */
> -	}
> +	} else if (IS_ERR(inode))
> +		error = PTR_ERR(inode);
>  	return error;
>  out_iput:
>  	iput(inode);
> @@ -2939,7 +3212,7 @@ shmem_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
>  	int error = -ENOSPC;
>  
>  	inode = shmem_get_inode(dir->i_sb, dir, mode, 0, VM_NORESERVE);
> -	if (inode) {
> +	if (!IS_ERR_OR_NULL(inode)) {
>  		error = security_inode_init_security(inode, dir,
>  						     NULL,
>  						     shmem_initxattrs, NULL);
> @@ -2949,7 +3222,8 @@ shmem_tmpfile(struct user_namespace *mnt_userns, struct inode *dir,
>  		if (error)
>  			goto out_iput;
>  		d_tmpfile(file, inode);
> -	}
> +	} else if (IS_ERR(inode))
> +		error = PTR_ERR(inode);
>  	return finish_open_simple(file, error);
>  out_iput:
>  	iput(inode);
> @@ -3126,6 +3400,8 @@ static int shmem_symlink(struct user_namespace *mnt_userns, struct inode *dir,
>  				VM_NORESERVE);
>  	if (!inode)
>  		return -ENOSPC;
> +	else if (IS_ERR(inode))
> +		return PTR_ERR(inode);
>  
>  	error = security_inode_init_security(inode, dir, &dentry->d_name,
>  					     shmem_initxattrs, NULL);
> @@ -3443,6 +3719,7 @@ enum shmem_param {
>  	Opt_uid,
>  	Opt_inode32,
>  	Opt_inode64,
> +	Opt_quota,
>  };
>  
>  static const struct constant_table shmem_param_enums_huge[] = {
> @@ -3464,6 +3741,9 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
>  	fsparam_u32   ("uid",		Opt_uid),
>  	fsparam_flag  ("inode32",	Opt_inode32),
>  	fsparam_flag  ("inode64",	Opt_inode64),
> +	fsparam_flag  ("quota",		Opt_quota),
> +	fsparam_flag  ("usrquota",	Opt_quota),
> +	fsparam_flag  ("grpquota",	Opt_quota),
>  	{}
>  };
>  
> @@ -3547,6 +3827,13 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
>  		ctx->full_inums = true;
>  		ctx->seen |= SHMEM_SEEN_INUMS;
>  		break;
> +	case Opt_quota:
> +#ifdef CONFIG_QUOTA
> +		ctx->seen |= SHMEM_SEEN_QUOTA;
> +#else
> +		goto unsupported_parameter;
> +#endif
> +		break;
>  	}
>  	return 0;
>  
> @@ -3646,6 +3933,12 @@ static int shmem_reconfigure(struct fs_context *fc)
>  		goto out;
>  	}
>  
> +	if (ctx->seen & SHMEM_SEEN_QUOTA &&
> +	    !sb_any_quota_loaded(fc->root->d_sb)) {
> +		err = "Cannot enable quota on remount";
> +		goto out;
> +	}
> +
>  	if (ctx->seen & SHMEM_SEEN_HUGE)
>  		sbinfo->huge = ctx->huge;
>  	if (ctx->seen & SHMEM_SEEN_INUMS)
> @@ -3728,6 +4021,9 @@ static void shmem_put_super(struct super_block *sb)
>  {
>  	struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
>  
> +#ifdef SHMEM_QUOTA_TMPFS
> +	shmem_disable_quotas(sb);
> +#endif
>  	free_percpu(sbinfo->ino_batch);
>  	percpu_counter_destroy(&sbinfo->used_blocks);
>  	mpol_put(sbinfo->mpol);
> @@ -3805,14 +4101,26 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
>  #endif
>  	uuid_gen(&sb->s_uuid);
>  
> +#ifdef SHMEM_QUOTA_TMPFS
> +	if (ctx->seen & SHMEM_SEEN_QUOTA) {
> +		sb->dq_op = &dquot_operations;
> +		sb->s_qcop = &dquot_quotactl_sysfile_ops;
> +		sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP;
> +
> +		if (shmem_enable_quotas(sb))
> +			goto failed;
> +	}
> +#endif  /* SHMEM_QUOTA_TMPFS */
> +
>  	inode = shmem_get_inode(sb, NULL, S_IFDIR | sbinfo->mode, 0, VM_NORESERVE);
> -	if (!inode)
> +	if (IS_ERR_OR_NULL(inode))
>  		goto failed;
>  	inode->i_uid = sbinfo->uid;
>  	inode->i_gid = sbinfo->gid;
>  	sb->s_root = d_make_root(inode);
>  	if (!sb->s_root)
>  		goto failed;
> +
>  	return 0;
>  
>  failed:
> @@ -3976,7 +4284,12 @@ static const struct super_operations shmem_ops = {
>  #ifdef CONFIG_TMPFS
>  	.statfs		= shmem_statfs,
>  	.show_options	= shmem_show_options,
> -#endif
> +#ifdef CONFIG_QUOTA
> +	.quota_read	= shmem_quota_read,
> +	.quota_write	= shmem_quota_write,
> +	.get_dquots	= shmem_get_dquots,
> +#endif /* CONFIG_QUOTA */
> +#endif /* CONFIG_TMPFS */
>  	.evict_inode	= shmem_evict_inode,
>  	.drop_inode	= generic_delete_inode,
>  	.put_super	= shmem_put_super,
> @@ -4196,8 +4509,10 @@ static struct file *__shmem_file_setup(struct vfsmount *mnt, const char *name, l
>  
>  	inode = shmem_get_inode(mnt->mnt_sb, NULL, S_IFREG | S_IRWXUGO, 0,
>  				flags);
> -	if (unlikely(!inode)) {
> +	if (IS_ERR_OR_NULL(inode)) {
>  		shmem_unacct_size(flags, size);
> +		if (IS_ERR(inode))
> +			return (struct file *)inode;
>  		return ERR_PTR(-ENOSPC);
>  	}
>  	inode->i_flags |= i_flags;
> -- 
> 2.38.1
> 



[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux