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

Reply via email to