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
30 changes: 26 additions & 4 deletions src/uu/install/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,13 @@ static ARG_FILES: &str = "files";
///
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// Match GNU install: zero the process umask so ancestor directories and
// target files are created with exact modes rather than umask-modified ones.
// chmod is applied explicitly for the target, and ancestors always use
// DEFAULT_MODE (0755) without umask interference.
#[cfg(unix)]
uucore::mode::zero_umask();

let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;

let paths: Vec<OsString> = matches
Expand Down Expand Up @@ -493,8 +500,23 @@ fn directory(paths: &[OsString], b: &Behavior) -> UResult<()> {
//
// 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.
// DEFAULT_MODE (0755). We use DirBuilder with an explicit mode
// rather than fs::create_dir_all so that the zeroed umask
// (set in uumain) does not cause ancestors to be created at 0777.
#[cfg(unix)]
{
use std::os::unix::fs::DirBuilderExt;
let mut builder = fs::DirBuilder::new();
builder.recursive(true).mode(DEFAULT_MODE);
if let Err(e) = builder
.create(path_to_create.as_path())
.map_err_context(|| translate!("install-error-create-dir-failed", "path" => path_to_create.as_path().quote()))
{
show!(e);
continue;
}
}
#[cfg(not(unix))]
if let Err(e) = fs::create_dir_all(path_to_create.as_path())
.map_err_context(|| translate!("install-error-create-dir-failed", "path" => path_to_create.as_path().quote()))
{
Expand Down Expand Up @@ -682,8 +704,8 @@ fn standard(mut paths: Vec<OsString>, b: &Behavior) -> UResult<()> {

#[cfg(unix)]
{
// Use DEFAULT_MODE (0o755) for created directories - this matches GNU install
// behavior. The actual mode will be modified by umask at the kernel level.
// Use DEFAULT_MODE (0o755) for created directories. umask was zeroed in
// uumain, so ancestors are created at exactly 0755, matching GNU install.
match create_dir_all_safe(to_create, DEFAULT_MODE) {
Ok(dir_fd) => {
if b.target_dir.is_none()
Expand Down
12 changes: 12 additions & 0 deletions src/uucore/src/lib/features/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,18 @@ pub fn get_umask() -> u32 {
}
}

/// Set the process umask to 0 and return the previous value.
///
/// GNU install calls umask(0) at startup so that directories and files are
/// created with exact modes rather than umask-modified ones. Call this early
/// in tools that need identical behavior.
#[cfg(unix)]
pub fn zero_umask() -> u32 {
use rustix::fs::Mode;
use rustix::process::umask;
umask(Mode::empty()).bits() as u32
}

#[cfg(test)]
mod tests {

Expand Down
5 changes: 3 additions & 2 deletions src/uucore/src/lib/features/safe_traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,9 @@ fn open_or_create_subdir(parent_fd: &DirFd, name: &OsStr, mode: u32) -> io::Resu
///
/// # Arguments
/// * `path` - The path to create directories for
/// * `mode` - The mode to use when creating new directories (e.g., 0o755). The actual
/// mode will be modified by the process umask.
/// * `mode` - The mode to use when creating new directories (e.g., 0o755). Callers
/// that require exact modes (e.g. install) should zero the process umask before
/// calling this function, as the kernel applies umask to the mode on each mkdir.
///
/// # Returns
/// A DirFd for the final created directory, or the first existing parent if
Expand Down
20 changes: 8 additions & 12 deletions tests/by-util/test_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ 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])
.succeeds()
Expand All @@ -119,8 +115,10 @@ fn test_install_ancestors_mode_directories() {
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());
// GNU install zeros umask at startup and creates ancestor dirs at exactly
// 0755 (DEFAULT_MODE). --mode applies only to the final target.
assert_eq!(0o40_755_u32, at.metadata(ancestor1).permissions().mode());
assert_eq!(0o40_755_u32, at.metadata(ancestor2).permissions().mode());

// Expected mode only on the target_dir.
assert_eq!(0o40_200_u32, at.metadata(target_dir).permissions().mode());
Expand All @@ -135,10 +133,6 @@ 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);

Expand All @@ -150,8 +144,10 @@ fn test_install_ancestors_mode_directories_with_file() {
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());
// GNU install zeros umask at startup and creates ancestor dirs at exactly
// 0755 (DEFAULT_MODE). --mode applies only to the final target.
assert_eq!(0o40_755_u32, at.metadata(ancestor1).permissions().mode());
assert_eq!(0o40_755_u32, at.metadata(ancestor2).permissions().mode());

// Expected mode only on the target_file.
assert_eq!(0o100_200_u32, at.metadata(target_file).permissions().mode());
Expand Down
Loading