diff --git a/Cargo.toml b/Cargo.toml index 1bf3c66..4051284 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ mode-buffered = ["jemalloc", "bytes"] # * Statically sized (the program can infer the size of standard input.) # * The standard input file/buffer pipe size is large enough to pre-allocate enough splicing space to use up the rest of your physical RAM. # (This will very likely not happen unless you're specifically trying to make it happen, however.) -memfile = ["bitflags", "lazy_static"] +memfile = ["bitflags"] # `memfile`: When unable to determine the size of the input, preallocate the buffer to a multiple of the system page-size before writing to it. This can save extra `ftruncate()` calls, but will also result in the buffer needing to be truncated to the correct size at the end if the sizes as not matched. # @@ -95,5 +95,5 @@ recolored = { version = "1.9.3", optional = true } memchr = "2.4.1" lazy_format = "1.10.0" bitflags = {version = "1.3.2", optional = true } -lazy_static = { version = "1.4.0", optional = true } +lazy_static = "1.4.0" #TODO: XXX: Required for dispersed error messages #smallvec = { version = "1.9.0", features = ["write", "const_generics", "const_new", "may_dangle", "union"] } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..9c67ded --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,203 @@ +//! Errors and helpers for errors. +use super::*; +use std::{ + fmt, + error, +}; + +pub const DEFAULT_USE_ENV: bool = std::option_env!("NO_RT_ERROR_CTL").is_none(); + +pub type DispersedResult = Result>; + +pub const ENV_NAME: &'static str = "RUST_VERBOSE"; +const DEFAULT_ENV_VERBOSE: DispersedVerbosity = match std::option_env!("DEFAULT_ERROR") { + Some("1") | + Some("V") | + Some("verbose") | + Some("VERBOSE") | + Some("v") => DispersedVerbosity::Verbose, + Some("0") | + _ => DispersedVerbosity::static_default(), +}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] +#[repr(u8)] +pub enum DispersedVerbosity +{ + Simple = 0, + Verbose = 1, +} + +impl From for bool +{ + #[inline] + fn from(from: DispersedVerbosity) -> Self + { + from.is_verbose() + } +} + + +impl Default for DispersedVerbosity +{ + #[inline] + fn default() -> Self + { + DEFAULT_ENV_VERBOSE + } +} + + +impl DispersedVerbosity +{ + #[inline(always)] + const fn static_default() -> Self + { + Self::Simple + } + + #[inline(always)] + pub const fn is_verbose(self) -> bool + { + (self as u8) != 0 + } +} + +fn get_env_value() -> DispersedVerbosity +{ + match std::env::var_os(ENV_NAME) { + Some(mut value) => { + value.make_ascii_lowercase(); + match value { + "1" | + "v" | + "verbose" => DispersedVerbosity::Verbose, + "0" | + "s" | + "simple" => DispersedVerbosity::Simple, + _ => DispersedVerbosity::default(), + } + }, + None => Default::default(), + } +} + +#[inline] +pub fn dispersed_env_verbosity() -> DispersedVerbosity +{ + lazy_static! { + static ref VALUE: DispersedVerbosity = get_env_value(); + } + *VALUE +} + +/// A simpler error message when returning an `eyre::Report` from main. +pub struct Dispersed(eyre::Report); + +impl From for Dispersed +{ + #[inline] + fn from(from: eyre::Report) -> Self + { + Self(from) + } +} + +impl Dispersed +{ + #[inline] + pub fn into_inner(self) -> eyre::Report + { + self.0 + } +} + +impl Dispersed +{ + #[inline(always)] + pub const fn obey_env(self) -> Dispersed + { + Dispersed(self.0) + } +} + +impl Dispersed +{ + #[inline(always)] + pub const fn ignore_env(self) -> Dispersed + { + Dispersed(self.1) + } +} + +impl Dispersed +{ + #[inline(always)] + pub const fn set_env(self) -> Dispersed + { + Dispersed(self.0) + } +} + +impl error::Error for Dispersed +{ + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + self.0 + } +} +impl error::Error for Dispersed +{ + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + self.0 + } +} + +impl fmt::Debug for Dispersed +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + fmt::Display::fmt(&self.0, f) + } +} + + +impl fmt::Display for Dispersed +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + fmt::Debug::fmt(&self.0, f) + } +} + + +impl fmt::Debug for Dispersed +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + if dispersed_env_verbosity().is_verbose() { + fmt::Debug::fmt(&self.0, f) + } else { + fmt::Display::fmt(&self.0, f) + } + } +} + + +impl fmt::Display for Dispersed +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + if dispersed_env_verbosity().is_verbose() { + fmt::Display::fmt(&self.0, f) + } else { + fmt::Debug::fmt(&self.0, f) + } + } +} + diff --git a/src/exec.rs b/src/exec.rs new file mode 100644 index 0000000..2494366 --- /dev/null +++ b/src/exec.rs @@ -0,0 +1,95 @@ +//! Used for implementation of `-exec[{}]` +use super::*; +use args::Options; +use std::{ + fs, + process, + path::{ + Path, + PathBuf, + }, + ffi::{ + OsStr, + OsString, + } +}; + +/// Get a path to the file-descriptor refered to by `file`. +fn proc_file(file: &F) -> PathBuf +{ + let fd = file.as_raw_fd(); + let pid = process::id(); + format!("/proc/{pid}/fd/{fd}").into() +} + +/// Attempt to `dup()` a file descriptor into a `RawFile`. +#[inline] +fn dup_file(file: &F) -> io::Result +{ + let fd = file.as_raw_fd(); + debug_assert!(fd >= 0, "Bad input file descriptor from {} (value was {fd})", std::any::type_name::()); + let fd = unsafe { + let res = libc::dup(fd); + if res < 0 { + return Err(io::Error::last_os_error()); + } else { + res + } + }; + Ok(memfile::RawFile::take_ownership_of_unchecked(fd)) +} + +fn run_stdin(file: impl Into, filename: impl AsRef, args: I) -> io::Result +where I: IntoIterator, +{ + let file = { + let mut file: fs::File = file.into(); + //TODO: Do we need to fcntl() this to make it (the fd) RW? + file + }; + let child = process::Command::new(filename) + .args(args) + .stdin(process::Stdio::from(file)) + .stdout(process::Stdio::inherit()) + .spawn()?; + + if_trace!(info!("Spawned child process: {}", child.id())); + /*Ok(child.wait()? + .code() + .unwrap_or(-1)) //XXX: What should we do if the process terminates without a code (i.e. by a signal?) + */ + Ok(child) +} + +/// Run a single `-exec` / `-exec{}` and return the (possibly still running) child process if succeeded in spawning. +/// +/// The caller must wait for all child processes to exit before the parent does. +#[inline] +pub fn run_single(file: &F, opt: args::ExecMode) -> io::Result +{ + let input = dup_file(file)?; + + match opt { + args::ExecMode::Positional { command, args } => { + todo!("implement this with `proc_file(&file)`") + }, + args::ExecMode::Stdin { command, args } => { + run_stdin(input, command, args) + } + } +} + +/// Spawn all `-exec/{}` commands and return all running children. +/// +/// # Returns +/// An iterator of each (possibly running) spawned child, or the error that occoured when trying to spawn that child from the `exec` option in `opt`. +pub fn spawn_from(file: &F, opt: Options) -> io::Result + 'static>> +{ + todo!("Loop through `opt.into_exec()`, map the call to `|x| run_single(file, x)`, and return that iterator") +} + +#[inline] +pub fn spawn_from(file: &F, opt: Options) -> io::Result + 'static>> +{ + todo!("Map `spawn_from(...)` and wait for each child to terminate concurrently. Then return an iterator or the return codes or spawning errors for that now terminated child.") +} diff --git a/src/main.rs b/src/main.rs index 02dc898..44f35f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ #[cfg(feature="logging")] #[macro_use] extern crate tracing; -#[cfg(feature="memfile")] +//#[cfg(feature="memfile")] #[macro_use] extern crate lazy_static; /// Run this statement only if `tracing` is enabled @@ -79,12 +79,15 @@ macro_rules! function { } mod ext; use ext::*; +mod errors; mod sys; use sys::{ try_get_size, tell_file, }; +mod exec; + mod buffers; use buffers::prelude::*; @@ -117,7 +120,6 @@ trait ModeReturn: Send { } - fn init() -> eyre::Result<()> { cfg_if!{ if #[cfg(feature="logging")] { @@ -486,7 +488,6 @@ mod work { } } - #[cfg_attr(feature="logging", instrument(err))] #[inline(always)] unsafe fn close_raw_fileno(fd: RawFd) -> io::Result<()> @@ -529,7 +530,7 @@ fn parse_args() -> eyre::Result } #[cfg_attr(feature="logging", instrument(err))] -fn main() -> eyre::Result<()> { +fn main() -> errors::DispersedResult<()> { init()?; feature_check()?; if_trace!(debug!("initialised")); diff --git a/src/memfile.rs b/src/memfile.rs index b57a723..ba37829 100644 --- a/src/memfile.rs +++ b/src/memfile.rs @@ -110,7 +110,7 @@ impl RawFile } #[inline(always)] - const fn take_ownership_of_unchecked(fd: RawFd) -> Self + pub(crate) const fn take_ownership_of_unchecked(fd: RawFd) -> Self { //! **Internal**: Non-`unsafe` and `const` version of `take_ownership_of_raw_unchecked()` //! : assumes `fd` is `>= 0`