diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 85c3a02d3..33f5c5223 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -6,13 +6,14 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) NEWROOT Userspec pstatus +// spell-checker:ignore (ToDO) NEWROOT Userspec pstatus chdir mod error; use crate::error::ChrootError; use clap::{crate_version, Arg, Command}; use std::ffi::CString; use std::io::Error; +use std::os::unix::prelude::OsStrExt; use std::path::Path; use std::process; use uucore::error::{set_exit_code, UClapError, UResult}; @@ -29,6 +30,7 @@ mod options { pub const GROUPS: &str = "groups"; pub const USERSPEC: &str = "userspec"; pub const COMMAND: &str = "command"; + pub const SKIP_CHDIR: &str = "skip-chdir"; } #[uucore::main] @@ -144,6 +146,15 @@ pub fn uu_app<'a>() -> Command<'a> { ) .value_name("USER:GROUP"), ) + .arg( + Arg::new(options::SKIP_CHDIR) + .long(options::SKIP_CHDIR) + .help( + "Use this option to not change the working directory \ + to / after changing the root directory to newroot, \ + i.e., inside the chroot.", + ), + ) .arg( Arg::new(options::COMMAND) .value_hint(clap::ValueHint::CommandName) @@ -158,6 +169,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) -> UResult<()> { let user_str = options.value_of(options::USER).unwrap_or_default(); let group_str = options.value_of(options::GROUP).unwrap_or_default(); let groups_str = options.value_of(options::GROUPS).unwrap_or_default(); + let skip_chdir = options.contains_id(options::SKIP_CHDIR); let userspec = match userspec_str { Some(u) => { let s: Vec<&str> = u.split(':').collect(); @@ -175,7 +187,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) -> UResult<()> { (userspec[0], userspec[1]) }; - enter_chroot(root)?; + enter_chroot(root, skip_chdir)?; set_groups_from_str(groups_str)?; set_main_group(group)?; @@ -183,12 +195,20 @@ fn set_context(root: &Path, options: &clap::ArgMatches) -> UResult<()> { Ok(()) } -fn enter_chroot(root: &Path) -> UResult<()> { - std::env::set_current_dir(root).unwrap(); +fn enter_chroot(root: &Path, skip_chdir: bool) -> UResult<()> { let err = unsafe { - chroot(CString::new(".").unwrap().as_bytes_with_nul().as_ptr() as *const libc::c_char) + chroot( + CString::new(root.as_os_str().as_bytes().to_vec()) + .unwrap() + .as_bytes_with_nul() + .as_ptr() as *const libc::c_char, + ) }; + if err == 0 { + if !skip_chdir { + std::env::set_current_dir(root).unwrap(); + } Ok(()) } else { Err(ChrootError::CannotEnter(format!("{}", root.display()), Error::last_os_error()).into()) diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 6d25f2906..c814b5815 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore (words) araba newroot userspec +// spell-checker:ignore (words) araba newroot userspec chdir pwd's use crate::common::util::*; @@ -112,15 +112,72 @@ fn test_default_shell() { at.mkdir(dir); let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()); - let _expected = format!( + let expected = format!( "chroot: failed to run command '{}': No such file or directory", shell ); - // TODO: [2021-09; jhscheer] uncomment if/when #2692 gets merged - // if let Ok(result) = run_ucmd_as_root(&ts, &[dir]) { - // result.stderr_contains(expected); - // } else { - // print!("TEST SKIPPED"); - // } + if let Ok(result) = run_ucmd_as_root(&ts, &[dir]) { + result.stderr_contains(expected); + } else { + print!("TEST SKIPPED"); + } +} + +#[test] +fn test_chroot() { + 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, "whoami"]) { + result.success().no_stderr().stdout_is("root"); + } else { + print!("Test skipped; requires root user"); + } + + if let Ok(result) = run_ucmd_as_root(&ts, &[dir, "pwd"]) { + result.success().no_stderr().stdout_is("/"); + } else { + print!("Test skipped; requires root user"); + } +} + +#[test] +fn test_chroot_skip_chdir() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let dir = "CHROOT_DIR"; + at.mkdir(dir); + let env_cd = std::env::current_dir().unwrap(); + if let Ok(result) = run_ucmd_as_root(&ts, &[dir, "--skip-chdir", "pwd"]) { + // Should return the same path + assert_eq!( + result.success().no_stderr().stdout_str(), + env_cd.to_str().unwrap() + ); + } else { + print!("Test skipped; requires root user"); + } +} + +#[test] +fn test_chroot_extra_arg() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let dir = "CHROOT_DIR"; + at.mkdir(dir); + let env_cd = std::env::current_dir().unwrap(); + // Verify that -P is pwd's and not chroot + if let Ok(result) = run_ucmd_as_root(&ts, &[dir, "pwd", "-P"]) { + assert_eq!( + result.success().no_stderr().stdout_str(), + env_cd.to_str().unwrap() + ); + } else { + print!("Test skipped; requires root user"); + } } diff --git a/util/build-gnu.sh b/util/build-gnu.sh index a63afd976..2abf062f3 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -3,7 +3,7 @@ # # UU_MAKE_PROFILE == 'debug' | 'release' ## build profile for *uutils* build; may be supplied by caller, defaults to 'debug' -# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW baddecode submodules ; (vars/env) SRCDIR vdir rcexp +# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW baddecode submodules ; (vars/env) SRCDIR vdir rcexp xpart set -e @@ -184,6 +184,10 @@ sed -i "s/ {ERR_SUBST=>\"s\/(unrecognized|unknown) option \[-' \]\*foobar\[' \] # Remove the check whether a util was built. Otherwise tests against utils like "arch" are not run. sed -i "s|require_built_ |# require_built_ |g" init.cfg +# Some tests are executed with the "nobody" user. +# The check to verify if it works is based on the GNU coreutils version +# making it too restrictive for us +sed -i "s|\$PACKAGE_VERSION|[0-9]*|g" tests/rm/fail-2eperm.sh tests/mv/sticky-to-xpart.sh init.cfg # usage_vs_getopt.sh is heavily modified as it runs all the binaries # with the option -/ is used, clap is returning a better error than GNU's. Adjust the GNU test