diff --git a/src/uu/install/locales/en-US.ftl b/src/uu/install/locales/en-US.ftl index 76265a2b1ff..05905a3e7df 100644 --- a/src/uu/install/locales/en-US.ftl +++ b/src/uu/install/locales/en-US.ftl @@ -48,7 +48,9 @@ install-error-mutually-exclusive-compare-preserve = Options --compare and --pres install-error-mutually-exclusive-compare-strip = Options --compare and --strip are mutually exclusive install-error-missing-file-operand = missing file operand install-error-missing-destination-operand = missing destination file operand after { $path } -install-error-failed-to-remove = Failed to remove existing file { $path }. Error: { $error } +install-error-failed-to-access = failed to access { $path } +install-error-failed-to-remove = cannot remove { $path } +install-error-cannot-create-file = cannot create regular file { $path } # Warning messages install-warning-compare-ignored = the --compare (-C) option is ignored when you specify a mode with non-permission bits diff --git a/src/uu/install/locales/fr-FR.ftl b/src/uu/install/locales/fr-FR.ftl index 330ceb7b477..29077e0e984 100644 --- a/src/uu/install/locales/fr-FR.ftl +++ b/src/uu/install/locales/fr-FR.ftl @@ -48,7 +48,9 @@ install-error-mutually-exclusive-compare-preserve = Les options --compare et --p install-error-mutually-exclusive-compare-strip = Les options --compare et --strip sont mutuellement exclusives install-error-missing-file-operand = opérande de fichier manquant install-error-missing-destination-operand = opérande de fichier de destination manquant après { $path } -install-error-failed-to-remove = Échec de la suppression du fichier existant { $path }. Erreur : { $error } +install-error-failed-to-access = impossible d'accéder à { $path } +install-error-failed-to-remove = impossible de supprimer { $path } +install-error-cannot-create-file = impossible de créer le fichier { $path } # Messages d'avertissement install-warning-compare-ignored = l'option --compare (-C) est ignorée quand un mode est indiqué avec des bits non liés à des droits diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 84eefdcc870..93b634031c3 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -678,7 +678,7 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { return copy_files_into_dir(sources, &target, b); } - if target.is_file() || is_new_file_path(&target) { + if (target.exists() && !target.is_dir()) || is_new_file_path(&target) { copy(source, &target, b) } else { Err(InstallError::InvalidTarget(target).into()) @@ -820,6 +820,12 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> { } } + if let Err(e) = to.try_exists() { + return Err(e.map_err_context( + || translate!("install-error-failed-to-access", "path" => to.quote()), + )); + } + if to.is_dir() && !from.is_dir() { return Err(InstallError::OverrideDirectoryFailed( to.to_path_buf().clone(), @@ -833,10 +839,9 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> { // appears at this path between the remove and create, it will fail safely if let Err(e) = fs::remove_file(to) { if e.kind() != std::io::ErrorKind::NotFound { - show_error!( - "{}", - translate!("install-error-failed-to-remove", "path" => to.quote(), "error" => format!("{e:?}")) - ); + return Err(e.map_err_context( + || translate!("install-error-failed-to-remove", "path" => to.quote()), + )); } } @@ -846,7 +851,8 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> { .write(true) .create_new(true) .mode(0o600) - .open(to)?; + .open(to) + .map_err_context(|| translate!("install-error-cannot-create-file", "path" => to.quote()))?; copy_stream(&mut handle, &mut dest).map_err(|err| { InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err.to_string()) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 32cdbee8ca0..96501b76f11 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -119,6 +119,30 @@ fn test_install_ancestors_mode_directories() { assert_eq!(0o40_200_u32, at.metadata(target_dir).permissions().mode()); } +#[test] +#[cfg(target_os = "linux")] +fn test_install_cannot_remove_destination() { + if geteuid() == 0 { + return; + } + new_ucmd!() + .args(&["/dev/null", "/dev/full"]) + .fails() + .stderr_only("install: cannot remove '/dev/full': Permission denied\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_install_cannot_create_destination() { + if geteuid() == 0 { + return; + } + new_ucmd!() + .args(&["/dev/null", "/root/file"]) + .fails() + .stderr_only("install: cannot create regular file '/root/file': Permission denied\n"); +} + #[test] fn test_install_ancestors_mode_directories_with_file() { let (at, mut ucmd) = at_and_ucmd!();