From: Josef Bacik <josef@toxicpanda.com>
To: Miklos Szeredi <miklos@szeredi.hu>,
Amir Goldstein <amir73il@gmail.com>,
Christian Brauner <brauner@kernel.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>, Jan Kara <jack@suse.cz>,
Christoph Hellwig <hch@infradead.org>,
Chris Mason <mason@kernel.org>,
Chengguang Xu <cgxu519@mykernel.net>,
linux-unionfs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-kernel@vger.kernel.org, Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH v5 4/8] ovl: mark the overlay inode dirty on modification
Date: Sat, 07 Nov 2026 19:19:33 +0000 [thread overview]
Message-ID: <20261107-b4-ovl-containerized-sync-v5-4-d34b4ae57fdf@toxicpanda.com> (raw)
In-Reply-To: <20261107-b4-ovl-containerized-sync-v5-0-d34b4ae57fdf@toxicpanda.com>
To sync only what was written through an overlay, we have to know which
upper inodes were modified through it. Mark the overlay inode dirty when
its upper inode is modified, which puts it on the dirty list of the
overlay's bdi.
Nearly every modification ends in ovl_copyattr(), so do it there. These do
not, so they mark the inode themselves:
- copy up, for the inode and for its parent
- ovl_instantiate(), for a new inode and for a new hard link
- atime updates
- shared writable mappings
- the removal of a stale "impure" xattr in readdir
Do not mark an unhashed inode. __mark_inode_dirty() would set its dirty
flags without queueing it, and every later call would return early. The
inode of a lower hardlink is unhashed until ovl_inode_update() hashes it.
The root inode is never hashed, a later patch takes care of it.
Based on work by Chengguang Xu.
Link: https://lore.kernel.org/r/20211122030038.1938875-1-cgxu519@mykernel.net
Suggested-by: Amir Goldstein <amir73il@gmail.com>
Suggested-by: Miklos Szeredi <miklos@szeredi.hu>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
fs/overlayfs/copy_up.c | 9 +++++++++
fs/overlayfs/dir.c | 3 +++
fs/overlayfs/file.c | 7 ++++++-
fs/overlayfs/inode.c | 4 +++-
fs/overlayfs/overlayfs.h | 20 ++++++++++++++++++++
fs/overlayfs/readdir.c | 1 +
fs/overlayfs/util.c | 12 +++++++++++-
7 files changed, 53 insertions(+), 3 deletions(-)
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index e963701b4c87..44bc96aa3d5d 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -1199,6 +1199,15 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
err = ovl_link_up(&ctx);
if (!err && ovl_dentry_needs_data_copy_up_locked(dentry, flags))
err = ovl_copy_up_meta_inode_data(&ctx);
+ /*
+ * All three write to the upper inode, and there may be no later
+ * write that would mark the overlay inode dirty. The first two
+ * also add an entry to the upper inode of the parent, set its
+ * "impure" xattr and restore its timestamps.
+ */
+ ovl_mark_inode_dirty(d_inode(dentry));
+ if (parent)
+ ovl_mark_inode_dirty(d_inode(parent));
ovl_copy_up_end(dentry);
}
do_delayed_call(&done);
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 1194ccf981c3..a24326446659 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -331,6 +331,9 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
inc_nlink(inode);
}
+ /* The upper inode was just created, or has got a new link */
+ ovl_mark_inode_dirty(inode);
+
if (tmpfile)
d_mark_tmpfile(tmpfile, inode);
diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
index 7433220d4ad6..53c5131a0482 100644
--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -472,8 +472,13 @@ static int ovl_mmap(struct file *file, struct vm_area_struct *vma)
.cred = ovl_creds(file_inode(file)->i_sb),
.accessed = ovl_file_accessed,
};
+ int err;
- return backing_file_mmap(of->realfile, vma, &ctx);
+ err = backing_file_mmap(of->realfile, vma, &ctx);
+ /* Stores through the mapping go straight to the upper inode */
+ if (!err && vma_is_shared_maywrite(vma))
+ ovl_mark_inode_dirty(file_inode(file));
+ return err;
}
static long ovl_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 63906b3dafcb..9a4bf7c2502b 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -587,6 +587,7 @@ int ovl_update_time(struct inode *inode, enum fs_update_time type,
touch_atime(&upperpath);
inode_set_atime_to_ts(inode,
inode_get_atime(d_inode(upperpath.dentry)));
+ ovl_mark_inode_dirty(inode);
}
}
return 0;
@@ -898,7 +899,8 @@ void ovl_inode_init(struct inode *inode, struct ovl_inode_params *oip,
oi->lowerdata_redirect = oip->lowerdata_redirect;
realinode = ovl_inode_real(inode);
- ovl_copyattr(inode);
+ /* Nothing was changed, the inode is only being looked up */
+ __ovl_copyattr(inode);
ovl_copyflags(realinode, inode);
ovl_map_ino(inode, ino, fsid);
}
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index 5b1a097ce543..d82c4bed5af2 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -691,6 +691,25 @@ static inline bool ovl_should_sync_upper_fs(struct ovl_fs *ofs)
return ovl_should_sync(ofs) && !ovl_tracks_dirty(ofs);
}
+/*
+ * The upper inode was modified through this overlay. Put the overlay inode on
+ * the dirty list of the overlay, where syncfs() and unmount find it.
+ *
+ * Do not look at the dirty flags of the upper inode here. They are cleared
+ * when the writeback of the upper inode starts, not when it completes.
+ *
+ * Do not mark an inode that is not hashed. __mark_inode_dirty() would set its
+ * dirty flags without putting it on a dirty list, and once the flags are set,
+ * later calls return early. The overlay inode of a lower hardlink is unhashed
+ * until ovl_inode_update() hashes it, and ovl_copy_up_one() marks it after
+ * that. The root inode is never hashed, so it is not tracked.
+ */
+static inline void ovl_mark_inode_dirty(struct inode *inode)
+{
+ if (ovl_tracks_dirty(OVL_FS(inode->i_sb)) && !inode_unhashed(inode))
+ mark_inode_dirty(inode);
+}
+
/*
* To avoid regressions in existing setups with overlay lower offline changes,
* we allow lower changes only if none of the new features are used.
@@ -878,6 +897,7 @@ bool ovl_lookup_trap_inode(struct super_block *sb, struct dentry *dir);
struct inode *ovl_get_trap_inode(struct super_block *sb, struct dentry *dir);
struct inode *ovl_get_inode(struct super_block *sb,
struct ovl_inode_params *oip);
+void __ovl_copyattr(struct inode *to);
void ovl_copyattr(struct inode *to);
/* vfs fileattr flags read from overlay.protattr xattr to ovl inode */
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index 7d6f7f6022eb..f02c37600df6 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -724,6 +724,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(const struct path *path)
if (!ovl_want_write(dentry)) {
ovl_removexattr(ofs, ovl_dentry_upper(dentry),
OVL_XATTR_IMPURE);
+ ovl_mark_inode_dirty(inode);
ovl_drop_write(dentry);
}
ovl_clear_flag(OVL_IMPURE, inode);
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 521717209b2e..a260c5a350e2 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -1492,7 +1492,7 @@ int ovl_sync_status(struct ovl_fs *ofs)
* map any lower or upper inode owned by id 1001 to id 1000. These mapping
* helpers are nops when the relevant layer isn't idmapped.
*/
-void ovl_copyattr(struct inode *inode)
+void __ovl_copyattr(struct inode *inode)
{
struct path realpath;
struct inode *realinode;
@@ -1516,3 +1516,13 @@ void ovl_copyattr(struct inode *inode)
i_size_write(inode, i_size_read(realinode));
spin_unlock(&inode->i_lock);
}
+
+/*
+ * The attributes are copied because the real inode was modified through the
+ * overlay, so this is also where the overlay inode is marked dirty.
+ */
+void ovl_copyattr(struct inode *inode)
+{
+ __ovl_copyattr(inode);
+ ovl_mark_inode_dirty(inode);
+}
--
2.55.0
next prev parent reply other threads:[~2026-11-07 19:19 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 19:19 [PATCH v5 0/8] ovl: only sync what was written through the overlay Josef Bacik
2026-11-07 19:19 ` [PATCH v5 1/8] ovl: expand fsync mount options Josef Bacik
2026-11-07 19:19 ` [PATCH v5 2/8] fs: add sync_fs_and_bdev() Josef Bacik
2026-11-07 19:19 ` [PATCH v5 3/8] ovl: setup overlayfs' private bdi Josef Bacik
2026-11-07 19:19 ` Josef Bacik [this message]
2026-11-07 19:19 ` [PATCH v5 5/8] ovl: implement overlayfs' own ->write_inode operation Josef Bacik
2026-11-07 19:19 ` [PATCH v5 6/8] ovl: cache dirty overlayfs' inode Josef Bacik
2026-11-07 19:19 ` [PATCH v5 7/8] ovl: write the timestamps that lazytime defers Josef Bacik
2026-11-07 19:19 ` [PATCH v5 8/8] ovl: implement containerized syncfs for overlayfs Josef Bacik
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261107-b4-ovl-containerized-sync-v5-4-d34b4ae57fdf@toxicpanda.com \
--to=josef@toxicpanda.com \
--cc=amir73il@gmail.com \
--cc=brauner@kernel.org \
--cc=cgxu519@mykernel.net \
--cc=hch@infradead.org \
--cc=jack@suse.cz \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-unionfs@vger.kernel.org \
--cc=mason@kernel.org \
--cc=miklos@szeredi.hu \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®