From 4eeeeb7500862fa1d924f555ab831aa1aa7ce5db Mon Sep 17 00:00:00 2001 From: Jake Wallin Date: Thu, 15 Oct 2026 12:57:08 +0000 Subject: [PATCH] mv: diagnose trailing slashes on regular-file sources --- .github/workflows/wasi.yml | 1 + src/uu/mv/src/mv.rs | 7 ++++++- tests/by-util/test_mv.rs | 12 ++++++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index 8d623d60654..bb21022bc8e 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -75,6 +75,7 @@ jobs: test_ls::test_ls_a_dotdot_no_error_on_wasi \ test_ls::test_ls_al_no_capabilities_insufficient_on_wasi \ test_md5sum:: test_mkdir:: test_mktemp:: test_nl:: test_nproc:: test_numfmt:: test_od:: \ + test_mv::test_mv_dir_into_file_where_both_are_files \ test_paste:: test_pathchk:: test_pr:: test_printenv:: test_printf:: test_ptx:: \ test_pwd:: test_rmdir:: test_sha1sum:: test_sha224sum:: test_sha256sum:: \ test_sha384sum:: test_sha512sum:: test_shred:: test_shuf:: test_sleep:: \ diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 89270769dc2..336a84e4fdb 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -396,7 +396,12 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> ) .into()); } - let Ok(source_metadata) = source.symlink_metadata() else { + // WASI may return file metadata even when a trailing slash requires a directory. + let Some(source_metadata) = source + .symlink_metadata() + .ok() + .filter(|metadata| !metadata.is_file() || !path_ends_with_terminator(source)) + else { return Err(if path_ends_with_terminator(source) { MvError::CannotStatNotADirectory(source.quote().to_string()).into() } else { diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 61f3660c37f..f88bf146b6b 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1917,14 +1917,18 @@ fn test_mv_seen_multiple_files_to_directory() { fn test_mv_dir_into_file_where_both_are_files() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.touch("a"); - at.touch("b"); + at.write("a", "source"); + at.write("b", "target"); scene .ucmd() .arg("a/") .arg("b") - .fails() - .stderr_contains("mv: cannot stat 'a/': Not a directory"); + .fails_with_code(1) + .no_stdout() + .stderr_is("mv: cannot stat 'a/': Not a directory\n"); + + assert_eq!(at.read("a"), "source"); + assert_eq!(at.read("b"), "target"); } #[test]