mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jeff Layton <jlayton@kernel.org>
To: David Sterba <dsterba@suse.com>, Chris Mason <mason@kernel.org>,
	 Chuck Lever <cel@kernel.org>
Cc: linux-btrfs@vger.kernel.org, linux-kernel@vger.kernel.org,
	 Jeff Layton <jlayton@kernel.org>
Subject: [PATCH 2/4] btrfs: honor FMODE_NOCMTIME when punching holes
Date: Thu, 05 Nov 2026 15:07:43 -0500	[thread overview]
Message-ID: <20261105-delegts-btrfs-v1-2-a42a2b93cdc2@kernel.org> (raw)
In-Reply-To: <20261105-delegts-btrfs-v1-0-a42a2b93cdc2@kernel.org>

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


  parent reply	other threads:[~2026-11-05 20:07 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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

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=20261105-delegts-btrfs-v1-2-a42a2b93cdc2@kernel.org \
    --to=jlayton@kernel.org \
    --cc=cel@kernel.org \
    --cc=dsterba@suse.com \
    --cc=linux-btrfs@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mason@kernel.org \
    /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®