Purchased files could fail to appear in Files because the backend UID cannot write into FileBrowser's rootless-owned folders. The current FileBrowser API path also checks for name conflicts before opening with truncation, so override=false does not protect concurrent saves. This change publishes complete purchased files through a no-overwrite hard link, using the rootless namespace when host permissions require it.
Changes
Bring the branch up to current main while preserving its refund handling, purchase cache and response compatibility.
Write to a private, uniquely named temporary file; finish and sync its contents before publishing it. Preserve existing files and choose numbered names when needed.
Use the same no-overwrite behavior for direct and rootless writes. ln -T treats an existing directory or dangling symlink as a conflict instead of placing a file inside it.
Validate filenames, refuse symlink destination directories, bound collision retries, verify the exact input length, and clean temporary files on ordinary failures.
Keep the Files copy optional and retain the durable purchase cache when Files storage is unavailable.
Validation
Full isolated backend suite: 1,568 passed, zero failed; four existing hardware/live tests ignored.
Production cargo check passed with the existing 16 warnings.
Real filesystem tests cover 24 concurrent writes, original-file preservation, directory/symlink conflicts, invalid names, collision exhaustion, permission fallback and temporary-file cleanup.
The actual namespace shell script is tested for quoting, ownership/mode, exact bytes and truncated input.
Scratch-only tests on the development node passed with real Podman rootless ownership and four concurrent writers; all fixtures were removed.
Combined testing with #161: 1,585 passed, zero failed; four existing tests ignored.
Targets the next release after 1.8.21. Signed 1.8.21 artifacts are unchanged.
## Problem and behavior
Purchased files could fail to appear in Files because the backend UID cannot write into FileBrowser's rootless-owned folders. The current FileBrowser API path also checks for name conflicts before opening with truncation, so `override=false` does not protect concurrent saves. This change publishes complete purchased files through a no-overwrite hard link, using the rootless namespace when host permissions require it.
## Changes
- Bring the branch up to current main while preserving its refund handling, purchase cache and response compatibility.
- Write to a private, uniquely named temporary file; finish and sync its contents before publishing it. Preserve existing files and choose numbered names when needed.
- Use the same no-overwrite behavior for direct and rootless writes. `ln -T` treats an existing directory or dangling symlink as a conflict instead of placing a file inside it.
- Validate filenames, refuse symlink destination directories, bound collision retries, verify the exact input length, and clean temporary files on ordinary failures.
- Keep the Files copy optional and retain the durable purchase cache when Files storage is unavailable.
## Validation
- Full isolated backend suite: 1,568 passed, zero failed; four existing hardware/live tests ignored.
- Production `cargo check` passed with the existing 16 warnings.
- Real filesystem tests cover 24 concurrent writes, original-file preservation, directory/symlink conflicts, invalid names, collision exhaustion, permission fallback and temporary-file cleanup.
- The actual namespace shell script is tested for quoting, ownership/mode, exact bytes and truncated input.
- Scratch-only tests on the development node passed with real Podman rootless ownership and four concurrent writers; all fixtures were removed.
- Combined testing with #161: 1,585 passed, zero failed; four existing tests ignored.
Targets the next release after 1.8.21. Signed 1.8.21 artifacts are unchanged.
Review evidence: [next-release PR review](https://source.archipelago-foundation.org/lfg2025/archy/src/branch/main/docs/pr-review-20260930.md).
Every paid download logged "filing into filebrowser/Music/... failed
(non-fatal): Permission denied". The purchase played in-app but never
appeared in Files. FileBrowser's folders belong to its rootless container
range (host uid 100000, mode 755). This service is host uid 1000, outside
that range, so it can read them but not create files in them.
New container::filebrowser::save_new_file:
- Writes directly when the folder allows it.
- Otherwise writes through `podman unshare`, where that uid range is
ours: to a temp file, then chowned to the folder's owner, set to 0644,
and hard-linked into place. FileBrowser never sees a partial file and an
existing file is never replaced. A missing folder is created and given
its parent's owner. No sudo.
- Keeps the "name (2).ext" de-duplication the RPC did inline.
Checked the unshare script on amishparadise in a scratch folder owned
like FileBrowser's: new folder + file OK, owner/mode right, no clobber,
no temp file left, and the service can read the result.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chaum
changed title from fix(files): file purchased content into FileBrowser folders again to fix(files): save purchased files atomically with rootless ownership2026-09-30 11:34:44 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem and behavior
Purchased files could fail to appear in Files because the backend UID cannot write into FileBrowser's rootless-owned folders. The current FileBrowser API path also checks for name conflicts before opening with truncation, so
override=falsedoes not protect concurrent saves. This change publishes complete purchased files through a no-overwrite hard link, using the rootless namespace when host permissions require it.Changes
ln -Ttreats an existing directory or dangling symlink as a conflict instead of placing a file inside it.Validation
cargo checkpassed with the existing 16 warnings.Targets the next release after 1.8.21. Signed 1.8.21 artifacts are unchanged.
Review evidence: next-release PR review.
fix(files): file purchased content into FileBrowser folders againto fix(files): save purchased files atomically with rootless ownership0677924a64to0677924a64