diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index cacaad1af19..2fd55eaf887 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -89,6 +89,7 @@ nocache nocreat noctty noerror +noexec nofollow nolinks nonblock diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 0ac59df17be..d079ea8b8a0 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -9,12 +9,13 @@ mod error; use crate::error::ChrootError; use clap::{Arg, ArgAction, Command}; use std::ffi::CString; -use std::io::Error; +use std::io::{Error, ErrorKind}; use std::os::unix::prelude::OsStrExt; +use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; -use std::process; +use std::process::Command as ProcessCommand; use uucore::entries::{Locate, Passwd, grp2gid, usr2uid}; -use uucore::error::{UResult, UUsageError, set_exit_code}; +use uucore::error::{UResult, UUsageError}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::{format_usage, show}; @@ -205,33 +206,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { assert!(!command.is_empty()); let chroot_command = command[0]; - let chroot_args = &command[1..]; // NOTE: Tests can only trigger code beyond this point if they're invoked with root permissions set_context(&options)?; - let pstatus = match process::Command::new(chroot_command) - .args(chroot_args) - .status() - { - Ok(status) => status, - Err(e) => { - return Err(if e.kind() == std::io::ErrorKind::NotFound { - ChrootError::CommandNotFound(command[0].to_string(), e) - } else { - ChrootError::CommandFailed(command[0].to_string(), e) - } - .into()); - } - }; + let err = ProcessCommand::new(chroot_command) + .args(&command[1..]) + .exec(); - let code = if pstatus.success() { - 0 + Err(if err.kind() == ErrorKind::NotFound { + ChrootError::CommandNotFound(chroot_command.to_owned(), err) } else { - pstatus.code().unwrap_or(-1) - }; - set_exit_code(code); - Ok(()) + ChrootError::CommandFailed(chroot_command.to_owned(), err) + } + .into()) } pub fn uu_app() -> Command { diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 38c3727b1cd..adeaf32bf6c 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -188,6 +188,53 @@ fn test_default_shell() { } } +#[test] +fn test_chroot_command_not_found_error() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let dir = "CHROOT_DIR"; + at.mkdir(dir); + + let missing = "definitely_missing_command"; + + if let Ok(result) = run_ucmd_as_root(&ts, &[dir, missing]) { + result + .failure() + .code_is(127) + .stderr_contains(format!("failed to run command '{missing}'")) + .stderr_contains("No such file or directory"); + } else { + print!("Test skipped; requires root user"); + } +} + +#[test] +fn test_chroot_command_permission_denied_error() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let dir = "CHROOT_DIR"; + at.mkdir(dir); + + let script_path = format!("{dir}/noexec.sh"); + at.write(&script_path, "#!/bin/sh\necho unreachable\n"); + #[cfg(not(windows))] + { + at.set_mode(&script_path, 0o644); + } + + if let Ok(result) = run_ucmd_as_root(&ts, &[dir, "/noexec.sh"]) { + result + .failure() + .code_is(126) + .stderr_contains("failed to run command '/noexec.sh'") + .stderr_contains("Permission denied"); + } else { + print!("Test skipped; requires root user"); + } +} + #[test] fn test_chroot() { let ts = TestScenario::new(util_name!()); @@ -208,6 +255,27 @@ fn test_chroot() { } } +#[test] +fn test_chroot_retains_uid_gid() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let dir = "CHROOT_DIR"; + at.mkdir(dir); + + if let Ok(result) = run_ucmd_as_root(&ts, &[dir, "id", "-u"]) { + result.success().no_stderr().stdout_is("0"); + } else { + print!("Test skipped; requires root user"); + } + + if let Ok(result) = run_ucmd_as_root(&ts, &[dir, "id", "-g"]) { + result.success().no_stderr().stdout_is("0"); + } else { + print!("Test skipped; requires root user"); + } +} + #[test] fn test_chroot_skip_chdir_not_root() { let (at, mut ucmd) = at_and_ucmd!();