fusefs: quiet some cache-related warnings

If the FUSE server does something that would make our cache incoherent,
we should print a warning to the user.  However, we previously warned in
some situations when we shouldn't, such as if the file's size changed on
the server _after_ our own attribute cache had expired.  This change
suppresses the warning in cases like that.  It also moves the warning
logic to a single place within the code.

PR:		256936
Reported by:	Agata <chogata@moosefs.pro>
Tested by:	Agata <chogata@moosefs.pro>, jSML4ThWwBID69YC@protonmail.com
MFC after:	2 weeks
This commit is contained in:
Alan Somers 2021-10-03 10:59:04 -06:00
parent a76de17715
commit 5d94aaacb5
7 changed files with 76 additions and 107 deletions

View File

@ -255,7 +255,8 @@ fuse_internal_access(struct vnode *vp,
*/
void
fuse_internal_cache_attrs(struct vnode *vp, struct fuse_attr *attr,
uint64_t attr_valid, uint32_t attr_valid_nsec, struct vattr *vap)
uint64_t attr_valid, uint32_t attr_valid_nsec, struct vattr *vap,
bool from_server)
{
struct mount *mp;
struct fuse_vnode_data *fvdat;
@ -271,9 +272,54 @@ fuse_internal_cache_attrs(struct vnode *vp, struct fuse_attr *attr,
fuse_validity_2_bintime(attr_valid, attr_valid_nsec,
&fvdat->attr_cache_timeout);
if (vnode_isreg(vp) &&
fvdat->cached_attrs.va_size != VNOVAL &&
attr->size != fvdat->cached_attrs.va_size)
{
if ( data->cache_mode == FUSE_CACHE_WB &&
fvdat->flag & FN_SIZECHANGE)
{
const char *msg;
/*
* The server changed the file's size even though we're
* using writeback cacheing and and we have outstanding
* dirty writes! That's a server bug.
*/
if (fuse_libabi_geq(data, 7, 23)) {
msg = "writeback cache incoherent!."
"To prevent data corruption, disable "
"the writeback cache according to your "
"FUSE server's documentation.";
} else {
msg = "writeback cache incoherent!."
"To prevent data corruption, disable "
"the writeback cache by setting "
"vfs.fusefs.data_cache_mode to 0 or 1.";
}
fuse_warn(data, FSESS_WARN_WB_CACHE_INCOHERENT, msg);
}
if (fuse_vnode_attr_cache_valid(vp) &&
data->cache_mode != FUSE_CACHE_UC)
{
/*
* The server changed the file's size even though we
* have it cached and our cache has not yet expired.
* That's a bug.
*/
fuse_warn(data, FSESS_WARN_CACHE_INCOHERENT,
"cache incoherent! "
"To prevent "
"data corruption, disable the data cache "
"by mounting with -o direct_io, or as "
"directed otherwise by your FUSE server's "
"documentation.");
}
}
/* Fix our buffers if the filesize changed without us knowing */
if (vnode_isreg(vp) && attr->size != fvdat->cached_attrs.va_size) {
(void)fuse_vnode_setsize(vp, attr->size);
(void)fuse_vnode_setsize(vp, attr->size, from_server);
fvdat->cached_attrs.va_size = attr->size;
}
@ -806,7 +852,7 @@ fuse_internal_newentry_core(struct vnode *dvp,
fuse_vnode_clear_attr_cache(dvp);
fuse_internal_cache_attrs(*vpp, &feo->attr, feo->attr_valid,
feo->attr_valid_nsec, NULL);
feo->attr_valid_nsec, NULL, true);
return err;
}
@ -912,26 +958,8 @@ fuse_internal_do_getattr(struct vnode *vp, struct vattr *vap,
fao->attr.mtime = old_mtime.tv_sec;
fao->attr.mtimensec = old_mtime.tv_nsec;
}
if (vnode_isreg(vp) &&
fvdat->cached_attrs.va_size != VNOVAL &&
fao->attr.size != fvdat->cached_attrs.va_size) {
/*
* The server changed the file's size even though we had it
* cached! That's a server bug.
*/
struct mount *mp = vnode_mount(vp);
struct fuse_data *data = fuse_get_mpdata(mp);
fuse_warn(data, FSESS_WARN_CACHE_INCOHERENT,
"cache incoherent! "
"To prevent data corruption, disable the data cache "
"by mounting with -o direct_io, or as directed "
"otherwise by your FUSE server's documentation.");
int iosize = fuse_iosize(vp);
v_inval_buf_range(vp, 0, INT64_MAX, iosize);
}
fuse_internal_cache_attrs(vp, &fao->attr, fao->attr_valid,
fao->attr_valid_nsec, vap);
fao->attr_valid_nsec, vap, true);
if (vtyp != vnode_vtype(vp)) {
fuse_internal_vnode_disappear(vp);
err = ENOENT;
@ -1231,7 +1259,7 @@ int fuse_internal_setattr(struct vnode *vp, struct vattr *vap,
struct fuse_attr_out *fao = (struct fuse_attr_out*)fdi.answ;
fuse_vnode_undirty_cached_timestamps(vp);
fuse_internal_cache_attrs(vp, &fao->attr, fao->attr_valid,
fao->attr_valid_nsec, NULL);
fao->attr_valid_nsec, NULL, false);
}
out:

View File

@ -223,7 +223,8 @@ int fuse_internal_access(struct vnode *vp, accmode_t mode,
/* attributes */
void fuse_internal_cache_attrs(struct vnode *vp, struct fuse_attr *attr,
uint64_t attr_valid, uint32_t attr_valid_nsec, struct vattr *vap);
uint64_t attr_valid, uint32_t attr_valid_nsec, struct vattr *vap,
bool from_server);
/* fsync */

View File

@ -571,7 +571,7 @@ retry:
as_written_offset = uio->uio_offset - diff;
if (as_written_offset - diff > filesize)
fuse_vnode_setsize(vp, as_written_offset);
fuse_vnode_setsize(vp, as_written_offset, false);
if (as_written_offset - diff >= filesize)
fvdat->flag &= ~FN_SIZECHANGE;
@ -715,7 +715,7 @@ again:
* Extend file _after_ locking buffer so we won't race
* with other readers
*/
err = fuse_vnode_setsize(vp, uio->uio_offset + n);
err = fuse_vnode_setsize(vp, uio->uio_offset + n, false);
filesize = uio->uio_offset + n;
fvdat->flag |= FN_SIZECHANGE;
if (err) {

View File

@ -389,11 +389,14 @@ fuse_vnode_savesize(struct vnode *vp, struct ucred *cred, pid_t pid)
}
/*
* Adjust the vnode's size to a new value, such as that provided by
* FUSE_GETATTR.
* Adjust the vnode's size to a new value.
*
* If the new value came from the server, such as from a FUSE_GETATTR
* operation, set `from_server` true. But if it came from a local operation,
* such as write(2) or truncate(2), set `from_server` false.
*/
int
fuse_vnode_setsize(struct vnode *vp, off_t newsize)
fuse_vnode_setsize(struct vnode *vp, off_t newsize, bool from_server)
{
struct fuse_vnode_data *fvdat = VTOFUD(vp);
struct vattr *attrs;
@ -434,6 +437,16 @@ fuse_vnode_setsize(struct vnode *vp, off_t newsize)
MPASS(bp->b_flags & B_VMIO);
vfs_bio_clrbuf(bp);
bp->b_dirtyend = MIN(bp->b_dirtyend, newsize - lbn * iosize);
} else if (from_server && newsize > oldsize && oldsize != VNOVAL) {
/*
* The FUSE server changed the file size behind our back. We
* should invalidate the entire cache.
*/
daddr_t left_lbn, end_lbn;
left_lbn = oldsize / iosize;
end_lbn = howmany(newsize, iosize);
v_inval_buf_range(vp, 0, end_lbn, iosize);
}
out:
if (bp)

View File

@ -201,7 +201,7 @@ void fuse_vnode_open(struct vnode *vp, int32_t fuse_open_flags,
int fuse_vnode_savesize(struct vnode *vp, struct ucred *cred, pid_t pid);
int fuse_vnode_setsize(struct vnode *vp, off_t newsize);
int fuse_vnode_setsize(struct vnode *vp, off_t newsize, bool from_server);
void fuse_vnode_undirty_cached_timestamps(struct vnode *vp);

View File

@ -539,7 +539,6 @@ fuse_vfsop_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp)
struct fuse_entry_out *feo;
struct fuse_vnode_data *fvdat;
const char dot[] = ".";
off_t filesize;
enum vtype vtyp;
int error;
@ -576,47 +575,10 @@ fuse_vfsop_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp)
error = fuse_vnode_get(mp, feo, nodeid, NULL, vpp, NULL, vtyp);
if (error)
goto out;
filesize = feo->attr.size;
/*
* In the case where we are looking up a FUSE node represented by an
* existing cached vnode, and the true size reported by FUSE_LOOKUP
* doesn't match the vnode's cached size, then any cached writes beyond
* the file's current size are lost.
*
* We can get here:
* * following attribute cache expiration, or
* * due a bug in the daemon, or
*/
fvdat = VTOFUD(*vpp);
if (vnode_isreg(*vpp) &&
filesize != fvdat->cached_attrs.va_size &&
fvdat->flag & FN_SIZECHANGE) {
if (data->cache_mode == fuse_data_cache_mode) {
const char *msg;
if (fuse_libabi_geq(data, 7, 23)) {
msg = "writeback cache incoherent!."
"To prevent data corruption, disable "
"the writeback cache according to your "
"FUSE server's documentation.";
} else {
msg = "writeback cache incoherent!."
"To prevent data corruption, disable "
"the writeback cache by setting "
"vfs.fusefs.data_cache_mode to 0 or 1.";
}
fuse_warn(data, FSESS_WARN_WB_CACHE_INCOHERENT, msg);
} else {
/* If we get here, it's likely a fusefs kernel bug */
printf("%s: WB cache incoherent on %s!\n", __func__,
vnode_mount(*vpp)->mnt_stat.f_mntonname);
}
fvdat->flag &= ~FN_SIZECHANGE;
}
fuse_internal_cache_attrs(*vpp, &feo->attr, feo->attr_valid,
feo->attr_valid_nsec, NULL);
feo->attr_valid_nsec, NULL, true);
fuse_validity_2_bintime(feo->entry_valid, feo->entry_valid_nsec,
&fvdat->entry_cache_timeout);
out:

View File

@ -895,7 +895,7 @@ fuse_vnop_create(struct vop_create_args *ap)
}
ASSERT_VOP_ELOCKED(*vpp, "fuse_vnop_create");
fuse_internal_cache_attrs(*vpp, &feo->attr, feo->attr_valid,
feo->attr_valid_nsec, NULL);
feo->attr_valid_nsec, NULL, true);
fuse_filehandle_init(*vpp, FUFH_RDWR, NULL, td, cred, foo);
fuse_vnode_open(*vpp, foo->open_flags, td);
@ -1151,7 +1151,7 @@ fuse_vnop_link(struct vop_link_args *ap)
*/
fuse_vnode_clear_attr_cache(tdvp);
fuse_internal_cache_attrs(vp, &feo->attr, feo->attr_valid,
feo->attr_valid_nsec, NULL);
feo->attr_valid_nsec, NULL, true);
}
out:
fdisp_destroy(&fdi);
@ -1360,52 +1360,17 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
*vpp = dvp;
} else {
struct fuse_vnode_data *fvdat;
struct vattr *vap;
err = fuse_vnode_get(vnode_mount(dvp), feo, nid, dvp,
&vp, cnp, vtyp);
if (err)
goto out;
*vpp = vp;
/*
* In the case where we are looking up a FUSE node
* represented by an existing cached vnode, and the
* true size reported by FUSE_LOOKUP doesn't match
* the vnode's cached size, then any cached writes
* beyond the file's current size are lost.
*
* We can get here:
* * following attribute cache expiration, or
* * due a bug in the daemon, or
*/
fvdat = VTOFUD(vp);
if (vnode_isreg(vp) &&
((filesize != fvdat->cached_attrs.va_size &&
fvdat->flag & FN_SIZECHANGE) ||
((vap = VTOVA(vp)) &&
filesize != vap->va_size)))
{
fvdat->flag &= ~FN_SIZECHANGE;
/*
* The server changed the file's size even
* though we had it cached, or had dirty writes
* in the WB cache!
*/
fuse_warn(data, FSESS_WARN_CACHE_INCOHERENT,
"cache incoherent! "
"To prevent "
"data corruption, disable the data cache "
"by mounting with -o direct_io, or as "
"directed otherwise by your FUSE server's "
"documentation.");
int iosize = fuse_iosize(vp);
v_inval_buf_range(vp, 0, INT64_MAX, iosize);
}
MPASS(feo != NULL);
fuse_internal_cache_attrs(*vpp, &feo->attr,
feo->attr_valid, feo->attr_valid_nsec, NULL);
feo->attr_valid, feo->attr_valid_nsec, NULL, true);
fuse_validity_2_bintime(feo->entry_valid,
feo->entry_valid_nsec,
&fvdat->entry_cache_timeout);