Message ID | 20221121142854.91109-4-lczerner@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | shmem: user and group quota support for tmpfs | expand |
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
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 > >
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 --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:
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(-)