From b5522e1132053ff77d9051aa54c40543f401591a Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Wed, 29 Dec 2021 19:59:38 -0500 Subject: [PATCH] runcon: return UResult from uumain() function --- src/uu/runcon/src/errors.rs | 55 +++++++++++++++--- src/uu/runcon/src/runcon.rs | 107 +++++++++++++----------------------- 2 files changed, 85 insertions(+), 77 deletions(-) diff --git a/src/uu/runcon/src/errors.rs b/src/uu/runcon/src/errors.rs index 5f8258de0..082b55055 100644 --- a/src/uu/runcon/src/errors.rs +++ b/src/uu/runcon/src/errors.rs @@ -1,12 +1,22 @@ use std::ffi::OsString; -use std::fmt::Write; +use std::fmt::{Display, Formatter, Write}; use std::io; use std::str::Utf8Error; use uucore::display::Quotable; +use uucore::error::UError; pub(crate) type Result = std::result::Result; +// This list is NOT exhaustive. This command might perform an `execvp()` to run +// a different program. When that happens successfully, the exit status of this +// process will be the exit status of that program. +pub(crate) mod error_exit_status { + pub const NOT_FOUND: i32 = 127; + pub const COULD_NOT_EXECUTE: i32 = 126; + pub const ANOTHER_ERROR: i32 = libc::EXIT_FAILURE; +} + #[derive(thiserror::Error, Debug)] pub(crate) enum Error { #[error("No command is specified")] @@ -63,13 +73,44 @@ impl Error { } } -pub(crate) fn report_full_error(mut err: &dyn std::error::Error) -> String { - let mut desc = String::with_capacity(256); - write!(&mut desc, "{}", err).unwrap(); +pub(crate) fn write_full_error(writer: &mut W, err: &dyn std::error::Error) -> std::fmt::Result +where + W: Write, +{ + write!(writer, "{}", err)?; + let mut err = err; while let Some(source) = err.source() { err = source; - write!(&mut desc, ": {}", err).unwrap(); + write!(writer, ": {}", err)?; + } + write!(writer, ".")?; + Ok(()) +} + +#[derive(Debug)] +pub(crate) struct RunconError { + inner: Error, + code: i32, +} + +impl RunconError { + pub(crate) fn new(e: Error) -> RunconError { + RunconError::with_code(error_exit_status::ANOTHER_ERROR, e) + } + + pub(crate) fn with_code(code: i32, e: Error) -> RunconError { + RunconError { inner: e, code } + } +} + +impl std::error::Error for RunconError {} +impl UError for RunconError { + fn code(&self) -> i32 { + self.code + } +} +impl Display for RunconError { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write_full_error(f, &self.inner) } - desc.push('.'); - desc } diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs index b2f1468bd..595cf3e65 100644 --- a/src/uu/runcon/src/runcon.rs +++ b/src/uu/runcon/src/runcon.rs @@ -1,6 +1,6 @@ // spell-checker:ignore (vars) RFILE -use uucore::{show_error, show_usage_error}; +use uucore::error::{UResult, UUsageError}; use clap::{App, Arg}; use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext}; @@ -13,7 +13,8 @@ use std::{io, ptr}; mod errors; -use errors::{report_full_error, Error, Result}; +use errors::error_exit_status; +use errors::{Error, Result, RunconError}; const VERSION: &str = env!("CARGO_PKG_VERSION"); const ABOUT: &str = "Run command with specified security context."; @@ -35,16 +36,6 @@ pub mod options { pub const RANGE: &str = "range"; } -// This list is NOT exhaustive. This command might perform an `execvp()` to run -// a different program. When that happens successfully, the exit status of this -// process will be the exit status of that program. -mod error_exit_status { - pub const SUCCESS: i32 = libc::EXIT_SUCCESS; - pub const NOT_FOUND: i32 = 127; - pub const COULD_NOT_EXECUTE: i32 = 126; - pub const ANOTHER_ERROR: i32 = libc::EXIT_FAILURE; -} - fn get_usage() -> String { format!( "{0} [CONTEXT COMMAND [ARG...]]\n \ @@ -53,7 +44,8 @@ fn get_usage() -> String { ) } -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = get_usage(); let config = uu_app().usage(usage.as_ref()); @@ -65,39 +57,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match r.kind { clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { println!("{}", r); - return error_exit_status::SUCCESS; + return Ok(()); } _ => {} } } - - show_usage_error!("{}.\n", r); - return error_exit_status::ANOTHER_ERROR; + return Err(UUsageError::new( + error_exit_status::ANOTHER_ERROR, + format!("{}", r), + )); } }; match &options.mode { - CommandLineMode::Print => { - if let Err(r) = print_current_context() { - show_error!("{}", report_full_error(&r)); - return error_exit_status::ANOTHER_ERROR; - } - } - + CommandLineMode::Print => print_current_context().map_err(|e| RunconError::new(e).into()), CommandLineMode::PlainContext { context, command } => { - let (exit_status, err) = - if let Err(err) = get_plain_context(context).and_then(set_next_exec_context) { - (error_exit_status::ANOTHER_ERROR, err) - } else { - // On successful execution, the following call never returns, - // and this process image is replaced. - execute_command(command, &options.arguments) - }; - - show_error!("{}", report_full_error(&err)); - return exit_status; + get_plain_context(context) + .and_then(set_next_exec_context) + .map_err(RunconError::new)?; + // On successful execution, the following call never returns, + // and this process image is replaced. + execute_command(command, &options.arguments) } - CommandLineMode::CustomContext { compute_transition_context, user, @@ -106,34 +87,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { range, command, } => { - if let Some(command) = command { - let (exit_status, err) = if let Err(err) = get_custom_context( - *compute_transition_context, - user.as_deref(), - role.as_deref(), - the_type.as_deref(), - range.as_deref(), - command, - ) - .and_then(set_next_exec_context) - { - (error_exit_status::ANOTHER_ERROR, err) - } else { + match command { + Some(command) => { + get_custom_context( + *compute_transition_context, + user.as_deref(), + role.as_deref(), + the_type.as_deref(), + range.as_deref(), + command, + ) + .and_then(set_next_exec_context) + .map_err(RunconError::new)?; // On successful execution, the following call never returns, // and this process image is replaced. execute_command(command, &options.arguments) - }; - - show_error!("{}", report_full_error(&err)); - return exit_status; - } else if let Err(r) = print_current_context() { - show_error!("{}", report_full_error(&r)); - return error_exit_status::ANOTHER_ERROR; + } + None => print_current_context().map_err(|e| RunconError::new(e).into()), } } } - - error_exit_status::SUCCESS } pub fn uu_app() -> App<'static, 'static> { @@ -406,25 +379,19 @@ fn get_custom_context( Ok(osc) } -/// The actual return type of this function should be `Result` +/// The actual return type of this function should be `UResult`. /// However, until the *never* type is stabilized, one way to indicate to the /// compiler the only valid return type is to say "if this returns, it will /// always return an error". -fn execute_command(command: &OsStr, arguments: &[OsString]) -> (i32, Error) { - let c_command = match os_str_to_c_string(command) { - Ok(v) => v, - Err(r) => return (error_exit_status::ANOTHER_ERROR, r), - }; +fn execute_command(command: &OsStr, arguments: &[OsString]) -> UResult<()> { + let c_command = os_str_to_c_string(command).map_err(RunconError::new)?; - let argv_storage: Vec = match arguments + let argv_storage: Vec = arguments .iter() .map(AsRef::as_ref) .map(os_str_to_c_string) .collect::>() - { - Ok(v) => v, - Err(r) => return (error_exit_status::ANOTHER_ERROR, r), - }; + .map_err(RunconError::new)?; let mut argv: Vec<*const c_char> = Vec::with_capacity(arguments.len().saturating_add(2)); argv.push(c_command.as_ptr()); @@ -441,7 +408,7 @@ fn execute_command(command: &OsStr, arguments: &[OsString]) -> (i32, Error) { }; let err = Error::from_io1("Executing command", command, err); - (exit_status, err) + Err(RunconError::with_code(exit_status, err).into()) } fn os_str_to_c_string(s: &OsStr) -> Result {