summaryrefslogtreecommitdiffstats
path: root/fs/ocfs2
diff options
context:
space:
mode:
authorJan Kara <jack@suse.cz>2009-06-02 14:24:01 +0200
committerJoel Becker <joel.becker@oracle.com>2009-06-03 19:14:29 -0700
commit65bac575e35915801ea518b9d8d8824367d125c8 (patch)
treebce57755cf8b9f783cdab046ea519c0e41f595e1 /fs/ocfs2
parentb4c30de39a2596503e888a7b47d19792f25913d6 (diff)
downloadlinux-stable-65bac575e35915801ea518b9d8d8824367d125c8.tar.gz
linux-stable-65bac575e35915801ea518b9d8d8824367d125c8.tar.bz2
linux-stable-65bac575e35915801ea518b9d8d8824367d125c8.zip
ocfs2: Fix possible deadlock with quotas in ocfs2_setattr()
We called vfs_dq_transfer() with global quota file lock held. This can lead to deadlocks as if vfs_dq_transfer() has to allocate new quota structure, it calls ocfs2_dquot_acquire() which tries to get quota file lock again and this can block if another node requested the lock in the mean time. Since we have to call vfs_dq_transfer() with transaction already started and quota file lock ranks above the transaction start, we cannot just rely on ocfs2_dquot_acquire() or ocfs2_dquot_release() on getting the lock if they need it. We fix the problem by acquiring pointers to all quota structures needed by vfs_dq_transfer() already before calling the function. By this we are sure that all quota structures are properly allocated and they can be freed only after we drop references to them. Thus we don't need quota file lock anywhere inside vfs_dq_transfer(). Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Joel Becker <joel.becker@oracle.com>
Diffstat (limited to 'fs/ocfs2')
-rw-r--r--fs/ocfs2/file.c53
1 files changed, 30 insertions, 23 deletions
diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c
index c2a87c885b73..1a96cac31791 100644
--- a/fs/ocfs2/file.c
+++ b/fs/ocfs2/file.c
@@ -894,9 +894,9 @@ int ocfs2_setattr(struct dentry *dentry, struct iattr *attr)
struct ocfs2_super *osb = OCFS2_SB(sb);
struct buffer_head *bh = NULL;
handle_t *handle = NULL;
- int locked[MAXQUOTAS] = {0, 0};
- int credits, qtype;
- struct ocfs2_mem_dqinfo *oinfo;
+ int qtype;
+ struct dquot *transfer_from[MAXQUOTAS] = { };
+ struct dquot *transfer_to[MAXQUOTAS] = { };
mlog_entry("(0x%p, '%.*s')\n", dentry,
dentry->d_name.len, dentry->d_name.name);
@@ -969,30 +969,37 @@ int ocfs2_setattr(struct dentry *dentry, struct iattr *attr)
if ((attr->ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid) ||
(attr->ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid)) {
- credits = OCFS2_INODE_UPDATE_CREDITS;
+ /*
+ * Gather pointers to quota structures so that allocation /
+ * freeing of quota structures happens here and not inside
+ * vfs_dq_transfer() where we have problems with lock ordering
+ */
if (attr->ia_valid & ATTR_UID && attr->ia_uid != inode->i_uid
&& OCFS2_HAS_RO_COMPAT_FEATURE(sb,
OCFS2_FEATURE_RO_COMPAT_USRQUOTA)) {
- oinfo = sb_dqinfo(sb, USRQUOTA)->dqi_priv;
- status = ocfs2_lock_global_qf(oinfo, 1);
- if (status < 0)
+ transfer_to[USRQUOTA] = dqget(sb, attr->ia_uid,
+ USRQUOTA);
+ transfer_from[USRQUOTA] = dqget(sb, inode->i_uid,
+ USRQUOTA);
+ if (!transfer_to[USRQUOTA] || !transfer_from[USRQUOTA]) {
+ status = -ESRCH;
goto bail_unlock;
- credits += ocfs2_calc_qinit_credits(sb, USRQUOTA) +
- ocfs2_calc_qdel_credits(sb, USRQUOTA);
- locked[USRQUOTA] = 1;
+ }
}
if (attr->ia_valid & ATTR_GID && attr->ia_gid != inode->i_gid
&& OCFS2_HAS_RO_COMPAT_FEATURE(sb,
OCFS2_FEATURE_RO_COMPAT_GRPQUOTA)) {
- oinfo = sb_dqinfo(sb, GRPQUOTA)->dqi_priv;
- status = ocfs2_lock_global_qf(oinfo, 1);
- if (status < 0)
+ transfer_to[GRPQUOTA] = dqget(sb, attr->ia_gid,
+ GRPQUOTA);
+ transfer_from[GRPQUOTA] = dqget(sb, inode->i_gid,
+ GRPQUOTA);
+ if (!transfer_to[GRPQUOTA] || !transfer_from[GRPQUOTA]) {
+ status = -ESRCH;
goto bail_unlock;
- credits += ocfs2_calc_qinit_credits(sb, GRPQUOTA) +
- ocfs2_calc_qdel_credits(sb, GRPQUOTA);
- locked[GRPQUOTA] = 1;
+ }
}
- handle = ocfs2_start_trans(osb, credits);
+ handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS +
+ 2 * ocfs2_quota_trans_credits(sb));
if (IS_ERR(handle)) {
status = PTR_ERR(handle);
mlog_errno(status);
@@ -1030,12 +1037,6 @@ int ocfs2_setattr(struct dentry *dentry, struct iattr *attr)
bail_commit:
ocfs2_commit_trans(osb, handle);
bail_unlock:
- for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
- if (!locked[qtype])
- continue;
- oinfo = sb_dqinfo(sb, qtype)->dqi_priv;
- ocfs2_unlock_global_qf(oinfo, 1);
- }
ocfs2_inode_unlock(inode, 1);
bail_unlock_rw:
if (size_change)
@@ -1043,6 +1044,12 @@ bail_unlock_rw:
bail:
brelse(bh);
+ /* Release quota pointers in case we acquired them */
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ dqput(transfer_to[qtype]);
+ dqput(transfer_from[qtype]);
+ }
+
if (!status && attr->ia_valid & ATTR_MODE) {
status = ocfs2_acl_chmod(inode);
if (status < 0)