diff --git a/src/uu/mv/src/error.rs b/src/uu/mv/src/error.rs index a6605e23234..e891fc2ec01 100644 --- a/src/uu/mv/src/error.rs +++ b/src/uu/mv/src/error.rs @@ -12,6 +12,7 @@ pub enum MvError { NoSuchFile(String), SameFile(String, String), SelfSubdirectory(String), + SelfTargetSubdirectory(String, String), DirectoryToNonDirectory(String), NonDirectoryToDirectory(String, String), NotADirectory(String), @@ -29,6 +30,10 @@ impl Display for MvError { f, "cannot move '{s}' to a subdirectory of itself, '{s}/{s}'" ), + Self::SelfTargetSubdirectory(s, t) => write!( + f, + "cannot move '{s}' to a subdirectory of itself, '{t}/{s}'" + ), Self::DirectoryToNonDirectory(t) => { write!(f, "cannot overwrite directory {t} with non-directory") } diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 7236907da77..47e0b864d32 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -326,6 +326,28 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> Err(MvError::DirectoryToNonDirectory(target.quote().to_string()).into()) } } else { + // Check that source & target do not contain same subdir/dir when both exist + // mkdir dir1/dir2; mv dir1 dir1/dir2 + let target_contains_itself = target + .as_os_str() + .to_str() + .ok_or("not a valid unicode string") + .and_then(|t| { + source + .as_os_str() + .to_str() + .ok_or("not a valid unicode string") + .map(|s| t.contains(s)) + }) + .unwrap(); + + if target_contains_itself { + return Err(MvError::SelfTargetSubdirectory( + source.display().to_string(), + target.display().to_string(), + ) + .into()); + } move_files_into_dir(&[source.to_path_buf()], target, opts) } } else if target.exists() && source.is_dir() { diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index f7f9622f52e..e8866732014 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2,6 +2,8 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// +// spell-checker:ignore mydir use crate::common::util::TestScenario; use filetime::FileTime; use std::thread::sleep; @@ -1389,6 +1391,29 @@ fn test_mv_into_self_data() { assert!(at.file_exists(file2)); assert!(!at.file_exists(file1)); } + +#[test] +fn test_mv_directory_into_subdirectory_of_itself_fails() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir1 = "mydir"; + let dir2 = "mydir/mydir_2"; + at.mkdir(dir1); + at.mkdir(dir2); + scene.ucmd().arg(dir1).arg(dir2).fails().stderr_contains( + "mv: cannot move 'mydir' to a subdirectory of itself, 'mydir/mydir_2/mydir'", + ); + + // check that it also errors out with / + scene + .ucmd() + .arg(format!("{}/", dir1)) + .arg(dir2) + .fails() + .stderr_contains( + "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir_2/mydir/'", + ); +} // Todo: // $ at.touch a b