summaryrefslogtreecommitdiff
path: root/fs/fat
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 14:21:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-17 14:21:00 -0700
commitff68e5f557f69a08fdcfa4ce8b1b809d63bd4f45 (patch)
treea0f66fa4118692cdc7bc58cc42f040b3d7f38421 /fs/fat
parent1781f0b3d75caa22376cf7fa0224f9aca696580d (diff)
parent974d0be0cb8e48d63b9d413a2e1a8fba16cd2583 (diff)
downloadlinux-ff68e5f557f69a08fdcfa4ce8b1b809d63bd4f45.tar.gz
linux-ff68e5f557f69a08fdcfa4ce8b1b809d63bd4f45.zip
Merge tag 'vfs-7.3-rc1.sync' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs writeback updates from Christian Brauner: "This makes sync_inode_metadata() and writeback_single_inode() persist not only the inode but all metadata associated with it. A new .sync_inode_metadata superblock operation is called from __writeback_single_inode(). Alongside it a new I_METADATA_WRITEBACK state flag is added. Filesystems no longer need their own mmb_fsync() implementations and can just use simple_fsync(). All metadata is now written for IS_SYNC and IS_DIRSYNC inodes. Races where several fsyncs raced and mmb_sync() could return before all buffers were really persisted are fixed since I_SYNC now serializes properly. The I_METADATA_WRITEBACK scheme also fixes the case where a WB_SYNC_NONE writeback landing between write(2) and fsync(2) left fsync(2) failing to persist the inode. That problem is not specific to filesystems using the generic metadata bh tracking, and the ones that do not are left alone. ext2, udf, bfs, minix, fat and ext4 in nojournal mode have their data integrity writeout fixed and are converted. affs drops metadata bh tracking and mmb_fsync() is removed. A few other fixes came out of this: - a UAF in mark_buffer_write_io_error() - missed inode writeback when racing with __writeback_single_inode() - ext4 allocating the mapping_metadata_bhs struct on demand - three fat fixes: a lost inode update in do_msdos_rename() with DIRSYNC, inode buffer write errors not propagating out of fat_sync_inode_metadata() and directory entries not being persisted on fsync(2) of the root directory" * tag 'vfs-7.3-rc1.sync' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (24 commits) writeback: Export __inode_attach_wb() fat: Fix persisting directory entries on fsync(2) of the root directory fat: Propagate inode buffer write errors from fat_sync_inode_metadata() fat: Fix lost inode update in do_msdos_rename() with DIRSYNC vfs: Remove mmb_fsync() fat: Replace fat_sync_inode() with sync_inode_metadata() fat: Fix missed inode writeback during fsync(2) ext4: Fix data integrity writeout issues in nojournal mode minix: Fix data integrity writeout issues bfs: Fix data integrity writeout issues udf: Fold udf_update_inode() into udf_write_inode() udf: Use sync_inode_metadata() in udf_evict_inode() udf: Drop udf_sync_inode() udf: Use sync_inode_metadata() to writeout IS_SYNC inode udf: Fix data integrity writeout issues ext2: Fix data integrity writeout issues ext2: Avoid unnecessary inode buffer writeback for sync(2) ext2: Drop __ext2_write_inode() ext2: Fix lost inode updates for IS_SYNC inodes fs: Provide way for filesystem to wait for metadata writeback ...
Diffstat (limited to 'fs/fat')
-rw-r--r--fs/fat/dir.c6
-rw-r--r--fs/fat/fat.h1
-rw-r--r--fs/fat/file.c9
-rw-r--r--fs/fat/inode.c63
-rw-r--r--fs/fat/misc.c7
-rw-r--r--fs/fat/namei_msdos.c33
-rw-r--r--fs/fat/namei_vfat.c20
7 files changed, 83 insertions, 56 deletions
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
index c6cca5d00ffd..35bdb62944a2 100644
--- a/fs/fat/dir.c
+++ b/fs/fat/dir.c
@@ -1109,10 +1109,10 @@ int fat_remove_entries(struct inode *dir, struct fat_slot_info *sinfo)
}
fat_truncate_time(dir, NULL, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
+ err = 0;
+ mark_inode_dirty(dir);
if (IS_DIRSYNC(dir))
- (void)fat_sync_inode(dir);
- else
- mark_inode_dirty(dir);
+ (void)sync_inode_metadata(dir, 1);
return 0;
}
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 2772675bd35a..61338413d9f3 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -421,7 +421,6 @@ extern void fat_detach(struct inode *inode);
extern struct inode *fat_iget(struct super_block *sb, loff_t i_pos);
extern struct inode *fat_build_inode(struct super_block *sb,
struct msdos_dir_entry *de, loff_t i_pos);
-extern int fat_sync_inode(struct inode *inode);
extern int fat_fill_super(struct super_block *sb, struct fs_context *fc,
void (*setup)(struct super_block *));
extern int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de);
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 37e7049b4c8c..1c835ca5f21a 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -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;
@@ -332,15 +331,15 @@ static int fat_free(struct inode *inode, int skip)
}
MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(inode);
if (wait) {
- err = fat_sync_inode(inode);
+ err = sync_inode_metadata(inode, 1);
if (err) {
MSDOS_I(inode)->i_start = i_start;
MSDOS_I(inode)->i_logstart = i_logstart;
return err;
}
- } else
- mark_inode_dirty(inode);
+ }
/* Write a new EOF, and get the remaining cluster chain for freeing. */
if (skip) {
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 3aa52481ad5c..5ea6f74a2a3f 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -623,7 +623,41 @@ 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;
+
+ /* The root directory has no directory entry of its own. */
+ if (inode->i_ino == MSDOS_ROOT_INO)
+ goto sync_bhs;
+ i_pos = fat_i_pos_read(sbi, inode);
+ if (!i_pos)
+ goto sync_bhs;
+
+ 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);
+ if (buffer_write_io_error(bh)) {
+ brelse(bh);
+ return -EIO;
+ }
+ }
+ brelse(bh);
+sync_bhs:
+ return mmb_sync(&MSDOS_I(inode)->i_metadata_bhs);
+}
static void fat_free_eofblocks(struct inode *inode)
{
@@ -640,7 +674,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 +888,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,10 +897,13 @@ 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)
+ if (inode->i_ino == MSDOS_ROOT_INO) {
+ /* No entry to update but the metadata bh list may need syncing. */
+ set_inode_metadata_writeback(inode);
return 0;
+ }
retry:
i_pos = fat_i_pos_read(sbi, inode);
@@ -907,11 +944,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,23 +960,17 @@ 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);
-}
-
-EXPORT_SYMBOL_GPL(fat_sync_inode);
-
static int fat_show_options(struct seq_file *m, struct dentry *root);
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,
diff --git a/fs/fat/misc.c b/fs/fat/misc.c
index 3027ef53af21..be18f6b5819b 100644
--- a/fs/fat/misc.c
+++ b/fs/fat/misc.c
@@ -146,16 +146,17 @@ int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)
} else {
MSDOS_I(inode)->i_start = new_dclus;
MSDOS_I(inode)->i_logstart = new_dclus;
+ mark_inode_dirty(inode);
/*
* Since generic_write_sync() synchronizes regular files later,
* we sync here only directories.
*/
if (S_ISDIR(inode->i_mode) && IS_DIRSYNC(inode)) {
- ret = fat_sync_inode(inode);
+ ret = sync_inode_metadata(inode, 1);
if (ret)
return ret;
- } else
- mark_inode_dirty(inode);
+ }
+
}
if (new_fclus != (inode->i_blocks >> (sbi->cluster_bits - 9))) {
fat_fs_error_ratelimit(
diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c
index ee6824c5d136..d46d1a3851f2 100644
--- a/fs/fat/namei_msdos.c
+++ b/fs/fat/namei_msdos.c
@@ -255,10 +255,9 @@ static int msdos_add_entry(struct inode *dir, const unsigned char *name,
return err;
fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(dir);
if (IS_DIRSYNC(dir))
- (void)fat_sync_inode(dir);
- else
- mark_inode_dirty(dir);
+ (void)sync_inode_metadata(dir, 1);
return 0;
}
@@ -476,21 +475,20 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
else
MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
+ mark_inode_dirty(old_inode);
if (IS_DIRSYNC(old_dir)) {
- err = fat_sync_inode(old_inode);
+ err = sync_inode_metadata(old_inode, 1);
if (err) {
MSDOS_I(old_inode)->i_attrs = old_attrs;
goto out;
}
- } else
- mark_inode_dirty(old_inode);
+ }
inode_inc_iversion(old_dir);
fat_truncate_time(old_dir, NULL, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(old_dir);
if (IS_DIRSYNC(old_dir))
- (void)fat_sync_inode(old_dir);
- else
- mark_inode_dirty(old_dir);
+ (void)sync_inode_metadata(old_dir, 1);
goto out;
}
}
@@ -521,12 +519,12 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
MSDOS_I(old_inode)->i_attrs |= ATTR_HIDDEN;
else
MSDOS_I(old_inode)->i_attrs &= ~ATTR_HIDDEN;
+ mark_inode_dirty(old_inode);
if (IS_DIRSYNC(new_dir)) {
- err = fat_sync_inode(old_inode);
+ err = sync_inode_metadata(old_inode, 1);
if (err)
goto error_inode;
- } else
- mark_inode_dirty(old_inode);
+ }
if (update_dotdot) {
fat_set_start(dotdot_de, MSDOS_I(new_dir)->i_logstart);
@@ -548,10 +546,9 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
goto error_dotdot;
inode_inc_iversion(old_dir);
fat_truncate_time(old_dir, &ts, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(old_dir);
if (IS_DIRSYNC(old_dir))
- (void)fat_sync_inode(old_dir);
- else
- mark_inode_dirty(old_dir);
+ (void)sync_inode_metadata(old_dir, 1);
if (new_inode) {
drop_nlink(new_inode);
@@ -580,8 +577,10 @@ error_inode:
MSDOS_I(old_inode)->i_attrs = old_attrs;
if (new_inode) {
fat_attach(new_inode, new_i_pos);
- if (corrupt)
- corrupt |= fat_sync_inode(new_inode);
+ if (corrupt) {
+ mark_inode_dirty(new_inode);
+ corrupt |= sync_inode_metadata(new_inode, 1);
+ }
} else {
/*
* If new entry was not sharing the data cluster, it
diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c
index 139d3ef4bfae..da3e89c0b16a 100644
--- a/fs/fat/namei_vfat.c
+++ b/fs/fat/namei_vfat.c
@@ -678,10 +678,9 @@ static int vfat_add_entry(struct inode *dir, const struct qstr *qname,
/* update timestamp */
fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(dir);
if (IS_DIRSYNC(dir))
- (void)fat_sync_inode(dir);
- else
- mark_inode_dirty(dir);
+ (void)sync_inode_metadata(dir, 1);
cleanup:
kfree(slots);
return err;
@@ -904,9 +903,9 @@ static int vfat_get_dotdot_de(struct inode *inode, struct buffer_head **bh,
static int vfat_sync_ipos(struct inode *dir, struct inode *inode)
{
- if (IS_DIRSYNC(dir))
- return fat_sync_inode(inode);
mark_inode_dirty(inode);
+ if (IS_DIRSYNC(dir))
+ return sync_inode_metadata(inode, 1);
return 0;
}
@@ -925,10 +924,9 @@ static void vfat_update_dir_metadata(struct inode *dir, struct timespec64 *ts)
{
inode_inc_iversion(dir);
fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
+ mark_inode_dirty(dir);
if (IS_DIRSYNC(dir))
- (void)fat_sync_inode(dir);
- else
- mark_inode_dirty(dir);
+ (void)sync_inode_metadata(dir, 1);
}
static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
@@ -1024,8 +1022,10 @@ error_inode:
fat_attach(old_inode, old_sinfo.i_pos);
if (new_inode) {
fat_attach(new_inode, new_i_pos);
- if (corrupt)
- corrupt |= fat_sync_inode(new_inode);
+ if (corrupt) {
+ mark_inode_dirty(new_inode);
+ corrupt |= sync_inode_metadata(new_inode, 1);
+ }
} else {
/*
* If new entry was not sharing the data cluster, it