diff options
author | Dmitry <dmonakhov@openvz.org> | 2010-10-09 23:15:30 +0400 |
---|---|---|
committer | Jan Kara <jack@suse.cz> | 2010-10-28 01:30:02 +0200 |
commit | 9e32784b71c2c84895016ca6ab271591669c02aa (patch) | |
tree | db69a152de57f65666408591a06f15cb865c695f /fs | |
parent | a910eefa511f9d1118effc13fba6773163502c4f (diff) | |
download | linux-3.10-9e32784b71c2c84895016ca6ab271591669c02aa.tar.gz linux-3.10-9e32784b71c2c84895016ca6ab271591669c02aa.tar.bz2 linux-3.10-9e32784b71c2c84895016ca6ab271591669c02aa.zip |
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 <dmonakhov@gmail.com>
Signed-off-by: Jan Kara <jack@suse.cz>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/quota/dquot.c | 18 |
1 files changed, 12 insertions, 6 deletions
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index aad1316a977..ed14beea8fe 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); |