[PATCH 10/10] ovl: implement containerized syncfs for overlayfs
From: Josef Bacik
Date: Tue Oct 27 2026 - 17:17:15 EST
From: Chengguang Xu <charliecgxu@xxxxxxxxxxx>
syncfs(2) on an overlay and unmounting one call sync_filesystem() on the
upper filesystem, which writes every dirty inode there is on it, whoever
dirtied it. When many containers share an upper filesystem, stopping one
of them writes out the dirty data of all the others. That can take a very
long time, and s_umount of the upper filesystem is held shared all the
while, so one remount or freeze of it which queues up behind that stops
everybody.
Now overlayfs can only sync own dirty inodes during syncfs, so remove
unnecessary sync_filesystem() on upper file system. What remains is to
have the upper filesystem commit its metadata, with ->sync_fs(), and to
write its block device as sync_filesystem() does.
That is for an upper filesystem with SB_I_OVL_USE_SYNC_FS. With any other
one nothing changes.
Signed-off-by: Chengguang Xu <charliecgxu@xxxxxxxxxxx>
[josef: no wait_sb_inodes() on the upper filesystem, which waits for
everybody's writeback: an overlay inode stays dirty until its own is done.
Only with SB_I_OVL_USE_SYNC_FS. Use sync_blockdev(). Return the error of
->sync_fs(). Nothing if the upper filesystem is read-only. More in the
commit message]
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
fs/overlayfs/super.c | 44 ++++++++++++++++++++++++++++++++++++--------
1 file changed, 36 insertions(+), 8 deletions(-)
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 631f68a97980..c570bf7103f8 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -15,6 +15,7 @@
#include <linux/seq_file.h>
#include <linux/posix_acl_xattr.h>
#include <linux/exportfs.h>
+#include <linux/blkdev.h>
#include <linux/pagemap.h>
#include <linux/writeback.h>
#include <linux/file.h>
@@ -269,18 +270,45 @@ 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_tracks_dirty(ofs)) {
+ /*
+ * 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;
+
+ down_read(&upper_sb->s_umount);
+ ret = sync_filesystem(upper_sb);
+ up_read(&upper_sb->s_umount);
+
+ return ret;
+ }
+
+ /*
+ * For syncfs(2) and for unmount, sync_filesystem() has written the
+ * dirty inodes of this overlay by now, see ovl_write_inode(). What is
+ * left is for the upper filesystem to commit its metadata. The dirty
+ * inodes of everybody else on the upper filesystem are none of our
+ * business.
+ */
down_read(&upper_sb->s_umount);
- ret = sync_filesystem(upper_sb);
+ /* As in sync_filesystem(): ->sync_fs() doesn't expect to be called then */
+ ret = 0;
+ if (sb_rdonly(upper_sb))
+ goto out;
+
+ if (upper_sb->s_op->sync_fs)
+ ret = upper_sb->s_op->sync_fs(upper_sb, wait);
+ if (!ret && wait)
+ ret = sync_blockdev(upper_sb->s_bdev);
+ else if (!ret)
+ ret = sync_blockdev_nowait(upper_sb->s_bdev);
+out:
up_read(&upper_sb->s_umount);
return ret;
--
2.55.0