]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
gfs2: Fix data loss during inode evict
authorAndreas Gruenbacher <agruenba@redhat.com>
Mon, 10 Nov 2025 21:18:44 +0000 (21:18 +0000)
committerAndreas Gruenbacher <agruenba@redhat.com>
Mon, 23 Mar 2026 19:55:27 +0000 (20:55 +0100)
When gfs2_evict_inode() is called on an inode with unwritten data in the
page cache, the page cache needs to be written before it can be
truncated.  This doesn't always happen.  Fix that by changing
gfs2_evict_inode() to always either call evict_linked_inode() or
evict_unlinked_inode().

Inside evict_unlinked_inode(), first check if the inode is dirty.  If it
is, make sure the inode glock is held and write back the data and
metadata.  If it isn't, skip those steps.

Also, make sure that gfs2_evict_inode() calls gfs2_evict_inode() and
evict_unlinked_inode() only if ip->i_gl is not NULL; this avoids
unnecessary complications there.

Fixes xfstest generic/211.

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

index b20494d6730a9abf8cf010d5937e28160ce85bcc..e4219a04d16ec2d534252ae0ec70d0632ade1974 100644 (file)
@@ -1241,6 +1241,9 @@ static enum evict_behavior evict_should_delete(struct inode *inode,
        struct gfs2_sbd *sdp = sb->s_fs_info;
        int ret;
 
+       if (inode->i_nlink)
+               return EVICT_SHOULD_SKIP_DELETE;
+
        if (gfs2_holder_initialized(&ip->i_iopen_gh) &&
            test_bit(GLF_DEFER_DELETE, &ip->i_iopen_gh.gh_gl->gl_flags))
                return EVICT_SHOULD_DEFER_DELETE;
@@ -1279,13 +1282,18 @@ static enum evict_behavior evict_should_delete(struct inode *inode,
 /**
  * evict_unlinked_inode - delete the pieces of an unlinked evicted inode
  * @inode: The inode to evict
+ * @gh: The glock holder structure
  */
-static int evict_unlinked_inode(struct inode *inode)
+static int evict_unlinked_inode(struct inode *inode, struct gfs2_holder *gh)
 {
        struct gfs2_inode *ip = GFS2_I(inode);
        struct gfs2_glock *gl = ip->i_gl;
        int ret;
 
+       /* The inode glock must be held exclusively and be instantiated. */
+       BUG_ON(!gfs2_holder_initialized(gh) ||
+              test_bit(GLF_INSTANTIATE_NEEDED, &gl->gl_flags));
+
        if (S_ISDIR(inode->i_mode) &&
            (ip->i_diskflags & GFS2_DIF_EXHASH)) {
                ret = gfs2_dir_exhash_dealloc(ip);
@@ -1318,7 +1326,7 @@ static int evict_unlinked_inode(struct inode *inode)
         */
 
        ret = gfs2_dinode_dealloc(ip);
-       if (!ret && gl)
+       if (!ret)
                gfs2_inode_remember_delete(gl, ip->i_no_formal_ino);
 
 out:
@@ -1357,19 +1365,30 @@ static int gfs2_truncate_inode_pages(struct inode *inode)
 /*
  * evict_linked_inode - evict an inode whose dinode has not been unlinked
  * @inode: The inode to evict
+ * @gh: The glock holder structure
  */
-static int evict_linked_inode(struct inode *inode)
+static int evict_linked_inode(struct inode *inode, struct gfs2_holder *gh)
 {
        struct super_block *sb = inode->i_sb;
        struct gfs2_sbd *sdp = sb->s_fs_info;
        struct gfs2_inode *ip = GFS2_I(inode);
        struct gfs2_glock *gl = ip->i_gl;
-       struct address_space *metamapping;
+       struct address_space *metamapping = gfs2_glock2aspace(gl);
        int ret;
 
+       if (!(test_bit(GLF_DIRTY, &gl->gl_flags) || inode->i_flags & I_DIRTY))
+               goto clean;
+
+       /* The inode glock must be held exclusively and be instantiated. */
+       if (!gfs2_holder_initialized(gh))
+               ret = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, gh);
+       else
+               ret = gfs2_instantiate(gh);
+       if (ret)
+               return ret;
+
        gfs2_log_flush(sdp, gl, GFS2_LOG_HEAD_FLUSH_NORMAL |
                       GFS2_LFC_EVICT_INODE);
-       metamapping = gfs2_glock2aspace(gl);
        if (test_bit(GLF_DIRTY, &gl->gl_flags)) {
                filemap_fdatawrite(metamapping);
                filemap_fdatawait(metamapping);
@@ -1377,6 +1396,7 @@ static int evict_linked_inode(struct inode *inode)
        write_inode_now(inode, 1);
        gfs2_ail_flush(gl, 0);
 
+clean:
        ret = gfs2_truncate_inode_pages(inode);
        if (ret)
                return ret;
@@ -1415,7 +1435,7 @@ static void gfs2_evict_inode(struct inode *inode)
        int ret;
 
        gfs2_holder_mark_uninitialized(&gh);
-       if (inode->i_nlink || sb_rdonly(sb) || !ip->i_no_addr)
+       if (sb_rdonly(sb) || !ip->i_no_addr || !ip->i_gl)
                goto out;
 
        /*
@@ -1440,9 +1460,9 @@ static void gfs2_evict_inode(struct inode *inode)
                behavior = EVICT_SHOULD_SKIP_DELETE;
        }
        if (behavior == EVICT_SHOULD_DELETE)
-               ret = evict_unlinked_inode(inode);
+               ret = evict_unlinked_inode(inode, &gh);
        else
-               ret = evict_linked_inode(inode);
+               ret = evict_linked_inode(inode, &gh);
 
        if (gfs2_rs_active(&ip->i_res))
                gfs2_rs_deltree(&ip->i_res);