From 79721444ba92a1477b842f29cfde157cde8c0d97 Mon Sep 17 00:00:00 2001 From: Avril Date: Tue, 28 Feb 2023 15:57:05 +0000 Subject: [PATCH] Implemented `-exec/{}` implementation functionality. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added file sealing for making `-exec/{}` better (esp. for memfile mode.) Fixed `errors` being shit. Removed `memfile-preallocate` from `mode-memfile` feature: It is unneeded. Fortune for collect's current commit: Middle blessing − 中吉 --- Cargo.toml | 2 +- src/errors.rs | 50 ++++++++++++++++++++----------------- src/exec.rs | 34 ++++++++++++++++++------- src/ext.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 29 +++++++++++++++++++--- 5 files changed, 148 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4051284..0767739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ default = ["mode-memfile", "logging"] # Mode: default # Use physical-memory backed kernel file-descriptors. (see feature `memfile`.) -mode-memfile = ["memfile-preallocate"] #, "tracing/release_max_level_warn"] +mode-memfile = ["memfile"] #, "tracing/release_max_level_warn"] # Mode: alternative # Use non-physical memory allocated buffers. diff --git a/src/errors.rs b/src/errors.rs index 9c67ded..674d2d2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,24 +1,28 @@ //! Errors and helpers for errors. +//TODO: Comment how this works (controllably optional simple or complex `main()` error messages.) use super::*; use std::{ fmt, error, }; +use std::os::unix::prelude::*; 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(), -}; +lazy_static!{ + static ref 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)] @@ -43,7 +47,7 @@ impl Default for DispersedVerbosity #[inline] fn default() -> Self { - DEFAULT_ENV_VERBOSE + *DEFAULT_ENV_VERBOSE } } @@ -68,13 +72,13 @@ 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, + match value.as_bytes() { + b"1" | + b"v" | + b"verbose" => DispersedVerbosity::Verbose, + b"0" | + b"s" | + b"simple" => DispersedVerbosity::Simple, _ => DispersedVerbosity::default(), } }, @@ -115,7 +119,7 @@ impl Dispersed impl Dispersed { #[inline(always)] - pub const fn obey_env(self) -> Dispersed + pub fn obey_env(self) -> Dispersed { Dispersed(self.0) } @@ -124,16 +128,16 @@ impl Dispersed impl Dispersed { #[inline(always)] - pub const fn ignore_env(self) -> Dispersed + pub fn ignore_env(self) -> Dispersed { - Dispersed(self.1) + Dispersed(self.0) } } impl Dispersed { #[inline(always)] - pub const fn set_env(self) -> Dispersed + pub fn set_env(self) -> Dispersed { Dispersed(self.0) } @@ -143,14 +147,14 @@ impl error::Error for Dispersed { #[inline] fn source(&self) -> Option<&(dyn error::Error + 'static)> { - self.0 + self.0.source() } } impl error::Error for Dispersed { #[inline] fn source(&self) -> Option<&(dyn error::Error + 'static)> { - self.0 + self.0.source() } } diff --git a/src/exec.rs b/src/exec.rs index 2494366..171b671 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -39,17 +39,17 @@ fn dup_file(file: &F) -> io::Result Ok(memfile::RawFile::take_ownership_of_unchecked(fd)) } -fn run_stdin(file: impl Into, filename: impl AsRef, args: I) -> io::Result +fn run_stdin(file: Option>, filename: impl AsRef, args: I) -> io::Result where I: IntoIterator, { let file = { - let mut file: fs::File = file.into(); + let mut file: Option = file.map(Into::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)) + .stdin(file.map(|file| process::Stdio::from(file)).unwrap_or_else(|| process::Stdio::inherit())) .stdout(process::Stdio::inherit()) .spawn()?; @@ -71,10 +71,10 @@ pub fn run_single(file: &F, opt: args::ExecMode) -> io::Res match opt { args::ExecMode::Positional { command, args } => { - todo!("implement this with `proc_file(&file)`") + run_stdin(None::, command, args.into_iter().map(move |x| x.unwrap_or_else(|| proc_file(&input).into()))) }, args::ExecMode::Stdin { command, args } => { - run_stdin(input, command, args) + run_stdin(Some(input), command, args) } } } @@ -83,13 +83,29 @@ pub fn run_single(file: &F, opt: args::ExecMode) -> io::Res /// /// # 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>> +pub fn spawn_from<'a, F: ?Sized + AsRawFd>(file: &'a F, opt: Options) -> impl IntoIterator> + 'a { - todo!("Loop through `opt.into_exec()`, map the call to `|x| run_single(file, x)`, and return that iterator") + opt.into_opt_exec().map(|x| run_single(file, x)) + //todo!("Loop through `opt.into_exec()`, map the call to `|x| run_single(file, x)`, and return that iterator") } +/// Spawn all `-exec/{}` commands and wait for all children to complete. +/// +/// # Returns +/// An iterator of the result of spawning each child and its exit status. #[inline] -pub fn spawn_from(file: &F, opt: Options) -> io::Result + 'static>> +pub fn spawn_from_sync<'a, F: ?Sized + AsRawFd>(file: &'a F, opt: Options) -> impl IntoIterator> + 'a { - 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.") + spawn_from(file, opt).into_iter().map(move |child| -> io::Result<_> { + match child { + Ok(mut child) => { + Ok(child.wait()?.code().unwrap_or(-1)) + }, + Err(err) => { + if_trace!(error!("Failed to spawn child: {err}")); + Err(err) + } + } + }) + //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/ext.rs b/src/ext.rs index 5972715..ae6784c 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -348,3 +348,72 @@ where F: FnOnce() -> T } } } + +#[inline(always)] +pub(crate) fn map_bool(ok: bool, value: T) -> T +where T: Default +{ + if ok { + value + } else { + T::default() + } +} +pub trait SealExt +{ + fn try_seal(&self, shrink: bool, grow: bool, write: bool) -> io::Result<()>; + + #[inline] + fn sealed(self, shrink: bool, grow: bool, write: bool) -> Self + where Self: Sized { + if let Err(e) = self.try_seal(shrink, grow, write) { + panic!("Failed to apply seals: {}", io::Error::last_os_error()) + } + self + } +} +#[cfg(any(feature="memfile", feature="exec"))] +const _: () = { + impl SealExt for T + { + #[cfg_attr(feature="logging", instrument(skip(self)))] + fn sealed(self, shrink: bool, grow: bool, write: bool) -> Self + where Self: Sized { + use libc::{ + F_SEAL_GROW, F_SEAL_SHRINK, F_SEAL_WRITE, + F_ADD_SEALS, + fcntl + }; + let fd = self.as_raw_fd(); + if unsafe { + fcntl(fd, F_ADD_SEALS + , map_bool(shrink, F_SEAL_SHRINK) + | map_bool(grow, F_SEAL_GROW) + | map_bool(write, F_SEAL_WRITE)) + } < 0 { + panic!("Failed to apply seals to file descriptor {fd}: {}", io::Error::last_os_error()) + } + self + } + + #[cfg_attr(feature="logging", instrument(skip(self), err))] + fn try_seal(&self, shrink: bool, grow: bool, write: bool) -> io::Result<()> { + use libc::{ + F_SEAL_GROW, F_SEAL_SHRINK, F_SEAL_WRITE, + F_ADD_SEALS, + fcntl + }; + let fd = self.as_raw_fd(); + if unsafe { + fcntl(fd, F_ADD_SEALS + , map_bool(shrink, F_SEAL_SHRINK) + | map_bool(grow, F_SEAL_GROW) + | map_bool(write, F_SEAL_WRITE)) + } < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } + } +}; diff --git a/src/main.rs b/src/main.rs index 44f35f9..efff9a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +//#![feature(const_trait_impl)] #[macro_use] extern crate cfg_if; #[cfg(feature="logging")] @@ -32,8 +33,8 @@ macro_rules! if_trace { $( #[cfg(not(feature="logging"))] let val = { $no }; - )? - val + )? + val } } }; @@ -170,6 +171,25 @@ fn feature_check() -> eyre::Result<()> Ok(()) } +#[inline] +fn try_seal_size(file: &F) -> eyre::Result<()> +{ + //if cfg!(feature="exec") { + if let Err(err) = file.try_seal(true,true,false) + .with_section(|| format!("Raw file descriptor: {}", file.as_raw_fd()).header("Attempted seal was on")) + .with_warning(|| "This may cause consumers of -exec{} to misbehave") { + let fd = file.as_raw_fd(); + if_trace!{{ + warn!("Failed to seal file descriptor {fd}: {err}"); + eprintln!("\t{err:?}"); + }} + Err(err).wrap_err("Failed to seal file's length") + } else { + Ok(()) + } + //} +} + mod work { use super::*; #[cfg_attr(feature="logging", instrument(err))] @@ -467,6 +487,9 @@ mod work { }; if_trace!(info!("collected {} from stdin. starting write.", read)); + // Seal memfile + let _ = try_seal_size(&file); + // TODO: XXX: Currently causes crash. But if we can get this to work, leaving this in is definitely safe (as opposed to the pre-setting (see above.)) set_stdout_len(read) .wrap_err(eyre!("Failed to `ftruncate()` stdout after collection of {read} bytes")) @@ -488,7 +511,7 @@ mod work { } } -#[cfg_attr(feature="logging", instrument(err))] +#[cfg_attr(feature="logging", instrument(err))] #[inline(always)] unsafe fn close_raw_fileno(fd: RawFd) -> io::Result<()> {