Skip to content

Conversation

@mattsu2020
Copy link
Contributor

Summary

Fix cross-filesystem mv (EXDEV copy+delete fallback) so that file ownership does not change to the invoking user (e.g. root) when moving a file across filesystems.

Fixes #9635.

Background / Problem

When mv cannot rename(2) across devices (EXDEV), uutils falls back to copy+delete. The copy path used std::fs::copy, which creates the destination owned by the caller. If root moves a file owned by another user to a different filesystem, the destination ends up owned by root (compatibility + security concern).

Changes

  • On Unix, preserve uid/gid and mode after the copy step in the EXDEV fallback:
    • regular files (including the hardlink-aware copy path)
    • directories (including recursively created subdirectories)
    • symlinks (use lchown, do not follow)
    • FIFOs
  • Restore mode after chown to keep correct permission bits (since chown may clear setuid/setgid).
  • Add a Linux-only regression test that runs only as root and verifies uid/gid are preserved across partitions (/dev/shm tmpfs).
  • Address clippy (bind_instead_of_map) in the copy path.

Testing

  • cargo test -p uu_mv
  • cargo clippy -p uu_mv -- -D warnings

related
#9635

… Unix

Add functions to preserve file ownership (UID/GID) and permissions (mode) during mv operations when fs::rename fails and falls back to copy+remove. This ensures consistency with GNU mv behavior across filesystems, applying preservation in rename fallbacks for files, directories, symlinks, FIFOs, and recursive copies. Changes are Unix-specific, using libc::chown/lchown and fs::set_permissions.
Replace and_then with map in rename_file_fallback's copy operation for Unix systems, as the inner closure performs side effects and returns unit, making map more idiomatic than chaining with Ok(()). This improves code readability without altering behavior.
@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/tail/overlay-headers (passes in this run but fails in the 'main' branch)
@oech3

This comment was marked as resolved.

Add a new Linux-only test to verify cross-device move behavior using user namespaces and tmpfs mounts, avoiding sudo. This mirrors GNU's part-fail scenario for directories containing dangling symlinks, ensuring the mv command preserves symlinks correctly in rootless environments.
mattsu2020 and others added 5 commits December 27, 2025 16:31
- Modified HardlinkGroupScanner to skip symlinks using symlink_metadata, as hardlink preservation does not apply to them
- Added copy_symlink functions for Unix, Windows, and other platforms to copy symlinks without dereferencing
- Updated copy_dir_contents_recursive to detect and copy symlinks, including verbose output, preventing dereferencing during directory moves
Updated the copy_symlink function to use .map() combinator instead of .and_then() for clarity, as the closure performs a side effect (preserving ownership) and returns a unit value without chaining Results. This improves code readability and appropriateness of the combinator used.
Simplify the map closure by removing the explicit return of an empty tuple, as it is implicit in Rust when no value is needed. This improves code clarity without affecting functionality.
.env("BASE", &base)
.env("UUTILS", &scene.bin_path)
.output()
.expect("failed to run unshare");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might better to create a new function for uhshare (at different PR).
Ref: #9973

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants