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
next prev 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®