From 9e32784b71c2c84895016ca6ab271591669c02aa Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 9 Oct 2010 23:15:30 +0400 Subject: quota: fix dquot_disable vs dquot_transfer race v2 I've got following lockup: dquot_disable dquot_transfer ->dqget() sb_has_quota_active dqopt->flags &= ~dquot_state_flag(f, cnt) atomic_inc(dq->dq_count) ->drop_dquot_ref(sb, cnt); down_write(dqptr_sem) inode->i_dquot[cnt] = NULL ->__dquot_transfer invalidate_dquots(sb, cnt); down_write(&dqptr_sem) ->wait for dq_wait_unused inode->i_dquot = new_dquot /* wait forever */ ^^^^New quota user^^^^^^ We cannot allow new references to dquots from inodes after drop_dquot_ref() has removed them. We have to recheck quota state under dqptr_sem and before assignment, as we do it in dquot_initialize(). Signed-off-by: Dmitry Monakhov Signed-off-by: Jan Kara --- fs/quota/dquot.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'fs/quota/dquot.c') diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index aad1316a977f..ed14beea8fe3 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -1736,6 +1736,7 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) qsize_t rsv_space = 0; struct dquot *transfer_from[MAXQUOTAS] = {}; int cnt, ret = 0; + char is_valid[MAXQUOTAS] = {}; char warntype_to[MAXQUOTAS]; char warntype_from_inodes[MAXQUOTAS], warntype_from_space[MAXQUOTAS]; @@ -1757,8 +1758,15 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) space = cur_space + rsv_space; /* Build the transfer_from list and check the limits */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { + /* + * Skip changes for same uid or gid or for turned off quota-type. + */ if (!transfer_to[cnt]) continue; + /* Avoid races with quotaoff() */ + if (!sb_has_quota_active(inode->i_sb, cnt)) + continue; + is_valid[cnt] = 1; transfer_from[cnt] = inode->i_dquot[cnt]; ret = check_idq(transfer_to[cnt], 1, warntype_to + cnt); if (ret) @@ -1772,12 +1780,8 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) * Finally perform the needed transfer from transfer_from to transfer_to */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) { - /* - * Skip changes for same uid or gid or for turned off quota-type. - */ - if (!transfer_to[cnt]) + if (!is_valid[cnt]) continue; - /* Due to IO error we might not have transfer_from[] structure */ if (transfer_from[cnt]) { warntype_from_inodes[cnt] = @@ -1803,7 +1807,9 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to) mark_all_dquot_dirty(transfer_to); /* Pass back references to put */ for (cnt = 0; cnt < MAXQUOTAS; cnt++) - transfer_to[cnt] = transfer_from[cnt]; + if (is_valid[cnt]) + transfer_to[cnt] = transfer_from[cnt]; + warn: flush_warnings(transfer_to, warntype_to); flush_warnings(transfer_from, warntype_from_inodes); -- cgit v1.2.2