diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 55097c1bb..66105b620 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -7,15 +7,15 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) NEWROOT Userspec pstatus +mod error; -#[macro_use] -extern crate uucore; +use crate::error::ChrootError; use clap::{crate_version, App, Arg}; use std::ffi::CString; use std::io::Error; use std::path::Path; use std::process::Command; -use uucore::display::Quotable; +use uucore::error::{set_exit_code, UResult}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::{entries, InvalidEncodingHandling}; @@ -31,7 +31,8 @@ mod options { pub const COMMAND: &str = "command"; } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -44,19 +45,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let newroot: &Path = match matches.value_of(options::NEWROOT) { Some(v) => Path::new(v), - None => crash!( - 1, - "Missing operand: NEWROOT\nTry '{} --help' for more information.", - uucore::execution_phrase() - ), + None => return Err(ChrootError::MissingNewRoot.into()), }; if !newroot.is_dir() { - crash!( - 1, - "cannot change root directory to {}: no such directory", - newroot.quote() - ); + return Err(ChrootError::NoSuchDirectory(format!("{}", newroot.display())).into()); } let commands = match matches.values_of(options::COMMAND) { @@ -82,29 +75,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let chroot_args = &command[1..]; // NOTE: Tests can only trigger code beyond this point if they're invoked with root permissions - set_context(newroot, &matches); + set_context(newroot, &matches)?; - let pstatus = Command::new(chroot_command) - .args(chroot_args) - .status() - .unwrap_or_else(|e| { - // TODO: Exit status: - // 125 if chroot itself fails - // 126 if command is found but cannot be invoked - // 127 if command cannot be found - crash!( - 1, - "failed to run command {}: {}", - command[0].to_string().quote(), - e - ) - }); + let pstatus = match Command::new(chroot_command).args(chroot_args).status() { + Ok(status) => status, + Err(e) => return Err(ChrootError::CommandFailed(command[0].to_string(), e).into()), + }; - if pstatus.success() { + let code = if pstatus.success() { 0 } else { pstatus.code().unwrap_or(-1) - } + }; + set_exit_code(code); + Ok(()) } pub fn uu_app() -> App<'static, 'static> { @@ -157,7 +141,7 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn set_context(root: &Path, options: &clap::ArgMatches) { +fn set_context(root: &Path, options: &clap::ArgMatches) -> UResult<()> { let userspec_str = options.value_of(options::USERSPEC); let user_str = options.value_of(options::USER).unwrap_or_default(); let group_str = options.value_of(options::GROUP).unwrap_or_default(); @@ -166,7 +150,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { Some(u) => { let s: Vec<&str> = u.split(':').collect(); if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { - crash!(1, "invalid userspec: {}", u.quote()) + return Err(ChrootError::InvalidUserspec(u.to_string()).into()); }; s } @@ -179,44 +163,40 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { (userspec[0], userspec[1]) }; - enter_chroot(root); + enter_chroot(root)?; - set_groups_from_str(groups_str); - set_main_group(group); - set_user(user); + set_groups_from_str(groups_str)?; + set_main_group(group)?; + set_user(user)?; + Ok(()) } -fn enter_chroot(root: &Path) { +fn enter_chroot(root: &Path) -> UResult<()> { std::env::set_current_dir(root).unwrap(); let err = unsafe { chroot(CString::new(".").unwrap().as_bytes_with_nul().as_ptr() as *const libc::c_char) }; - if err != 0 { - crash!( - 1, - "cannot chroot to {}: {}", - root.quote(), - Error::last_os_error() - ) - }; + if err == 0 { + Ok(()) + } else { + Err(ChrootError::CannotEnter(format!("{}", root.display()), Error::last_os_error()).into()) + } } -fn set_main_group(group: &str) { +fn set_main_group(group: &str) -> UResult<()> { if !group.is_empty() { let group_id = match entries::grp2gid(group) { Ok(g) => g, - _ => crash!(1, "no such group: {}", group.maybe_quote()), + _ => return Err(ChrootError::NoSuchGroup(group.to_string()).into()), }; let err = unsafe { setgid(group_id) }; if err != 0 { - crash!( - 1, - "cannot set gid to {}: {}", - group_id, - Error::last_os_error() - ) + return Err( + ChrootError::SetGidFailed(group_id.to_string(), Error::last_os_error()).into(), + ); } } + Ok(()) } #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] @@ -229,33 +209,33 @@ fn set_groups(groups: Vec) -> libc::c_int { unsafe { setgroups(groups.len() as libc::size_t, groups.as_ptr()) } } -fn set_groups_from_str(groups: &str) { +fn set_groups_from_str(groups: &str) -> UResult<()> { if !groups.is_empty() { - let groups_vec: Vec = groups - .split(',') - .map(|x| match entries::grp2gid(x) { + let mut groups_vec = vec![]; + for group in groups.split(',') { + let gid = match entries::grp2gid(group) { Ok(g) => g, - _ => crash!(1, "no such group: {}", x), - }) - .collect(); + Err(_) => return Err(ChrootError::NoSuchGroup(group.to_string()).into()), + }; + groups_vec.push(gid); + } let err = set_groups(groups_vec); if err != 0 { - crash!(1, "cannot set groups: {}", Error::last_os_error()) + return Err(ChrootError::SetGroupsFailed(Error::last_os_error()).into()); } } + Ok(()) } -fn set_user(user: &str) { +fn set_user(user: &str) -> UResult<()> { if !user.is_empty() { let user_id = entries::usr2uid(user).unwrap(); let err = unsafe { setuid(user_id as libc::uid_t) }; if err != 0 { - crash!( - 1, - "cannot set user to {}: {}", - user.maybe_quote(), - Error::last_os_error() - ) + return Err( + ChrootError::SetUserFailed(user.to_string(), Error::last_os_error()).into(), + ); } } + Ok(()) } diff --git a/src/uu/chroot/src/error.rs b/src/uu/chroot/src/error.rs new file mode 100644 index 000000000..ebf8f6212 --- /dev/null +++ b/src/uu/chroot/src/error.rs @@ -0,0 +1,81 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +// spell-checker:ignore NEWROOT Userspec userspec +//! Errors returned by chroot. +use std::fmt::Display; +use std::io::Error; +use uucore::display::Quotable; +use uucore::error::UError; + +/// Errors that can happen while executing chroot. +#[derive(Debug)] +pub enum ChrootError { + /// Failed to enter the specified directory. + CannotEnter(String, Error), + + /// Failed to execute the specified command. + CommandFailed(String, Error), + + /// The given user and group specification was invalid. + InvalidUserspec(String), + + /// The new root directory was not given. + MissingNewRoot, + + /// Failed to find the specified group. + NoSuchGroup(String), + + /// The given directory does not exist. + NoSuchDirectory(String), + + /// The call to `setgid()` failed. + SetGidFailed(String, Error), + + /// The call to `setgroups()` failed. + SetGroupsFailed(Error), + + /// The call to `setuid()` failed. + SetUserFailed(String, Error), +} + +impl std::error::Error for ChrootError {} + +impl UError for ChrootError { + // TODO: Exit status: + // 125 if chroot itself fails + // 126 if command is found but cannot be invoked + // 127 if command cannot be found + fn code(&self) -> i32 { + 1 + } +} + +impl Display for ChrootError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ChrootError::CannotEnter(s, e) => write!(f, "cannot chroot to {}: {}", s.quote(), e,), + ChrootError::CommandFailed(s, e) => { + write!(f, "failed to run command {}: {}", s.to_string().quote(), e,) + } + ChrootError::InvalidUserspec(s) => write!(f, "invalid userspec: {}", s.quote(),), + ChrootError::MissingNewRoot => write!( + f, + "Missing operand: NEWROOT\nTry '{} --help' for more information.", + uucore::execution_phrase(), + ), + ChrootError::NoSuchGroup(s) => write!(f, "no such group: {}", s.maybe_quote(),), + ChrootError::NoSuchDirectory(s) => write!( + f, + "cannot change root directory to {}: no such directory", + s.quote(), + ), + ChrootError::SetGidFailed(s, e) => write!(f, "cannot set gid to {}: {}", s, e), + ChrootError::SetGroupsFailed(e) => write!(f, "cannot set groups: {}", e), + ChrootError::SetUserFailed(s, e) => { + write!(f, "cannot set user to {}: {}", s.maybe_quote(), e) + } + } + } +}