Package: rclone
Version: 1.60.1+dfsg-4
Severity: normal
X-Debbugs-CC: [email protected]

Dear maintainers,

rclone mount built from Debian sources answers FUSE xattr requests with
EIO instead of ENOSYS/EOPNOTSUPP, which makes tools that probe for
xattrs/ACLs (ls -l, getfattr, cp -a, rsync -X, tar --xattrs, ...) print
spurious "Input/output error" messages and return non-zero exit codes.

This is a packaging-combination bug between rclone and the very old
golang-bazil-fuse (2016 snapshot), not an upstream rclone bug.

# How to reproduce

    rclone config create test webdav url=... vendor=other user=... pass=...
    rclone mount test: /mnt/test &
    touch /mnt/test/file
    ls -l /mnt/test

Output:

    ls: '/mnt/test/file': Input/output error
    -rw-r--r-- 1 user user 0 ... file        <- entry is still listed, stat is 
fine

Direct syscall check:

    $ python3 -c "import os; os.listxattr('/mnt/test/file')"
    OSError: [Errno 5] Input/output error: '/mnt/test/file'

Directories behave correctly (EOPNOTSUPP), only regular files fail.

# Root cause analysis

rclone's FUSE handlers for regular files return a bare syscall.Errno
(cmd/mount/file.go, both 1.60.1+dfsg-4 and current sid 1.69.3+dfsg-3):

    func (f *File) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, 
resp *fuse.ListxattrResponse) error {
        return syscall.ENOSYS // we never implement this
    }

(same for Getxattr/Setxattr/Removexattr; introduced by upstream commit
"mount: replace deprecated fuse.ENOSYS with syscall.ENOSYS", 2022-06-13,
first released in v1.60).

Upstream rclone pins bazil.org/fuse v0.0.0-20200524192727-fb710f7dfd05,
whose ToErrno() explicitly handles bare syscall.Errno:

    func ToErrno(err error) Errno {
        if err, ok := err.(syscall.Errno); ok {
            return Errno(err)
        }
        ...

However, the Debian build uses golang-bazil-fuse
0.0~git20160811.0.371fbbd-4 (2016-08-11), whose Header.RespondError only
recognizes types implementing the bazil ErrorNumber interface:

    func (h *Header) RespondError(err error) {
        errno := DefaultErrno                  // == EIO
        if ferr, ok := err.(ErrorNumber); ok {
            errno = ferr.Errno()
        }
        ...
    }

syscall.Errno does not implement bazil's ErrorNumber, so the assertion
fails and the kernel gets -EIO. (Directories work because the serve
layer returns fuse.ENOTSUP, which is a bazil Errno and thus implements
ErrorNumber.)

Confirmation with --debug-fuse:

    fuse: <- Listxattr [ID=0x12 Node=0x2 ...] 256 @0
    fuse: -> [ID=0x12] Listxattr error=EIO: function not implemented

Note the contradiction: errno says EIO while the underlying error string
is "function not implemented" (ENOSYS).

golang-bazil-fuse is at the same 2016 revision in trixie, forky and sid,
so upgrading rclone alone does not fix this; every rclone release since
v1.60 packaged in Debian is affected.

# Possible fixes

A) Minimal one-line-per-symbol patch to rclone: return fuse.ENOSYS
   instead of syscall.ENOSYS in cmd/mount/file.go xattr handlers. The
   fuse.ENOSYS constant exists in both the 2016 and current library
   versions, so this is safe regardless of library version.

B) Alternatively, update golang-bazil-fuse to a newer upstream snapshot
   (>= 2020-05-24, e.g. current master with FUSE protocol 7.33), whose
   ToErrno handles bare syscall.Errno. This may need a rebuild of other
   reverse dependencies.

I verified the analysis against both the trixie rclone 1.60.1+dfsg-4
(reproduced locally) and the sid source package 1.69.3+dfsg-3 (code
inspection via sources.debian.org).

No data corruption is involved: reads/writes/stat all work fine; the
impact is diagnostic noise and polluted exit codes for xattr-probing
tools.

Thanks for maintaining rclone in Debian!

-- 
choury

Reply via email to