Hi Hanna,
Am 08.05.26 um 3:11 PM schrieb Hanna Czenczek:
> On 08.05.26 15:06, Hanna Czenczek wrote:
>> On 08.05.26 13:55, Fiona Ebner wrote:
>>> Dear maintainers,
>>>
>>> Am 10.03.26 um 6:37 PM schrieb Kevin Wolf:
>>>> From: Hanna Czenczek <[email protected]>
>>>>
>>>> Manually read requests from the /dev/fuse FD and process them, without
>>>> using libfuse. This allows us to safely add parallel request
>>>> processing
>>>> in coroutines later, without having to worry about libfuse internals.
>>>> (Technically, we already have exactly that problem with
>>>> read_from_fuse_export()/read_from_fuse_fd() nesting.)
>>>>
>>>> We will continue to use libfuse for mounting the filesystem;
>>>> fusermount3
>>>> is a effectively a helper program of libfuse, so it should know best
>>>> how
>>>> to interact with it. (Doing it manually without libfuse, while doable,
>>>> is a bit of a pain, and it is not clear to me how stable the "protocol"
>>>> actually is.)
>>>>
>>>> Take this opportunity of quite a major rewrite to update the Copyright
>>>> line with corrected information that has surfaced in the meantime.
>>>
>>> a colleague ran into another issue with a fuse export, this time in
>>> combination with virt-fw-vars and bisecting points to this patch.
>>> Before commit a94a1d7699 ("fuse: Manually process requests (without
>>> libfuse)") the reproducer [0] completes successfully, after that
>>> commit it hangs at [1]. The issue is still present with current
>>> master. I can dig into the details next week.
>>
>> On my system, virt-fw-vars opens the file with O_TRUNC (after “INFO:
>> writing raw edk2 varstore...”). It then tries to write to the file,
>> but this returns a short write because the export is not marked as
>> growable, and for some reason, the write is infinitely retried instead
>> of aborting on ret=0 (which should indicate ENOSPC for writes).
>>
>> Using growable=true makes it work. (For me :))
>>
>> For some reason, before said commit, libfuse just did not pass that
>> truncate on. I’ll look into why exactly, but so far I would say the
>> FUSE export behavior is as I would expect it.
>
> Ah, I see. libfuse sets FUSE_CAP_ATOMIC_O_TRUNC, which has O_TRUNC be
> passed on as an open option, but the old export code just ignored all
> open options.
>
> The new code does not set that option, so O_TRUNC is executed as an
> explicit truncate by the kernel, which then *is* executed.
thank you for digging into it!
> I’m not entirely sure how we should handle it. There is a case to be
> made that with growable=off, it may make sense to ignore O_TRUNC (which
> requires turning on FUSE_CAP_ATOMIC_O_TRUNC again and then continue to
> ignore it). But on the other hand, O_TRUNC is O_TRUNC, and growable=off
> does not explicitly mean “please behave as much as a block device as
> possible”.
>
> What do you suggest?
It turns out that the missing FUSE_ATOMIC_O_TRUNC flag also regresses
exports from block devices, making open() with O_TRUNC fail with
EOPNOTSUPP [0], which worked before. Intuitively to me, growable=off
feels like it should imply "shrinkable=off" as well.
My suggestions would be one of the following, with a preference for 2.:
1. Always set FUSE_ATOMIC_O_TRUNC
Most backwards compatible. There is the issue that O_TRUNC does not
apply when it's actually a file, which is surprising.
2. Set FUSE_ATOMIC_O_TRUNC if growable=off
Feels natural to me. People using growable=on with block device based
exports are still affected by the change in behavior.
3. Set FUSE_ATOMIC_O_TRUNC if driver not file-based
What should be the exact condition to consider the driver
not file-based? Here, O_TRUNC for a growable=off file-based export
does apply, which feels surprising to me. Continues breaking the use
I reported this issue for, requiring using growable=on in such cases.
4. Set FUSE_ATOMIC_O_TRUNC if growable=off or if driver not file-based
Similar to 2., but reduces cases affected by the change in behavior.
Best Regards,
Fiona
[0]:
> #!/bin/bash
>
> rm /tmp/export.fuse
> lvremove lvm/test
>
> touch /tmp/export.fuse
> lvcreate -n test --size 1M lvm
>
> (
> ./storage-daemon/qemu-storage-daemon \
> --blockdev
> raw,node-name=node0,file.driver=host_device,file.filename=/dev/lvm/test \
> --export
> fuse,id=exp0,mountpoint=/tmp/export.fuse,node-name=node0,writable=true \
> --chardev socket,id=qmp,path=/run/qsd.qmp,server=on,wait=off \
> --monitor chardev=qmp,mode=control
> ) &
>
> sleep 1 # too lazy to do proper synchronization for the reproducer here
>
> python3 -c 'open("/tmp/export.fuse", "wb")'
> echo '{"execute": "qmp_capabilities"}{"execute": "quit"}' | socat -
> /run/qsd.qmp