]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
fat: Fix missed inode writeback during fsync(2)
authorJan Kara <jack@suse.cz>
Mon, 27 Jul 2026 10:49:36 +0000 (12:49 +0200)
committerChristian Brauner <brauner@kernel.org>
Mon, 27 Jul 2026 14:25:33 +0000 (16:25 +0200)
FAT could fail to properly write out inode on fsync(2) due to races with
WB_SYNC_NONE writeback. Several racing fsyncs could also result in some
fsync returning earlier than all metadata buffers were properly
persisted.

Fix these issues by using new .sync_inode_metadata method which makes
sure all inode related metadata is written to disk during any
WB_SYNC_ALL writeback. The slight disadvantage of this approach is that
when fsync(2) of an inode races with rename(2) of the inode, the window
during which inode isn't properly persisted becomes wider.

Signed-off-by: Jan Kara <jack@suse.cz>
Link: https://patch.msgid.link/20260727104923.3828017-38-jack@suse.cz
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
fs/fat/file.c
fs/fat/inode.c

index 37e7049b4c8c4497ba7cfabd2192bfb4840b5b3d..8a7585c25207282092d68221a4c270a89534e7ba 100644 (file)
@@ -190,8 +190,7 @@ int fat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
        struct inode *fat_inode = MSDOS_SB(inode->i_sb)->fat_inode;
        int err;
 
-       err = mmb_fsync_noflush(filp, &MSDOS_I(inode)->i_metadata_bhs,
-                               start, end, datasync);
+       err = simple_fsync_noflush(filp, start, end, datasync);
        if (err)
                return err;
 
index 3aa52481ad5cf321198e215776dab60e283fd8dc..f6f847ff1b1cb7c148b0f617c4b0778dfebe7262 100644 (file)
@@ -623,7 +623,34 @@ out:
 
 EXPORT_SYMBOL_GPL(fat_build_inode);
 
-static int __fat_write_inode(struct inode *inode, int wait);
+static int __fat_write_inode(struct inode *inode);
+
+static int fat_sync_inode_metadata(struct inode *inode,
+                                  struct writeback_control *wbc)
+{
+       struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+       struct buffer_head *bh;
+       loff_t i_pos;
+       sector_t blocknr;
+       int offset;
+
+       if (inode->i_ino == MSDOS_ROOT_INO)
+               return 0;
+       i_pos = fat_i_pos_read(sbi, inode);
+       if (!i_pos)
+               return 0;
+
+       fat_get_blknr_offset(sbi, i_pos, &blocknr, &offset);
+       bh = sb_find_get_block_nonatomic(inode->i_sb, blocknr);
+       /*
+        * Buffer present? We leave buffer_dirty check for sync_dirty_buffer()
+        * for proper synchronization with ongoing IO.
+        */
+       if (bh && buffer_uptodate(bh))
+               sync_dirty_buffer(bh);
+       brelse(bh);
+       return mmb_sync(&MSDOS_I(inode)->i_metadata_bhs);
+}
 
 static void fat_free_eofblocks(struct inode *inode)
 {
@@ -640,7 +667,7 @@ static void fat_free_eofblocks(struct inode *inode)
                 * any corruption on the next access to the cluster
                 * chain for the file.
                 */
-               err = __fat_write_inode(inode, inode_needs_sync(inode));
+               err = sync_inode_metadata(inode, inode_needs_sync(inode));
                if (err) {
                        fat_msg(inode->i_sb, KERN_WARNING, "Failed to "
                                        "update on disk inode for unused "
@@ -854,7 +881,7 @@ static int fat_statfs(struct dentry *dentry, struct kstatfs *buf)
        return 0;
 }
 
-static int __fat_write_inode(struct inode *inode, int wait)
+static int __fat_write_inode(struct inode *inode)
 {
        struct super_block *sb = inode->i_sb;
        struct msdos_sb_info *sbi = MSDOS_SB(sb);
@@ -863,7 +890,7 @@ static int __fat_write_inode(struct inode *inode, int wait)
        struct timespec64 mtime;
        loff_t i_pos;
        sector_t blocknr;
-       int err, offset;
+       int offset;
 
        if (inode->i_ino == MSDOS_ROOT_INO)
                return 0;
@@ -907,11 +934,9 @@ retry:
        }
        spin_unlock(&sbi->inode_hash_lock);
        mark_buffer_dirty(bh);
-       err = 0;
-       if (wait)
-               err = sync_dirty_buffer(bh);
        brelse(bh);
-       return err;
+       set_inode_metadata_writeback(inode);
+       return 0;
 }
 
 static int fat_write_inode(struct inode *inode, struct writeback_control *wbc)
@@ -925,14 +950,22 @@ static int fat_write_inode(struct inode *inode, struct writeback_control *wbc)
                err = fat_clusters_flush(sb);
                mutex_unlock(&MSDOS_SB(sb)->s_lock);
        } else
-               err = __fat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
+               err = __fat_write_inode(inode);
 
        return err;
 }
 
 int fat_sync_inode(struct inode *inode)
 {
-       return __fat_write_inode(inode, 1);
+       int err;
+       struct writeback_control wbc = {
+               .sync_mode = WB_SYNC_ALL,
+       };
+
+       err = __fat_write_inode(inode);
+       if (err)
+               return err;
+       return fat_sync_inode_metadata(inode, &wbc);
 }
 
 EXPORT_SYMBOL_GPL(fat_sync_inode);
@@ -942,6 +975,7 @@ static const struct super_operations fat_sops = {
        .alloc_inode    = fat_alloc_inode,
        .free_inode     = fat_free_inode,
        .write_inode    = fat_write_inode,
+       .sync_inode_metadata = fat_sync_inode_metadata,
        .evict_inode    = fat_evict_inode,
        .put_super      = fat_put_super,
        .statfs         = fat_statfs,