Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 84 additions & 8 deletions fs/fuse/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ static void fuse_dir_changed(struct inode *dir)
*/
void fuse_invalidate_atime(struct inode *inode)
{
/* don't invalidate atime if it's updated by kernel locally */
if (!(inode->i_flags & S_NOATIME))
return;

if (!IS_RDONLY(inode))
fuse_invalidate_attr_mask(inode, STATX_ATIME);
}
Expand Down Expand Up @@ -1011,6 +1015,9 @@ static void fuse_update_ctime_in_cache(struct inode *inode)
{
if (!IS_NOCMTIME(inode)) {
inode_set_ctime_current(inode);
/* see comment in fuse_update_time() */
smp_mb__before_atomic();
set_bit(FUSE_I_CMTIME_DIRTY, &get_fuse_inode(inode)->state);
mark_inode_dirty_sync(inode);
fuse_flush_time_update(inode);
}
Expand Down Expand Up @@ -1947,26 +1954,88 @@ static void fuse_setattr_fill(struct fuse_conn *fc, struct fuse_args *args,
}

/*
* Flush inode->i_mtime to the server
* Record which timestamps the VFS updated locally, so that
* fuse_flush_times() only sends the ones that are actually dirty.
*/
static int fuse_update_time(struct inode *inode, int flags)
{
struct fuse_inode *fi = get_fuse_inode(inode);
int updated;
int dirty_flags = 0;

updated = inode_update_timestamps(inode, flags);
if (updated & (S_ATIME|S_MTIME|S_CTIME)) {
/*
* Pair with smp_mb__after_atomic() in redfs_flush_times().
* Ensure timestamps are visible before we set the dirty bits.
*/
smp_mb__before_atomic();

if (updated & S_ATIME)
set_bit(FUSE_I_ATIME_DIRTY, &fi->state);
if (updated & (S_CTIME | S_MTIME))
set_bit(FUSE_I_CMTIME_DIRTY, &fi->state);

dirty_flags = inode->i_sb->s_flags & SB_LAZYTIME ? I_DIRTY_TIME : I_DIRTY_SYNC;
}

__mark_inode_dirty(inode, dirty_flags);

return updated;
}

/*
* Flush locally maintained timestamps to the server
*/
int fuse_flush_times(struct inode *inode, struct fuse_file *ff)
{
struct fuse_mount *fm = get_fuse_mount(inode);
struct fuse_inode *fi = get_fuse_inode(inode);
FUSE_ARGS(args);
struct fuse_setattr_in inarg;
struct fuse_attr_out outarg;

memset(&inarg, 0, sizeof(inarg));
memset(&outarg, 0, sizeof(outarg));

inarg.valid = FATTR_MTIME;
inarg.mtime = inode_get_mtime_sec(inode);
inarg.mtimensec = inode_get_mtime_nsec(inode);
if (fm->fc->minor >= 23) {
inarg.valid |= FATTR_CTIME;
inarg.ctime = inode_get_ctime_sec(inode);
inarg.ctimensec = inode_get_ctime_nsec(inode);
/*
* Only flush the timestamps that were dirtied locally. The other
* cached values originate from the server and pushing them back
* could overwrite newer server state.
*/
if (test_and_clear_bit(FUSE_I_CMTIME_DIRTY, &fi->state)) {
/*
* Pair with smp_mb__before_atomic() in redfs_update_time().
* If we clear the dirty bit, we must see the matching or
* the subsequent ctime/mtime.
*/
smp_mb__after_atomic();

inarg.valid |= FATTR_MTIME;
inarg.mtime = inode_get_mtime_sec(inode);
inarg.mtimensec = inode_get_mtime_nsec(inode);
if (fm->fc->minor >= 23) {
inarg.valid |= FATTR_CTIME;
inarg.ctime = inode_get_ctime_sec(inode);
inarg.ctimensec = inode_get_ctime_nsec(inode);
}
}
if (test_and_clear_bit(FUSE_I_ATIME_DIRTY, &fi->state)) {
smp_mb__after_atomic();

/*
* The server, not the client, is the authority of timestamps.
* Ask the server to update atime according the dirty value.
* FATTR_ATIME_NOW tells the server that the update is not
* from utimes(2)
*/
inarg.valid |= FATTR_ATIME | FATTR_ATIME_NOW;
inarg.atime = inode_get_atime_sec(inode);
inarg.atimensec = inode_get_atime_nsec(inode);
}
if (!inarg.valid)
return 0;

if (ff) {
inarg.valid |= FATTR_FH;
inarg.fh = ff->fh;
Expand Down Expand Up @@ -2106,6 +2175,10 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
}

spin_lock(&fi->lock);
/* explicitly set atime, discard the local dirty value */
if ((attr->ia_valid & ATTR_ATIME) && (attr->ia_valid & ATTR_ATIME_SET))
clear_bit(FUSE_I_ATIME_DIRTY, &fi->state);

/* the kernel maintains i_mtime locally */
if (trust_local_cmtime) {
if (attr->ia_valid & ATTR_MTIME)
Expand Down Expand Up @@ -2272,6 +2345,7 @@ static const struct inode_operations fuse_dir_inode_operations = {
.mknod = fuse_mknod,
.permission = fuse_permission,
.getattr = fuse_getattr,
.update_time = fuse_update_time,
.listxattr = fuse_listxattr,
.get_inode_acl = fuse_get_inode_acl,
.get_acl = fuse_get_acl,
Expand All @@ -2295,6 +2369,7 @@ static const struct inode_operations fuse_common_inode_operations = {
.setattr = fuse_setattr,
.permission = fuse_permission,
.getattr = fuse_getattr,
.update_time = fuse_update_time,
.listxattr = fuse_listxattr,
.get_inode_acl = fuse_get_inode_acl,
.get_acl = fuse_get_acl,
Expand All @@ -2307,6 +2382,7 @@ static const struct inode_operations fuse_symlink_inode_operations = {
.setattr = fuse_setattr,
.get_link = fuse_get_link,
.getattr = fuse_getattr,
.update_time = fuse_update_time,
.listxattr = fuse_listxattr,
};

Expand Down
5 changes: 3 additions & 2 deletions fs/fuse/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -1960,7 +1960,7 @@ static struct fuse_file *fuse_write_file_get(struct fuse_inode *fi)
int fuse_write_inode(struct inode *inode, struct writeback_control *wbc)
{
struct fuse_inode *fi = get_fuse_inode(inode);
struct fuse_file *ff;
struct fuse_file *ff = NULL;
int err;

/*
Expand All @@ -1974,7 +1974,8 @@ int fuse_write_inode(struct inode *inode, struct writeback_control *wbc)
*/
WARN_ON(wbc->for_reclaim);

ff = __fuse_write_file_get(fi);
if (S_ISREG(inode->i_mode))
ff = __fuse_write_file_get(fi);
err = fuse_flush_times(inode, ff);
if (ff)
fuse_file_put(ff, false);
Expand Down
4 changes: 4 additions & 0 deletions fs/fuse/fuse_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ enum {
FUSE_I_BTIME,
/* Wants or already has page cache IO */
FUSE_I_CACHE_IO_MODE,
/* Locally updated atime needs to be flushed to the server */
FUSE_I_ATIME_DIRTY,
/* Locally updated mtime/ctime need to be flushed to the server */
FUSE_I_CMTIME_DIRTY,
};

struct fuse_conn;
Expand Down
17 changes: 16 additions & 1 deletion fs/fuse/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,19 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
attr->mtimensec = min_t(u32, attr->mtimensec, NSEC_PER_SEC - 1);
attr->ctimensec = min_t(u32, attr->ctimensec, NSEC_PER_SEC - 1);

if (test_bit(FUSE_I_ATIME_DIRTY, &fi->state)) {
/*
* The kernel updated atime, but another client may have
* advanced it on the server. Keep whichever is newer.
*/
struct timespec64 atime = inode_get_atime(inode);
if ((u64) atime.tv_sec > attr->atime ||
((u64) atime.tv_sec == attr->atime &&
(u32) atime.tv_nsec > attr->atimensec)) {
attr->atime = atime.tv_sec;
attr->atimensec = atime.tv_nsec;
}
}
inode_set_atime(inode, attr->atime, attr->atimensec);
/* mtime from server may be stale due to local buffered write */
if (!(cache_mask & STATX_MTIME)) {
Expand Down Expand Up @@ -571,6 +584,7 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr,
{
inode->i_mode = attr->mode & S_IFMT;
inode->i_size = attr->size;
inode_set_atime(inode, attr->atime, attr->atimensec);
inode_set_mtime(inode, attr->mtime, attr->mtimensec);
inode_set_ctime(inode, attr->ctime, attr->ctimensec);
if (S_ISREG(inode->i_mode)) {
Expand Down Expand Up @@ -655,7 +669,8 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
return NULL;

if ((inode->i_state & I_NEW)) {
inode->i_flags |= S_NOATIME;
if (sb->s_flags & SB_NOATIME)
inode->i_flags |= S_NOATIME;
if (!fc->writeback_cache || !S_ISREG(attr->mode))
inode->i_flags |= S_NOCMTIME;
inode->i_generation = generation;
Expand Down