Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/uu/install/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ install-error-invalid-user = invalid user: { $user }
install-error-invalid-group = invalid group: { $group }
install-error-omitting-directory = omitting directory { $path }
install-error-not-a-directory = failed to access { $path }: Not a directory
install-error-target-dir-access = failed to access { $path }: { $error }
install-error-existing-file-not-directory = cannot create directory { $path }: File exists
install-error-override-directory-failed = cannot overwrite directory { $dir } with non-directory { $file }
install-error-same-file = { $file1 } and { $file2 } are the same file
Expand Down
1 change: 1 addition & 0 deletions src/uu/install/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ install-error-invalid-user = utilisateur invalide : { $user }
install-error-invalid-group = groupe invalide : { $group }
install-error-omitting-directory = omission du répertoire { $path }
install-error-not-a-directory = échec de l'accès à { $path } : N'est pas un répertoire
install-error-target-dir-access = échec de l'accès à { $path } : { $error }
install-error-existing-file-not-directory = impossible de créer le répertoire { $path } : Le fichier existe
install-error-override-directory-failed = impossible d'écraser le répertoire { $dir } avec un non-répertoire { $file }
install-error-same-file = { $file1 } et { $file2 } sont le même fichier
Expand Down
14 changes: 13 additions & 1 deletion src/uu/install/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ enum InstallError {
#[error("{}", translate!("install-error-not-a-directory", "path" => .0.quote()))]
NotADirectory(PathBuf),

#[error("{}", translate!("install-error-target-dir-access", "path" => .0.quote(), "error" => strip_errno(.1)))]
TargetDirAccess(PathBuf, #[source] std::io::Error),

#[error("{}", translate!("install-error-existing-file-not-directory", "path" => .0.quote()))]
ExistingFileNotADirectory(PathBuf),

Expand Down Expand Up @@ -759,7 +762,9 @@ fn standard(mut paths: Vec<OsString>, b: &Behavior) -> UResult<()> {
}
}

if sources.len() > 1 {
// A `-t` target is always the directory to install into, never the file
// to install as, even with a single source.
if sources.len() > 1 || b.target_dir.is_some() {
copy_files_into_dir(sources, &target, b)
} else {
let source = sources.first().unwrap();
Expand Down Expand Up @@ -835,6 +840,13 @@ fn metadata_for_source(path: &Path) -> UResult<fs::Metadata> {
///
fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UResult<()> {
if !target_dir.is_dir() {
if b.target_dir.is_some() {
// GNU names why the `-t` directory cannot be used.
let reason = metadata(target_dir)
.err()
.unwrap_or_else(|| rustix::io::Errno::NOTDIR.into());
return Err(InstallError::TargetDirAccess(target_dir.to_path_buf(), reason).into());
}
return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into());
}
let mut installed_destinations: HashSet<PathBuf> = HashSet::with_capacity(files.len());
Expand Down
26 changes: 26 additions & 0 deletions tests/by-util/test_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2203,6 +2203,32 @@ fn test_target_file_ends_with_slash() {
.stderr_contains("failed to access 'dir/target_file/': Not a directory");
}

/// Without `-D`, a `-t` target that is not a directory is refused, even with a
/// single source, rather than installed over or created as a file.
#[test]
fn test_install_target_dir_not_a_directory() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("source", "new");
at.write("regular", "old");
at.symlink_file("nowhere", "dangling");

for (target, reason) in [
("regular", "Not a directory"),
("dangling", "No such file or directory"),
("missing", "No such file or directory"),
] {
scene
.ucmd()
.args(&["-t", target, "source"])
.fails()
.stderr_only(format!("install: failed to access '{target}': {reason}\n"));
}
assert_eq!(at.read("regular"), "old");
assert!(at.is_symlink("dangling"));
assert!(!at.file_exists("missing"));
}

#[test]
fn test_install_root_combined() {
let ts = TestScenario::new(util_name!());
Expand Down
Loading