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