[PATCH v6 9/9] ovl: implement containerized syncfs for overlayfs
From: Josef Bacik
Date: Tue Nov 10 2026 - 09:58:56 EST
From: Chengguang Xu <charliecgxu@xxxxxxxxxxx>
Now overlayfs can only sync own dirty inodes during syncfs,
so remove unnecessary sync_filesystem() on upper file system.
Do that with fsync=relaxed only. Call sync_fs_and_bdev() on the upper
filesystem instead, which is what sync_filesystem() does after it has
written the inodes. Do the same for a read-only remount.
The root inode of an overlay is not hashed and cannot be on a dirty list,
so write its upper inode every time.
Upper inodes that have no overlay inode, like whiteouts and the work
directories, are not tracked. It is up to ->sync_fs() and the block device
to get them to disk. ext4, xfs and btrfs log all metadata, and ext4
without a journal keeps it in the buffers of the block device.
With many containers on one upper filesystem, stopping one of them no
longer writes out the dirty data of all the others.
Signed-off-by: Chengguang Xu <charliecgxu@xxxxxxxxxxx>
[josef: everything after the first paragraph, no wait_sb_inodes() on the
upper filesystem]
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
Documentation/filesystems/overlayfs.rst | 2 +-
fs/overlayfs/params.c | 14 +++----------
fs/overlayfs/super.c | 36 +++++++++++++++++++++++++--------
3 files changed, 32 insertions(+), 20 deletions(-)
diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst
index 1a59500834a1..34fc4e6f8a84 100644
--- a/Documentation/filesystems/overlayfs.rst
+++ b/Documentation/filesystems/overlayfs.rst
@@ -830,7 +830,7 @@ Traditionally, syncfs(2) and unmount of an overlay sync the entire upper
filesystem, including the dirty data of other overlays that share it.
Overlayfs can be told to only write back the files that were modified
through the overlay, and then ask the upper filesystem to commit its
-metadata.
+metadata and write its block device.
On traditional local filesystems with a single journal (e.g. ext4, xfs),
fsync on a file also persists the parent directory changes, because they
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
index 549fc1e83ac9..6de99e79eb6b 100644
--- a/fs/overlayfs/params.c
+++ b/fs/overlayfs/params.c
@@ -774,22 +774,14 @@ static int ovl_reconfigure(struct fs_context *fc)
{
struct super_block *sb = fc->root->d_sb;
struct ovl_fs *ofs = OVL_FS(sb);
- struct super_block *upper_sb;
- int ret = 0;
if (!(fc->sb_flags & SB_RDONLY) && ovl_force_readonly(ofs))
return -EROFS;
- if (fc->sb_flags & SB_RDONLY && !sb_rdonly(sb)) {
- upper_sb = ovl_upper_mnt(ofs)->mnt_sb;
- if (ovl_should_sync(ofs)) {
- down_read(&upper_sb->s_umount);
- ret = sync_filesystem(upper_sb);
- up_read(&upper_sb->s_umount);
- }
- }
+ if (fc->sb_flags & SB_RDONLY && !sb_rdonly(sb) && ovl_should_sync(ofs))
+ return sync_filesystem(sb);
- return ret;
+ return 0;
}
static const struct fs_context_operations ovl_context_ops = {
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index a0fb85405f68..570e2ea6af93 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -270,18 +270,38 @@ static int ovl_sync_fs(struct super_block *sb, int wait)
/*
* Not called for sync(2) call or an emergency sync (SB_I_SKIP_SYNC).
* All the super blocks will be iterated, including upper_sb.
- *
- * If this is a syncfs(2) call, then we do need to call
- * sync_filesystem() on upper_sb, but enough if we do it when being
- * called with wait == 1.
*/
- if (!wait)
- return 0;
-
upper_sb = ovl_upper_mnt(ofs)->mnt_sb;
+ if (ovl_should_sync_upper_fs(ofs)) {
+ /* Enough if we do it when being called with wait == 1 */
+ if (!wait)
+ return 0;
+
+ down_read(&upper_sb->s_umount);
+ ret = sync_filesystem(upper_sb);
+ up_read(&upper_sb->s_umount);
+
+ return ret;
+ }
+
+ /*
+ * sync_filesystem() has already written the dirty inodes of this
+ * overlay through ovl_write_inode(). What is left are the last two
+ * steps of sync_filesystem() on the upper filesystem. They have to
+ * take care of the upper inodes that have no overlay inode, like
+ * whiteouts and the work directories. Do not write the inodes that
+ * others have dirtied on the upper filesystem.
+ *
+ * The root inode is not hashed, so it is never on the dirty list.
+ * Write its upper inode every time. sb->s_root is gone during an
+ * unmount. An error ends up in s_wb_err, like for any other inode.
+ */
+ ovl_write_upper_inode(sb, d_inode(ovl_upper_mnt(ofs)->mnt_root),
+ wait ? OVL_WRITE_WAIT : OVL_WRITE_START);
+
down_read(&upper_sb->s_umount);
- ret = sync_filesystem(upper_sb);
+ ret = sync_fs_and_bdev(upper_sb, wait);
up_read(&upper_sb->s_umount);
return ret;
--
2.55.0