Implemented `-exec/{}` implementation functionality.

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 − 中吉
exec
Avril 1 year ago
parent 715fa4d5a8
commit 79721444ba
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -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.

@ -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<T, const USE_ENV: bool = DEFAULT_USE_ENV> = Result<T, Dispersed<USE_ENV>>;
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<const E: bool> Dispersed<E>
impl Dispersed<false>
{
#[inline(always)]
pub const fn obey_env(self) -> Dispersed<true>
pub fn obey_env(self) -> Dispersed<true>
{
Dispersed(self.0)
}
@ -124,16 +128,16 @@ impl Dispersed<false>
impl Dispersed<true>
{
#[inline(always)]
pub const fn ignore_env(self) -> Dispersed<false>
pub fn ignore_env(self) -> Dispersed<false>
{
Dispersed(self.1)
Dispersed(self.0)
}
}
impl<const E: bool> Dispersed<E>
{
#[inline(always)]
pub const fn set_env<const To: bool>(self) -> Dispersed<To>
pub fn set_env<const To: bool>(self) -> Dispersed<To>
{
Dispersed(self.0)
}
@ -143,14 +147,14 @@ impl error::Error for Dispersed<true>
{
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.0
self.0.source()
}
}
impl error::Error for Dispersed<false>
{
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.0
self.0.source()
}
}

@ -39,17 +39,17 @@ fn dup_file<F: ?Sized + AsRawFd>(file: &F) -> io::Result<memfile::RawFile>
Ok(memfile::RawFile::take_ownership_of_unchecked(fd))
}
fn run_stdin<I>(file: impl Into<fs::File>, filename: impl AsRef<OsStr>, args: I) -> io::Result<process::Child>
fn run_stdin<I>(file: Option<impl Into<fs::File>>, filename: impl AsRef<OsStr>, args: I) -> io::Result<process::Child>
where I: IntoIterator<Item = OsString>,
{
let file = {
let mut file: fs::File = file.into();
let mut file: Option<fs::File> = 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<F: ?Sized + AsRawFd>(file: &F, opt: args::ExecMode) -> io::Res
match opt {
args::ExecMode::Positional { command, args } => {
todo!("implement this with `proc_file(&file)`")
run_stdin(None::<fs::File>, 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<F: ?Sized + AsRawFd>(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<F: ?Sized + AsRawFd>(file: &F, opt: Options) -> io::Result<impl IntoIterator<Item = io::Result<process::Child> + 'static>>
pub fn spawn_from<'a, F: ?Sized + AsRawFd>(file: &'a F, opt: Options) -> impl IntoIterator<Item = io::Result<process::Child>> + '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<F: ?Sized + AsRawFd>(file: &F, opt: Options) -> io::Result<impl IntoIterator<Item = io::Result<i32> + 'static>>
pub fn spawn_from_sync<'a, F: ?Sized + AsRawFd>(file: &'a F, opt: Options) -> impl IntoIterator<Item = io::Result<i32>> + '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.")
}

@ -348,3 +348,72 @@ where F: FnOnce() -> T
}
}
}
#[inline(always)]
pub(crate) fn map_bool<T>(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<T: AsRawFd + ?Sized> 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(())
}
}
}
};

@ -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<F: AsRawFd + ?Sized>(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<()>
{

Loading…
Cancel
Save