Skip to content

Commit f768b41

Browse files
committed
mv: Make mv command fallback to copy only if the src and dst are on different device
1 parent 118a65a commit f768b41

File tree

4 files changed

+62
-1
lines changed

4 files changed

+62
-1
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/mv/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ uucore = { workspace = true, features = [
2525
"update-control",
2626
] }
2727

28+
[target.'cfg(target_os = "windows")'.dependencies]
29+
winapi = { version="0.3" }
30+
31+
[target.'cfg(target_os = "linux")'.dependencies]
32+
libc = { workspace = true }
33+
2834
[[bin]]
2935
name = "mv"
3036
path = "src/main.rs"

src/uu/mv/src/mv.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,15 @@ fn rename_with_fallback(
591591
to: &Path,
592592
multi_progress: Option<&MultiProgress>,
593593
) -> io::Result<()> {
594-
if fs::rename(from, to).is_err() {
594+
if let Err(err) = fs::rename(from, to) {
595+
// We will only copy if they're not on the same device, otherwise we'll report an error.
596+
#[cfg(windows)]
597+
const EXDEV: i32 = winapi::shared::winerror::ERROR_NOT_SAME_DEVICE as _;
598+
#[cfg(unix)]
599+
const EXDEV: i32 = libc::EXDEV as _;
600+
if err.raw_os_error() != Some(EXDEV) {
601+
return Err(err);
602+
}
595603
// Get metadata without following symlinks
596604
let metadata = from.symlink_metadata()?;
597605
let file_type = metadata.file_type();

tests/by-util/test_mv.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,51 @@ fn test_acl() {
16101610
assert!(compare_xattrs(&file, &file_target));
16111611
}
16121612

1613+
#[test]
1614+
#[cfg(windows)]
1615+
fn test_move_should_not_fallback_to_copy() {
1616+
use std::os::windows::fs::OpenOptionsExt;
1617+
1618+
let (at, mut ucmd) = at_and_ucmd!();
1619+
1620+
let locked_file = "a_file_is_locked";
1621+
let locked_file_path = at.plus(locked_file);
1622+
let file = std::fs::OpenOptions::new()
1623+
.create(true)
1624+
.write(true)
1625+
.share_mode(
1626+
uucore::windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ
1627+
| uucore::windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE,
1628+
)
1629+
.open(locked_file_path);
1630+
1631+
let target_file = "target_file";
1632+
ucmd.arg(locked_file).arg(target_file).fails();
1633+
1634+
assert!(at.file_exists(locked_file));
1635+
assert!(!at.file_exists(target_file));
1636+
1637+
drop(file);
1638+
}
1639+
1640+
#[test]
1641+
#[cfg(unix)]
1642+
fn test_move_should_not_fallback_to_copy() {
1643+
let (at, mut ucmd) = at_and_ucmd!();
1644+
1645+
let readonly_dir = "readonly_dir";
1646+
let locked_file = "readonly_dir/a_file_is_locked";
1647+
at.mkdir(readonly_dir);
1648+
at.touch(locked_file);
1649+
at.set_mode(readonly_dir, 0o555);
1650+
1651+
let target_file = "target_file";
1652+
ucmd.arg(locked_file).arg(target_file).fails();
1653+
1654+
assert!(at.file_exists(locked_file));
1655+
assert!(!at.file_exists(target_file));
1656+
}
1657+
16131658
// Todo:
16141659

16151660
// $ at.touch a b

0 commit comments

Comments
 (0)