diff options
author | Alan Somers <asomers@FreeBSD.org> | 2021-12-06 05:43:17 +0000 |
---|---|---|
committer | Alan Somers <asomers@FreeBSD.org> | 2022-01-03 02:36:38 +0000 |
commit | 139764c4613cde14c97ff8dd8007eb0f27f5fb9f (patch) | |
tree | 1f4f5b55b35af2770d02894678060b89fee1c30f /sys/fs/fuse | |
parent | 2472f847281a8827acfdab88141b63228381ab6e (diff) | |
download | src-139764c4613cde14c97ff8dd8007eb0f27f5fb9f.tar.gz src-139764c4613cde14c97ff8dd8007eb0f27f5fb9f.zip |
fusefs: correctly handle an inode that changes file types
Correctly handle the situation where a FUSE server unlinks a file, then
creates a new file of a different type but with the same inode number.
Previously fuse_vnop_lookup in this situation would return EAGAIN. But
since it didn't call vgone(), the vnode couldn't be reused right away.
Fix this by immediately calling vgone() and reallocating a new vnode.
This problem can occur in three code paths, during VOP_LOOKUP,
VOP_SETATTR, or following FUSE_GETATTR, which usually happens during
VOP_GETATTR but can occur during other vops, too. Note that the correct
response actually doesn't depend on whether the entry cache has expired.
In fact, during VOP_LOOKUP, we can't even tell. Either it has expired
already, or else the vnode got reclaimed by vnlru.
Also, correct the error code during the VOP_SETATTR path.
PR: 258022
Reported by: chogata@moosefs.pro
Reviewed by: pfg
Differential Revision: https://reviews.freebsd.org/D33283
(cherry picked from commit 25927e068fcbcac0a5111a881de723bd984b04b3)
Diffstat (limited to 'sys/fs/fuse')
-rw-r--r-- | sys/fs/fuse/fuse_internal.c | 9 | ||||
-rw-r--r-- | sys/fs/fuse/fuse_node.c | 25 |
2 files changed, 20 insertions, 14 deletions
diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index 403116593bd9..b9df64921fc8 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -1256,12 +1256,15 @@ int fuse_internal_setattr(struct vnode *vp, struct vattr *vap, * STALE vnode, ditch * * The vnode has changed its type "behind our back". + * This probably means that the file got deleted and + * recreated on the server, with the same inode. * There's nothing really we can do, so let us just - * force an internal revocation and tell the caller to - * try again, if interested. + * return ENOENT. After all, the entry must not have + * existed in the recent past. If the user tries + * again, it will work. */ fuse_internal_vnode_disappear(vp); - err = EAGAIN; + err = ENOENT; } } if (err == 0) { diff --git a/sys/fs/fuse/fuse_node.c b/sys/fs/fuse/fuse_node.c index f74de2faa702..b5f46daf025d 100644 --- a/sys/fs/fuse/fuse_node.c +++ b/sys/fs/fuse/fuse_node.c @@ -212,24 +212,27 @@ fuse_vnode_alloc(struct mount *mp, return (err); if (*vpp) { - if ((*vpp)->v_type != vtyp) { + if ((*vpp)->v_type == vtyp) { + /* Reuse a vnode that hasn't yet been reclaimed */ + MPASS((*vpp)->v_data != NULL); + MPASS(VTOFUD(*vpp)->nid == nodeid); + SDT_PROBE2(fusefs, , node, trace, 1, + "vnode taken from hash"); + return (0); + } else { /* - * STALE vnode! This probably indicates a buggy - * server, but it could also be the result of a race - * between FUSE_LOOKUP and another client's - * FUSE_UNLINK/FUSE_CREATE + * The inode changed types! If we get here, we can't + * tell whether the inode's entry cache had expired + * yet. So this could be the result of a buggy server, + * but more likely the server just reused an inode + * number following an entry cache expiration. */ SDT_PROBE3(fusefs, , node, stale_vnode, *vpp, vtyp, nodeid); fuse_internal_vnode_disappear(*vpp); + vgone(*vpp); lockmgr((*vpp)->v_vnlock, LK_RELEASE, NULL); - *vpp = NULL; - return (EAGAIN); } - MPASS((*vpp)->v_data != NULL); - MPASS(VTOFUD(*vpp)->nid == nodeid); - SDT_PROBE2(fusefs, , node, trace, 1, "vnode taken from hash"); - return (0); } fvdat = malloc(sizeof(*fvdat), M_FUSEVN, M_WAITOK | M_ZERO); switch (vtyp) { |