Skip to content
Merged
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
14 changes: 14 additions & 0 deletions fs/fuse/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -2403,7 +2403,11 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
}

if (is_truncate) {
/* Drop read replies that may predate the truncate. */
spin_lock(&fi->lock);
fuse_bump_size_gen(fi);
set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
spin_unlock(&fi->lock);
if (trust_local_cmtime && attr->ia_size != inode->i_size)
attr->ia_valid |= ATTR_MTIME | ATTR_CTIME;
}
Expand Down Expand Up @@ -2500,6 +2504,11 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
fuse_range_lock_release(fi, &rlock);
}

if (is_truncate) {
spin_lock(&fi->lock);
fuse_bump_size_gen(fi);
spin_unlock(&fi->lock);
}
clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
out:
if (fault_blocked)
Expand All @@ -2508,6 +2517,11 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
return 0;

error:
if (is_truncate) {
spin_lock(&fi->lock);
fuse_bump_size_gen(fi);
spin_unlock(&fi->lock);
}
clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);

if (fault_blocked)
Expand Down
41 changes: 41 additions & 0 deletions fs/fuse/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,36 @@ static void fuse_short_read(struct inode *inode, u64 attr_ver, size_t num_read,
}
}

/*
* Under DLM with the writeback cache, i_size covers only this node's own
* writes. A read reply proves the file extends at least to the bytes it
* returned (the server clips reads at its size), and they may be another
* node's data in a page this node shares. Raise i_size over them, so that
* iomap_writeback_handle_eof() never zeroes them as if they were past EOF.
*
* Every revoke bumps attr_version, including the revoke of the very range
* being read, so attr_version cannot gate this. size_gen moves only for a
* local truncate or a whole-file invalidate, which is how a remote truncate
* reaches us; a reply that may predate either is dropped.
*/
static void fuse_dlm_read_extend_size(struct inode *inode, loff_t end,
u64 size_gen)
{
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_inode *fi = get_fuse_inode(inode);

if (!fc->dlm || !fc->writeback_cache)
return;

spin_lock(&fi->lock);
if (fi->size_gen == size_gen && end > inode->i_size &&
!test_bit(FUSE_I_SIZE_UNSTABLE, &fi->state)) {
fi->attr_version = atomic64_inc_return(&fc->attr_version);
i_size_write(inode, end);
}
spin_unlock(&fi->lock);
}

static int fuse_do_readfolio(struct file *file, struct folio *folio,
size_t off, size_t len)
{
Expand All @@ -1028,8 +1058,10 @@ static int fuse_do_readfolio(struct file *file, struct folio *folio,
};
ssize_t res;
u64 attr_ver;
u64 size_gen;

attr_ver = fuse_get_attr_version(fm->fc);
size_gen = READ_ONCE(get_fuse_inode(inode)->size_gen);

/* Don't overflow end offset */
if (pos + (desc.length - 1) == LLONG_MAX)
Expand Down Expand Up @@ -1057,6 +1089,9 @@ static int fuse_do_readfolio(struct file *file, struct folio *folio,
if (res < desc.length)
fuse_short_read(inode, attr_ver, res, &ia.ap);

if (res > 0)
fuse_dlm_read_extend_size(inode, pos + res, size_gen);

return 0;
}

Expand Down Expand Up @@ -1148,6 +1183,11 @@ static void fuse_readpages_end(struct fuse_mount *fm, struct fuse_args *args,
if (!err && num_read < count)
fuse_short_read(inode, ia->read.attr_ver, num_read, ap);

if (!err && num_read)
fuse_dlm_read_extend_size(inode,
ia->read.in.offset + num_read,
ia->read.size_gen);

fuse_invalidate_atime(inode);
}

Expand Down Expand Up @@ -1185,6 +1225,7 @@ static void fuse_send_readpages(struct fuse_io_args *ia, struct file *file,

fuse_read_args_fill(ia, file, pos, count, FUSE_READ);
ia->read.attr_ver = fuse_get_attr_version(fm->fc);
ia->read.size_gen = READ_ONCE(get_fuse_inode(file_inode(file))->size_gen);
if (fm->fc->async_read) {
ia->ff = fuse_file_get(ff);
ap->args.end = fuse_readpages_end;
Expand Down
14 changes: 14 additions & 0 deletions fs/fuse/fuse_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,13 @@ struct fuse_inode {
/** Version of last attribute change */
u64 attr_version;

/*
* Bumped under fi->lock whenever the file may have shrunk behind a
* read in flight: a local truncate, or a whole-file invalidate (the
* shape a remote truncate takes). See fuse_dlm_read_extend_size().
*/
u64 size_gen;

union {
/* read/write io cache (regular file only) */
struct {
Expand Down Expand Up @@ -1248,6 +1255,12 @@ static inline u64 fuse_get_attr_version(struct fuse_conn *fc)
return atomic64_read(&fc->attr_version);
}

static inline void fuse_bump_size_gen(struct fuse_inode *fi)
{
lockdep_assert_held(&fi->lock);
WRITE_ONCE(fi->size_gen, fi->size_gen + 1);
}

static inline u64 fuse_get_evict_ctr(struct fuse_conn *fc)
{
return atomic64_read(&fc->evict_ctr);
Expand Down Expand Up @@ -1333,6 +1346,7 @@ struct fuse_io_args {
struct {
struct fuse_read_in in;
u64 attr_ver;
u64 size_gen;
} read;
struct {
struct fuse_write_in in;
Expand Down
6 changes: 6 additions & 0 deletions fs/fuse/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,12 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid,
fi = get_fuse_inode(inode);
spin_lock(&fi->lock);
fi->attr_version = atomic64_inc_return(&fc->attr_version);
/*
* A remote truncate reaches us as a whole-file invalidate; read
* replies in flight may predate it (see fuse_dlm_read_extend_size()).
*/
if (offset <= 0 && len <= 0)
fuse_bump_size_gen(fi);
spin_unlock(&fi->lock);

if (fc->inval_inode_entries)
Expand Down