Skip to content
Closed
4 changes: 3 additions & 1 deletion src/uu/install/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/uu/install/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 12 additions & 6 deletions src/uu/install/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ fn standard(mut paths: Vec<OsString>, 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())
Expand Down Expand Up @@ -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(),
Expand All @@ -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()),
));
}
}

Expand All @@ -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())
Expand Down
24 changes: 24 additions & 0 deletions tests/by-util/test_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!();
Expand Down
Loading