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? (Are there any plans for a soft limit?) > 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. usrquota_block_hardlimit? usrquota_inode_hardlimit? 8/24... usrquota_block_limit? usrquota_inode_limit? 8/20... (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 >