From 5d94aaacb5180798b2f698e33937f068386004eb Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sun, 3 Oct 2021 10:59:04 -0600 Subject: [PATCH] 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 Tested by: Agata , jSML4ThWwBID69YC@protonmail.com MFC after: 2 weeks --- sys/fs/fuse/fuse_internal.c | 74 +++++++++++++++++++++++++------------ sys/fs/fuse/fuse_internal.h | 3 +- sys/fs/fuse/fuse_io.c | 4 +- sys/fs/fuse/fuse_node.c | 19 ++++++++-- sys/fs/fuse/fuse_node.h | 2 +- sys/fs/fuse/fuse_vfsops.c | 40 +------------------- sys/fs/fuse/fuse_vnops.c | 41 ++------------------ 7 files changed, 76 insertions(+), 107 deletions(-) diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index c52f12d3a71..ffa4a40095b 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -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: diff --git a/sys/fs/fuse/fuse_internal.h b/sys/fs/fuse/fuse_internal.h index 20a10d7dfda..e9fa3857227 100644 --- a/sys/fs/fuse/fuse_internal.h +++ b/sys/fs/fuse/fuse_internal.h @@ -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 */ diff --git a/sys/fs/fuse/fuse_io.c b/sys/fs/fuse/fuse_io.c index 47c7fb97e55..637c36424d3 100644 --- a/sys/fs/fuse/fuse_io.c +++ b/sys/fs/fuse/fuse_io.c @@ -571,7 +571,7 @@ fuse_write_directbackend(struct vnode *vp, struct uio *uio, 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 @@ fuse_write_biobackend(struct vnode *vp, struct uio *uio, * 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) { diff --git a/sys/fs/fuse/fuse_node.c b/sys/fs/fuse/fuse_node.c index 6acd1df2936..c296a3b3c33 100644 --- a/sys/fs/fuse/fuse_node.c +++ b/sys/fs/fuse/fuse_node.c @@ -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) diff --git a/sys/fs/fuse/fuse_node.h b/sys/fs/fuse/fuse_node.h index e5fb13d43be..dcf5e404777 100644 --- a/sys/fs/fuse/fuse_node.h +++ b/sys/fs/fuse/fuse_node.h @@ -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); diff --git a/sys/fs/fuse/fuse_vfsops.c b/sys/fs/fuse/fuse_vfsops.c index b188996066d..3d5e168bcde 100644 --- a/sys/fs/fuse/fuse_vfsops.c +++ b/sys/fs/fuse/fuse_vfsops.c @@ -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: diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index 4c8c8c1d446..dd8ff0fcc45 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -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); -- 2.45.0