Skip to content

Commit c5e63bb

Browse files
committed
fs: handle delegated timestamps in setattr_copy_mgtime
JIRA: https://issues.redhat.com/browse/RHEL-121527 An update to the inode ctime typically requires the latest clock value possible. The exception to this rule is when there is a nfsd write delegation and the server is proxying timestamps from the client. When nfsd gets a CB_GETATTR response, update the timestamp value in the inode to the values that the client is tracking. The client doesn't send a ctime value (since that's always determined by the exported filesystem), but it can send a mtime value. In the case where it does, update the ctime to a value commensurate with that instead of the current time. If ATTR_DELEG is set, then use ia_ctime value instead of setting the timestamp to the current time. With the addition of delegated timestamps, the server may receive a request to update only the atime, which doesn't involve a ctime update. Trust the ATTR_CTIME flag in the update and only update the ctime when it's set. Tested-by: Randy Dunlap <rdunlap@infradead.org> # documentation bits Reviewed-by: Jan Kara <jack@suse.cz> Signed-off-by: Jeff Layton <jlayton@kernel.org> Link: https://lore.kernel.org/r/20241002-mgtime-v10-5-d1c4717f5284@kernel.org Signed-off-by: Christian Brauner <brauner@kernel.org> (cherry picked from commit 7f2c86c) Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
1 parent 7d6c8b6 commit c5e63bb

File tree

3 files changed

+94
-10
lines changed

3 files changed

+94
-10
lines changed

fs/attr.c

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -286,16 +286,21 @@ static void setattr_copy_mgtime(struct inode *inode, const struct iattr *attr)
286286
unsigned int ia_valid = attr->ia_valid;
287287
struct timespec64 now;
288288

289-
/*
290-
* If the ctime isn't being updated then nothing else should be
291-
* either.
292-
*/
293-
if (!(ia_valid & ATTR_CTIME)) {
294-
WARN_ON_ONCE(ia_valid & (ATTR_ATIME|ATTR_MTIME));
295-
return;
289+
if (ia_valid & ATTR_CTIME) {
290+
/*
291+
* In the case of an update for a write delegation, we must respect
292+
* the value in ia_ctime and not use the current time.
293+
*/
294+
if (ia_valid & ATTR_DELEG)
295+
now = inode_set_ctime_deleg(inode, attr->ia_ctime);
296+
else
297+
now = inode_set_ctime_current(inode);
298+
} else {
299+
/* If ATTR_CTIME isn't set, then ATTR_MTIME shouldn't be either. */
300+
WARN_ON_ONCE(ia_valid & ATTR_MTIME);
301+
now = current_time(inode);
296302
}
297303

298-
now = inode_set_ctime_current(inode);
299304
if (ia_valid & ATTR_ATIME_SET)
300305
inode_set_atime_to_ts(inode, attr->ia_atime);
301306
else if (ia_valid & ATTR_ATIME)
@@ -354,8 +359,12 @@ void setattr_copy(struct mnt_idmap *idmap, struct inode *inode,
354359
inode_set_atime_to_ts(inode, attr->ia_atime);
355360
if (ia_valid & ATTR_MTIME)
356361
inode_set_mtime_to_ts(inode, attr->ia_mtime);
357-
if (ia_valid & ATTR_CTIME)
358-
inode_set_ctime_to_ts(inode, attr->ia_ctime);
362+
if (ia_valid & ATTR_CTIME) {
363+
if (ia_valid & ATTR_DELEG)
364+
inode_set_ctime_deleg(inode, attr->ia_ctime);
365+
else
366+
inode_set_ctime_to_ts(inode, attr->ia_ctime);
367+
}
359368
}
360369
EXPORT_SYMBOL(setattr_copy);
361370

fs/inode.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,6 +2754,79 @@ struct timespec64 inode_set_ctime_current(struct inode *inode)
27542754
}
27552755
EXPORT_SYMBOL(inode_set_ctime_current);
27562756

2757+
/**
2758+
* inode_set_ctime_deleg - try to update the ctime on a delegated inode
2759+
* @inode: inode to update
2760+
* @update: timespec64 to set the ctime
2761+
*
2762+
* Attempt to atomically update the ctime on behalf of a delegation holder.
2763+
*
2764+
* The nfs server can call back the holder of a delegation to get updated
2765+
* inode attributes, including the mtime. When updating the mtime, update
2766+
* the ctime to a value at least equal to that.
2767+
*
2768+
* This can race with concurrent updates to the inode, in which
2769+
* case the update is skipped.
2770+
*
2771+
* Note that this works even when multigrain timestamps are not enabled,
2772+
* so it is used in either case.
2773+
*/
2774+
struct timespec64 inode_set_ctime_deleg(struct inode *inode, struct timespec64 update)
2775+
{
2776+
struct timespec64 now, cur_ts;
2777+
u32 cur, old;
2778+
2779+
/* pairs with try_cmpxchg below */
2780+
cur = smp_load_acquire(&inode->i_ctime_nsec);
2781+
cur_ts.tv_nsec = cur & ~I_CTIME_QUERIED;
2782+
cur_ts.tv_sec = inode->i_ctime_sec;
2783+
2784+
/* If the update is older than the existing value, skip it. */
2785+
if (timespec64_compare(&update, &cur_ts) <= 0)
2786+
return cur_ts;
2787+
2788+
ktime_get_coarse_real_ts64_mg(&now);
2789+
2790+
/* Clamp the update to "now" if it's in the future */
2791+
if (timespec64_compare(&update, &now) > 0)
2792+
update = now;
2793+
2794+
update = timestamp_truncate(update, inode);
2795+
2796+
/* No need to update if the values are already the same */
2797+
if (timespec64_equal(&update, &cur_ts))
2798+
return cur_ts;
2799+
2800+
/*
2801+
* Try to swap the nsec value into place. If it fails, that means
2802+
* it raced with an update due to a write or similar activity. That
2803+
* stamp takes precedence, so just skip the update.
2804+
*/
2805+
retry:
2806+
old = cur;
2807+
if (try_cmpxchg(&inode->i_ctime_nsec, &cur, update.tv_nsec)) {
2808+
inode->i_ctime_sec = update.tv_sec;
2809+
mgtime_counter_inc(mg_ctime_swaps);
2810+
return update;
2811+
}
2812+
2813+
/*
2814+
* Was the change due to another task marking the old ctime QUERIED?
2815+
*
2816+
* If so, then retry the swap. This can only happen once since
2817+
* the only way to clear I_CTIME_QUERIED is to stamp the inode
2818+
* with a new ctime.
2819+
*/
2820+
if (!(old & I_CTIME_QUERIED) && (cur == (old | I_CTIME_QUERIED)))
2821+
goto retry;
2822+
2823+
/* Otherwise, it was a new timestamp. */
2824+
cur_ts.tv_sec = inode->i_ctime_sec;
2825+
cur_ts.tv_nsec = cur & ~I_CTIME_QUERIED;
2826+
return cur_ts;
2827+
}
2828+
EXPORT_SYMBOL(inode_set_ctime_deleg);
2829+
27572830
/**
27582831
* in_group_or_capable - check whether caller is CAP_FSETID privileged
27592832
* @idmap: idmap of the mount @inode was found from

include/linux/fs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,8 @@ static inline bool fsuidgid_has_mapping(struct super_block *sb,
15861586

15871587
struct timespec64 current_time(struct inode *inode);
15881588
struct timespec64 inode_set_ctime_current(struct inode *inode);
1589+
struct timespec64 inode_set_ctime_deleg(struct inode *inode,
1590+
struct timespec64 update);
15891591

15901592
static inline time64_t inode_get_atime_sec(const struct inode *inode)
15911593
{

0 commit comments

Comments
 (0)