diff --git a/src/uu/install/locales/en-US.ftl b/src/uu/install/locales/en-US.ftl index 1b5b92e532..9279575183 100644 --- a/src/uu/install/locales/en-US.ftl +++ b/src/uu/install/locales/en-US.ftl @@ -44,7 +44,7 @@ install-error-override-directory-failed = cannot overwrite directory { $dir } wi install-error-same-file = { $file1 } and { $file2 } are the same file install-error-extra-operand = extra operand { $operand } { $usage } -install-error-not-permitted = cannot remove { $path }: Operation not permitted +install-error-not-permitted = cannot remove { $path }: Permission denied install-error-invalid-mode = Invalid mode string: { $error } install-error-mutually-exclusive-target = Options --target-directory and --no-target-directory are mutually exclusive install-error-mutually-exclusive-compare-strip = Options --compare and --strip are mutually exclusive diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 58e16a381e..9b4eca5968 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -776,7 +776,11 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { return Err(InstallError::SameFile(source.clone(), target.clone()).into()); } - if target.is_file() || is_new_file_path(&target) { + // Existing non-directory targets include regular files *and* special + // nodes (e.g. /dev/full). Rely on exists()+!is_dir() rather than + // is_file(), which is false for char devices and would wrongly report + // "invalid target ... No such file" (issue #9934). + if is_new_file_path(&target) || (target.exists() && !target.is_dir()) { #[cfg(unix)] if let (Some(ref parent_fd), Some(ref filename)) = (target_parent_fd, target_filename) { if b.compare && !need_copy(source, &target, b) { diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 949c67b989..8b0aab61e2 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -2933,6 +2933,20 @@ fn test_install_proc_self_mem_as_dst() { .stderr_contains("cannot remove"); } +#[test] +#[cfg(target_os = "linux")] +fn test_install_dev_null_to_dev_full_permission() { + // GNU reports cannot remove '/dev/full': Permission denied for char devices + // that refuse unlink, not "invalid target ... No such file" (issue #9934). + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .arg("/dev/null") + .arg("/dev/full") + .fails() + .stderr_is("install: cannot remove '/dev/full': Permission denied\n"); +} + #[test] fn test_install_backup_nil_same_file() { let scene = TestScenario::new(util_name!());