-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
mv:fix file ownership changes when a file is mv'ed by root to a different file system #9672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
GNU testsuite comparison: |
This comment was marked as resolved.
This comment was marked as resolved.
| .env("BASE", &base) | ||
| .env("UUTILS", &scene.bin_path) | ||
| .output() | ||
| .expect("failed to run unshare"); |
There was a problem hiding this comment.
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
|
GNU testsuite comparison: |
| } | ||
|
|
||
| #[cfg(unix)] | ||
| fn try_preserve_ownership(from_meta: &fs::Metadata, to: &Path, follow_symlinks: bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document this function please
src/uu/mv/src/mv.rs
Outdated
|
|
||
| unsafe { | ||
| if follow_symlinks { | ||
| let _ = libc::chown(to_cstr.as_ptr(), uid, gid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please manage the errors
| } | ||
|
|
||
| #[cfg(unix)] | ||
| fn try_preserve_permissions(from_meta: &fs::Metadata, to: &Path) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same, please document it
src/uu/mv/src/mv.rs
Outdated
|
|
||
| // Keep mode bits only (file type bits are not allowed in chmod). | ||
| let mode = from_meta.mode() & 0o7777; | ||
| let _ = fs::set_permissions(to, fs::Permissions::from_mode(mode)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please manage the error
Merging this PR will not alter performance
Comparing Footnotes
|
|
GNU testsuite comparison: |
… 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.
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.
- 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.
…ervation - Capture return values from chown/lchown and set_permissions calls - Emit warnings for non-permission errors to inform users of preservation failures - Keep operations non-fatal as preservation is best-effort on Unix systems
| /// Copy the given symlink to the given destination without dereferencing. | ||
| /// On Windows, dangling symlinks return an error. | ||
| #[cfg(unix)] | ||
| fn copy_symlink(from: &Path, to: &Path) -> io::Result<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duplicates logic from rename_symlink_fallback(). Refactor rename_symlink_fallback to call copy_symlink() + fs::remove_file()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix it
src/uu/mv/src/mv.rs
Outdated
| fn copy_symlink(from: &Path, to: &Path) -> io::Result<()> { | ||
| let from_meta = from.symlink_metadata()?; | ||
| let path_symlink_points_to = fs::read_link(from)?; | ||
| unix::fs::symlink(path_symlink_points_to, to).map(|_| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should copy xattrs like rename_symlink_fallback does. no ?
|
GNU testsuite comparison: |
Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
Replace the duplicated symlink handling logic in rename_symlink_fallback functions across Unix, Windows, and other platforms with a call to the existing copy_symlink function. This reduces code duplication and centralizes symlink copying logic while maintaining the same functionality of copying the symlink and then removing the original file.
|
GNU testsuite comparison: |
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
mvcannotrename(2)across devices (EXDEV), uutils falls back to copy+delete. The copy path usedstd::fs::copy, which creates the destination owned by the caller. Ifrootmoves a file owned by another user to a different filesystem, the destination ends up owned byroot(compatibility + security concern).Changes
lchown, do not follow)chownto keep correct permission bits (sincechownmay clear setuid/setgid)./dev/shmtmpfs).bind_instead_of_map) in the copy path.Testing
cargo test -p uu_mvcargo clippy -p uu_mv -- -D warningsrelated
#9635