During GFS2 unmount, do_sync() in gfs2_quota_sync() may try to re-acquire
quota data glocks that the current process already holds (from earlier
file operations that performed quota accounting). The GFS2 glock layer's
add_to_queue() detects this as recursive locking via the trap_recursive
mechanism and hits BUG().
Fix this by checking gfs2_glock_is_locked_by_me() before each
gfs2_glock_nq_init() call in do_sync(). If the current process already
holds the glock, skip the acquisition. The cleanup path is updated to
only call gfs2_glock_dq_uninit() on holders that were actually acquired.
Fixes: 1e72c0f7c40e ("GFS2: Clean up gfs2_adjust_quota() and do_glock()")
Signed-off-by: Jiakai Xu <xujiakai24@mails.ucas.ac.cn>
---
fs/gfs2/quota.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c
index 91e9975d25e8..f2a09869ee8f 100644
--- a/fs/gfs2/quota.c
+++ b/fs/gfs2/quota.c
@@ -929,6 +929,19 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda,
sort(qda, num_qd, sizeof(struct gfs2_quota_data *), sort_qd, NULL);
inode_lock(&ip->i_inode);
for (qx = 0; qx < num_qd; qx++) {
+ /*
+ * If we already hold this quota glock (because do_sync was
+ * called from a context where quota glocks are already held,
+ * such as during unmount), skip the recursive acquisition.
+ *
+ * We mark the holder with a NULL glock pointer so the cleanup
+ * path knows not to release it.
+ */
+ if (gfs2_glock_is_locked_by_me(qda[qx]->qd_gl)) {
+ memset(&ghs[qx], 0, sizeof(ghs[qx]));
+ continue;
+ }
+
error = gfs2_glock_nq_init(qda[qx]->qd_gl, LM_ST_EXCLUSIVE,
GL_NOCACHE, &ghs[qx]);
if (error)
@@ -990,7 +1003,8 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda,
gfs2_glock_dq_uninit(&i_gh);
out_dq:
while (qx--)
- gfs2_glock_dq_uninit(&ghs[qx]);
+ if (ghs[qx].gh_gl)
+ gfs2_glock_dq_uninit(&ghs[qx]);
inode_unlock(&ip->i_inode);
kfree(ghs);
gfs2_log_flush(glock_sbd(ip->i_gl), ip->i_gl,
--
2.34.1