docs: document the CIFS/forceuid utime gotcha for FileWorker.move (shutil.copy2 path) #27
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ModernLeft/athena-file#27
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
`FileWorker.move()` in `athena_file/main.py:156` uses `shutil.copy2` (via `self.copy()` at `main.py:136`). `copy2` is `copyfile` + `copystat`; the latter calls `os.utime(dst, ns=(...))` with explicit times.
Linux requires the calling process to own the file (or hold `CAP_FOWNER`) for `utime(path, times=non-NULL)`. Group-write permission is not sufficient.
The footgun
When the destination directory is on a CIFS / SMB mount with `uid=…,forceuid` options (very common with Hetzner StorageBox, Synology shares, etc.), the kernel forces every file's owner to the mount-specified UID regardless of who actually wrote it. If the writing process runs as a different UID, the `utime(non-NULL)` call inside `copystat` fails with `PermissionError: [Errno 1] Operation not permitted`.
I just hit this in `athena-archive-api`'s media upload route (`POST /media/upload/`). The file lands on disk successfully (the `copyfile` step inside `copy2` succeeds), then `copystat` raises and `FileWorker.move` propagates the exception → the route 500s. From the user's perspective: "uploads broke."
Traceback excerpt (full traceback in the BE repo's deploy logs):
```
File "athena_file/main.py:136" in copy:
shutil.copy2(self.path, new_path)
File "shutil.py:530" in copy2:
copystat(src, dst, follow_symlinks=follow)
File "shutil.py:446" in copystat:
lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns), …)
PermissionError: [Errno 1] Operation not permitted: '/media/storage/4.webp'
```
Reproduction
Docs ask
A README or `FileWorker.move` docstring note along these lines:
Optional follow-up: drop `copystat`
`shutil.copy2` → `shutil.copyfile` in `FileWorker.copy()` would resolve this library-side at the cost of not preserving src timestamps on the destination. For archive-storage use cases (`athena-archive-api`'s primary consumer) that trade-off is fine — the destination's creation time is the move time, not the temp file's mtime. Happy to PR if that's acceptable.
Surfaced from https://code.modernleft.org/ModernLeft/athena-archive-api debugging.