Skip to content

Commit 08fb390

Browse files
dgchinnerBen Myers
authored andcommitted
xfs: avoid nesting transactions in xfs_qm_scall_setqlim()
Lockdep reports: ============================================= [ INFO: possible recursive locking detected ] 3.9.0+ #3 Not tainted --------------------------------------------- setquota/28368 is trying to acquire lock: (sb_internal){++++.?}, at: [<c11e8846>] xfs_trans_alloc+0x26/0x50 but task is already holding lock: (sb_internal){++++.?}, at: [<c11e8846>] xfs_trans_alloc+0x26/0x50 from xfs_qm_scall_setqlim()->xfs_dqread() when a dquot needs to be allocated. xfs_qm_scall_setqlim() is starting a transaction and then not passing it into xfs_qm_dqet() and so it starts it's own transaction when allocating the dquot. Splat! Fix this by not allocating the dquot in xfs_qm_scall_setqlim() inside the setqlim transaction. This requires getting the dquot first (and allocating it if necessary) then dropping and relocking the dquot before joining it to the setqlim transaction. Reported-by: Michael L. Semon <mlsemon35@gmail.com> Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Ben Myers <bpm@sgi.com> Signed-off-by: Ben Myers <bpm@sgi.com> (cherry picked from commit f648167)
1 parent 7ae0778 commit 08fb390

File tree

1 file changed

+23
-17
lines changed

1 file changed

+23
-17
lines changed

fs/xfs/xfs_qm_syscalls.c

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -489,31 +489,36 @@ xfs_qm_scall_setqlim(
489489
if ((newlim->d_fieldmask & XFS_DQ_MASK) == 0)
490490
return 0;
491491

492-
tp = xfs_trans_alloc(mp, XFS_TRANS_QM_SETQLIM);
493-
error = xfs_trans_reserve(tp, 0, XFS_QM_SETQLIM_LOG_RES(mp),
494-
0, 0, XFS_DEFAULT_LOG_COUNT);
495-
if (error) {
496-
xfs_trans_cancel(tp, 0);
497-
return (error);
498-
}
499-
500492
/*
501493
* We don't want to race with a quotaoff so take the quotaoff lock.
502-
* (We don't hold an inode lock, so there's nothing else to stop
503-
* a quotaoff from happening). (XXXThis doesn't currently happen
504-
* because we take the vfslock before calling xfs_qm_sysent).
494+
* We don't hold an inode lock, so there's nothing else to stop
495+
* a quotaoff from happening.
505496
*/
506497
mutex_lock(&q->qi_quotaofflock);
507498

508499
/*
509-
* Get the dquot (locked), and join it to the transaction.
510-
* Allocate the dquot if this doesn't exist.
500+
* Get the dquot (locked) before we start, as we need to do a
501+
* transaction to allocate it if it doesn't exist. Once we have the
502+
* dquot, unlock it so we can start the next transaction safely. We hold
503+
* a reference to the dquot, so it's safe to do this unlock/lock without
504+
* it being reclaimed in the mean time.
511505
*/
512-
if ((error = xfs_qm_dqget(mp, NULL, id, type, XFS_QMOPT_DQALLOC, &dqp))) {
513-
xfs_trans_cancel(tp, XFS_TRANS_ABORT);
506+
error = xfs_qm_dqget(mp, NULL, id, type, XFS_QMOPT_DQALLOC, &dqp);
507+
if (error) {
514508
ASSERT(error != ENOENT);
515509
goto out_unlock;
516510
}
511+
xfs_dqunlock(dqp);
512+
513+
tp = xfs_trans_alloc(mp, XFS_TRANS_QM_SETQLIM);
514+
error = xfs_trans_reserve(tp, 0, XFS_QM_SETQLIM_LOG_RES(mp),
515+
0, 0, XFS_DEFAULT_LOG_COUNT);
516+
if (error) {
517+
xfs_trans_cancel(tp, 0);
518+
goto out_rele;
519+
}
520+
521+
xfs_dqlock(dqp);
517522
xfs_trans_dqjoin(tp, dqp);
518523
ddq = &dqp->q_core;
519524

@@ -621,9 +626,10 @@ xfs_qm_scall_setqlim(
621626
xfs_trans_log_dquot(tp, dqp);
622627

623628
error = xfs_trans_commit(tp, 0);
624-
xfs_qm_dqrele(dqp);
625629

626-
out_unlock:
630+
out_rele:
631+
xfs_qm_dqrele(dqp);
632+
out_unlock:
627633
mutex_unlock(&q->qi_quotaofflock);
628634
return error;
629635
}

0 commit comments

Comments
 (0)