From bc8230ee8e2ba967af780cdaf2dcc0f8e5eb45ca Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 14:39:48 +0200 Subject: quota: Convert dqio_mutex to rwsem Convert dqio_mutex to rwsem and call it dqio_sem. No functional changes yet. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 28 ++++++++++++++-------------- fs/quota/quota_tree.c | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 53a17496c5c5..29d447598642 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -120,7 +120,7 @@ * spinlock to internal buffers before writing. * * Lock ordering (including related VFS locks) is the following: - * s_umount > i_mutex > journal_lock > dquot->dq_lock > dqio_mutex + * s_umount > i_mutex > journal_lock > dquot->dq_lock > dqio_sem */ static __cacheline_aligned_in_smp DEFINE_SPINLOCK(dq_list_lock); @@ -406,7 +406,7 @@ int dquot_acquire(struct dquot *dquot) struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); mutex_lock(&dquot->dq_lock); - mutex_lock(&dqopt->dqio_mutex); + down_write(&dqopt->dqio_sem); if (!test_bit(DQ_READ_B, &dquot->dq_flags)) ret = dqopt->ops[dquot->dq_id.type]->read_dqblk(dquot); if (ret < 0) @@ -436,7 +436,7 @@ int dquot_acquire(struct dquot *dquot) smp_mb__before_atomic(); set_bit(DQ_ACTIVE_B, &dquot->dq_flags); out_iolock: - mutex_unlock(&dqopt->dqio_mutex); + up_write(&dqopt->dqio_sem); mutex_unlock(&dquot->dq_lock); return ret; } @@ -450,7 +450,7 @@ int dquot_commit(struct dquot *dquot) int ret = 0; struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); - mutex_lock(&dqopt->dqio_mutex); + down_write(&dqopt->dqio_sem); spin_lock(&dq_list_lock); if (!clear_dquot_dirty(dquot)) { spin_unlock(&dq_list_lock); @@ -464,7 +464,7 @@ int dquot_commit(struct dquot *dquot) else ret = -EIO; out_sem: - mutex_unlock(&dqopt->dqio_mutex); + up_write(&dqopt->dqio_sem); return ret; } EXPORT_SYMBOL(dquot_commit); @@ -481,7 +481,7 @@ int dquot_release(struct dquot *dquot) /* Check whether we are not racing with some other dqget() */ if (atomic_read(&dquot->dq_count) > 1) goto out_dqlock; - mutex_lock(&dqopt->dqio_mutex); + down_write(&dqopt->dqio_sem); if (dqopt->ops[dquot->dq_id.type]->release_dqblk) { ret = dqopt->ops[dquot->dq_id.type]->release_dqblk(dquot); /* Write the info */ @@ -493,7 +493,7 @@ int dquot_release(struct dquot *dquot) ret = ret2; } clear_bit(DQ_ACTIVE_B, &dquot->dq_flags); - mutex_unlock(&dqopt->dqio_mutex); + up_write(&dqopt->dqio_sem); out_dqlock: mutex_unlock(&dquot->dq_lock); return ret; @@ -2060,9 +2060,9 @@ int dquot_commit_info(struct super_block *sb, int type) int ret; struct quota_info *dqopt = sb_dqopt(sb); - mutex_lock(&dqopt->dqio_mutex); + down_write(&dqopt->dqio_sem); ret = dqopt->ops[type]->write_file_info(sb, type); - mutex_unlock(&dqopt->dqio_mutex); + up_write(&dqopt->dqio_sem); return ret; } EXPORT_SYMBOL(dquot_commit_info); @@ -2076,9 +2076,9 @@ int dquot_get_next_id(struct super_block *sb, struct kqid *qid) return -ESRCH; if (!dqopt->ops[qid->type]->get_next_id) return -ENOSYS; - mutex_lock(&dqopt->dqio_mutex); + down_write(&dqopt->dqio_sem); err = dqopt->ops[qid->type]->get_next_id(sb, qid); - mutex_unlock(&dqopt->dqio_mutex); + up_write(&dqopt->dqio_sem); return err; } EXPORT_SYMBOL(dquot_get_next_id); @@ -2328,15 +2328,15 @@ static int vfs_load_quota_inode(struct inode *inode, int type, int format_id, dqopt->info[type].dqi_format = fmt; dqopt->info[type].dqi_fmt_id = format_id; INIT_LIST_HEAD(&dqopt->info[type].dqi_dirty_list); - mutex_lock(&dqopt->dqio_mutex); + down_write(&dqopt->dqio_sem); error = dqopt->ops[type]->read_file_info(sb, type); if (error < 0) { - mutex_unlock(&dqopt->dqio_mutex); + up_write(&dqopt->dqio_sem); goto out_file_init; } if (dqopt->flags & DQUOT_QUOTA_SYS_FILE) dqopt->info[type].dqi_flags |= DQF_SYS_FILE; - mutex_unlock(&dqopt->dqio_mutex); + up_write(&dqopt->dqio_sem); spin_lock(&dq_state_lock); dqopt->flags |= dquot_state_flag(flags, type); spin_unlock(&dq_state_lock); diff --git a/fs/quota/quota_tree.c b/fs/quota/quota_tree.c index 0738972e8d3f..54f85eb2609c 100644 --- a/fs/quota/quota_tree.c +++ b/fs/quota/quota_tree.c @@ -379,7 +379,7 @@ int qtree_write_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot) if (!ddquot) return -ENOMEM; - /* dq_off is guarded by dqio_mutex */ + /* dq_off is guarded by dqio_sem */ if (!dquot->dq_off) { ret = dq_insert_tree(info, dquot); if (ret < 0) { -- cgit v1.2.3 From 62676838cb39f4e4f44dd697c4d5d4214bda8cb1 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 15:00:20 +0200 Subject: quota: Do more fine-grained locking in dquot_acquire() We need dqio_sem held just for reading when calling ->read_dqblk() in dquot_acquire(). Also dqio_sem is not needed when setting DQ_READ_B and DQ_ACTIVE_B as concurrent reads and dquot activations are serialized by dq_lock. So acquire and release dqio_sem closer to the place where it is needed. This reduces lock hold time and will make locking changes easier. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 29d447598642..21358f31923d 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -406,9 +406,11 @@ int dquot_acquire(struct dquot *dquot) struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); mutex_lock(&dquot->dq_lock); - down_write(&dqopt->dqio_sem); - if (!test_bit(DQ_READ_B, &dquot->dq_flags)) + if (!test_bit(DQ_READ_B, &dquot->dq_flags)) { + down_read(&dqopt->dqio_sem); ret = dqopt->ops[dquot->dq_id.type]->read_dqblk(dquot); + up_read(&dqopt->dqio_sem); + } if (ret < 0) goto out_iolock; /* Make sure flags update is visible after dquot has been filled */ @@ -416,12 +418,14 @@ int dquot_acquire(struct dquot *dquot) set_bit(DQ_READ_B, &dquot->dq_flags); /* Instantiate dquot if needed */ if (!test_bit(DQ_ACTIVE_B, &dquot->dq_flags) && !dquot->dq_off) { + down_write(&dqopt->dqio_sem); ret = dqopt->ops[dquot->dq_id.type]->commit_dqblk(dquot); /* Write the info if needed */ if (info_dirty(&dqopt->info[dquot->dq_id.type])) { ret2 = dqopt->ops[dquot->dq_id.type]->write_file_info( dquot->dq_sb, dquot->dq_id.type); } + up_write(&dqopt->dqio_sem); if (ret < 0) goto out_iolock; if (ret2 < 0) { @@ -436,7 +440,6 @@ int dquot_acquire(struct dquot *dquot) smp_mb__before_atomic(); set_bit(DQ_ACTIVE_B, &dquot->dq_flags); out_iolock: - up_write(&dqopt->dqio_sem); mutex_unlock(&dquot->dq_lock); return ret; } -- cgit v1.2.3 From 0cff9151d3fa27574c2201377a080e1b9b87b8ba Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 15:05:08 +0200 Subject: quota: Acquire dqio_sem for reading in dquot_get_next_id() dquot_get_next_id() needs dqio_sem only for reading to protect against racing with modification of quota file structure. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 21358f31923d..8d5ccad3bf3e 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -2079,9 +2079,9 @@ int dquot_get_next_id(struct super_block *sb, struct kqid *qid) return -ESRCH; if (!dqopt->ops[qid->type]->get_next_id) return -ENOSYS; - down_write(&dqopt->dqio_sem); + down_read(&dqopt->dqio_sem); err = dqopt->ops[qid->type]->get_next_id(sb, qid); - up_write(&dqopt->dqio_sem); + up_read(&dqopt->dqio_sem); return err; } EXPORT_SYMBOL(dquot_get_next_id); -- cgit v1.2.3 From d6ab3661020cbf8a8909b49a8e1408d5ae434001 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 15:06:28 +0200 Subject: quota: Acquire dqio_sem for reading in vfs_load_quota_inode() vfs_load_quota_inode() needs dqio_sem only for reading. In fact dqio_sem is not needed there at all since the function can be called only during quota on when quota file cannot be modified but let's leave the protection there since it is logical and the path is in no way performance critical. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 8d5ccad3bf3e..3852a3c79ac9 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -2331,15 +2331,15 @@ static int vfs_load_quota_inode(struct inode *inode, int type, int format_id, dqopt->info[type].dqi_format = fmt; dqopt->info[type].dqi_fmt_id = format_id; INIT_LIST_HEAD(&dqopt->info[type].dqi_dirty_list); - down_write(&dqopt->dqio_sem); + down_read(&dqopt->dqio_sem); error = dqopt->ops[type]->read_file_info(sb, type); if (error < 0) { - up_write(&dqopt->dqio_sem); + up_read(&dqopt->dqio_sem); goto out_file_init; } if (dqopt->flags & DQUOT_QUOTA_SYS_FILE) dqopt->info[type].dqi_flags |= DQF_SYS_FILE; - up_write(&dqopt->dqio_sem); + up_read(&dqopt->dqio_sem); spin_lock(&dq_state_lock); dqopt->flags |= dquot_state_flag(flags, type); spin_unlock(&dq_state_lock); -- cgit v1.2.3 From 5e8cb9b6249de3ac036ef4cf4b7babc2f4b95d90 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 15:16:41 +0200 Subject: quota: Protect dquot writeout with dq_lock Currently dquot writeout is only protected by dqio_sem held for writing. As we transition to a finer grained locking we will use dquot->dq_lock instead. So acquire it in dquot_commit() and move dqio_sem just around ->commit_dqblk() call as it is still needed to serialize quota file changes. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 3852a3c79ac9..8b52e852eba2 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -110,14 +110,11 @@ * sure they cannot race with quotaon which first sets S_NOQUOTA flag and * then drops all pointers to dquots from an inode. * - * Each dquot has its dq_lock mutex. Locked dquots might not be referenced - * from inodes (dquot_alloc_space() and such don't check the dq_lock). - * Currently dquot is locked only when it is being read to memory (or space for - * it is being allocated) on the first dqget() and when it is being released on - * the last dqput(). The allocation and release oparations are serialized by - * the dq_lock and by checking the use count in dquot_release(). Write - * operations on dquots don't hold dq_lock as they copy data under dq_data_lock - * spinlock to internal buffers before writing. + * Each dquot has its dq_lock mutex. Dquot is locked when it is being read to + * memory (or space for it is being allocated) on the first dqget(), when it is + * being written out, and when it is being released on the last dqput(). The + * allocation and release operations are serialized by the dq_lock and by + * checking the use count in dquot_release(). * * Lock ordering (including related VFS locks) is the following: * s_umount > i_mutex > journal_lock > dquot->dq_lock > dqio_sem @@ -453,21 +450,24 @@ int dquot_commit(struct dquot *dquot) int ret = 0; struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); - down_write(&dqopt->dqio_sem); + mutex_lock(&dquot->dq_lock); spin_lock(&dq_list_lock); if (!clear_dquot_dirty(dquot)) { spin_unlock(&dq_list_lock); - goto out_sem; + goto out_lock; } spin_unlock(&dq_list_lock); /* Inactive dquot can be only if there was error during read/init * => we have better not writing it */ - if (test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) + if (test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) { + down_write(&dqopt->dqio_sem); ret = dqopt->ops[dquot->dq_id.type]->commit_dqblk(dquot); - else + up_write(&dqopt->dqio_sem); + } else { ret = -EIO; -out_sem: - up_write(&dqopt->dqio_sem); + } +out_lock: + mutex_unlock(&dquot->dq_lock); return ret; } EXPORT_SYMBOL(dquot_commit); -- cgit v1.2.3 From e342e38df925973b86cd46d40bbe7f083414e2ad Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 15:30:45 +0200 Subject: quota: Push dqio_sem down to ->read_dqblk() Push down acquisition of dqio_sem into ->read_dqblk() callback. It will allow quota formats to decide whether they need it or not. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 5 +---- fs/quota/quota_v1.c | 5 ++++- fs/quota/quota_v2.c | 10 +++++++++- 3 files changed, 14 insertions(+), 6 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 8b52e852eba2..46046523abf0 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -403,11 +403,8 @@ int dquot_acquire(struct dquot *dquot) struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); mutex_lock(&dquot->dq_lock); - if (!test_bit(DQ_READ_B, &dquot->dq_flags)) { - down_read(&dqopt->dqio_sem); + if (!test_bit(DQ_READ_B, &dquot->dq_flags)) ret = dqopt->ops[dquot->dq_id.type]->read_dqblk(dquot); - up_read(&dqopt->dqio_sem); - } if (ret < 0) goto out_iolock; /* Make sure flags update is visible after dquot has been filled */ diff --git a/fs/quota/quota_v1.c b/fs/quota/quota_v1.c index 8fe79beced5c..d534c4043237 100644 --- a/fs/quota/quota_v1.c +++ b/fs/quota/quota_v1.c @@ -56,10 +56,12 @@ static int v1_read_dqblk(struct dquot *dquot) { int type = dquot->dq_id.type; struct v1_disk_dqblk dqblk; + struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); - if (!sb_dqopt(dquot->dq_sb)->files[type]) + if (!dqopt->files[type]) return -EINVAL; + down_read(&dqopt->dqio_sem); /* Set structure to 0s in case read fails/is after end of file */ memset(&dqblk, 0, sizeof(struct v1_disk_dqblk)); dquot->dq_sb->s_op->quota_read(dquot->dq_sb, type, (char *)&dqblk, @@ -73,6 +75,7 @@ static int v1_read_dqblk(struct dquot *dquot) dquot->dq_dqb.dqb_isoftlimit == 0) set_bit(DQ_FAKE_B, &dquot->dq_flags); dqstats_inc(DQST_READS); + up_read(&dqopt->dqio_sem); return 0; } diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index ca71bf881ad1..b2cd34f6b3da 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -285,7 +285,15 @@ static int v2r1_is_id(void *dp, struct dquot *dquot) static int v2_read_dquot(struct dquot *dquot) { - return qtree_read_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv, dquot); + struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); + int ret; + + down_read(&dqopt->dqio_sem); + ret = qtree_read_dquot( + sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv, + dquot); + up_read(&dqopt->dqio_sem); + return ret; } static int v2_write_dquot(struct dquot *dquot) -- cgit v1.2.3 From 47cdc11deed639ae1d4050efbc284d328c3c2fa5 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 15:43:13 +0200 Subject: quota: Remove locking for reading from the old quota format The old quota format has fixed offset in quota file based on ID so there's no locking needed against concurrent modifications of the file (locking against concurrent IO on the same dquot is still provided by dq_lock). Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/quota_v1.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/quota_v1.c b/fs/quota/quota_v1.c index d534c4043237..12d69cda57cc 100644 --- a/fs/quota/quota_v1.c +++ b/fs/quota/quota_v1.c @@ -61,7 +61,6 @@ static int v1_read_dqblk(struct dquot *dquot) if (!dqopt->files[type]) return -EINVAL; - down_read(&dqopt->dqio_sem); /* Set structure to 0s in case read fails/is after end of file */ memset(&dqblk, 0, sizeof(struct v1_disk_dqblk)); dquot->dq_sb->s_op->quota_read(dquot->dq_sb, type, (char *)&dqblk, @@ -75,7 +74,6 @@ static int v1_read_dqblk(struct dquot *dquot) dquot->dq_dqb.dqb_isoftlimit == 0) set_bit(DQ_FAKE_B, &dquot->dq_flags); dqstats_inc(DQST_READS); - up_read(&dqopt->dqio_sem); return 0; } -- cgit v1.2.3 From 8fc32c2b0db2c9ee0dffebea65bcdea03a29ba5a Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 15:48:16 +0200 Subject: quota: Push dqio_sem down to ->write_dqblk() Push down acquisition of dqio_sem into ->write_dqblk() callback. It will allow quota formats to decide whether they need it or not. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 11 ++++------- fs/quota/quota_v1.c | 3 +++ fs/quota/quota_v2.c | 10 +++++++++- 3 files changed, 16 insertions(+), 8 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 46046523abf0..562f5978488f 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -412,14 +412,14 @@ int dquot_acquire(struct dquot *dquot) set_bit(DQ_READ_B, &dquot->dq_flags); /* Instantiate dquot if needed */ if (!test_bit(DQ_ACTIVE_B, &dquot->dq_flags) && !dquot->dq_off) { - down_write(&dqopt->dqio_sem); ret = dqopt->ops[dquot->dq_id.type]->commit_dqblk(dquot); /* Write the info if needed */ if (info_dirty(&dqopt->info[dquot->dq_id.type])) { + down_write(&dqopt->dqio_sem); ret2 = dqopt->ops[dquot->dq_id.type]->write_file_info( dquot->dq_sb, dquot->dq_id.type); + up_write(&dqopt->dqio_sem); } - up_write(&dqopt->dqio_sem); if (ret < 0) goto out_iolock; if (ret2 < 0) { @@ -456,13 +456,10 @@ int dquot_commit(struct dquot *dquot) spin_unlock(&dq_list_lock); /* Inactive dquot can be only if there was error during read/init * => we have better not writing it */ - if (test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) { - down_write(&dqopt->dqio_sem); + if (test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) ret = dqopt->ops[dquot->dq_id.type]->commit_dqblk(dquot); - up_write(&dqopt->dqio_sem); - } else { + else ret = -EIO; - } out_lock: mutex_unlock(&dquot->dq_lock); return ret; diff --git a/fs/quota/quota_v1.c b/fs/quota/quota_v1.c index 12d69cda57cc..94cceb76b9a3 100644 --- a/fs/quota/quota_v1.c +++ b/fs/quota/quota_v1.c @@ -83,7 +83,9 @@ static int v1_commit_dqblk(struct dquot *dquot) short type = dquot->dq_id.type; ssize_t ret; struct v1_disk_dqblk dqblk; + struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); + down_write(&dqopt->dqio_sem); v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb); if (((type == USRQUOTA) && uid_eq(dquot->dq_id.uid, GLOBAL_ROOT_UID)) || ((type == GRPQUOTA) && gid_eq(dquot->dq_id.gid, GLOBAL_ROOT_GID))) { @@ -97,6 +99,7 @@ static int v1_commit_dqblk(struct dquot *dquot) ret = dquot->dq_sb->s_op->quota_write(dquot->dq_sb, type, (char *)&dqblk, sizeof(struct v1_disk_dqblk), v1_dqoff(from_kqid(&init_user_ns, dquot->dq_id))); + up_write(&dqopt->dqio_sem); if (ret != sizeof(struct v1_disk_dqblk)) { quota_error(dquot->dq_sb, "dquota write failed"); if (ret >= 0) diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index b2cd34f6b3da..482733abe4ac 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -298,7 +298,15 @@ static int v2_read_dquot(struct dquot *dquot) static int v2_write_dquot(struct dquot *dquot) { - return qtree_write_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv, dquot); + struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); + int ret; + + down_write(&dqopt->dqio_sem); + ret = qtree_write_dquot( + sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv, + dquot); + up_write(&dqopt->dqio_sem); + return ret; } static int v2_release_dquot(struct dquot *dquot) -- cgit v1.2.3 From d2faa415166b2883428efa92f451774ef44373ac Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 16:09:46 +0200 Subject: quota: Do not acquire dqio_sem for dquot overwrites in v2 format When dquot has space already allocated in a quota file, we just overwrite that place when writing dquot. So we don't need any protection against other modifications of quota file as these keep dquot in place. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/quota_v2.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index 482733abe4ac..7a05b80f3b8c 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -300,12 +300,23 @@ static int v2_write_dquot(struct dquot *dquot) { struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); int ret; - - down_write(&dqopt->dqio_sem); + bool alloc = false; + + /* + * If space for dquot is already allocated, we don't need any + * protection as we'll only overwrite the place of dquot. We are + * still protected by concurrent writes of the same dquot by + * dquot->dq_lock. + */ + if (!dquot->dq_off) { + alloc = true; + down_write(&dqopt->dqio_sem); + } ret = qtree_write_dquot( sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv, dquot); - up_write(&dqopt->dqio_sem); + if (alloc) + up_write(&dqopt->dqio_sem); return ret; } -- cgit v1.2.3 From f0c5bae5cc562307f804335ddec3f793254c3766 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 16:18:54 +0200 Subject: quota: Remove locking for writing to the old quota format The old quota quota format has fixed offset in quota file based on ID so there's no locking needed against concurrent modifications of the file (locking against concurrent IO on the same dquot is still provided by dq_lock). Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/quota_v1.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/quota_v1.c b/fs/quota/quota_v1.c index 94cceb76b9a3..12d69cda57cc 100644 --- a/fs/quota/quota_v1.c +++ b/fs/quota/quota_v1.c @@ -83,9 +83,7 @@ static int v1_commit_dqblk(struct dquot *dquot) short type = dquot->dq_id.type; ssize_t ret; struct v1_disk_dqblk dqblk; - struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); - down_write(&dqopt->dqio_sem); v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb); if (((type == USRQUOTA) && uid_eq(dquot->dq_id.uid, GLOBAL_ROOT_UID)) || ((type == GRPQUOTA) && gid_eq(dquot->dq_id.gid, GLOBAL_ROOT_GID))) { @@ -99,7 +97,6 @@ static int v1_commit_dqblk(struct dquot *dquot) ret = dquot->dq_sb->s_op->quota_write(dquot->dq_sb, type, (char *)&dqblk, sizeof(struct v1_disk_dqblk), v1_dqoff(from_kqid(&init_user_ns, dquot->dq_id))); - up_write(&dqopt->dqio_sem); if (ret != sizeof(struct v1_disk_dqblk)) { quota_error(dquot->dq_sb, "dquota write failed"); if (ret >= 0) -- cgit v1.2.3 From b9a1a7f4b6b5861c6ae89a125271103ceb8c8690 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 8 Jun 2017 17:20:36 +0200 Subject: quota: Push dqio_sem down to ->release_dqblk() Push down acquisition of dqio_sem into ->release_dqblk() callback. It will allow quota formats to decide whether they need it or not. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 4 ++-- fs/quota/quota_v2.c | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 562f5978488f..3b3c7f094ff8 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -478,19 +478,19 @@ int dquot_release(struct dquot *dquot) /* Check whether we are not racing with some other dqget() */ if (atomic_read(&dquot->dq_count) > 1) goto out_dqlock; - down_write(&dqopt->dqio_sem); if (dqopt->ops[dquot->dq_id.type]->release_dqblk) { ret = dqopt->ops[dquot->dq_id.type]->release_dqblk(dquot); /* Write the info */ if (info_dirty(&dqopt->info[dquot->dq_id.type])) { + down_write(&dqopt->dqio_sem); ret2 = dqopt->ops[dquot->dq_id.type]->write_file_info( dquot->dq_sb, dquot->dq_id.type); + up_write(&dqopt->dqio_sem); } if (ret >= 0) ret = ret2; } clear_bit(DQ_ACTIVE_B, &dquot->dq_flags); - up_write(&dqopt->dqio_sem); out_dqlock: mutex_unlock(&dquot->dq_lock); return ret; diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index 7a05b80f3b8c..8f54b159e423 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -322,7 +322,14 @@ static int v2_write_dquot(struct dquot *dquot) static int v2_release_dquot(struct dquot *dquot) { - return qtree_release_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv, dquot); + struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); + int ret; + + down_write(&dqopt->dqio_sem); + ret = qtree_release_dquot(sb_dqinfo(dquot->dq_sb, dquot->dq_id.type)->dqi_priv, dquot); + up_write(&dqopt->dqio_sem); + + return ret; } static int v2_free_file_info(struct super_block *sb, int type) -- cgit v1.2.3 From f14618c6823ee0f9f92a87aad7d5ad26916ccff1 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Fri, 9 Jun 2017 08:36:16 +0200 Subject: quota: Push dqio_sem down to ->get_next_id() Push down acquisition of dqio_sem into ->get_next_id() callback. Mostly for consistency with other operations. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 6 +----- fs/quota/quota_v2.c | 8 +++++++- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 3b3c7f094ff8..332f7026edad 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -2067,16 +2067,12 @@ EXPORT_SYMBOL(dquot_commit_info); int dquot_get_next_id(struct super_block *sb, struct kqid *qid) { struct quota_info *dqopt = sb_dqopt(sb); - int err; if (!sb_has_quota_active(sb, qid->type)) return -ESRCH; if (!dqopt->ops[qid->type]->get_next_id) return -ENOSYS; - down_read(&dqopt->dqio_sem); - err = dqopt->ops[qid->type]->get_next_id(sb, qid); - up_read(&dqopt->dqio_sem); - return err; + return dqopt->ops[qid->type]->get_next_id(sb, qid); } EXPORT_SYMBOL(dquot_get_next_id); diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index 8f54b159e423..f82638e43c07 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -340,7 +340,13 @@ static int v2_free_file_info(struct super_block *sb, int type) static int v2_get_next_id(struct super_block *sb, struct kqid *qid) { - return qtree_get_next_id(sb_dqinfo(sb, qid->type)->dqi_priv, qid); + struct quota_info *dqopt = sb_dqopt(sb); + int ret; + + down_read(&dqopt->dqio_sem); + ret = qtree_get_next_id(sb_dqinfo(sb, qid->type)->dqi_priv, qid); + up_read(&dqopt->dqio_sem); + return ret; } static const struct quota_format_ops v2_format_ops = { -- cgit v1.2.3 From 9a8ae30e73cb8827dd0a8ae5fd505db457cfb7ed Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Fri, 9 Jun 2017 08:45:43 +0200 Subject: quota: Push dqio_sem down to ->write_file_info() Push down acquisition of dqio_sem into ->write_file_info() callback. Mostly for consistency with other operations. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 10 +--------- fs/quota/quota_v1.c | 2 ++ fs/quota/quota_v2.c | 5 ++++- 3 files changed, 7 insertions(+), 10 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 332f7026edad..1e1ff97098ec 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -415,10 +415,8 @@ int dquot_acquire(struct dquot *dquot) ret = dqopt->ops[dquot->dq_id.type]->commit_dqblk(dquot); /* Write the info if needed */ if (info_dirty(&dqopt->info[dquot->dq_id.type])) { - down_write(&dqopt->dqio_sem); ret2 = dqopt->ops[dquot->dq_id.type]->write_file_info( dquot->dq_sb, dquot->dq_id.type); - up_write(&dqopt->dqio_sem); } if (ret < 0) goto out_iolock; @@ -482,10 +480,8 @@ int dquot_release(struct dquot *dquot) ret = dqopt->ops[dquot->dq_id.type]->release_dqblk(dquot); /* Write the info */ if (info_dirty(&dqopt->info[dquot->dq_id.type])) { - down_write(&dqopt->dqio_sem); ret2 = dqopt->ops[dquot->dq_id.type]->write_file_info( dquot->dq_sb, dquot->dq_id.type); - up_write(&dqopt->dqio_sem); } if (ret >= 0) ret = ret2; @@ -2054,13 +2050,9 @@ EXPORT_SYMBOL(dquot_transfer); */ int dquot_commit_info(struct super_block *sb, int type) { - int ret; struct quota_info *dqopt = sb_dqopt(sb); - down_write(&dqopt->dqio_sem); - ret = dqopt->ops[type]->write_file_info(sb, type); - up_write(&dqopt->dqio_sem); - return ret; + return dqopt->ops[type]->write_file_info(sb, type); } EXPORT_SYMBOL(dquot_commit_info); diff --git a/fs/quota/quota_v1.c b/fs/quota/quota_v1.c index 12d69cda57cc..fe68bf544b29 100644 --- a/fs/quota/quota_v1.c +++ b/fs/quota/quota_v1.c @@ -186,6 +186,7 @@ static int v1_write_file_info(struct super_block *sb, int type) struct v1_disk_dqblk dqblk; int ret; + down_write(&dqopt->dqio_sem); dqopt->info[type].dqi_flags &= ~DQF_INFO_DIRTY; ret = sb->s_op->quota_read(sb, type, (char *)&dqblk, sizeof(struct v1_disk_dqblk), v1_dqoff(0)); @@ -203,6 +204,7 @@ static int v1_write_file_info(struct super_block *sb, int type) else if (ret > 0) ret = -EIO; out: + up_write(&dqopt->dqio_sem); return ret; } diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index f82638e43c07..5e47012d2f57 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -154,10 +154,12 @@ static int v2_read_file_info(struct super_block *sb, int type) static int v2_write_file_info(struct super_block *sb, int type) { struct v2_disk_dqinfo dinfo; - struct mem_dqinfo *info = sb_dqinfo(sb, type); + struct quota_info *dqopt = sb_dqopt(sb); + struct mem_dqinfo *info = &dqopt->info[type]; struct qtree_mem_dqinfo *qinfo = info->dqi_priv; ssize_t size; + down_write(&dqopt->dqio_sem); spin_lock(&dq_data_lock); info->dqi_flags &= ~DQF_INFO_DIRTY; dinfo.dqi_bgrace = cpu_to_le32(info->dqi_bgrace); @@ -170,6 +172,7 @@ static int v2_write_file_info(struct super_block *sb, int type) dinfo.dqi_free_entry = cpu_to_le32(qinfo->dqi_free_entry); size = sb->s_op->quota_write(sb, type, (char *)&dinfo, sizeof(struct v2_disk_dqinfo), V2_DQINFOOFF); + up_write(&dqopt->dqio_sem); if (size != sizeof(struct v2_disk_dqinfo)) { quota_error(sb, "Can't write info structure"); return -1; -- cgit v1.2.3 From 42fdb8583d5a7eaf916c7323fce6cb4728f364c4 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Fri, 9 Jun 2017 08:59:46 +0200 Subject: quota: Push dqio_sem down to ->read_file_info() Push down acquisition of dqio_sem into ->read_file_info() callback. This is for consistency with other operations and it also allows us to get rid of an ugliness in OCFS2. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 6 +----- fs/quota/quota_v1.c | 2 ++ fs/quota/quota_v2.c | 28 ++++++++++++++++++++-------- 3 files changed, 23 insertions(+), 13 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 1e1ff97098ec..5e77c4da69a6 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -2313,15 +2313,11 @@ static int vfs_load_quota_inode(struct inode *inode, int type, int format_id, dqopt->info[type].dqi_format = fmt; dqopt->info[type].dqi_fmt_id = format_id; INIT_LIST_HEAD(&dqopt->info[type].dqi_dirty_list); - down_read(&dqopt->dqio_sem); error = dqopt->ops[type]->read_file_info(sb, type); - if (error < 0) { - up_read(&dqopt->dqio_sem); + if (error < 0) goto out_file_init; - } if (dqopt->flags & DQUOT_QUOTA_SYS_FILE) dqopt->info[type].dqi_flags |= DQF_SYS_FILE; - up_read(&dqopt->dqio_sem); spin_lock(&dq_state_lock); dqopt->flags |= dquot_state_flag(flags, type); spin_unlock(&dq_state_lock); diff --git a/fs/quota/quota_v1.c b/fs/quota/quota_v1.c index fe68bf544b29..b2d8e04e567a 100644 --- a/fs/quota/quota_v1.c +++ b/fs/quota/quota_v1.c @@ -161,6 +161,7 @@ static int v1_read_file_info(struct super_block *sb, int type) struct v1_disk_dqblk dqblk; int ret; + down_read(&dqopt->dqio_sem); ret = sb->s_op->quota_read(sb, type, (char *)&dqblk, sizeof(struct v1_disk_dqblk), v1_dqoff(0)); if (ret != sizeof(struct v1_disk_dqblk)) { @@ -177,6 +178,7 @@ static int v1_read_file_info(struct super_block *sb, int type) dqopt->info[type].dqi_bgrace = dqblk.dqb_btime ? dqblk.dqb_btime : MAX_DQ_TIME; out: + up_read(&dqopt->dqio_sem); return ret; } diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index 5e47012d2f57..373d7cfea5b0 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -90,29 +90,38 @@ static int v2_read_file_info(struct super_block *sb, int type) { struct v2_disk_dqinfo dinfo; struct v2_disk_dqheader dqhead; - struct mem_dqinfo *info = sb_dqinfo(sb, type); + struct quota_info *dqopt = sb_dqopt(sb); + struct mem_dqinfo *info = &dqopt->info[type]; struct qtree_mem_dqinfo *qinfo; ssize_t size; unsigned int version; + int ret; - if (!v2_read_header(sb, type, &dqhead)) - return -1; + down_read(&dqopt->dqio_sem); + if (!v2_read_header(sb, type, &dqhead)) { + ret = -1; + goto out; + } version = le32_to_cpu(dqhead.dqh_version); if ((info->dqi_fmt_id == QFMT_VFS_V0 && version != 0) || - (info->dqi_fmt_id == QFMT_VFS_V1 && version != 1)) - return -1; + (info->dqi_fmt_id == QFMT_VFS_V1 && version != 1)) { + ret = -1; + goto out; + } size = sb->s_op->quota_read(sb, type, (char *)&dinfo, sizeof(struct v2_disk_dqinfo), V2_DQINFOOFF); if (size != sizeof(struct v2_disk_dqinfo)) { quota_error(sb, "Can't read info structure"); - return -1; + ret = -1; + goto out; } info->dqi_priv = kmalloc(sizeof(struct qtree_mem_dqinfo), GFP_NOFS); if (!info->dqi_priv) { printk(KERN_WARNING "Not enough memory for quota information structure.\n"); - return -ENOMEM; + ret = -ENOMEM; + goto out; } qinfo = info->dqi_priv; if (version == 0) { @@ -147,7 +156,10 @@ static int v2_read_file_info(struct super_block *sb, int type) qinfo->dqi_entry_size = sizeof(struct v2r1_disk_dqblk); qinfo->dqi_ops = &v2r1_qtree_ops; } - return 0; + ret = 0; +out: + up_read(&dqopt->dqio_sem); + return ret; } /* Write information header to quota file */ -- cgit v1.2.3 From cb8d01b4f624bbf34fd82cbca19e34a22d1eeef6 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Fri, 9 Jun 2017 09:04:47 +0200 Subject: quota: Fix error codes in v2_read_file_info() v2_read_file_info() returned -1 instead of proper error codes on error. Luckily this is not easily visible from userspace as we have called ->check_quota_file shortly before and thus already verified the quota file is sane. Still set the error codes to proper values. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/quota_v2.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index 373d7cfea5b0..21a8bdf7cfec 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -99,13 +99,13 @@ static int v2_read_file_info(struct super_block *sb, int type) down_read(&dqopt->dqio_sem); if (!v2_read_header(sb, type, &dqhead)) { - ret = -1; + ret = -EIO; goto out; } version = le32_to_cpu(dqhead.dqh_version); if ((info->dqi_fmt_id == QFMT_VFS_V0 && version != 0) || (info->dqi_fmt_id == QFMT_VFS_V1 && version != 1)) { - ret = -1; + ret = -EINVAL; goto out; } @@ -113,7 +113,7 @@ static int v2_read_file_info(struct super_block *sb, int type) sizeof(struct v2_disk_dqinfo), V2_DQINFOOFF); if (size != sizeof(struct v2_disk_dqinfo)) { quota_error(sb, "Can't read info structure"); - ret = -1; + ret = -EIO; goto out; } info->dqi_priv = kmalloc(sizeof(struct qtree_mem_dqinfo), GFP_NOFS); -- cgit v1.2.3 From f98bbe37ae96873ce93f36f4cdc7b47d85cc4455 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 17 Aug 2017 19:20:28 +0200 Subject: quota: Propagate ->quota_read errors from v2_read_file_info() Currently we return -EIO on any error (or short read) from ->quota_read() while reading quota info. Propagate the error code instead. Suggested-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/quota_v2.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/quota_v2.c b/fs/quota/quota_v2.c index 21a8bdf7cfec..cdbf71664cdc 100644 --- a/fs/quota/quota_v2.c +++ b/fs/quota/quota_v2.c @@ -65,9 +65,11 @@ static int v2_read_header(struct super_block *sb, int type, if (size != sizeof(struct v2_disk_dqheader)) { quota_error(sb, "Failed header read: expected=%zd got=%zd", sizeof(struct v2_disk_dqheader), size); - return 0; + if (size < 0) + return size; + return -EIO; } - return 1; + return 0; } /* Check whether given file is really vfsv0 quotafile */ @@ -77,7 +79,7 @@ static int v2_check_quota_file(struct super_block *sb, int type) static const uint quota_magics[] = V2_INITQMAGICS; static const uint quota_versions[] = V2_INITQVERSIONS; - if (!v2_read_header(sb, type, &dqhead)) + if (v2_read_header(sb, type, &dqhead)) return 0; if (le32_to_cpu(dqhead.dqh_magic) != quota_magics[type] || le32_to_cpu(dqhead.dqh_version) > quota_versions[type]) @@ -98,10 +100,9 @@ static int v2_read_file_info(struct super_block *sb, int type) int ret; down_read(&dqopt->dqio_sem); - if (!v2_read_header(sb, type, &dqhead)) { - ret = -EIO; + ret = v2_read_header(sb, type, &dqhead); + if (ret < 0) goto out; - } version = le32_to_cpu(dqhead.dqh_version); if ((info->dqi_fmt_id == QFMT_VFS_V0 && version != 0) || (info->dqi_fmt_id == QFMT_VFS_V1 && version != 1)) { @@ -113,7 +114,10 @@ static int v2_read_file_info(struct super_block *sb, int type) sizeof(struct v2_disk_dqinfo), V2_DQINFOOFF); if (size != sizeof(struct v2_disk_dqinfo)) { quota_error(sb, "Can't read info structure"); - ret = -EIO; + if (size < 0) + ret = size; + else + ret = -EIO; goto out; } info->dqi_priv = kmalloc(sizeof(struct qtree_mem_dqinfo), GFP_NOFS); -- cgit v1.2.3 From 15512377bd971ecc86f2eab40b841b265b5043de Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Fri, 9 Jun 2017 11:56:06 +0200 Subject: quota: Fix possible corruption of dqi_flags dqi_flags modifications are protected by dq_data_lock. However the modifications in vfs_load_quota_inode() and in mark_info_dirty() were not which could lead to corruption of dqi_flags. Since modifications to dqi_flags are rare, this is hard to observe in practice but in theory it could happen. Fix the problem by always using dq_data_lock for protection. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 9 +++++++-- fs/quota/quota_v1.c | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 5e77c4da69a6..e1a155e8db15 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -389,7 +389,9 @@ static inline int clear_dquot_dirty(struct dquot *dquot) void mark_info_dirty(struct super_block *sb, int type) { - set_bit(DQF_INFO_DIRTY_B, &sb_dqopt(sb)->info[type].dqi_flags); + spin_lock(&dq_data_lock); + sb_dqopt(sb)->info[type].dqi_flags |= DQF_INFO_DIRTY; + spin_unlock(&dq_data_lock); } EXPORT_SYMBOL(mark_info_dirty); @@ -2316,8 +2318,11 @@ static int vfs_load_quota_inode(struct inode *inode, int type, int format_id, error = dqopt->ops[type]->read_file_info(sb, type); if (error < 0) goto out_file_init; - if (dqopt->flags & DQUOT_QUOTA_SYS_FILE) + if (dqopt->flags & DQUOT_QUOTA_SYS_FILE) { + spin_lock(&dq_data_lock); dqopt->info[type].dqi_flags |= DQF_SYS_FILE; + spin_unlock(&dq_data_lock); + } spin_lock(&dq_state_lock); dqopt->flags |= dquot_state_flag(flags, type); spin_unlock(&dq_state_lock); diff --git a/fs/quota/quota_v1.c b/fs/quota/quota_v1.c index b2d8e04e567a..7ac5298aba70 100644 --- a/fs/quota/quota_v1.c +++ b/fs/quota/quota_v1.c @@ -189,7 +189,6 @@ static int v1_write_file_info(struct super_block *sb, int type) int ret; down_write(&dqopt->dqio_sem); - dqopt->info[type].dqi_flags &= ~DQF_INFO_DIRTY; ret = sb->s_op->quota_read(sb, type, (char *)&dqblk, sizeof(struct v1_disk_dqblk), v1_dqoff(0)); if (ret != sizeof(struct v1_disk_dqblk)) { @@ -197,8 +196,11 @@ static int v1_write_file_info(struct super_block *sb, int type) ret = -EIO; goto out; } + spin_lock(&dq_data_lock); + dqopt->info[type].dqi_flags &= ~DQF_INFO_DIRTY; dqblk.dqb_itime = dqopt->info[type].dqi_igrace; dqblk.dqb_btime = dqopt->info[type].dqi_bgrace; + spin_unlock(&dq_data_lock); ret = sb->s_op->quota_write(sb, type, (char *)&dqblk, sizeof(struct v1_disk_dqblk), v1_dqoff(0)); if (ret == sizeof(struct v1_disk_dqblk)) -- cgit v1.2.3 From 4580b30ea887fc27e57dabd56724ca24d936dc8a Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 2 Aug 2017 11:44:38 +0200 Subject: quota: Do not dirty bad dquots Currently we mark dirty even dquots that are not active (i.e., initialization or reading failed for them). Thus later we have to check whether dirty dquot is really active and just clear the dirty bit if not. Avoid this complication by just never marking non-active dquot as dirty. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index e1a155e8db15..0393581fe1a3 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -339,6 +339,9 @@ int dquot_mark_dquot_dirty(struct dquot *dquot) { int ret = 1; + if (!test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) + return 0; + /* If quota is dirty already, we don't have to acquire dq_list_lock */ if (test_bit(DQ_MOD_B, &dquot->dq_flags)) return 1; @@ -624,11 +627,9 @@ int dquot_writeback_dquots(struct super_block *sb, int type) while (!list_empty(dirty)) { dquot = list_first_entry(dirty, struct dquot, dq_dirty); - /* Dirty and inactive can be only bad dquot... */ - if (!test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) { - clear_dquot_dirty(dquot); - continue; - } + + WARN_ON(!test_bit(DQ_ACTIVE_B, &dquot->dq_flags)); + /* Now we have active dquot from which someone is * holding reference so we can safely just increase * use count */ @@ -759,7 +760,7 @@ we_slept: return; } /* Need to release dquot? */ - if (test_bit(DQ_ACTIVE_B, &dquot->dq_flags) && dquot_dirty(dquot)) { + if (dquot_dirty(dquot)) { spin_unlock(&dq_list_lock); /* Commit dquot before releasing */ ret = dquot->dq_sb->dq_op->write_dquot(dquot); @@ -777,8 +778,6 @@ we_slept: } goto we_slept; } - /* Clear flag in case dquot was inactive (something bad happened) */ - clear_dquot_dirty(dquot); if (test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) { spin_unlock(&dq_list_lock); dquot->dq_sb->dq_op->release_dquot(dquot); -- cgit v1.2.3 From 1e0b7cb062f227439a1d8e7921e85c8df52adc41 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 2 Aug 2017 11:54:26 +0200 Subject: quota: Move locking into clear_dquot_dirty() Move locking of dq_list_lock into clear_dquot_dirty(). It makes the function more self-contained and will simplify our life later. Reviewed-by: Andreas Dilger Signed-off-by: Jan Kara --- fs/quota/dquot.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 0393581fe1a3..93adcdd6a260 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -381,12 +381,15 @@ static inline void dqput_all(struct dquot **dquot) dqput(dquot[cnt]); } -/* This function needs dq_list_lock */ static inline int clear_dquot_dirty(struct dquot *dquot) { - if (!test_and_clear_bit(DQ_MOD_B, &dquot->dq_flags)) + spin_lock(&dq_list_lock); + if (!test_and_clear_bit(DQ_MOD_B, &dquot->dq_flags)) { + spin_unlock(&dq_list_lock); return 0; + } list_del_init(&dquot->dq_dirty); + spin_unlock(&dq_list_lock); return 1; } @@ -451,12 +454,8 @@ int dquot_commit(struct dquot *dquot) struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); mutex_lock(&dquot->dq_lock); - spin_lock(&dq_list_lock); - if (!clear_dquot_dirty(dquot)) { - spin_unlock(&dq_list_lock); + if (!clear_dquot_dirty(dquot)) goto out_lock; - } - spin_unlock(&dq_list_lock); /* Inactive dquot can be only if there was error during read/init * => we have better not writing it */ if (test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) @@ -772,9 +771,7 @@ we_slept: * We clear dirty bit anyway, so that we avoid * infinite loop here */ - spin_lock(&dq_list_lock); clear_dquot_dirty(dquot); - spin_unlock(&dq_list_lock); } goto we_slept; } -- cgit v1.2.3 From 503330f3820fab13aa2a7b1f9e7633686acc7c79 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 2 Aug 2017 17:18:50 +0200 Subject: quota: Remove dq_wait_unused from dquot Currently every dquot carries a wait_queue_head_t used only when we are turning quotas off to wait for last users to drop dquot references. Since such rare case is not performance sensitive in any means, just use a global waitqueue for this and save space in struct dquot. Also convert the logic to use wait_event() instead of open-coding it. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 93adcdd6a260..361a2a6f13e1 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -126,6 +126,8 @@ __cacheline_aligned_in_smp DEFINE_SPINLOCK(dq_data_lock); EXPORT_SYMBOL(dq_data_lock); DEFINE_STATIC_SRCU(dquot_srcu); +static DECLARE_WAIT_QUEUE_HEAD(dquot_ref_wq); + void __quota_error(struct super_block *sb, const char *func, const char *fmt, ...) { @@ -527,22 +529,18 @@ restart: continue; /* Wait for dquot users */ if (atomic_read(&dquot->dq_count)) { - DEFINE_WAIT(wait); - dqgrab(dquot); - prepare_to_wait(&dquot->dq_wait_unused, &wait, - TASK_UNINTERRUPTIBLE); spin_unlock(&dq_list_lock); - /* Once dqput() wakes us up, we know it's time to free + /* + * Once dqput() wakes us up, we know it's time to free * the dquot. * IMPORTANT: we rely on the fact that there is always * at most one process waiting for dquot to free. * Otherwise dq_count would be > 1 and we would never * wake up. */ - if (atomic_read(&dquot->dq_count) > 1) - schedule(); - finish_wait(&dquot->dq_wait_unused, &wait); + wait_event(dquot_ref_wq, + atomic_read(&dquot->dq_count) == 1); dqput(dquot); /* At this moment dquot() need not exist (it could be * reclaimed by prune_dqcache(). Hence we must @@ -754,7 +752,7 @@ we_slept: /* Releasing dquot during quotaoff phase? */ if (!sb_has_quota_active(dquot->dq_sb, dquot->dq_id.type) && atomic_read(&dquot->dq_count) == 1) - wake_up(&dquot->dq_wait_unused); + wake_up(&dquot_ref_wq); spin_unlock(&dq_list_lock); return; } @@ -809,7 +807,6 @@ static struct dquot *get_empty_dquot(struct super_block *sb, int type) INIT_LIST_HEAD(&dquot->dq_inuse); INIT_HLIST_NODE(&dquot->dq_hash); INIT_LIST_HEAD(&dquot->dq_dirty); - init_waitqueue_head(&dquot->dq_wait_unused); dquot->dq_sb = sb; dquot->dq_id = make_kqid_invalid(type); atomic_set(&dquot->dq_count, 1); -- cgit v1.2.3 From 834057bf846691552a8906f7ed3f67546e5f897c Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 3 Aug 2017 11:18:23 +0200 Subject: quota: Allow disabling tracking of dirty dquots in a list Filesystems that are journalling quotas generally don't need tracking of dirty dquots in a list since forcing a transaction commit flushes all quotas anyway. Allow filesystem to say it doesn't want dquots to be tracked as it reduces contention on the dq_list_lock. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 361a2a6f13e1..b867578e62c0 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -344,6 +344,9 @@ int dquot_mark_dquot_dirty(struct dquot *dquot) if (!test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) return 0; + if (sb_dqopt(dquot->dq_sb)->flags & DQUOT_NOLIST_DIRTY) + return test_and_set_bit(DQ_MOD_B, &dquot->dq_flags); + /* If quota is dirty already, we don't have to acquire dq_list_lock */ if (test_bit(DQ_MOD_B, &dquot->dq_flags)) return 1; @@ -385,6 +388,9 @@ static inline void dqput_all(struct dquot **dquot) static inline int clear_dquot_dirty(struct dquot *dquot) { + if (sb_dqopt(dquot->dq_sb)->flags & DQUOT_NOLIST_DIRTY) + return test_and_clear_bit(DQ_MOD_B, &dquot->dq_flags); + spin_lock(&dq_list_lock); if (!test_and_clear_bit(DQ_MOD_B, &dquot->dq_flags)) { spin_unlock(&dq_list_lock); -- cgit v1.2.3 From 0ed60de34a975804a256fb3fe233b1466c603be6 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Mon, 7 Aug 2017 17:07:28 +0200 Subject: quota: Inline functions into their callsites inode_add_rsv_space() and inode_sub_rsv_space() had only one callsite. Inline them there directly. inode_claim_rsv_space() and inode_reclaim_rsv_space() had two callsites so inline them there as well. This will simplify further locking changes. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 72 ++++++++++++++++++++++---------------------------------- 1 file changed, 28 insertions(+), 44 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index b867578e62c0..d881d5a073b9 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -1583,40 +1583,6 @@ static qsize_t *inode_reserved_space(struct inode * inode) return inode->i_sb->dq_op->get_reserved_space(inode); } -void inode_add_rsv_space(struct inode *inode, qsize_t number) -{ - spin_lock(&inode->i_lock); - *inode_reserved_space(inode) += number; - spin_unlock(&inode->i_lock); -} -EXPORT_SYMBOL(inode_add_rsv_space); - -void inode_claim_rsv_space(struct inode *inode, qsize_t number) -{ - spin_lock(&inode->i_lock); - *inode_reserved_space(inode) -= number; - __inode_add_bytes(inode, number); - spin_unlock(&inode->i_lock); -} -EXPORT_SYMBOL(inode_claim_rsv_space); - -void inode_reclaim_rsv_space(struct inode *inode, qsize_t number) -{ - spin_lock(&inode->i_lock); - *inode_reserved_space(inode) += number; - __inode_sub_bytes(inode, number); - spin_unlock(&inode->i_lock); -} -EXPORT_SYMBOL(inode_reclaim_rsv_space); - -void inode_sub_rsv_space(struct inode *inode, qsize_t number) -{ - spin_lock(&inode->i_lock); - *inode_reserved_space(inode) -= number; - spin_unlock(&inode->i_lock); -} -EXPORT_SYMBOL(inode_sub_rsv_space); - static qsize_t inode_get_rsv_space(struct inode *inode) { qsize_t ret; @@ -1632,18 +1598,24 @@ static qsize_t inode_get_rsv_space(struct inode *inode) static void inode_incr_space(struct inode *inode, qsize_t number, int reserve) { - if (reserve) - inode_add_rsv_space(inode, number); - else + if (reserve) { + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) += number; + spin_unlock(&inode->i_lock); + } else { inode_add_bytes(inode, number); + } } static void inode_decr_space(struct inode *inode, qsize_t number, int reserve) { - if (reserve) - inode_sub_rsv_space(inode, number); - else + if (reserve) { + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) -= number; + spin_unlock(&inode->i_lock); + } else { inode_sub_bytes(inode, number); + } } /* @@ -1759,7 +1731,10 @@ int dquot_claim_space_nodirty(struct inode *inode, qsize_t number) int cnt, index; if (!dquot_active(inode)) { - inode_claim_rsv_space(inode, number); + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) -= number; + __inode_add_bytes(inode, number); + spin_unlock(&inode->i_lock); return 0; } @@ -1772,7 +1747,10 @@ int dquot_claim_space_nodirty(struct inode *inode, qsize_t number) dquot_claim_reserved_space(dquots[cnt], number); } /* Update inode bytes */ - inode_claim_rsv_space(inode, number); + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) -= number; + __inode_add_bytes(inode, number); + spin_unlock(&inode->i_lock); spin_unlock(&dq_data_lock); mark_all_dquot_dirty(dquots); srcu_read_unlock(&dquot_srcu, index); @@ -1789,7 +1767,10 @@ void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number) int cnt, index; if (!dquot_active(inode)) { - inode_reclaim_rsv_space(inode, number); + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) += number; + __inode_sub_bytes(inode, number); + spin_unlock(&inode->i_lock); return; } @@ -1802,7 +1783,10 @@ void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number) dquot_reclaim_reserved_space(dquots[cnt], number); } /* Update inode bytes */ - inode_reclaim_rsv_space(inode, number); + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) += number; + __inode_sub_bytes(inode, number); + spin_unlock(&inode->i_lock); spin_unlock(&dq_data_lock); mark_all_dquot_dirty(dquots); srcu_read_unlock(&dquot_srcu, index); -- cgit v1.2.3 From a478e522e3145eef7ff032b32805c659e956b5f4 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Mon, 7 Aug 2017 17:17:10 +0200 Subject: quota: Inline inode_{incr,decr}_space() into callsites inode_incr_space() and inode_decr_space() have only two callsites. Inline them there as that will make locking changes simpler. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 55 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index d881d5a073b9..6c0cc7870e8f 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -1595,29 +1595,6 @@ static qsize_t inode_get_rsv_space(struct inode *inode) return ret; } -static void inode_incr_space(struct inode *inode, qsize_t number, - int reserve) -{ - if (reserve) { - spin_lock(&inode->i_lock); - *inode_reserved_space(inode) += number; - spin_unlock(&inode->i_lock); - } else { - inode_add_bytes(inode, number); - } -} - -static void inode_decr_space(struct inode *inode, qsize_t number, int reserve) -{ - if (reserve) { - spin_lock(&inode->i_lock); - *inode_reserved_space(inode) -= number; - spin_unlock(&inode->i_lock); - } else { - inode_sub_bytes(inode, number); - } -} - /* * This functions updates i_blocks+i_bytes fields and quota information * (together with appropriate checks). @@ -1639,7 +1616,13 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags) struct dquot **dquots; if (!dquot_active(inode)) { - inode_incr_space(inode, number, reserve); + if (reserve) { + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) += number; + spin_unlock(&inode->i_lock); + } else { + inode_add_bytes(inode, number); + } goto out; } @@ -1667,7 +1650,13 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags) else dquot_incr_space(dquots[cnt], number); } - inode_incr_space(inode, number, reserve); + if (reserve) { + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) += number; + spin_unlock(&inode->i_lock); + } else { + inode_add_bytes(inode, number); + } spin_unlock(&dq_data_lock); if (reserve) @@ -1805,7 +1794,13 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags) int reserve = flags & DQUOT_SPACE_RESERVE, index; if (!dquot_active(inode)) { - inode_decr_space(inode, number, reserve); + if (reserve) { + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) -= number; + spin_unlock(&inode->i_lock); + } else { + inode_sub_bytes(inode, number); + } return; } @@ -1826,7 +1821,13 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags) else dquot_decr_space(dquots[cnt], number); } - inode_decr_space(inode, number, reserve); + if (reserve) { + spin_lock(&inode->i_lock); + *inode_reserved_space(inode) -= number; + spin_unlock(&inode->i_lock); + } else { + inode_sub_bytes(inode, number); + } spin_unlock(&dq_data_lock); if (reserve) -- cgit v1.2.3 From 3ab167d2ba10017a430e427ddd3d690a74f8692e Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Mon, 7 Aug 2017 17:37:03 +0200 Subject: quota: Inline dquot_[re]claim_reserved_space() into callsite dquot_claim_reserved_space() and dquot_reclaim_reserved_space() have only a single callsite. Inline them there. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 6c0cc7870e8f..411142a2f074 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -1088,27 +1088,6 @@ static inline void dquot_resv_space(struct dquot *dquot, qsize_t number) dquot->dq_dqb.dqb_rsvspace += number; } -/* - * Claim reserved quota space - */ -static void dquot_claim_reserved_space(struct dquot *dquot, qsize_t number) -{ - if (dquot->dq_dqb.dqb_rsvspace < number) { - WARN_ON_ONCE(1); - number = dquot->dq_dqb.dqb_rsvspace; - } - dquot->dq_dqb.dqb_curspace += number; - dquot->dq_dqb.dqb_rsvspace -= number; -} - -static void dquot_reclaim_reserved_space(struct dquot *dquot, qsize_t number) -{ - if (WARN_ON_ONCE(dquot->dq_dqb.dqb_curspace < number)) - number = dquot->dq_dqb.dqb_curspace; - dquot->dq_dqb.dqb_rsvspace += number; - dquot->dq_dqb.dqb_curspace -= number; -} - static inline void dquot_free_reserved_space(struct dquot *dquot, qsize_t number) { @@ -1732,8 +1711,14 @@ int dquot_claim_space_nodirty(struct inode *inode, qsize_t number) spin_lock(&dq_data_lock); /* Claim reserved quotas to allocated quotas */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (dquots[cnt]) - dquot_claim_reserved_space(dquots[cnt], number); + if (dquots[cnt]) { + struct dquot *dquot = dquots[cnt]; + + if (WARN_ON_ONCE(dquot->dq_dqb.dqb_rsvspace < number)) + number = dquot->dq_dqb.dqb_rsvspace; + dquot->dq_dqb.dqb_curspace += number; + dquot->dq_dqb.dqb_rsvspace -= number; + } } /* Update inode bytes */ spin_lock(&inode->i_lock); @@ -1768,8 +1753,14 @@ void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number) spin_lock(&dq_data_lock); /* Claim reserved quotas to allocated quotas */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (dquots[cnt]) - dquot_reclaim_reserved_space(dquots[cnt], number); + if (dquots[cnt]) { + struct dquot *dquot = dquots[cnt]; + + if (WARN_ON_ONCE(dquot->dq_dqb.dqb_curspace < number)) + number = dquot->dq_dqb.dqb_curspace; + dquot->dq_dqb.dqb_rsvspace += number; + dquot->dq_dqb.dqb_curspace -= number; + } } /* Update inode bytes */ spin_lock(&inode->i_lock); -- cgit v1.2.3 From 7b9ca4c61bc278b771fb57d6290a31ab1fc7fdac Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Mon, 7 Aug 2017 13:19:50 +0200 Subject: quota: Reduce contention on dq_data_lock dq_data_lock is currently used to protect all modifications of quota accounting information, consistency of quota accounting on the inode, and dquot pointers from inode. As a result contention on the lock can be pretty heavy. Reduce the contention on the lock by protecting quota accounting information by a new dquot->dq_dqb_lock and consistency of quota accounting with inode usage by inode->i_lock. This change reduces time to create 500000 files on ext4 on ramdisk by 50 different processes in separate directories by 6% when user quota is turned on. When those 50 processes belong to 50 different users, the improvement is about 9%. Signed-off-by: Jan Kara --- fs/quota/dquot.c | 287 ++++++++++++++++++++++++++++++-------------------- fs/quota/quota_tree.c | 8 +- 2 files changed, 174 insertions(+), 121 deletions(-) (limited to 'fs/quota') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 411142a2f074..d51797f850c5 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -82,16 +82,19 @@ #include /* - * There are three quota SMP locks. dq_list_lock protects all lists with quotas - * and quota formats. - * dq_data_lock protects data from dq_dqb and also mem_dqinfo structures and - * also guards consistency of dquot->dq_dqb with inode->i_blocks, i_bytes. - * i_blocks and i_bytes updates itself are guarded by i_lock acquired directly - * in inode_add_bytes() and inode_sub_bytes(). dq_state_lock protects - * modifications of quota state (on quotaon and quotaoff) and readers who care - * about latest values take it as well. + * There are five quota SMP locks: + * * dq_list_lock protects all lists with quotas and quota formats. + * * dquot->dq_dqb_lock protects data from dq_dqb + * * inode->i_lock protects inode->i_blocks, i_bytes and also guards + * consistency of dquot->dq_dqb with inode->i_blocks, i_bytes so that + * dquot_transfer() can stabilize amount it transfers + * * dq_data_lock protects mem_dqinfo structures and modifications of dquot + * pointers in the inode + * * dq_state_lock protects modifications of quota state (on quotaon and + * quotaoff) and readers who care about latest values take it as well. * - * The spinlock ordering is hence: dq_data_lock > dq_list_lock > i_lock, + * The spinlock ordering is hence: + * dq_data_lock > dq_list_lock > i_lock > dquot->dq_dqb_lock, * dq_list_lock > dq_state_lock * * Note that some things (eg. sb pointer, type, id) doesn't change during @@ -246,6 +249,7 @@ struct dqstats dqstats; EXPORT_SYMBOL(dqstats); static qsize_t inode_get_rsv_space(struct inode *inode); +static qsize_t __inode_get_rsv_space(struct inode *inode); static int __dquot_initialize(struct inode *inode, int type); static inline unsigned int @@ -816,6 +820,7 @@ static struct dquot *get_empty_dquot(struct super_block *sb, int type) dquot->dq_sb = sb; dquot->dq_id = make_kqid_invalid(type); atomic_set(&dquot->dq_count, 1); + spin_lock_init(&dquot->dq_dqb_lock); return dquot; } @@ -1073,21 +1078,6 @@ static void drop_dquot_ref(struct super_block *sb, int type) } } -static inline void dquot_incr_inodes(struct dquot *dquot, qsize_t number) -{ - dquot->dq_dqb.dqb_curinodes += number; -} - -static inline void dquot_incr_space(struct dquot *dquot, qsize_t number) -{ - dquot->dq_dqb.dqb_curspace += number; -} - -static inline void dquot_resv_space(struct dquot *dquot, qsize_t number) -{ - dquot->dq_dqb.dqb_rsvspace += number; -} - static inline void dquot_free_reserved_space(struct dquot *dquot, qsize_t number) { @@ -1246,21 +1236,24 @@ static int ignore_hardlimit(struct dquot *dquot) !(info->dqi_flags & DQF_ROOT_SQUASH)); } -/* needs dq_data_lock */ -static int check_idq(struct dquot *dquot, qsize_t inodes, - struct dquot_warn *warn) +static int dquot_add_inodes(struct dquot *dquot, qsize_t inodes, + struct dquot_warn *warn) { - qsize_t newinodes = dquot->dq_dqb.dqb_curinodes + inodes; + qsize_t newinodes; + int ret = 0; + spin_lock(&dquot->dq_dqb_lock); + newinodes = dquot->dq_dqb.dqb_curinodes + inodes; if (!sb_has_quota_limits_enabled(dquot->dq_sb, dquot->dq_id.type) || test_bit(DQ_FAKE_B, &dquot->dq_flags)) - return 0; + goto add; if (dquot->dq_dqb.dqb_ihardlimit && newinodes > dquot->dq_dqb.dqb_ihardlimit && !ignore_hardlimit(dquot)) { prepare_warning(warn, dquot, QUOTA_NL_IHARDWARN); - return -EDQUOT; + ret = -EDQUOT; + goto out; } if (dquot->dq_dqb.dqb_isoftlimit && @@ -1269,7 +1262,8 @@ static int check_idq(struct dquot *dquot, qsize_t inodes, ktime_get_real_seconds() >= dquot->dq_dqb.dqb_itime && !ignore_hardlimit(dquot)) { prepare_warning(warn, dquot, QUOTA_NL_ISOFTLONGWARN); - return -EDQUOT; + ret = -EDQUOT; + goto out; } if (dquot->dq_dqb.dqb_isoftlimit && @@ -1279,30 +1273,40 @@ static int check_idq(struct dquot *dquot, qsize_t inodes, dquot->dq_dqb.dqb_itime = ktime_get_real_seconds() + sb_dqopt(dquot->dq_sb)->info[dquot->dq_id.type].dqi_igrace; } +add: + dquot->dq_dqb.dqb_curinodes = newinodes; - return 0; +out: + spin_unlock(&dquot->dq_dqb_lock); + return ret; } -/* needs dq_data_lock */ -static int check_bdq(struct dquot *dquot, qsize_t space, int prealloc, - struct dquot_warn *warn) +static int dquot_add_space(struct dquot *dquot, qsize_t space, + qsize_t rsv_space, unsigned int flags, + struct dquot_warn *warn) { qsize_t tspace; struct super_block *sb = dquot->dq_sb; + int ret = 0; + spin_lock(&dquot->dq_dqb_lock); if (!sb_has_quota_limits_enabled(sb, dquot->dq_id.type) || test_bit(DQ_FAKE_B, &dquot->dq_flags)) - return 0; + goto add; tspace = dquot->dq_dqb.dqb_curspace + dquot->dq_dqb.dqb_rsvspace - + space; + + space + rsv_space; + + if (flags & DQUOT_SPACE_NOFAIL) + goto add; if (dquot->dq_dqb.dqb_bhardlimit && tspace > dquot->dq_dqb.dqb_bhardlimit && !ignore_hardlimit(dquot)) { - if (!prealloc) + if (flags & DQUOT_SPACE_WARN) prepare_warning(warn, dquot, QUOTA_NL_BHARDWARN); - return -EDQUOT; + ret = -EDQUOT; + goto out; } if (dquot->dq_dqb.dqb_bsoftlimit && @@ -1310,28 +1314,34 @@ static int check_bdq(struct dquot *dquot, qsize_t space, int prealloc, dquot->dq_dqb.dqb_btime && ktime_get_real_seconds() >= dquot->dq_dqb.dqb_btime && !ignore_hardlimit(dquot)) { - if (!prealloc) + if (flags & DQUOT_SPACE_WARN) prepare_warning(warn, dquot, QUOTA_NL_BSOFTLONGWARN); - return -EDQUOT; + ret = -EDQUOT; + goto out; } if (dquot->dq_dqb.dqb_bsoftlimit && tspace > dquot->dq_dqb.dqb_bsoftlimit && dquot->dq_dqb.dqb_btime == 0) { - if (!prealloc) { + if (flags & DQUOT_SPACE_WARN) { prepare_warning(warn, dquot, QUOTA_NL_BSOFTWARN); dquot->dq_dqb.dqb_btime = ktime_get_real_seconds() + sb_dqopt(sb)->info[dquot->dq_id.type].dqi_bgrace; - } - else + } else { /* * We don't allow preallocation to exceed softlimit so exceeding will * be always printed */ - return -EDQUOT; + ret = -EDQUOT; + goto out; + } } - - return 0; +add: + dquot->dq_dqb.dqb_rsvspace += rsv_space; + dquot->dq_dqb.dqb_curspace += space; +out: + spin_unlock(&dquot->dq_dqb_lock); + return ret; } static int info_idq_free(struct dquot *dquot, qsize_t inodes) @@ -1466,8 +1476,15 @@ static int __dquot_initialize(struct inode *inode, int type) * did a write before quota was turned on */ rsv = inode_get_rsv_space(inode); - if (unlikely(rsv)) - dquot_resv_space(dquots[cnt], rsv); + if (unlikely(rsv)) { + spin_lock(&inode->i_lock); + /* Get reservation again under proper lock */ + rsv = __inode_get_rsv_space(inode); + spin_lock(&dquots[cnt]->dq_dqb_lock); + dquots[cnt]->dq_dqb.dqb_rsvspace += rsv; + spin_unlock(&dquots[cnt]->dq_dqb_lock); + spin_unlock(&inode->i_lock); + } } } out_lock: @@ -1562,6 +1579,13 @@ static qsize_t *inode_reserved_space(struct inode * inode) return inode->i_sb->dq_op->get_reserved_space(inode); } +static qsize_t __inode_get_rsv_space(struct inode *inode) +{ + if (!inode->i_sb->dq_op->get_reserved_space) + return 0; + return *inode_reserved_space(inode); +} + static qsize_t inode_get_rsv_space(struct inode *inode) { qsize_t ret; @@ -1569,7 +1593,7 @@ static qsize_t inode_get_rsv_space(struct inode *inode) if (!inode->i_sb->dq_op->get_reserved_space) return 0; spin_lock(&inode->i_lock); - ret = *inode_reserved_space(inode); + ret = __inode_get_rsv_space(inode); spin_unlock(&inode->i_lock); return ret; } @@ -1610,33 +1634,41 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags) dquots = i_dquot(inode); index = srcu_read_lock(&dquot_srcu); - spin_lock(&dq_data_lock); + spin_lock(&inode->i_lock); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (!dquots[cnt]) continue; - ret = check_bdq(dquots[cnt], number, - !(flags & DQUOT_SPACE_WARN), &warn[cnt]); - if (ret && !(flags & DQUOT_SPACE_NOFAIL)) { - spin_unlock(&dq_data_lock); + if (flags & DQUOT_SPACE_RESERVE) { + ret = dquot_add_space(dquots[cnt], 0, number, flags, + &warn[cnt]); + } else { + ret = dquot_add_space(dquots[cnt], number, 0, flags, + &warn[cnt]); + } + if (ret) { + /* Back out changes we already did */ + for (cnt--; cnt >= 0; cnt--) { + if (!dquots[cnt]) + continue; + spin_lock(&dquots[cnt]->dq_dqb_lock); + if (flags & DQUOT_SPACE_RESERVE) { + dquots[cnt]->dq_dqb.dqb_rsvspace -= + number; + } else { + dquots[cnt]->dq_dqb.dqb_curspace -= + number; + } + spin_unlock(&dquots[cnt]->dq_dqb_lock); + } + spin_unlock(&inode->i_lock); goto out_flush_warn; } } - for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (!dquots[cnt]) - continue; - if (reserve) - dquot_resv_space(dquots[cnt], number); - else - dquot_incr_space(dquots[cnt], number); - } - if (reserve) { - spin_lock(&inode->i_lock); + if (reserve) *inode_reserved_space(inode) += number; - spin_unlock(&inode->i_lock); - } else { - inode_add_bytes(inode, number); - } - spin_unlock(&dq_data_lock); + else + __inode_add_bytes(inode, number); + spin_unlock(&inode->i_lock); if (reserve) goto out_flush_warn; @@ -1665,23 +1697,26 @@ int dquot_alloc_inode(struct inode *inode) dquots = i_dquot(inode); index = srcu_read_lock(&dquot_srcu); - spin_lock(&dq_data_lock); + spin_lock(&inode->i_lock); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (!dquots[cnt]) continue; - ret = check_idq(dquots[cnt], 1, &warn[cnt]); - if (ret) + ret = dquot_add_inodes(dquots[cnt], 1, &warn[cnt]); + if (ret) { + for (cnt--; cnt >= 0; cnt--) { + if (!dquots[cnt]) + continue; + /* Back out changes we already did */ + spin_lock(&dquots[cnt]->dq_dqb_lock); + dquots[cnt]->dq_dqb.dqb_curinodes--; + spin_unlock(&dquots[cnt]->dq_dqb_lock); + } goto warn_put_all; - } - - for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - if (!dquots[cnt]) - continue; - dquot_incr_inodes(dquots[cnt], 1); + } } warn_put_all: - spin_unlock(&dq_data_lock); + spin_unlock(&inode->i_lock); if (ret == 0) mark_all_dquot_dirty(dquots); srcu_read_unlock(&dquot_srcu, index); @@ -1708,24 +1743,24 @@ int dquot_claim_space_nodirty(struct inode *inode, qsize_t number) dquots = i_dquot(inode); index = srcu_read_lock(&dquot_srcu); - spin_lock(&dq_data_lock); + spin_lock(&inode->i_lock); /* Claim reserved quotas to allocated quotas */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (dquots[cnt]) { struct dquot *dquot = dquots[cnt]; + spin_lock(&dquot->dq_dqb_lock); if (WARN_ON_ONCE(dquot->dq_dqb.dqb_rsvspace < number)) number = dquot->dq_dqb.dqb_rsvspace; dquot->dq_dqb.dqb_curspace += number; dquot->dq_dqb.dqb_rsvspace -= number; + spin_unlock(&dquot->dq_dqb_lock); } } /* Update inode bytes */ - spin_lock(&inode->i_lock); *inode_reserved_space(inode) -= number; __inode_add_bytes(inode, number); spin_unlock(&inode->i_lock); - spin_unlock(&dq_data_lock); mark_all_dquot_dirty(dquots); srcu_read_unlock(&dquot_srcu, index); return 0; @@ -1750,24 +1785,24 @@ void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number) dquots = i_dquot(inode); index = srcu_read_lock(&dquot_srcu); - spin_lock(&dq_data_lock); + spin_lock(&inode->i_lock); /* Claim reserved quotas to allocated quotas */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (dquots[cnt]) { struct dquot *dquot = dquots[cnt]; + spin_lock(&dquot->dq_dqb_lock); if (WARN_ON_ONCE(dquot->dq_dqb.dqb_curspace < number)) number = dquot->dq_dqb.dqb_curspace; dquot->dq_dqb.dqb_rsvspace += number; dquot->dq_dqb.dqb_curspace -= number; + spin_unlock(&dquot->dq_dqb_lock); } } /* Update inode bytes */ - spin_lock(&inode->i_lock); *inode_reserved_space(inode) += number; __inode_sub_bytes(inode, number); spin_unlock(&inode->i_lock); - spin_unlock(&dq_data_lock); mark_all_dquot_dirty(dquots); srcu_read_unlock(&dquot_srcu, index); return; @@ -1797,13 +1832,14 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags) dquots = i_dquot(inode); index = srcu_read_lock(&dquot_srcu); - spin_lock(&dq_data_lock); + spin_lock(&inode->i_lock); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { int wtype; warn[cnt].w_type = QUOTA_NL_NOWARN; if (!dquots[cnt]) continue; + spin_lock(&dquots[cnt]->dq_dqb_lock); wtype = info_bdq_free(dquots[cnt], number); if (wtype != QUOTA_NL_NOWARN) prepare_warning(&warn[cnt], dquots[cnt], wtype); @@ -1811,15 +1847,13 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags) dquot_free_reserved_space(dquots[cnt], number); else dquot_decr_space(dquots[cnt], number); + spin_unlock(&dquots[cnt]->dq_dqb_lock); } - if (reserve) { - spin_lock(&inode->i_lock); + if (reserve) *inode_reserved_space(inode) -= number; - spin_unlock(&inode->i_lock); - } else { - inode_sub_bytes(inode, number); - } - spin_unlock(&dq_data_lock); + else + __inode_sub_bytes(inode, number); + spin_unlock(&inode->i_lock); if (reserve) goto out_unlock; @@ -1845,19 +1879,21 @@ void dquot_free_inode(struct inode *inode) dquots = i_dquot(inode); index = srcu_read_lock(&dquot_srcu); - spin_lock(&dq_data_lock); + spin_lock(&inode->i_lock); for (cnt = 0; cnt < MAXQUOTAS; cnt++) { int wtype; warn[cnt].w_type = QUOTA_NL_NOWARN; if (!dquots[cnt]) continue; + spin_lock(&dquots[cnt]->dq_dqb_lock); wtype = info_idq_free(dquots[cnt], 1); if (wtype != QUOTA_NL_NOWARN) prepare_warning(&warn[cnt], dquots[cnt], wtype); dquot_decr_inodes(dquots[cnt], 1); + spin_unlock(&dquots[cnt]->dq_dqb_lock); } - spin_unlock(&dq_data_lock); + spin_unlock(&inode->i_lock); mark_all_dquot_dirty(dquots); srcu_read_unlock(&dquot_srcu, index); flush_warnings(warn); @@ -1878,7 +1914,7 @@ EXPORT_SYMBOL(dquot_free_inode); */ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) { - qsize_t space, cur_space; + qsize_t cur_space; qsize_t rsv_space = 0; qsize_t inode_usage = 1; struct dquot *transfer_from[MAXQUOTAS] = {}; @@ -1905,14 +1941,18 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) } spin_lock(&dq_data_lock); + spin_lock(&inode->i_lock); if (IS_NOQUOTA(inode)) { /* File without quota accounting? */ + spin_unlock(&inode->i_lock); spin_unlock(&dq_data_lock); return 0; } - cur_space = inode_get_bytes(inode); - rsv_space = inode_get_rsv_space(inode); - space = cur_space + rsv_space; - /* Build the transfer_from list and check the limits */ + cur_space = __inode_get_bytes(inode); + rsv_space = __inode_get_rsv_space(inode); + /* + * Build the transfer_from list, check limits, and update usage in + * the target structures. + */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { /* * Skip changes for same uid or gid or for turned off quota-type. @@ -1924,28 +1964,33 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) continue; is_valid[cnt] = 1; transfer_from[cnt] = i_dquot(inode)[cnt]; - ret = check_idq(transfer_to[cnt], inode_usage, &warn_to[cnt]); + ret = dquot_add_inodes(transfer_to[cnt], inode_usage, + &warn_to[cnt]); if (ret) goto over_quota; - ret = check_bdq(transfer_to[cnt], space, 0, &warn_to[cnt]); - if (ret) + ret = dquot_add_space(transfer_to[cnt], cur_space, rsv_space, 0, + &warn_to[cnt]); + if (ret) { + dquot_decr_inodes(transfer_to[cnt], inode_usage); goto over_quota; + } } - /* - * Finally perform the needed transfer from transfer_from to transfer_to - */ + /* Decrease usage for source structures and update quota pointers */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { if (!is_valid[cnt]) continue; /* Due to IO error we might not have transfer_from[] structure */ if (transfer_from[cnt]) { int wtype; + + spin_lock(&transfer_from[cnt]->dq_dqb_lock); wtype = info_idq_free(transfer_from[cnt], inode_usage); if (wtype != QUOTA_NL_NOWARN) prepare_warning(&warn_from_inodes[cnt], transfer_from[cnt], wtype); - wtype = info_bdq_free(transfer_from[cnt], space); + wtype = info_bdq_free(transfer_from[cnt], + cur_space + rsv_space); if (wtype != QUOTA_NL_NOWARN) prepare_warning(&warn_from_space[cnt], transfer_from[cnt], wtype); @@ -1953,14 +1998,11 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) dquot_decr_space(transfer_from[cnt], cur_space); dquot_free_reserved_space(transfer_from[cnt], rsv_space); + spin_unlock(&transfer_from[cnt]->dq_dqb_lock); } - - dquot_incr_inodes(transfer_to[cnt], inode_usage); - dquot_incr_space(transfer_to[cnt], cur_space); - dquot_resv_space(transfer_to[cnt], rsv_space); - i_dquot(inode)[cnt] = transfer_to[cnt]; } + spin_unlock(&inode->i_lock); spin_unlock(&dq_data_lock); mark_all_dquot_dirty(transfer_from); @@ -1974,6 +2016,17 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) transfer_to[cnt] = transfer_from[cnt]; return 0; over_quota: + /* Back out changes we already did */ + for (cnt--; cnt >= 0; cnt--) { + if (!is_valid[cnt]) + continue; + spin_lock(&transfer_to[cnt]->dq_dqb_lock); + dquot_decr_inodes(transfer_to[cnt], inode_usage); + dquot_decr_space(transfer_to[cnt], cur_space); + dquot_free_reserved_space(transfer_to[cnt], rsv_space); + spin_unlock(&transfer_to[cnt]->dq_dqb_lock); + } + spin_unlock(&inode->i_lock); spin_unlock(&dq_data_lock); flush_warnings(warn_to); return ret; @@ -2524,7 +2577,7 @@ static void do_get_dqblk(struct dquot *dquot, struct qc_dqblk *di) struct mem_dqblk *dm = &dquot->dq_dqb; memset(di, 0, sizeof(*di)); - spin_lock(&dq_data_lock); + spin_lock(&dquot->dq_dqb_lock); di->d_spc_hardlimit = dm->dqb_bhardlimit; di->d_spc_softlimit = dm->dqb_bsoftlimit; di->d_ino_hardlimit = dm->dqb_ihardlimit; @@ -2533,7 +2586,7 @@ static void do_get_dqblk(struct dquot *dquot, struct qc_dqblk *di) di->d_ino_count = dm->dqb_curinodes; di->d_spc_timer = dm->dqb_btime; di->d_ino_timer = dm->dqb_itime; - spin_unlock(&dq_data_lock); + spin_unlock(&dquot->dq_dqb_lock); } int dquot_get_dqblk(struct super_block *sb, struct kqid qid, @@ -2597,7 +2650,7 @@ static int do_set_dqblk(struct dquot *dquot, struct qc_dqblk *di) (di->d_ino_hardlimit > dqi->dqi_max_ino_limit))) return -ERANGE; - spin_lock(&dq_data_lock); + spin_lock(&dquot->dq_dqb_lock); if (di->d_fieldmask & QC_SPACE) { dm->dqb_curspace = di->d_space - dm->dqb_rsvspace; check_blim = 1; @@ -2663,7 +2716,7 @@ static int do_set_dqblk(struct dquot *dquot, struct qc_dqblk *di) clear_bit(DQ_FAKE_B, &dquot->dq_flags); else set_bit(DQ_FAKE_B, &dquot->dq_flags); - spin_unlock(&dq_data_lock); + spin_unlock(&dquot->dq_dqb_lock); mark_dquot_dirty(dquot); return 0; diff --git a/fs/quota/quota_tree.c b/fs/quota/quota_tree.c index 54f85eb2609c..bb3f59bcfcf5 100644 --- a/fs/quota/quota_tree.c +++ b/fs/quota/quota_tree.c @@ -389,9 +389,9 @@ int qtree_write_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot) return ret; } } - spin_lock(&dq_data_lock); + spin_lock(&dquot->dq_dqb_lock); info->dqi_ops->mem2disk_dqblk(ddquot, dquot); - spin_unlock(&dq_data_lock); + spin_unlock(&dquot->dq_dqb_lock); ret = sb->s_op->quota_write(sb, type, ddquot, info->dqi_entry_size, dquot->dq_off); if (ret != info->dqi_entry_size) { @@ -649,14 +649,14 @@ int qtree_read_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot) kfree(ddquot); goto out; } - spin_lock(&dq_data_lock); + spin_lock(&dquot->dq_dqb_lock); info->dqi_ops->disk2mem_dqblk(dquot, ddquot); if (!dquot->dq_dqb.dqb_bhardlimit && !dquot->dq_dqb.dqb_bsoftlimit && !dquot->dq_dqb.dqb_ihardlimit && !dquot->dq_dqb.dqb_isoftlimit) set_bit(DQ_FAKE_B, &dquot->dq_flags); - spin_unlock(&dq_data_lock); + spin_unlock(&dquot->dq_dqb_lock); kfree(ddquot); out: dqstats_inc(DQST_READS); -- cgit v1.2.3