Skip to content

Commit c74eb71

Browse files
committed
mv: preserve ownership (uid/gid) on cross-device moves (#9714)
1 parent 3211d85 commit c74eb71

File tree

1 file changed

+62
-0
lines changed

1 file changed

+62
-0
lines changed

src/uu/mv/src/mv.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,8 @@ fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
907907
{
908908
let _ = copy_xattrs_if_supported(from, to);
909909
}
910+
// Preserve ownership (uid/gid) from the source symlink
911+
let _ = preserve_ownership(from, to);
910912
fs::remove_file(from)
911913
}
912914

@@ -1005,6 +1007,12 @@ fn copy_dir_contents(
10051007
// Create the destination directory
10061008
fs::create_dir_all(to)?;
10071009

1010+
// Preserve ownership (uid/gid) of the top-level directory
1011+
#[cfg(unix)]
1012+
{
1013+
let _ = preserve_ownership(from, to);
1014+
}
1015+
10081016
// Recursively copy contents
10091017
#[cfg(unix)]
10101018
{
@@ -1085,6 +1093,12 @@ fn copy_dir_contents_recursive(
10851093
// Recursively copy subdirectory (only real directories, not symlinks)
10861094
fs::create_dir_all(&to_path)?;
10871095

1096+
// Preserve ownership (uid/gid) of the subdirectory
1097+
#[cfg(unix)]
1098+
{
1099+
let _ = preserve_ownership(&from_path, &to_path);
1100+
}
1101+
10881102
print_verbose(&from_path, &to_path);
10891103

10901104
copy_dir_contents_recursive(
@@ -1148,9 +1162,12 @@ fn copy_file_with_hardlinks_helper(
11481162

11491163
if from.is_symlink() {
11501164
// Copy a symlink file (no-follow).
1165+
// rename_symlink_fallback already preserves ownership and removes the source.
11511166
rename_symlink_fallback(from, to)?;
11521167
} else if is_fifo(from.symlink_metadata()?.file_type()) {
11531168
make_fifo(to)?;
1169+
// Preserve ownership (uid/gid) from the source
1170+
let _ = preserve_ownership(from, to);
11541171
} else {
11551172
// Copy a regular file.
11561173
fs::copy(from, to)?;
@@ -1159,6 +1176,8 @@ fn copy_file_with_hardlinks_helper(
11591176
{
11601177
let _ = copy_xattrs_if_supported(from, to);
11611178
}
1179+
// Preserve ownership (uid/gid) from the source
1180+
let _ = preserve_ownership(from, to);
11621181
}
11631182

11641183
Ok(())
@@ -1208,11 +1227,54 @@ fn rename_file_fallback(
12081227
let _ = copy_xattrs_if_supported(from, to);
12091228
}
12101229

1230+
// Preserve ownership (uid/gid) from the source file
1231+
#[cfg(unix)]
1232+
{
1233+
let _ = preserve_ownership(from, to);
1234+
}
1235+
12111236
fs::remove_file(from)
12121237
.map_err(|err| io::Error::new(err.kind(), translate!("mv-error-permission-denied")))?;
12131238
Ok(())
12141239
}
12151240

1241+
/// Preserve ownership (uid/gid) from source to destination.
1242+
/// Uses lchown so it works on symlinks without following them.
1243+
/// Errors are silently ignored for non-root users who cannot chown.
1244+
#[cfg(unix)]
1245+
fn preserve_ownership(from: &Path, to: &Path) -> io::Result<()> {
1246+
use std::os::unix::fs::MetadataExt;
1247+
1248+
let source_meta = from.symlink_metadata()?;
1249+
let uid = source_meta.uid();
1250+
let gid = source_meta.gid();
1251+
1252+
let dest_meta = to.symlink_metadata()?;
1253+
let dest_uid = dest_meta.uid();
1254+
let dest_gid = dest_meta.gid();
1255+
1256+
// Only chown if ownership actually differs
1257+
if uid != dest_uid || gid != dest_gid {
1258+
use uucore::perms::{Verbosity, VerbosityLevel, wrap_chown};
1259+
// Use follow=false so lchown is used (works on symlinks)
1260+
// Silently ignore errors: non-root users typically cannot chown to
1261+
// arbitrary uid, matching GNU mv behavior which also uses best-effort.
1262+
let _ = wrap_chown(
1263+
to,
1264+
&dest_meta,
1265+
Some(uid),
1266+
Some(gid),
1267+
false,
1268+
Verbosity {
1269+
groups_only: false,
1270+
level: VerbosityLevel::Silent,
1271+
},
1272+
);
1273+
}
1274+
1275+
Ok(())
1276+
}
1277+
12161278
/// Copy xattrs from source to destination, ignoring ENOTSUP/EOPNOTSUPP errors.
12171279
/// These errors indicate the filesystem doesn't support extended attributes,
12181280
/// which is acceptable when moving files across filesystems.

0 commit comments

Comments
 (0)