[PATCH 09/10] ovl: handle lazytime on the upper filesystem
From: Josef Bacik
Date: Tue Oct 27 2026 - 17:16:22 EST
With lazytime the timestamps of the upper inode stay in memory. A pass of
the flusher writes the data and leaves them alone, for up to 12 hours, so
the overlay inode can't be clean after it: a syncfs() which only looks at
dirty overlay inodes would miss the timestamps.
Keeping it dirty for that long isn't an option either. The flusher would
walk it every time, and a dirty inode can't be reclaimed. The upper inode
doesn't have that problem. It sits on a list of its own, and its
timestamps are written when its last reference goes away, which is what
happens under memory pressure. But the overlay inode holds a reference to
it.
Do the same one level up. When the timestamps are all that is left, give
the overlay inode I_DIRTY_TIME. That takes it off the list which the
flusher walks, while syncfs() still gets to it. Implement
->sync_lazytime() to pass on to the upper inode what the VFS tells the
overlay inode: on syncfs() and fsync(), when the 12 hours are over, and
when the last reference to the overlay inode goes away.
A change through the overlay to an inode which is parked like this also
writes the timestamps, because marking it dirty ends in ->sync_lazytime().
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
fs/overlayfs/dir.c | 1 +
fs/overlayfs/inode.c | 17 +++++++++++++++++
fs/overlayfs/overlayfs.h | 1 +
fs/overlayfs/super.c | 24 ++++++++++++++++++++++++
4 files changed, 43 insertions(+)
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index 1194ccf981c3..fc47beffa67e 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -1489,6 +1489,7 @@ const struct inode_operations ovl_dir_inode_operations = {
.get_acl = ovl_get_acl,
.set_acl = ovl_set_acl,
.update_time = ovl_update_time,
+ .sync_lazytime = ovl_sync_lazytime,
.fileattr_get = ovl_fileattr_get,
.fileattr_set = ovl_fileattr_set,
.tmpfile = ovl_tmpfile,
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 9a4bf7c2502b..858802969017 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -743,6 +743,20 @@ int ovl_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
return err;
}
+/*
+ * The overlay inode has I_DIRTY_TIME because the upper inode has, see
+ * ovl_park_inode(). Pass it on, and keep track of the upper inode until that
+ * has been written.
+ */
+void ovl_sync_lazytime(struct inode *inode)
+{
+ struct inode *upper = ovl_inode_upper(inode);
+
+ if (upper && (inode_state_read_once(upper) & I_DIRTY_TIME))
+ mark_inode_dirty_sync(upper);
+ ovl_mark_inode_dirty(inode);
+}
+
static const struct inode_operations ovl_file_inode_operations = {
.setattr = ovl_setattr,
.permission = ovl_permission,
@@ -752,6 +766,7 @@ static const struct inode_operations ovl_file_inode_operations = {
.get_acl = ovl_get_acl,
.set_acl = ovl_set_acl,
.update_time = ovl_update_time,
+ .sync_lazytime = ovl_sync_lazytime,
.fiemap = ovl_fiemap,
.fileattr_get = ovl_fileattr_get,
.fileattr_set = ovl_fileattr_set,
@@ -763,6 +778,7 @@ static const struct inode_operations ovl_symlink_inode_operations = {
.getattr = ovl_getattr,
.listxattr = ovl_listxattr,
.update_time = ovl_update_time,
+ .sync_lazytime = ovl_sync_lazytime,
};
static const struct inode_operations ovl_special_inode_operations = {
@@ -774,6 +790,7 @@ static const struct inode_operations ovl_special_inode_operations = {
.get_acl = ovl_get_acl,
.set_acl = ovl_set_acl,
.update_time = ovl_update_time,
+ .sync_lazytime = ovl_sync_lazytime,
};
static const struct address_space_operations ovl_aops = {
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index f5a17bc258b6..ad1084be6191 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -867,6 +867,7 @@ static inline struct posix_acl *ovl_get_acl_path(const struct path *path,
int ovl_update_time(struct inode *inode, enum fs_update_time type,
unsigned int flags);
+void ovl_sync_lazytime(struct inode *inode);
bool ovl_is_private_xattr(struct super_block *sb, const char *name);
struct ovl_inode_params {
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 954648d41700..631f68a97980 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -344,6 +344,28 @@ static bool ovl_upper_unfinished(struct inode *upper)
(inode_state_read_once(upper) & I_SYNC);
}
+/*
+ * All that is left of @inode are timestamps which lazytime holds back on the
+ * upper filesystem. A pass which does not wait leaves them alone, for up to 12
+ * hours. Do to the overlay inode what was done to the upper inode: with
+ * I_DIRTY_TIME it is off the list which the flusher walks, syncfs() still gets
+ * to it, and ovl_sync_lazytime() is told when the time has come.
+ *
+ * One such time is when the last reference goes away, which it never does for
+ * the upper inode as long as the overlay inode is there. It may have gone away
+ * already. iput() looks for I_DIRTY_TIME under i_lock before it drops the last
+ * reference, so either it sees the flag or the count is zero here.
+ */
+static void ovl_park_inode(struct inode *inode)
+{
+ if (!ovl_tracks_dirty(OVL_FS(inode->i_sb)))
+ return;
+
+ __mark_inode_dirty(inode, I_DIRTY_TIME);
+ if (!icount_read_once(inode))
+ mark_inode_dirty_sync(inode);
+}
+
static int ovl_write_inode(struct inode *inode, struct writeback_control *wbc)
{
struct inode *upper = ovl_inode_upper(inode);
@@ -360,6 +382,8 @@ static int ovl_write_inode(struct inode *inode, struct writeback_control *wbc)
if (ovl_upper_unfinished(upper))
ovl_mark_inode_dirty(inode);
+ else if (inode_state_read_once(upper) & I_DIRTY_TIME)
+ ovl_park_inode(inode);
return ret;
}
--
2.55.0