]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
gfs2: fix address space truncation during withdraw
authorAndreas Gruenbacher <agruenba@redhat.com>
Fri, 3 Apr 2026 12:42:18 +0000 (14:42 +0200)
committerAndreas Gruenbacher <agruenba@redhat.com>
Tue, 7 Apr 2026 20:19:59 +0000 (22:19 +0200)
When a withdrawn filesystem's inodes are being evicted, the address spaces of
those inodes still need to be truncated but we can no longer start new
transactions.  We still don't want gfs2_invalidate_folio() to race with
gfs2_log_flush(), so take a read lock on sdp->sd_log_flush_lock in that case.
(It may not be obvious, but gfs2_invalidate_folio() is a jdata-only address
space operation.)

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
fs/gfs2/log.c
fs/gfs2/super.c

index 8397d34527a41dad7f71232cedf9ed9e6fb72c49..31ee7a0e86a261c77ff963970501d87aef6937fc 100644 (file)
@@ -1024,17 +1024,22 @@ void gfs2_remove_from_journal(struct buffer_head *bh, int meta)
                trace_gfs2_pin(bd, 0);
                atomic_dec(&sdp->sd_log_pinned);
                list_del_init(&bd->bd_list);
-               if (meta == REMOVE_META)
-                       tr->tr_num_buf_rm++;
-               else
-                       tr->tr_num_databuf_rm++;
-               set_bit(TR_TOUCHED, &tr->tr_flags);
+               if (tr) {
+                       if (meta == REMOVE_META)
+                               tr->tr_num_buf_rm++;
+                       else
+                               tr->tr_num_databuf_rm++;
+                       set_bit(TR_TOUCHED, &tr->tr_flags);
+               }
                was_pinned = 1;
                brelse(bh);
        }
        if (bd) {
                if (bd->bd_tr) {
-                       gfs2_trans_add_revoke(sdp, bd);
+                       if (tr)
+                               gfs2_trans_add_revoke(sdp, bd);
+                       else
+                                gfs2_remove_from_ail(bd);
                } else if (was_pinned) {
                        bh->b_private = NULL;
                        kmem_cache_free(gfs2_bufdata_cachep, bd);
index e4219a04d16ec2d534252ae0ec70d0632ade1974..83b5bab56377616934e592aa2be1caab649470cc 100644 (file)
@@ -1339,27 +1339,44 @@ static int gfs2_truncate_inode_pages(struct inode *inode)
        struct gfs2_sbd *sdp = GFS2_SB(inode);
        struct address_space *mapping = &inode->i_data;
        bool need_trans = gfs2_is_jdata(ip) && mapping->nrpages;
-       int ret;
+       int ret = 0;
 
        /*
         * Truncating a jdata inode address space may create revokes in
         * truncate_inode_pages() -> gfs2_invalidate_folio() -> ... ->
         * gfs2_remove_from_journal(), so we need a transaction here.
         *
-        * FIXME: During a withdraw, no new transactions can be created.
-        * In that case, we skip the truncate, but that doesn't help because
-        * truncate_inode_pages_final() will then call gfs2_invalidate_folio()
-        * again, and outside of a transaction.
+        * During a withdraw, no new transactions can be created.  We still
+        * take the log flush lock to prevent truncate from racing with
+        * gfs2_log_flush().
         */
        if (need_trans) {
                ret = gfs2_trans_begin(sdp, 0, sdp->sd_jdesc->jd_blocks);
                if (ret)
-                       return ret;
+                       down_read(&sdp->sd_log_flush_lock);
        }
        truncate_inode_pages(mapping, 0);
-       if (need_trans)
-               gfs2_trans_end(sdp);
-       return 0;
+       if (need_trans) {
+               if (ret)
+                       up_read(&sdp->sd_log_flush_lock);
+               else
+                       gfs2_trans_end(sdp);
+       }
+       return ret;
+}
+
+static void gfs2_truncate_inode_pages_final(struct inode *inode)
+{
+       struct gfs2_inode *ip = GFS2_I(inode);
+       struct gfs2_sbd *sdp = GFS2_SB(inode);
+       struct address_space *mapping = &inode->i_data;
+       bool need_lock = gfs2_is_jdata(ip) && mapping->nrpages;
+
+       if (need_lock)
+               down_read(&sdp->sd_log_flush_lock);
+       truncate_inode_pages_final(mapping);
+       if (need_lock)
+               up_read(&sdp->sd_log_flush_lock);
 }
 
 /*
@@ -1398,10 +1415,8 @@ static int evict_linked_inode(struct inode *inode, struct gfs2_holder *gh)
 
 clean:
        ret = gfs2_truncate_inode_pages(inode);
-       if (ret)
-               return ret;
        truncate_inode_pages(metamapping, 0);
-       return 0;
+       return ret;
 }
 
 /**
@@ -1472,7 +1487,7 @@ static void gfs2_evict_inode(struct inode *inode)
 out:
        if (gfs2_holder_initialized(&gh))
                gfs2_glock_dq_uninit(&gh);
-       truncate_inode_pages_final(&inode->i_data);
+       gfs2_truncate_inode_pages_final(inode);
        if (ip->i_qadata)
                gfs2_assert_warn(sdp, ip->i_qadata->qa_ref == 0);
        gfs2_rs_deltree(&ip->i_res);