Skip to content
Open
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 @@ -37,7 +37,7 @@ use uucore::translate;
use uucore::{format_usage, show, show_error, show_if_err, show_warning};

#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::{DirBuilderExt, MetadataExt};
#[cfg(unix)]
use std::os::unix::prelude::OsStrExt;

Expand Down Expand Up @@ -211,10 +211,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 @@ -521,11 +523,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 @@ -737,8 +739,6 @@ fn standard(mut paths: Vec<OsString>, b: &Behavior) -> UResult<()> {

#[cfg(all(unix, 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 @@ -779,7 +779,10 @@ fn standard(mut paths: Vec<OsString>, b: &Behavior) -> UResult<()> {
}
}
#[cfg(any(not(unix), 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
96 changes: 76 additions & 20 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 @@ -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,69 @@ 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 in a child to avoid inherited writable script fds (see write_strip_program).
scene
.cmd("sh")
.env("STRIP_SCRIPT", "#!/bin/sh\numask > \"$1\"\n")
.arg("-c")
.arg("printf '%s' \"$STRIP_SCRIPT\" > probe && chmod 755 probe")
.succeeds();
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");
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 Down
Loading