On Tue, 25 Mar 2025 at 12:34:26 +0000, Simon McVittie wrote:
On Tue, 25 Mar 2025 at 14:17:32 +0300, Vladimir K wrote:
$ /usr/libexec/gvfsd-fuse -f /run/user/1000/gvfs
fuse: both 'want' and 'want_ext' are set
Laszlo, I assume this is not the result you expected after updating
fuse3? In gvfs-fuse_1.57.2-2, the only references to FUSE_CAP are:
client/gvfsfusedaemon.c: fuse_set_feature_flag(conn, FUSE_CAP_ATOMIC_O_TRUNC);
client/gvfsfusedaemon.c: fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ);
which if I understand correctly is the style that is recommended by the
upstream developers of FUSE. I verified that this worked as intended with
libfuse3-4_3.17.1~rc1-3, but I can confirm the regression with 3.17.1-1.
In the thread that calls gvfs' vfs_init() from FUSE's fuse_fs_init(),
it looks like we end up with:
conn->capable = 0x7fbfffdb
conn->want = 0x0000f819
(ASYNC_READ | ATOMIC_O_TRUNC | EXPORT_SUPPORT | IOCTL_DIR |
AUTO_INVAL_DATA | READDIRPLUS | READDIRPLUS_AUTO | ASYNC_DIO)
conn->capable_ext = 0x7fbfffdb
conn->want_ext = 0x0000f818
(ATOMIC_O_TRUNC | EXPORT_SUPPORT | IOCTL_DIR |
AUTO_INVAL_DATA | READDIRPLUS | READDIRPLUS_AUTO | ASYNC_DIO)
when control returns from gvfs to libfuse: gvfs has all of the default
capability flags, except for ASYNC_READ which it explicitly disables
(and it also explicitly enables ATOMIC_O_TRUNC, but that's the default
in fuse3 anyway). Then in fuse_fs_init() we have
want_ext_default = 0x0000f819
want_default = 0x0000f819
(both ASYNC_READ | ATOMIC_O_TRUNC | EXPORT_SUPPORT | IOCTL_DIR |
AUTO_INVAL_DATA | READDIRPLUS | READDIRPLUS_AUTO | ASYNC_DIO)
which means convert_to_conn_want_ext() shouldn't be failing, because
the condition "conn->want != want_default" shouldn't be met.
But when I put a breakpoint on fuse_log(), the backtrace is from a
different caller:
#0 fuse_log
(level=level@entry=FUSE_LOG_ERR, fmt=fmt@entry=0x7ffff7625568 "fuse: both 'want'
and 'want_ext' are set\n")
at ../lib/fuse_log.c:78
#1 0x00007ffff761ad1b in convert_to_conn_want_ext
(conn=0x55555556c130, want_ext_default=<optimized out>, want_default=65033)
at ../lib/fuse_i.h:247
#2 do_init (req=0x7ffff0000d20, nodeid=<optimized out>, inarg=0x7ffff0001fd8)
at ../lib/fuse_lowlevel.c:2176
#3 0x00007ffff761b3f9 in fuse_session_process_buf_internal
(se=0x55555556bf80, buf=buf@entry=0x55555556ba38, ch=<optimized out>) at
../lib/fuse_lowlevel.c:2909
#4 0x00007ffff76160d7 in fuse_do_work (data=0x55555556ba20) at
../lib/fuse_loop_mt.c:179
#5 0x00007ffff714eb7b in start_thread (arg=<optimized out>) at
./nptl/pthread_create.c:448
#6 0x00007ffff71cc7b8 in __GI___clone3 () at
../sysdeps/unix/sysv/linux/x86_64/clone3.S:78
... is gvfs perhaps initializing its capabilities in the wrong place or
something, such that FUSE receives an unintended capability set?
In this backtrace, want_default is 0xfe09:
ASYNC_READ | ATOMIC_O_TRUNC | SPLICE_READ | FLOCK_LOCKS | IOCTL_DIR |
AUTO_INVAL_DATA | READDIRPLUS | READDIRPLUS_AUTO | ASYNC_DIO
which differs from the defaults seen when gvfs' vfs_init() is called:
in vfs_init() we have EXPORT_SUPPORT, but in fuse_do_work() we have
SPLICE_READ and FLOCK_LOCKS. Which one is correct? Presumably not both?
I wonder whether there might be a thread-safety issue here that is
either resulting in initialization happening in both threads, or
writes from one thread not being visible to the other? do_init() says
/* Prevent bogus data races (bogus since "init" is called before
* multi-threading becomes relevant */
but I can't help wondering whether those data races reported by tsan
are, in fact, not bogus at all.
I also wonder whether it would be better for fuse_set_feature_flag() and
fuse_unset_feature_flag() to set/unset the relevant flag in conn->want
*and* conn->want_ext, if it happens to be below the 32-bit boundary.
That way, "fuse_lower_32_bits(conn->want_ext) != conn->want" would
usually be false and we would not have any inconsistency.
smcv