Skip to content

fix: skip chown on filesystems without Unix ownership#326

Merged
lovehunter9 merged 1 commit into
mainfrom
fix/copy_to_usb
May 21, 2026
Merged

fix: skip chown on filesystems without Unix ownership#326
lovehunter9 merged 1 commit into
mainfrom
fix/copy_to_usb

Conversation

@lovehunter9
Copy link
Copy Markdown
Collaborator

Summary

  • Paste / upload / download to an external USB formatted as FAT/exFAT/NTFS fails with operation not permitted on the post-copy chown(2), even though the data is already written to the device.
  • These filesystems have no on-disk owner/group fields; the kernel rejects chown(2) regardless of caller privilege.
  • Detect such mounts by statfs magic number and short-circuit files.Chown / files.ChownRecursive with a warning. Behaviour on every other filesystem, and on every afero.Fs-backed caller, is byte-for-byte unchanged.

Reproduction

  1. Plug a FAT32 / exFAT / NTFS USB drive into an Olares device; it auto-mounts under /data/External/<id>.
  2. From the Files UI, copy a file from any folder to that USB.
  3. The file is physically written to disk, but the task is reported as failed:
    + "```" +
    chown /data/External// to 1000:1000: chown ...: operation not permitted
    + "```" +

Root cause

+ "pkg/tasks/task_paste_posix.go::rsync()" + runs + "rsync --no-o --no-g" + (so rsync itself avoids preserving ownership) and then calls + "files.Chown(nil, dstPath, 1000, 1000)" + to enforce + "1000:1000" + on the result. FAT/exFAT/NTFS carry no on-disk ownership, so + "chown(2)" + always returns + "EPERM" + there. The error is propagated and the task is marked failed despite the data being fine.

Fix

Add + "files.SupportsOwnership(path string) bool" + (Linux only, + "syscall.Statfs" + -based). It returns + "false" + only for these six filesystem magics, which cannot honour + "chown" + regardless of caller privilege:

magic filesystem
+ "0x4d44" + FAT12/16/32 (vfat, msdos)
+ "0x2011BAB0" + exFAT (kernel native, >=5.7)
+ "0x5346544e" + NTFS (legacy)
+ "0x7366746e" + NTFS3 (kernel native, >=5.15)
+ "0x9660" + ISO 9660
+ "0x15013346" + UDF

+ "files.Chown" + and + "files.ChownRecursive" + short-circuit with a warning when + "SupportsOwnership" + returns + "false" + . The guard inside + "Chown" + is gated on + "fs == nil" + , so every + "afero.Fs" + -backed caller is left untouched.

Impact

Unchanged on every non-FAT-class filesystem. + "SupportsOwnership" + falls back to + "true" + on anything outside the table above and on + "statfs" + errors, so the original code path runs verbatim on ext4 / btrfs / xfs / tmpfs / overlay / nfs / sshfs, etc.

Unchanged for + "afero.Fs" + -backed callers: + "pkg/drivers/posix/posix/posix.go" + , + "pkg/preview/preview.go" + , + "pkg/diskcache/file_cache.go" + , and the non-recursive branch of + "pkg/hertz/biz/handler/api/permission/permission_service.go" + .

Fixed in passing (same root cause): paste / upload / download / + "MkdirAllWithChown" + to External when the target is FAT/exFAT/NTFS - covers + "task_paste_posix.go" + , + "task_paste_sync.go" + , + "task_paste_cloud.go" + , + "task_paste_download.go" + , and + "pkg/drivers/posix/upload/handlefunc.go" + .

One behavioural shift to note: the recursive branch of the permission API on a FAT-class destination now returns + "200 OK" + (with a warning log) instead of + "500" + . The Olares UI does not expose ownership for External, so the surface contract is preserved.

Known limitations

FUSE-mounted ntfs-3g / exfat-fuse are not covered: + "statfs" + reports the FUSE magic, which is deliberately left out of the list. Modern Ubuntu (22.04+) ships native + "ntfs3" + / + "exfat" + drivers, so this is unlikely in practice. If it ever surfaces, a + "/proc/self/mountinfo" + parser can be bolted on as a follow-up.

Test plan

  • + "gofmt" + clean
  • + "GOOS=linux GOARCH=amd64 go vet ./pkg/files/..." +
  • + "GOOS=linux GOARCH=arm64 go vet ./pkg/files/..." +
  • + "GOOS=linux GOARCH={amd64,arm64} go build ./pkg/files/..." +
  • + "go test -c" + compiles on both archs
  • Manual: copy a single file from Drive to a FAT32 / exFAT USB on a real Olares device -> task succeeds, file is on disk, no error toast
  • Manual: copy a directory tree (exercises + "ChownRecursive" + ) to the same USB -> task succeeds
  • Regression: copy / paste / mkdir on a Drive-side path (ext4) -> identical to before, no new error-level log lines
  • Regression: + "PUT /api/v1/permission" + on a Drive-side path (ext4) -> owner actually changes, behaviour identical

Made with Cursor

Pasting to a USB drive mounted as FAT/exFAT/NTFS surfaced
"operation not permitted" from the post-copy chown even though
the data was already on disk. These filesystems carry no on-disk
owner/group fields and the kernel rejects chown(2) regardless of
caller privilege; the rsync itself ran with --no-o --no-g so the
copy was actually successful.

Detect such mounts by statfs magic number and let Chown /
ChownRecursive short-circuit with a warning, so paste / upload /
download to External no longer fails on FAT-class drives. Other
filesystems and afero-backed callers behave identically to before.

Co-authored-by: Cursor <cursoragent@cursor.com>
@lovehunter9 lovehunter9 merged commit c64ac84 into main May 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant