mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/4] btrfs: fix FMODE_NOCMTIME handling in btrfs
@ 2026-11-05 20:07 Jeff Layton
  2026-11-05 20:07 ` [PATCH 1/4] btrfs: honor FMODE_NOCMTIME in btrfs_write_check() Jeff Layton
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Jeff Layton @ 2026-11-05 20:07 UTC (permalink / raw)
  To: David Sterba, Chris Mason, Chuck Lever
  Cc: linux-btrfs, linux-kernel, Jeff Layton

I tested the delegated timestamp support in nfsd recently. That support
relies heavily on FMODE_NOCMTIME, and I found several places where btrfs
didn't handle that flag properly. This series seems to fix them for me.
I tested mostly with the git regression testsuite, because it is very
sensitive to subtle bugs in timestamp handling.

Please consider for v7.5.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
Jeff Layton (4):
      btrfs: honor FMODE_NOCMTIME in btrfs_write_check()
      btrfs: honor FMODE_NOCMTIME when punching holes
      btrfs: honor FMODE_NOCMTIME in fallocate
      btrfs: honor FMODE_NOCMTIME when cloning

 fs/btrfs/btrfs_inode.h |  3 ++-
 fs/btrfs/ctree.h       |  2 --
 fs/btrfs/file.c        | 60 +++++++++++++++++++++++++++++++++++---------------
 fs/btrfs/file.h        |  2 ++
 fs/btrfs/inode.c       | 13 ++++++-----
 fs/btrfs/reflink.c     |  9 ++++----
 fs/btrfs/relocation.c  |  2 +-
 7 files changed, 59 insertions(+), 32 deletions(-)
---
base-commit: d692f252c52b8d1b4bc7481554f314df31ed8249
change-id: 20261104-delegts-btrfs-124230048ca7

Best regards,
-- 
Jeff Layton <jlayton@kernel.org>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] btrfs: honor FMODE_NOCMTIME in btrfs_write_check()
  2026-11-05 20:07 [PATCH 0/4] btrfs: fix FMODE_NOCMTIME handling in btrfs Jeff Layton
@ 2026-11-05 20:07 ` Jeff Layton
  2026-11-05 20:07 ` [PATCH 2/4] btrfs: honor FMODE_NOCMTIME when punching holes Jeff Layton
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Jeff Layton @ 2026-11-05 20:07 UTC (permalink / raw)
  To: David Sterba, Chris Mason, Chuck Lever
  Cc: linux-btrfs, linux-kernel, Jeff Layton

btrfs_write_check() open-codes the c/mtime update and checks only
IS_NOCMTIME(inode). file_update_time() also skips the update when the
file has FMODE_NOCMTIME. nfsd sets this flag on files that have a
delegation with delegated timestamps.

Thus, on a btrfs export, WRITEs from the delegation holder update the
server's c/mtime. When the holder returns its times, nfsd restores the
mtime. nfsd cannot move ctime backward, so ctime stays newer than the
time that the holder reported. Over NFSv4.2, git test t1092 finds a
stat-dirty file.

Skip the update for FMODE_NOCMTIME files too.

Fixes: e5e9b24ab8fa ("nfsd: freeze c/mtime updates with outstanding WRITE_ATTRS delegation")
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 fs/btrfs/file.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index f978c6524aa0..94475976d8cf 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1053,7 +1053,7 @@ int btrfs_write_check(struct kiocb *iocb, size_t count)
 	 * need to start yet another transaction to update the inode as we will
 	 * update the inode when we finish writing whatever data we write.
 	 */
-	if (!IS_NOCMTIME(inode)) {
+	if (!IS_NOCMTIME(inode) && !(file->f_mode & FMODE_NOCMTIME)) {
 		inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
 		inode_inc_iversion(inode);
 	}

-- 
2.56.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 2/4] btrfs: honor FMODE_NOCMTIME when punching holes
  2026-11-05 20:07 [PATCH 0/4] btrfs: fix FMODE_NOCMTIME handling in btrfs Jeff Layton
  2026-11-05 20:07 ` [PATCH 1/4] btrfs: honor FMODE_NOCMTIME in btrfs_write_check() Jeff Layton
@ 2026-11-05 20:07 ` Jeff Layton
  2026-11-05 20:07 ` [PATCH 3/4] btrfs: honor FMODE_NOCMTIME in fallocate Jeff Layton
  2026-11-05 20:07 ` [PATCH 4/4] btrfs: honor FMODE_NOCMTIME when cloning Jeff Layton
  3 siblings, 0 replies; 5+ messages in thread
From: Jeff Layton @ 2026-11-05 20:07 UTC (permalink / raw)
  To: David Sterba, Chris Mason, Chuck Lever
  Cc: linux-btrfs, linux-kernel, Jeff Layton

btrfs_punch_hole() calls file_modified(), which skips the c/mtime update
for FMODE_NOCMTIME files. But btrfs_punch_hole() then updates c/mtime
itself in two places: after it replaces the extents, and in the
partial-block path. Also, when extent_info is NULL,
btrfs_replace_file_extents() updates c/mtime for each transaction.

nfsd sets FMODE_NOCMTIME while a client holds delegated timestamps. Thus
a DEALLOCATE from the holder advances the server's ctime, and the times
that the holder returns cannot move it back.

- Add btrfs_should_update_cmtime() and use it in btrfs_write_check() too.
- Replace btrfs_replace_extent_info::update_times with an update_times
  argument to btrfs_replace_file_extents(). This lets the NULL
  extent_info (hole punch) case skip the update.
- In btrfs_punch_hole(), skip the c/mtime updates for FMODE_NOCMTIME
  files.

The reflink caller that passes a NULL extent_info for a trailing hole
now also honors no_time_update, as clone_finish_inode_update() does.

Fixes: e5e9b24ab8fa ("nfsd: freeze c/mtime updates with outstanding WRITE_ATTRS delegation")
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 fs/btrfs/ctree.h   |  2 --
 fs/btrfs/file.c    | 28 +++++++++++++++++++++-------
 fs/btrfs/file.h    |  1 +
 fs/btrfs/inode.c   |  3 +--
 fs/btrfs/reflink.c |  6 +++---
 5 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 00d4c4184e76..56d8e36a54ff 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -402,8 +402,6 @@ struct btrfs_replace_extent_info {
 	 * existing extent into a file range.
 	 */
 	bool is_new_extent;
-	/* Indicate if we should update the inode's mtime and ctime. */
-	bool update_times;
 	/* Meaningful only if is_new_extent is true. */
 	int qgroup_reserved;
 	/*
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 94475976d8cf..eab20cc6e18e 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1024,6 +1024,17 @@ void btrfs_check_nocow_unlock(struct btrfs_inode *inode)
 	btrfs_drew_write_unlock(&inode->root->snapshot_lock);
 }
 
+/*
+ * Return whether a modification through @file should update c/mtime. Like
+ * file_update_time(), skip it for S_NOCMTIME inodes and for files opened
+ * with FMODE_NOCMTIME (e.g. by nfsd while a client holds delegated
+ * timestamps).
+ */
+static bool btrfs_should_update_cmtime(const struct file *file)
+{
+	return !IS_NOCMTIME(file_inode(file)) && !(file->f_mode & FMODE_NOCMTIME);
+}
+
 int btrfs_write_check(struct kiocb *iocb, size_t count)
 {
 	struct file *file = iocb->ki_filp;
@@ -1053,7 +1064,7 @@ int btrfs_write_check(struct kiocb *iocb, size_t count)
 	 * need to start yet another transaction to update the inode as we will
 	 * update the inode when we finish writing whatever data we write.
 	 */
-	if (!IS_NOCMTIME(inode) && !(file->f_mode & FMODE_NOCMTIME)) {
+	if (btrfs_should_update_cmtime(file)) {
 		inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
 		inode_inc_iversion(inode);
 	}
@@ -2356,6 +2367,7 @@ static int btrfs_insert_replace_extent(struct btrfs_trans_handle *trans,
  * The end offset is inclusive (last byte of the range).
  * @extent_info is NULL for fallocate's hole punching and non-NULL when replacing
  * the file range with an extent.
+ * @update_times says whether to update the inode's mtime and ctime.
  * When not punching a hole, we don't want to end up in a state where we dropped
  * extents without inserting a new one, so we must abort the transaction to avoid
  * a corruption.
@@ -2364,6 +2376,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
 			       struct btrfs_path *path, const u64 start,
 			       const u64 end,
 			       struct btrfs_replace_extent_info *extent_info,
+			       bool update_times,
 			       struct btrfs_trans_handle **trans_out)
 {
 	struct btrfs_drop_extents_args drop_args = { 0 };
@@ -2504,7 +2517,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
 		 */
 		inode_inc_iversion(&inode->vfs_inode);
 
-		if (!extent_info || extent_info->update_times)
+		if (update_times)
 			inode_set_mtime_to_ts(&inode->vfs_inode,
 					      inode_set_ctime_current(&inode->vfs_inode));
 
@@ -2636,6 +2649,7 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len)
 	u64 ino_size;
 	bool truncated_block = false;
 	bool updated_inode = false;
+	const bool update_times = btrfs_should_update_cmtime(file);
 
 	btrfs_inode_lock(BTRFS_I(inode), BTRFS_ILOCK_MMAP);
 
@@ -2738,14 +2752,15 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len)
 	}
 
 	ret = btrfs_replace_file_extents(BTRFS_I(inode), path, lockstart,
-					 lockend, NULL, &trans);
+					 lockend, NULL, update_times, &trans);
 	btrfs_free_path(path);
 	if (ret)
 		goto out;
 
 	ASSERT(trans != NULL);
 	inode_inc_iversion(inode);
-	inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+	if (update_times)
+		inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
 	ret = btrfs_update_inode(trans, BTRFS_I(inode));
 	updated_inode = true;
 	btrfs_end_transaction(trans);
@@ -2762,10 +2777,9 @@ static int btrfs_punch_hole(struct file *file, loff_t offset, loff_t len)
 		 * for detecting, at fsync time, if the inode isn't yet in the
 		 * log tree or it's there but not up to date.
 		 */
-		struct timespec64 now = inode_set_ctime_current(inode);
-
 		inode_inc_iversion(inode);
-		inode_set_mtime_to_ts(inode, now);
+		if (update_times)
+			inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
 		trans = btrfs_start_transaction(root, 1);
 		if (IS_ERR(trans)) {
 			ret = PTR_ERR(trans);
diff --git a/fs/btrfs/file.h b/fs/btrfs/file.h
index d7df81388cbe..18ebc8c2d6d2 100644
--- a/fs/btrfs/file.h
+++ b/fs/btrfs/file.h
@@ -30,6 +30,7 @@ int btrfs_replace_file_extents(struct btrfs_inode *inode,
 			   struct btrfs_path *path, const u64 start,
 			   const u64 end,
 			   struct btrfs_replace_extent_info *extent_info,
+			   bool update_times,
 			   struct btrfs_trans_handle **trans_out);
 int btrfs_mark_extent_written(struct btrfs_trans_handle *trans,
 			      struct btrfs_inode *inode, u64 start, u64 end);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index abaa33471456..a6ce83a94fe6 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -9132,7 +9132,6 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 	extent_info.file_offset = file_offset;
 	extent_info.extent_buf = (char *)&stack_fi;
 	extent_info.is_new_extent = true;
-	extent_info.update_times = true;
 	extent_info.qgroup_reserved = qgroup_released;
 	extent_info.insertions = 0;
 
@@ -9144,7 +9143,7 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 
 	ret = btrfs_replace_file_extents(inode, path, file_offset,
 				     file_offset + len - 1, &extent_info,
-				     &trans);
+				     true, &trans);
 	btrfs_free_path(path);
 	if (ret)
 		goto free_qgroup;
diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c
index b5d2c094941d..f67350bba9b9 100644
--- a/fs/btrfs/reflink.c
+++ b/fs/btrfs/reflink.c
@@ -556,10 +556,9 @@ static int btrfs_clone(struct btrfs_inode *src, struct btrfs_inode *inode,
 			clone_info.file_offset = new_key.offset;
 			clone_info.extent_buf = buf;
 			clone_info.is_new_extent = false;
-			clone_info.update_times = !no_time_update;
 			ret = btrfs_replace_file_extents(inode, path,
 					drop_start, new_key.offset + datal - 1,
-					&clone_info, &trans);
+					&clone_info, !no_time_update, &trans);
 			if (ret)
 				goto out;
 		} else {
@@ -656,7 +655,8 @@ static int btrfs_clone(struct btrfs_inode *src, struct btrfs_inode *inode,
 			btrfs_set_inode_full_sync(inode);
 
 		ret = btrfs_replace_file_extents(inode, path,
-				last_dest_end, destoff + len - 1, NULL, &trans);
+				last_dest_end, destoff + len - 1, NULL,
+				!no_time_update, &trans);
 		if (ret)
 			goto out;
 

-- 
2.56.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 3/4] btrfs: honor FMODE_NOCMTIME in fallocate
  2026-11-05 20:07 [PATCH 0/4] btrfs: fix FMODE_NOCMTIME handling in btrfs Jeff Layton
  2026-11-05 20:07 ` [PATCH 1/4] btrfs: honor FMODE_NOCMTIME in btrfs_write_check() Jeff Layton
  2026-11-05 20:07 ` [PATCH 2/4] btrfs: honor FMODE_NOCMTIME when punching holes Jeff Layton
@ 2026-11-05 20:07 ` Jeff Layton
  2026-11-05 20:07 ` [PATCH 4/4] btrfs: honor FMODE_NOCMTIME when cloning Jeff Layton
  3 siblings, 0 replies; 5+ messages in thread
From: Jeff Layton @ 2026-11-05 20:07 UTC (permalink / raw)
  To: David Sterba, Chris Mason, Chuck Lever
  Cc: linux-btrfs, linux-kernel, Jeff Layton

btrfs_fallocate() calls file_modified(), which skips the c/mtime update
for FMODE_NOCMTIME files. But the helpers that btrfs_fallocate() calls
update the times anyway:

- btrfs_prealloc_file_range() sets ctime. Its
  insert_prealloc_file_extent() tells btrfs_replace_file_extents() to
  update c/mtime.
- btrfs_fallocate_update_isize() sets ctime.

btrfs_zero_range() also calls these helpers. nfsd sets FMODE_NOCMTIME
while a client holds delegated timestamps. Thus an ALLOCATE from the
holder advances the server's ctime, and the times that the holder
returns cannot move it back.

Pass an update_times flag from btrfs_fallocate() to these helpers. Set
the flag from btrfs_should_update_cmtime(). Relocation still updates the
times.

btrfs_fallocate_update_isize() never incremented i_version. When a size
extension stayed within existing preallocation, only the ctime update
changed the NFS change attribute. Increment i_version unconditionally
there, as btrfs_prealloc_file_range() does.

Fixes: e5e9b24ab8fa ("nfsd: freeze c/mtime updates with outstanding WRITE_ATTRS delegation")
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 fs/btrfs/btrfs_inode.h |  3 ++-
 fs/btrfs/file.c        | 32 +++++++++++++++++++++-----------
 fs/btrfs/inode.c       | 12 +++++++-----
 fs/btrfs/relocation.c  |  2 +-
 4 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index 0782f917fccc..fbcf8ca05fb7 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -595,7 +595,8 @@ void btrfs_run_delayed_iputs(struct btrfs_fs_info *fs_info);
 int btrfs_wait_on_delayed_iputs(struct btrfs_fs_info *fs_info);
 int btrfs_prealloc_file_range(struct inode *inode, int mode,
 			      u64 start, u64 num_bytes, u64 min_size,
-			      loff_t actual_len, u64 *alloc_hint);
+			      loff_t actual_len, u64 *alloc_hint,
+			      bool update_times);
 int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct folio *locked_folio,
 			     u64 start, u64 end, struct writeback_control *wbc);
 void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio);
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index eab20cc6e18e..6ff494846aea 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -2836,7 +2836,8 @@ static int add_falloc_range(struct list_head *head, u64 start, u64 len)
 
 static int btrfs_fallocate_update_isize(struct inode *inode,
 					const u64 end,
-					const int mode)
+					const int mode,
+					bool update_times)
 {
 	struct btrfs_trans_handle *trans;
 	struct btrfs_root *root = BTRFS_I(inode)->root;
@@ -2860,7 +2861,9 @@ static int btrfs_fallocate_update_isize(struct inode *inode,
 	if (IS_ERR(trans))
 		return PTR_ERR(trans);
 
-	inode_set_ctime_current(inode);
+	inode_inc_iversion(inode);
+	if (update_times)
+		inode_set_ctime_current(inode);
 	i_size_write(inode, end);
 	btrfs_inode_safe_disk_i_size_write(BTRFS_I(inode), 0);
 	ret = btrfs_update_inode(trans, BTRFS_I(inode));
@@ -2901,7 +2904,8 @@ static int btrfs_zero_range_check_range_boundary(struct btrfs_inode *inode,
 static int btrfs_zero_range(struct inode *inode,
 			    loff_t offset,
 			    loff_t len,
-			    const int mode)
+			    const int mode,
+			    bool update_times)
 {
 	struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info;
 	struct extent_map *em;
@@ -2942,7 +2946,7 @@ static int btrfs_zero_range(struct inode *inode,
 			 */
 			btrfs_free_extent_map(em);
 			ret = btrfs_fallocate_update_isize(inode, offset + len,
-							   mode);
+							   mode, update_times);
 			goto out;
 		}
 		/*
@@ -2968,7 +2972,7 @@ static int btrfs_zero_range(struct inode *inode,
 		if (em->flags & EXTENT_FLAG_PREALLOC) {
 			btrfs_free_extent_map(em);
 			ret = btrfs_fallocate_update_isize(inode, offset + len,
-							   mode);
+							   mode, update_times);
 			goto out;
 		}
 		if (len < sectorsize && em->disk_bytenr != EXTENT_MAP_HOLE) {
@@ -2978,7 +2982,8 @@ static int btrfs_zero_range(struct inode *inode,
 			if (!ret)
 				ret = btrfs_fallocate_update_isize(inode,
 								   offset + len,
-								   mode);
+								   mode,
+								   update_times);
 			return ret;
 		}
 		btrfs_free_extent_map(em);
@@ -3056,7 +3061,8 @@ static int btrfs_zero_range(struct inode *inode,
 		ret = btrfs_prealloc_file_range(inode, mode, alloc_start,
 						alloc_end - alloc_start,
 						fs_info->sectorsize,
-						offset + len, &alloc_hint);
+						offset + len, &alloc_hint,
+						update_times);
 		btrfs_unlock_extent(&BTRFS_I(inode)->io_tree, lockstart, lockend,
 				    &cached_state);
 		/* btrfs_prealloc_file_range releases reserved space on error */
@@ -3065,7 +3071,8 @@ static int btrfs_zero_range(struct inode *inode,
 			goto out;
 		}
 	}
-	ret = btrfs_fallocate_update_isize(inode, offset + len, mode);
+	ret = btrfs_fallocate_update_isize(inode, offset + len, mode,
+					   update_times);
  out:
 	if (ret && space_reserved)
 		btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
@@ -3096,6 +3103,7 @@ static long btrfs_fallocate(struct file *file, int mode,
 	u64 qgroup_reserved = 0;
 	struct extent_map *em;
 	int blocksize = BTRFS_I(inode)->root->fs_info->sectorsize;
+	const bool update_times = btrfs_should_update_cmtime(file);
 	int ret;
 
 	if (btrfs_is_shutdown(inode_to_fs_info(inode)))
@@ -3167,7 +3175,7 @@ static long btrfs_fallocate(struct file *file, int mode,
 		goto out;
 
 	if (mode & FALLOC_FL_ZERO_RANGE) {
-		ret = btrfs_zero_range(inode, offset, len, mode);
+		ret = btrfs_zero_range(inode, offset, len, mode, update_times);
 		btrfs_inode_unlock(BTRFS_I(inode), BTRFS_ILOCK_MMAP);
 		return ret;
 	}
@@ -3232,7 +3240,8 @@ static long btrfs_fallocate(struct file *file, int mode,
 			ret = btrfs_prealloc_file_range(inode, mode,
 					range->start,
 					range->len, blocksize,
-					offset + len, &alloc_hint);
+					offset + len, &alloc_hint,
+					update_times);
 			/*
 			 * btrfs_prealloc_file_range() releases space even
 			 * if it returns an error.
@@ -3260,7 +3269,8 @@ static long btrfs_fallocate(struct file *file, int mode,
 	 * We didn't need to allocate any more space, but we still extended the
 	 * size of the file so we need to update i_size and the inode item.
 	 */
-	ret = btrfs_fallocate_update_isize(inode, actual_end, mode);
+	ret = btrfs_fallocate_update_isize(inode, actual_end, mode,
+					   update_times);
 out_unlock:
 	btrfs_unlock_extent(&BTRFS_I(inode)->io_tree, alloc_start, locked_end,
 			    &cached_state);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index a6ce83a94fe6..1a6bd5025274 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -9100,7 +9100,7 @@ static int btrfs_symlink(const struct mnt_idmap *idmap, struct inode *dir,
 static struct btrfs_trans_handle *insert_prealloc_file_extent(
 				       struct btrfs_inode *inode,
 				       struct btrfs_key *ins,
-				       u64 file_offset)
+				       u64 file_offset, bool update_times)
 {
 	struct btrfs_file_extent_item stack_fi;
 	struct btrfs_replace_extent_info extent_info;
@@ -9143,7 +9143,7 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 
 	ret = btrfs_replace_file_extents(inode, path, file_offset,
 				     file_offset + len - 1, &extent_info,
-				     true, &trans);
+				     update_times, &trans);
 	btrfs_free_path(path);
 	if (ret)
 		goto free_qgroup;
@@ -9165,7 +9165,8 @@ static struct btrfs_trans_handle *insert_prealloc_file_extent(
 
 int btrfs_prealloc_file_range(struct inode *inode, int mode,
 			      u64 start, u64 num_bytes, u64 min_size,
-			      loff_t actual_len, u64 *alloc_hint)
+			      loff_t actual_len, u64 *alloc_hint,
+			      bool update_times)
 {
 	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
 	struct btrfs_trans_handle *trans;
@@ -9206,7 +9207,7 @@ int btrfs_prealloc_file_range(struct inode *inode, int mode,
 
 		last_alloc = ins.offset;
 		trans = insert_prealloc_file_extent(BTRFS_I(inode), &ins,
-						    cur_offset);
+						    cur_offset, update_times);
 		/*
 		 * Now that we inserted the prealloc extent we can finally
 		 * decrement the number of reservations in the block group.
@@ -9246,7 +9247,8 @@ int btrfs_prealloc_file_range(struct inode *inode, int mode,
 		*alloc_hint = ins.objectid + ins.offset;
 
 		inode_inc_iversion(inode);
-		inode_set_ctime_current(inode);
+		if (update_times)
+			inode_set_ctime_current(inode);
 		BTRFS_I(inode)->flags |= BTRFS_INODE_PREALLOC;
 		if (!(mode & FALLOC_FL_KEEP_SIZE) &&
 		    (actual_len > inode->i_size) &&
diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
index b2ac5b836b21..9af207446848 100644
--- a/fs/btrfs/relocation.c
+++ b/fs/btrfs/relocation.c
@@ -2834,7 +2834,7 @@ static noinline_for_stack int prealloc_file_extent_cluster(struct reloc_control
 		num_bytes = end + 1 - start;
 		ret = btrfs_prealloc_file_range(&inode->vfs_inode, 0, start,
 						num_bytes, num_bytes,
-						end + 1, &alloc_hint);
+						end + 1, &alloc_hint, true);
 		cur_offset = end + 1;
 		btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state);
 		if (ret)

-- 
2.56.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 4/4] btrfs: honor FMODE_NOCMTIME when cloning
  2026-11-05 20:07 [PATCH 0/4] btrfs: fix FMODE_NOCMTIME handling in btrfs Jeff Layton
                   ` (2 preceding siblings ...)
  2026-11-05 20:07 ` [PATCH 3/4] btrfs: honor FMODE_NOCMTIME in fallocate Jeff Layton
@ 2026-11-05 20:07 ` Jeff Layton
  3 siblings, 0 replies; 5+ messages in thread
From: Jeff Layton @ 2026-11-05 20:07 UTC (permalink / raw)
  To: David Sterba, Chris Mason, Chuck Lever
  Cc: linux-btrfs, linux-kernel, Jeff Layton

btrfs_remap_file_range() calls generic_remap_file_range_prep() through
btrfs_remap_file_range_prep(). The file_modified() call in
generic_remap_file_range_prep() skips the c/mtime update for
FMODE_NOCMTIME files, but btrfs_clone_files() then always tells
btrfs_clone() to update the c/mtime of the destination.

nfsd sets FMODE_NOCMTIME while a client holds delegated timestamps. Thus
a CLONE from the holder advances the server's ctime, and the times that
the holder returns cannot move it back.

Pass no_time_update for FMODE_NOCMTIME destinations, as dedupe already
does. Make btrfs_should_update_cmtime() available to reflink.c.

Fixes: e5e9b24ab8fa ("nfsd: freeze c/mtime updates with outstanding WRITE_ATTRS delegation")
Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
 fs/btrfs/file.c    | 2 +-
 fs/btrfs/file.h    | 1 +
 fs/btrfs/reflink.c | 3 ++-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 6ff494846aea..6f525ce42a2d 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1030,7 +1030,7 @@ void btrfs_check_nocow_unlock(struct btrfs_inode *inode)
  * with FMODE_NOCMTIME (e.g. by nfsd while a client holds delegated
  * timestamps).
  */
-static bool btrfs_should_update_cmtime(const struct file *file)
+bool btrfs_should_update_cmtime(const struct file *file)
 {
 	return !IS_NOCMTIME(file_inode(file)) && !(file->f_mode & FMODE_NOCMTIME);
 }
diff --git a/fs/btrfs/file.h b/fs/btrfs/file.h
index 18ebc8c2d6d2..9e35db3f4466 100644
--- a/fs/btrfs/file.h
+++ b/fs/btrfs/file.h
@@ -43,6 +43,7 @@ int btrfs_fdatawrite_range(struct btrfs_inode *inode, loff_t start, loff_t end);
 int btrfs_check_nocow_lock(struct btrfs_inode *inode, loff_t pos,
 			   size_t *write_bytes, bool nowait);
 void btrfs_check_nocow_unlock(struct btrfs_inode *inode);
+bool btrfs_should_update_cmtime(const struct file *file);
 bool btrfs_find_delalloc_in_range(struct btrfs_inode *inode, u64 start, u64 end,
 				  struct extent_state **cached_state,
 				  u64 *delalloc_start_ret, u64 *delalloc_end_ret);
diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c
index f67350bba9b9..5154db82ef3e 100644
--- a/fs/btrfs/reflink.c
+++ b/fs/btrfs/reflink.c
@@ -801,7 +801,8 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src,
 	 */
 	end = round_up(destoff + len, bs) - 1;
 	btrfs_lock_extent(&inode->io_tree, destoff, end, &cached_state);
-	ret = btrfs_clone(src, inode, off, olen, len, destoff, false);
+	ret = btrfs_clone(src, inode, off, olen, len, destoff,
+			  !btrfs_should_update_cmtime(file));
 	btrfs_unlock_extent(&inode->io_tree, destoff, end, &cached_state);
 	if (ret < 0)
 		return ret;

-- 
2.56.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-05 20:07 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 20:07 [PATCH 0/4] btrfs: fix FMODE_NOCMTIME handling in btrfs Jeff Layton
2026-11-05 20:07 ` [PATCH 1/4] btrfs: honor FMODE_NOCMTIME in btrfs_write_check() Jeff Layton
2026-11-05 20:07 ` [PATCH 2/4] btrfs: honor FMODE_NOCMTIME when punching holes Jeff Layton
2026-11-05 20:07 ` [PATCH 3/4] btrfs: honor FMODE_NOCMTIME in fallocate Jeff Layton
2026-11-05 20:07 ` [PATCH 4/4] btrfs: honor FMODE_NOCMTIME when cloning Jeff Layton

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®