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
25 changes: 14 additions & 11 deletions src/uu/install/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::fmt::Debug;
use std::fs::{self, metadata};
use std::fs::{File, OpenOptions};
use std::io::{Write, stdout};
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::{DirBuilderExt, MetadataExt};
use std::os::unix::prelude::OsStrExt;
use std::path::{MAIN_SEPARATOR, Path, PathBuf};
use std::process;
Expand Down Expand Up @@ -210,10 +210,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

let behavior = behavior(&matches, diag_args.as_deref())?;

match behavior.main_function {
// Like GNU, work with the umask cleared: new ancestor directories get
// exactly DEFAULT_MODE and the strip program sees a umask of 0.
uucore::mode::with_umask(0, || match behavior.main_function {
MainFunction::Directory => directory(&paths, &behavior),
MainFunction::Standard => standard(paths, &behavior),
}
})
}

pub fn uu_app() -> Command {
Expand Down Expand Up @@ -520,11 +522,11 @@ fn directory(paths: &[OsString], b: &Behavior) -> UResult<()> {
// create all ancestors (or components) of a directory
// regardless of the presence of the "-D" flag.
//
// NOTE: the GNU "install" sets the expected mode only for the
// target directory. All created ancestor directories will have
// the default mode. Hence it is safe to use fs::create_dir_all
// and then only modify the target's dir mode.
if let Err(e) = fs::create_dir_all(&path_to_create).map_err_context(
// Only the target gets the requested mode; newly created ancestors
// use DEFAULT_MODE, which create_dir_all would request as 0777.
let mut builder = fs::DirBuilder::new();
builder.recursive(true).mode(DEFAULT_MODE);
if let Err(e) = builder.create(&path_to_create).map_err_context(
|| translate!("install-error-create-dir-failed", "path" => path_to_create.quote()),
) {
show!(e);
Expand Down Expand Up @@ -727,8 +729,6 @@ fn standard(mut paths: Vec<OsString>, b: &Behavior) -> UResult<()> {

#[cfg(not(target_os = "redox"))]
{
// Use DEFAULT_MODE (0o755) for created directories - this matches GNU install
// behavior. The actual mode will be modified by umask at the kernel level.
match create_dir_all_safe(to_create, DEFAULT_MODE) {
Ok(dir_fd) => {
if b.target_dir.is_none()
Expand Down Expand Up @@ -769,7 +769,10 @@ fn standard(mut paths: Vec<OsString>, b: &Behavior) -> UResult<()> {
}
}
#[cfg(target_os = "redox")]
fs::create_dir_all(to_create)
fs::DirBuilder::new()
.recursive(true)
.mode(DEFAULT_MODE)
.create(to_create)
.map_err(|e| InstallError::CreateDirFailed(to_create.to_path_buf(), e))?;
}
}
Expand Down
100 changes: 75 additions & 25 deletions tests/by-util/test_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,24 +113,20 @@ fn test_install_ancestors_mode_directories() {
let target_dir = "ancestor1/ancestor2/target_dir";
let directories_arg = "-d";
let mode_arg = "--mode=200";
let probe = "probe";

at.mkdir(probe);
let default_perms = at.metadata(probe).permissions().mode();

ucmd.args(&[mode_arg, directories_arg, target_dir])
ucmd.umask(0o077)
.args(&[mode_arg, directories_arg, target_dir])
.succeeds()
.no_stderr();

assert!(at.dir_exists(ancestor1));
assert!(at.dir_exists(ancestor2));
assert!(at.dir_exists(target_dir));

assert_eq!(default_perms, at.metadata(ancestor1).permissions().mode());
assert_eq!(default_perms, at.metadata(ancestor2).permissions().mode());

// Expected mode only on the target_dir.
assert_eq!(0o40_200_u32, at.metadata(target_dir).permissions().mode());
// Ancestors use the default mode; --mode applies only to the target.
assert_eq!(0o755, at.metadata(ancestor1).permissions().mode() & 0o777);
assert_eq!(0o755, at.metadata(ancestor2).permissions().mode() & 0o777);
assert_eq!(0o200, at.metadata(target_dir).permissions().mode() & 0o777);
}

#[test]
Expand All @@ -142,26 +138,22 @@ fn test_install_ancestors_mode_directories_with_file() {
let directories_arg = "-D";
let mode_arg = "--mode=200";
let file = "file";
let probe = "probe";

at.mkdir(probe);
let default_perms = at.metadata(probe).permissions().mode();

at.touch(file);

ucmd.args(&[mode_arg, directories_arg, file, target_file])
ucmd.umask(0o077)
.args(&[mode_arg, directories_arg, file, target_file])
.succeeds()
.no_stderr();

assert!(at.dir_exists(ancestor1));
assert!(at.dir_exists(ancestor2));
assert!(at.file_exists(target_file));

assert_eq!(default_perms, at.metadata(ancestor1).permissions().mode());
assert_eq!(default_perms, at.metadata(ancestor2).permissions().mode());

// Expected mode only on the target_file.
assert_eq!(0o100_200_u32, at.metadata(target_file).permissions().mode());
// Ancestors use the default mode; --mode applies only to the target.
assert_eq!(0o755, at.metadata(ancestor1).permissions().mode() & 0o777);
assert_eq!(0o755, at.metadata(ancestor2).permissions().mode() & 0o777);
assert_eq!(0o200, at.metadata(target_file).permissions().mode() & 0o777);
}

#[test]
Expand Down Expand Up @@ -816,14 +808,14 @@ fn test_install_copy_then_compare_file_with_extra_mode() {
const STRIP_TARGET_FILE: &str = "helloworld_installed";
const STRIP_PROGRAM: &str = "#!/bin/sh\n: > \"$1\"\n";

/// Create the fake strip program from a child process. Tests run as threads of
/// Create a fake strip program from a child process. Tests run as threads of
/// one process: if this process held the script open for writing, a sibling
/// test forking at that moment would inherit the fd, and exec'ing the script
/// would then fail with ETXTBSY.
fn write_strip_program(scene: &TestScenario, name: &str) {
fn write_strip_program(scene: &TestScenario, name: &str, script: &str) {
scene
.cmd("sh")
.env("STRIP_SCRIPT", STRIP_PROGRAM)
.env("STRIP_SCRIPT", script)
.arg("-c")
.arg(format!(
"printf '%s' \"$STRIP_SCRIPT\" > {name} && chmod 755 {name}"
Expand All @@ -836,7 +828,7 @@ fn test_install_and_strip() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

write_strip_program(&scene, "strip");
write_strip_program(&scene, "strip", STRIP_PROGRAM);
at.write("source", "file contents");
let path = format!(
"{}:{}",
Expand All @@ -846,6 +838,7 @@ fn test_install_and_strip() {

scene
.ucmd()
.umask(0o777)
.env("PATH", path)
.arg("-s")
.arg("source")
Expand All @@ -856,6 +849,63 @@ fn test_install_and_strip() {
assert_eq!(at.metadata(STRIP_TARGET_FILE).len(), 0);
}

#[test]
fn test_install_strip_program_sees_zeroed_umask() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

write_strip_program(&scene, "probe", "#!/bin/sh\numask > \"$1\"\n");
at.write("source", "file contents");

scene
.ucmd()
.umask(0o077)
.args(&[
"-s",
"--strip-program",
"./probe",
"source",
STRIP_TARGET_FILE,
])
.succeeds()
.no_stderr();

// Follow GNU's behavior: the strip program sees a zeroed umask. Shells
// differ on whether they print it as 000 or 0000, so compare the value.
let reported = at.read(STRIP_TARGET_FILE);
let reported = reported.trim();
assert_eq!(
u32::from_str_radix(reported, 8).unwrap(),
0,
"strip program saw umask {reported}"
);
}

#[test]
fn test_install_and_strip_with_ancestor_directories() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

write_strip_program(&scene, "strip", STRIP_PROGRAM);
at.write("source", "file contents");
let path = format!(
"{}:{}",
at.plus(".").display(),
std::env::var("PATH").unwrap_or_default()
);
let target = "ancestor/helloworld_installed";

scene
.ucmd()
.umask(0o777)
.env("PATH", path)
.args(&["-s", "-D", "source", target])
.succeeds()
.no_stderr();

assert_eq!(at.metadata(target).len(), 0);
}

#[test]
fn test_install_no_strip_with_program() {
let source = current_exe().unwrap();
Expand All @@ -876,7 +926,7 @@ fn test_install_and_strip_with_program() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

write_strip_program(&scene, "strip-program");
write_strip_program(&scene, "strip-program", STRIP_PROGRAM);
at.write("source", "file contents");

scene
Expand Down
Loading