[PATCH 04/19] quota: protect dqget() from parallels quotaoff via SRCU

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

 



In order to hide quota internals inside didicated structure pointer.
We have to serialize that object lifetime with dqget(), and change/uncharge
functions.
Quota_info construction/destruction will be protected via ->dq_srcu.
SRCU counter temproraly placed inside sb, but will be moved inside
quota object pointer in next patch.

Signed-off-by: Dmitry Monakhov <dmonakhov@xxxxxxxxxx>
---
 fs/quota/dquot.c      |  113 ++++++++++++++++++++++++++++++++++++++----------
 fs/super.c            |    9 ++++
 include/linux/quota.h |    2 +
 3 files changed, 100 insertions(+), 24 deletions(-)

diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index 748d744..7e937b0 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -805,7 +805,7 @@ static struct dquot *get_empty_dquot(struct super_block *sb, int type)
 /*
  * Get reference to dquot
  *
- * Locking is slightly tricky here. We are guarded from parallel quotaoff()
+ * We are guarded from parallel quotaoff() by holding srcu_read_lock
  * destroying our dquot by:
  *   a) checking for quota flags under dq_list_lock and
  *   b) getting a reference to dquot before we release dq_list_lock
@@ -814,9 +814,15 @@ struct dquot *dqget(struct super_block *sb, unsigned int id, int type)
 {
 	unsigned int hashent = hashfn(sb, id, type);
 	struct dquot *dquot = NULL, *empty = NULL;
+	int idx;
 
-        if (!sb_has_quota_active(sb, type))
+	rcu_read_lock();
+	if (!sb_has_quota_active(sb, type)) {
+		rcu_read_unlock();
 		return NULL;
+	}
+	idx = srcu_read_lock(&dqopts(sb)->dq_srcu);
+	rcu_read_unlock();
 we_slept:
 	spin_lock(&dq_list_lock);
 	spin_lock(&dq_state_lock);
@@ -867,6 +873,7 @@ we_slept:
 	BUG_ON(!dquot->dq_sb);	/* Has somebody invalidated entry under us? */
 #endif
 out:
+	srcu_read_unlock(&dqopts(sb)->dq_srcu, idx);
 	if (empty)
 		do_destroy_dquot(empty);
 
@@ -1351,16 +1358,20 @@ static int dquot_active(const struct inode *inode)
 static void __dquot_initialize(struct inode *inode, int type)
 {
 	unsigned int id = 0;
-	int cnt;
+	int cnt, idx;
 	struct dquot *got[MAXQUOTAS];
 	struct super_block *sb = inode->i_sb;
 	qsize_t rsv;
 
 	/* First test before acquiring mutex - solves deadlocks when we
          * re-enter the quota code and are already holding the mutex */
-	if (!dquot_active(inode))
+	rcu_read_lock();
+	if (!dquot_active(inode)) {
+		rcu_read_unlock();
 		return;
-
+	}
+	idx = srcu_read_lock(&dqopts(sb)->dq_srcu);
+	rcu_read_unlock();
 	/* First get references to structures we might need. */
 	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 		got[cnt] = NULL;
@@ -1403,6 +1414,7 @@ static void __dquot_initialize(struct inode *inode, int type)
 	}
 out_err:
 	up_write(&dqopts(sb)->dqptr_sem);
+	srcu_read_unlock(&dqopts(sb)->dq_srcu, idx);
 	/* Drop unused references */
 	dqput_all(got);
 }
@@ -1432,11 +1444,10 @@ static void __dquot_drop(struct inode *inode)
 
 void dquot_drop(struct inode *inode)
 {
-	int cnt;
+	int cnt, idx;
 
 	if (IS_NOQUOTA(inode))
 		return;
-
 	/*
 	 * Test before calling to rule out calls from proc and such
 	 * where we are not allowed to block. Note that this is
@@ -1444,13 +1455,19 @@ void dquot_drop(struct inode *inode)
 	 * must assure that nobody can come after the DQUOT_DROP and
 	 * add quota pointers back anyway.
 	 */
+	rcu_read_lock();
 	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 		if (inode->i_dquot[cnt])
 			break;
 	}
-
-	if (cnt < MAXQUOTAS)
-		__dquot_drop(inode);
+	if (cnt == MAXQUOTAS) {
+		rcu_read_unlock();
+		return;
+	}
+	idx = srcu_read_lock(&dqopts(inode->i_sb)->dq_srcu);
+	rcu_read_unlock();
+	__dquot_drop(inode);
+	srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 }
 EXPORT_SYMBOL(dquot_drop);
 
@@ -1535,7 +1552,7 @@ static void inode_decr_space(struct inode *inode, qsize_t number, int reserve)
  */
 int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags)
 {
-	int cnt, ret = 0;
+	int cnt, idx, ret = 0;
 	char warntype[MAXQUOTAS];
 	int warn = flags & DQUOT_SPACE_WARN;
 	int reserve = flags & DQUOT_SPACE_RESERVE;
@@ -1545,11 +1562,14 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags)
 	 * First test before acquiring mutex - solves deadlocks when we
 	 * re-enter the quota code and are already holding the mutex
 	 */
+	rcu_read_lock();
 	if (!dquot_active(inode)) {
 		inode_incr_space(inode, number, reserve);
+		rcu_read_unlock();
 		goto out;
 	}
-
+	idx = srcu_read_lock(&dqopts(inode->i_sb)->dq_srcu);
+	rcu_read_unlock();
 	down_read(&dqopts(inode->i_sb)->dqptr_sem);
 	for (cnt = 0; cnt < MAXQUOTAS; cnt++)
 		warntype[cnt] = QUOTA_NL_NOWARN;
@@ -1582,6 +1602,7 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags)
 out_flush_warn:
 	flush_warnings(inode->i_dquot, warntype);
 	up_read(&dqopts(inode->i_sb)->dqptr_sem);
+	srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 out:
 	return ret;
 }
@@ -1592,13 +1613,19 @@ EXPORT_SYMBOL(__dquot_alloc_space);
  */
 int dquot_alloc_inode(const struct inode *inode)
 {
-	int cnt, ret = 0;
+	int cnt, idx, ret = 0;
 	char warntype[MAXQUOTAS];
 
 	/* First test before acquiring mutex - solves deadlocks when we
          * re-enter the quota code and are already holding the mutex */
-	if (!dquot_active(inode))
+	rcu_read_lock();
+	if (!dquot_active(inode)) {
+		rcu_read_unlock();
 		return 0;
+	}
+	idx = srcu_read_lock(&dqopts(inode->i_sb)->dq_srcu);
+	rcu_read_unlock();
+
 	for (cnt = 0; cnt < MAXQUOTAS; cnt++)
 		warntype[cnt] = QUOTA_NL_NOWARN;
 	down_read(&dqopts(inode->i_sb)->dqptr_sem);
@@ -1623,6 +1650,7 @@ warn_put_all:
 		mark_all_dquot_dirty(inode->i_dquot);
 	flush_warnings(inode->i_dquot, warntype);
 	up_read(&dqopts(inode->i_sb)->dqptr_sem);
+	srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 	return ret;
 }
 EXPORT_SYMBOL(dquot_alloc_inode);
@@ -1632,13 +1660,16 @@ EXPORT_SYMBOL(dquot_alloc_inode);
  */
 int dquot_claim_space_nodirty(struct inode *inode, qsize_t number)
 {
-	int cnt;
+	int cnt, idx;
 
+	rcu_read_lock();
 	if (!dquot_active(inode)) {
 		inode_claim_rsv_space(inode, number);
+		rcu_read_unlock();
 		return 0;
 	}
-
+	idx = srcu_read_lock(&dqopts(inode->i_sb)->dq_srcu);
+	rcu_read_unlock();
 	down_read(&dqopts(inode->i_sb)->dqptr_sem);
 	spin_lock(&dq_data_lock);
 	/* Claim reserved quotas to allocated quotas */
@@ -1652,6 +1683,7 @@ int dquot_claim_space_nodirty(struct inode *inode, qsize_t number)
 	spin_unlock(&dq_data_lock);
 	mark_all_dquot_dirty(inode->i_dquot);
 	up_read(&dqopts(inode->i_sb)->dqptr_sem);
+	srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 	return 0;
 }
 EXPORT_SYMBOL(dquot_claim_space_nodirty);
@@ -1661,17 +1693,21 @@ EXPORT_SYMBOL(dquot_claim_space_nodirty);
  */
 void __dquot_free_space(struct inode *inode, qsize_t number, int flags)
 {
-	unsigned int cnt;
+	unsigned int cnt, idx;
 	char warntype[MAXQUOTAS];
 	int reserve = flags & DQUOT_SPACE_RESERVE;
 
 	/* First test before acquiring mutex - solves deadlocks when we
          * re-enter the quota code and are already holding the mutex */
+	rcu_read_lock();
 	if (!dquot_active(inode)) {
 		inode_decr_space(inode, number, reserve);
+		rcu_read_unlock();
 		return;
 	}
 
+	idx = srcu_read_lock(&dqopts(inode->i_sb)->dq_srcu);
+	rcu_read_unlock();
 	down_read(&dqopts(inode->i_sb)->dqptr_sem);
 	spin_lock(&dq_data_lock);
 	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
@@ -1692,6 +1728,7 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags)
 out_unlock:
 	flush_warnings(inode->i_dquot, warntype);
 	up_read(&dqopts(inode->i_sb)->dqptr_sem);
+	srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 }
 EXPORT_SYMBOL(__dquot_free_space);
 
@@ -1700,14 +1737,18 @@ EXPORT_SYMBOL(__dquot_free_space);
  */
 void dquot_free_inode(const struct inode *inode)
 {
-	unsigned int cnt;
+	unsigned int cnt, idx;
 	char warntype[MAXQUOTAS];
 
 	/* First test before acquiring mutex - solves deadlocks when we
          * re-enter the quota code and are already holding the mutex */
-	if (!dquot_active(inode))
+	rcu_read_lock();
+	if (!dquot_active(inode)) {
+		rcu_read_unlock();
 		return;
-
+	}
+	idx = srcu_read_lock(&dqopts(inode->i_sb)->dq_srcu);
+	rcu_read_unlock();
 	down_read(&dqopts(inode->i_sb)->dqptr_sem);
 	spin_lock(&dq_data_lock);
 	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
@@ -1720,6 +1761,7 @@ void dquot_free_inode(const struct inode *inode)
 	mark_all_dquot_dirty(inode->i_dquot);
 	flush_warnings(inode->i_dquot, warntype);
 	up_read(&dqopts(inode->i_sb)->dqptr_sem);
+	srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 }
 EXPORT_SYMBOL(dquot_free_inode);
 
@@ -1738,21 +1780,28 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
 	qsize_t space, cur_space;
 	qsize_t rsv_space = 0;
 	struct dquot *transfer_from[MAXQUOTAS] = {};
-	int cnt, ret = 0;
+	int cnt, idx, ret = 0;
 	char is_valid[MAXQUOTAS] = {};
 	char warntype_to[MAXQUOTAS];
 	char warntype_from_inodes[MAXQUOTAS], warntype_from_space[MAXQUOTAS];
 
 	/* First test before acquiring mutex - solves deadlocks when we
          * re-enter the quota code and are already holding the mutex */
-	if (IS_NOQUOTA(inode))
+	rcu_read_lock();
+	if (!dquot_active(inode)) {
+		rcu_read_unlock();
 		return 0;
+	}
 	/* Initialize the arrays */
 	for (cnt = 0; cnt < MAXQUOTAS; cnt++)
 		warntype_to[cnt] = QUOTA_NL_NOWARN;
+
+	idx = srcu_read_lock(&dqopts(inode->i_sb)->dq_srcu);
+	rcu_read_unlock();
 	down_write(&dqopts(inode->i_sb)->dqptr_sem);
 	if (IS_NOQUOTA(inode)) {	/* File without quota accounting? */
 		up_write(&dqopts(inode->i_sb)->dqptr_sem);
+		srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 		return 0;
 	}
 	spin_lock(&dq_data_lock);
@@ -1805,7 +1854,7 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
 	}
 	spin_unlock(&dq_data_lock);
 	up_write(&dqopts(inode->i_sb)->dqptr_sem);
-
+	srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 	mark_all_dquot_dirty(transfer_from);
 	mark_all_dquot_dirty(transfer_to);
 	flush_warnings(transfer_to, warntype_to);
@@ -1819,6 +1868,7 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
 over_quota:
 	spin_unlock(&dq_data_lock);
 	up_write(&dqopts(inode->i_sb)->dqptr_sem);
+	srcu_read_unlock(&dqopts(inode->i_sb)->dq_srcu, idx);
 	flush_warnings(transfer_to, warntype_to);
 	return ret;
 }
@@ -1963,6 +2013,22 @@ int dquot_disable(struct super_block *sb, int type, unsigned int flags)
 		if (sb_has_quota_loaded(sb, cnt) && !(flags & DQUOT_SUSPENDED))
 			continue;
 
+		toputinode[cnt] = dqopt->files[cnt];
+	}
+	/*
+	 * Wait for all dqget() callers to finish.
+	 */
+	synchronize_rcu();
+	synchronize_srcu(&dqopt->dq_srcu);
+
+	/*
+	 * At this moment all quota functions disabled, is is now safe to
+	 * perform final cleanup.
+	 */
+	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
+
+		if (!toputinode[cnt])
+			continue;
 		/* Note: these are blocking operations */
 		drop_dquot_ref(sb, cnt);
 		invalidate_dquots(sb, cnt);
@@ -1976,7 +2042,6 @@ int dquot_disable(struct super_block *sb, int type, unsigned int flags)
 			dqopt->ops[cnt]->free_file_info(sb, cnt);
 		put_quota_format(dqopt->info[cnt].dqi_format);
 
-		toputinode[cnt] = dqopt->files[cnt];
 		if (!sb_has_quota_loaded(sb, cnt))
 			dqopt->files[cnt] = NULL;
 		dqopt->info[cnt].dqi_flags = 0;
diff --git a/fs/super.c b/fs/super.c
index 8819e3a..473bdf6 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -54,9 +54,16 @@ static struct super_block *alloc_super(struct file_system_type *type)
 			s = NULL;
 			goto out;
 		}
+		if (init_srcu_struct(&s->s_dquot.dq_srcu)) {
+			security_sb_free(s);
+			kfree(s);
+			s = NULL;
+			goto out;
+		}
 #ifdef CONFIG_SMP
 		s->s_files = alloc_percpu(struct list_head);
 		if (!s->s_files) {
+			cleanup_srcu_struct(&s->s_dquot.dq_srcu);
 			security_sb_free(s);
 			kfree(s);
 			s = NULL;
@@ -106,6 +113,7 @@ static struct super_block *alloc_super(struct file_system_type *type)
 		mutex_init(&s->s_dquot.dqio_mutex);
 		mutex_init(&s->s_dquot.dqonoff_mutex);
 		init_rwsem(&s->s_dquot.dqptr_sem);
+
 		init_waitqueue_head(&s->s_wait_unfrozen);
 		s->s_maxbytes = MAX_NON_LFS;
 		s->s_op = &default_op;
@@ -126,6 +134,7 @@ static inline void destroy_super(struct super_block *s)
 #ifdef CONFIG_SMP
 	free_percpu(s->s_files);
 #endif
+	cleanup_srcu_struct(&s->s_dquot.dq_srcu);
 	security_sb_free(s);
 	kfree(s->s_subtype);
 	kfree(s->s_options);
diff --git a/include/linux/quota.h b/include/linux/quota.h
index bc495d0..7e859eb 100644
--- a/include/linux/quota.h
+++ b/include/linux/quota.h
@@ -175,6 +175,7 @@ enum {
 #include <linux/spinlock.h>
 #include <linux/wait.h>
 #include <linux/percpu_counter.h>
+#include <linux/srcu.h>
 
 #include <linux/dqblk_xfs.h>
 #include <linux/dqblk_v1.h>
@@ -402,6 +403,7 @@ struct quota_info {
 	struct inode *files[MAXQUOTAS];		/* inodes of quotafiles */
 	struct mem_dqinfo info[MAXQUOTAS];	/* Information for each quota type */
 	const struct quota_format_ops *ops[MAXQUOTAS];	/* Operations for each type */
+	struct srcu_struct dq_srcu;
 };
 
 int register_quota_format(struct quota_format_type *fmt);
-- 
1.6.5.2

--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


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