Compare commits
No commits in common. 'master' and 'safe-memfd' have entirely different histories.
master
...
safe-memfd
@ -1,8 +0,0 @@
|
||||
From `strace` examinations, far fewer `splice()/send_file()` are used from the consumer of a `collect` in the middle of a pipe.
|
||||
A reduction of over 5 times. But still not just a single one.
|
||||
|
||||
# TODO: single syscall reads from consumers of `collect` in pipelines
|
||||
Is there a way we can set the size of `stdout` before exiting?
|
||||
I dunno what `sealing` is, but maybe that can be used? Or, if not, a specific `fcntl()` call? Finding this out will allow consumers of `collect`'s output to use a single `splice()` instead of many, greatly improving its performance in pipelines as its output can be used like an actual file's...
|
||||
|
||||
|
@ -1,744 +0,0 @@
|
||||
//! For handling arguments.
|
||||
use super::*;
|
||||
use std::ffi::{
|
||||
OsStr,
|
||||
OsString,
|
||||
};
|
||||
use std::{
|
||||
iter,
|
||||
fmt, error,
|
||||
borrow::Cow,
|
||||
};
|
||||
use std::any::type_name;
|
||||
//TODO: When added, the `args` comptime feature will need to enable `lazy_static`.
|
||||
use ::lazy_static::lazy_static;
|
||||
|
||||
/// The string used for positional argument replacements in `-exec{}`.
|
||||
pub const POSITIONAL_ARG_STRING: &'static str = "{}";
|
||||
|
||||
/// The token that terminates adding arguments for `-exec` / `-exec{}`.
|
||||
///
|
||||
/// # Usage
|
||||
/// If the user wants multiple `-exec/{}` parameters, they must be seperated with this token. e.g. `sh$ collect -exec c a b c \; -exec{} c2 d {} e f {} g`
|
||||
///
|
||||
/// It is not required for the user to provide the terminator when the `-exec/{}` is the final argument passed, but they can if they wish. e.g. `sh$ collect -exec command a b c` is valid, and `sh$ collect -exec command a b c \;` is *also* valid.
|
||||
pub const EXEC_MODE_STRING_TERMINATOR: &'static str = ";";
|
||||
|
||||
/// Mode for `-exec` / `-exec{}`
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum ExecMode
|
||||
{
|
||||
Stdin{command: OsString, args: Vec<OsString>},
|
||||
Positional{command: OsString, args: Vec<Option<OsString>>},
|
||||
}
|
||||
|
||||
impl fmt::Display for ExecMode
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
#[inline]
|
||||
fn quote_into<'a, const QUOTE: u8>(string: &'a [u8], f: &mut (impl fmt::Write + ?Sized)) -> fmt::Result
|
||||
{
|
||||
let data = if let Some(mut location) = memchr::memchr(QUOTE, string) {
|
||||
let mut data = Vec::with_capacity(string.len() * 2);
|
||||
Cow::Owned(loop {
|
||||
data.extend_from_slice(&string[..location]);
|
||||
data.extend([b'\\', QUOTE]);
|
||||
location += match memchr::memchr(QUOTE, &string[location..]) {
|
||||
Some(x) if !&string[(location + x)..].is_empty() => x,
|
||||
_ => break data,
|
||||
};
|
||||
})
|
||||
} else {
|
||||
Cow::Borrowed(string)
|
||||
};
|
||||
let string = String::from_utf8_lossy(data.as_ref());
|
||||
|
||||
if string.split_whitespace().take(2).count() == 1
|
||||
{
|
||||
f.write_char(QUOTE as char)?;
|
||||
f.write_str(string.as_ref())?;
|
||||
f.write_char(QUOTE as char)
|
||||
} else {
|
||||
f.write_str(string.as_ref())
|
||||
}
|
||||
}
|
||||
match self {
|
||||
Self::Stdin { command, args } => {
|
||||
quote_into::<b'\''>(command.as_bytes(), f)?;
|
||||
args.iter().map(move |arg| {
|
||||
use fmt::Write;
|
||||
f.write_char(' ').and_then(|_| quote_into::<b'"'>(arg.as_bytes(), f))
|
||||
}).collect()
|
||||
},
|
||||
Self::Positional { command, args } => {
|
||||
quote_into::<b'\''>(command.as_bytes(), f)?;
|
||||
args.iter().map(move |arg| {
|
||||
use fmt::Write;
|
||||
f.write_char(' ').and_then(|_| match arg.as_ref() {
|
||||
Some(arg) => quote_into::<b'"'>(arg.as_bytes(), f),
|
||||
None => f.write_str(POSITIONAL_ARG_STRING),
|
||||
})
|
||||
}).collect()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl ExecMode {
|
||||
#[inline(always)]
|
||||
pub fn is_positional(&self) -> bool
|
||||
{
|
||||
if let Self::Positional { .. } = &self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn is_stdin(&self) -> bool
|
||||
{
|
||||
!self.is_positional()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn command(&self) -> &OsStr
|
||||
{
|
||||
match self {
|
||||
Self::Positional { command, .. } |
|
||||
Self::Stdin { command, .. } =>
|
||||
command.as_os_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the arguments.
|
||||
///
|
||||
/// Its output type is `Option<&OsStr>`, because the variant may be `Positional`. If it is instead `Stdin`, all values yielded will be `Some()`.
|
||||
#[inline]
|
||||
pub fn arguments(&self) -> impl Iterator<Item = Option<&'_ OsStr>>
|
||||
{
|
||||
#[derive(Debug, Clone)]
|
||||
struct ArgIter<'a>(Result<std::slice::Iter<'a, Option<OsString>>, std::slice::Iter<'a, OsString>>);
|
||||
|
||||
|
||||
impl<'a> Iterator for ArgIter<'a>
|
||||
{
|
||||
type Item = Option<&'a OsStr>;
|
||||
|
||||
#[inline(always)]
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
Some(match &mut self.0 {
|
||||
Err(n) => Some(n.next()?.as_os_str()),
|
||||
Ok(n) => n.next().map(|x| x.as_ref().map(|x| x.as_os_str()))?
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match &self.0 {
|
||||
Err(n) => n.size_hint(),
|
||||
Ok(n) => n.size_hint()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> ExactSizeIterator for ArgIter<'a>{}
|
||||
impl<'a> iter::FusedIterator for ArgIter<'a>{}
|
||||
|
||||
ArgIter(match self {
|
||||
Self::Positional { args, .. } => Ok(args.iter()),
|
||||
Self::Stdin { args, .. } => Err(args.iter())
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a tuple of `(command, args)`.
|
||||
///
|
||||
/// # Modes
|
||||
/// * When invariant is `Stdin`, `positional` is ignored and can be `iter::empty()` or an empty array. If it is not, it is still ignored.
|
||||
/// * When invariant is `Positional`, `positional` is iterated on for every instance a positional argument should appear.
|
||||
/// If the iterator completes and there are positional arguments left, they are removed from the iterator's output, and the next argument is shifted along. `iter::repeat(arg)` can be used to insert the same argument into each instance where a positional argument is expected.
|
||||
#[inline]
|
||||
pub fn into_process_info<T, I>(self, positional: I) -> (OsString, ExecModeArgIterator<I>)
|
||||
where I: IntoIterator<Item=OsString>,
|
||||
{
|
||||
|
||||
match self {
|
||||
Self::Stdin { command, args } => (command, ExecModeArgIterator::Stdin(args.into_iter())),
|
||||
Self::Positional { command, args } => (command,
|
||||
ExecModeArgIterator::Positional(ArgZippingIter(args.into_iter(),
|
||||
positional.into_iter().fuse()))),
|
||||
}
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
/// If the invariant of the enum was `Positional`.
|
||||
#[inline]
|
||||
pub fn into_process_info_stdin(self) -> (OsString, ExecModeArgIterator<NoPositionalArgs>)
|
||||
{
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn _panic_invalid_invariant() -> !
|
||||
{
|
||||
panic!("Invalid invariant for ExecMode: Expected `Stdin`, was `Positional`.")
|
||||
}
|
||||
match self {
|
||||
Self::Stdin { command, args } => (command, ExecModeArgIterator::Stdin(args.into_iter())),
|
||||
_ => _panic_invalid_invariant()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArgZippingIter<T>(std::vec::IntoIter<Option<OsString>>, iter::Fuse<T::IntoIter>)
|
||||
where T: IntoIterator<Item = OsString>;
|
||||
|
||||
/// Private trait used to mark an instantiation of `ExecModeArgIterator<T>` as not ever being the `Positional` invariant.
|
||||
unsafe trait NoPositional{}
|
||||
pub enum NoPositionalArgs{}
|
||||
impl Iterator for NoPositionalArgs
|
||||
{
|
||||
type Item = OsString;
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
match *self{}
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(0, Some(0))
|
||||
}
|
||||
}
|
||||
unsafe impl NoPositional for NoPositionalArgs{}
|
||||
unsafe impl NoPositional for std::convert::Infallible{}
|
||||
impl ExactSizeIterator for NoPositionalArgs{}
|
||||
impl DoubleEndedIterator for NoPositionalArgs
|
||||
{
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
match *self{}
|
||||
}
|
||||
}
|
||||
impl iter::FusedIterator for NoPositionalArgs{}
|
||||
impl From<std::convert::Infallible> for NoPositionalArgs
|
||||
{
|
||||
fn from(from: std::convert::Infallible) -> Self
|
||||
{
|
||||
match from{}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ExecModeArgIterator<T: IntoIterator<Item = OsString>> {
|
||||
Stdin(std::vec::IntoIter<OsString>),
|
||||
Positional(ArgZippingIter<T>),
|
||||
}
|
||||
|
||||
impl<I> Iterator for ExecModeArgIterator<I>
|
||||
where I: IntoIterator<Item = OsString>
|
||||
{
|
||||
type Item = OsString;
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
loop {
|
||||
break match self {
|
||||
Self::Stdin(vec) => vec.next(),
|
||||
Self::Positional(ArgZippingIter(ref mut vec, ref mut pos)) => {
|
||||
match vec.next()? {
|
||||
None => {
|
||||
match pos.next() {
|
||||
None => continue,
|
||||
replace => replace,
|
||||
}
|
||||
},
|
||||
set => set,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
Self::Stdin(vec, ..) => vec.size_hint(),
|
||||
Self::Positional(ArgZippingIter(vec, ..)) => vec.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<I> iter::FusedIterator for ExecModeArgIterator<I>
|
||||
where I: IntoIterator<Item = OsString>{}
|
||||
// ExecModeArgIterator can never be FixedSizeIterator if it is *ever* `Positional`
|
||||
impl<I: NoPositional> ExactSizeIterator for ExecModeArgIterator<I>
|
||||
where I: IntoIterator<Item = OsString>{}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
||||
pub struct Options {
|
||||
/// For `-exec` (stdin exec) and `-ecec{}` (positional exec)
|
||||
exec: Vec<ExecMode>,
|
||||
}
|
||||
|
||||
impl Options
|
||||
{
|
||||
#[inline(always)]
|
||||
fn count_exec(&self) -> (usize, usize)
|
||||
{
|
||||
self.exec.is_empty().then(|| (0, 0))
|
||||
.or_else(move ||
|
||||
self.exec.iter().map(|x| {
|
||||
x.is_positional().then(|| (0, 1)).unwrap_or((1, 0))
|
||||
})
|
||||
.reduce(|(s, p), (s1, p1)| (s + s1, p + p1)))
|
||||
.unwrap_or((0,0))
|
||||
}
|
||||
/// Has `-exec` (stdin) or `-exec{}` (positional)
|
||||
///
|
||||
/// Tuple element 1 is for `-exec`; element 2 is for `-exec{}`.
|
||||
#[inline(always)]
|
||||
pub fn has_exec(&self) -> (bool, bool)
|
||||
{
|
||||
self.exec.is_empty().then(|| (false, false))
|
||||
.or_else(move ||
|
||||
self.exec.iter().map(|x| {
|
||||
let x = x.is_positional();
|
||||
(!x, x)
|
||||
})
|
||||
.reduce(|(s, p), (s1, p1)| (s || s1, p || p1)))
|
||||
.unwrap_or((false, false))
|
||||
}
|
||||
#[inline]
|
||||
pub fn has_positional_exec(&self) -> bool
|
||||
{
|
||||
self.has_exec().1
|
||||
}
|
||||
#[inline]
|
||||
pub fn has_stdin_exec(&self) -> bool
|
||||
{
|
||||
self.has_exec().0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn opt_exec(&self) -> impl Iterator<Item= &'_ ExecMode> + ExactSizeIterator + iter::FusedIterator + DoubleEndedIterator
|
||||
{
|
||||
self.exec.iter()
|
||||
}
|
||||
#[inline]
|
||||
pub fn into_opt_exec(self) -> impl Iterator<Item=ExecMode> + ExactSizeIterator + iter::FusedIterator
|
||||
{
|
||||
self.exec.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// The executable name of this program.
|
||||
///
|
||||
/// # Returns
|
||||
/// * If the program's executable name is a valid UTF8 string, that string.
|
||||
/// * If it is not, then that string is lossily-converted to a UTF8 string, with invalid characters replaced accordingly. This can be checked by checking if the return value is `Cow::Owned`, if it is, then this is not a reliable indication of the exetuable path's basename.
|
||||
/// * If there is no program name provided, i.e. if `argc == 0`, then an empty string is returned.
|
||||
#[inline(always)]
|
||||
pub fn program_name() -> Cow<'static, str>
|
||||
{
|
||||
lazy_static! {
|
||||
static ref NAME: OsString = std::env::args_os().next().unwrap_or(OsString::from_vec(Vec::new()));
|
||||
}
|
||||
String::from_utf8_lossy(NAME.as_bytes())
|
||||
}
|
||||
|
||||
/// Parse the program's arguments into an `Options` array.
|
||||
/// If parsing fails, an `ArgParseError` is returned detailing why it failed.
|
||||
#[inline]
|
||||
#[cfg_attr(feature="logging", instrument(err(Debug)))]
|
||||
pub fn parse_args() -> Result<Options, ArgParseError>
|
||||
{
|
||||
let iter = std::env::args_os();
|
||||
if_trace!(trace!("argc == {}, argv == {iter:?}", iter.len()));
|
||||
|
||||
parse_from(iter.skip(1))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn type_name_short<T: ?Sized>() -> &'static str
|
||||
{
|
||||
let mut s = std::any::type_name::<T>();
|
||||
if let Some(idx) = memchr::memrchr(b':', s.as_bytes()) {
|
||||
s = &s[idx.saturating_sub(1)..];
|
||||
if s.len() >= 2 && &s[..2] == "::" {
|
||||
s = &s[2..];
|
||||
}
|
||||
}
|
||||
if s.len() > 0 && (s.as_bytes()[s.len()-1] == b'>' && s.as_bytes()[0] != b'<') {
|
||||
s = &s[..(s.len()-1)];
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="logging", instrument(level="debug", skip_all, fields(args = ?type_name_short::<I>())))]
|
||||
fn parse_from<I, T>(args: I) -> Result<Options, ArgParseError>
|
||||
where I: IntoIterator<Item = T>,
|
||||
T: Into<OsString>
|
||||
{
|
||||
let mut args = args.into_iter().map(Into::into);
|
||||
let mut output = Options::default();
|
||||
let mut idx = 0;
|
||||
//XXX: When `-exec{}` is provided, but no `{}` arguments are found, maybe issue a warning with `if_trace!(warning!())`? There are valid situations to do this in, but they are rare...
|
||||
let mut parser = || -> Result<_, ArgParseError> {
|
||||
while let Some(mut arg) = args.next() {
|
||||
idx += 1;
|
||||
macro_rules! try_parse_for {
|
||||
(@ assert_parser_okay $parser:path) => {
|
||||
const _:() = {
|
||||
const fn _assert_is_parser<P: TryParse + ?Sized>() {}
|
||||
const fn _assert_is_result<P: TryParse + ?Sized>(res: P::Output) -> P::Output { res }
|
||||
|
||||
_assert_is_parser::<$parser>();
|
||||
};
|
||||
};
|
||||
($parser:path => $then:expr) => {
|
||||
{
|
||||
try_parse_for!(@ assert_parser_okay $parser);
|
||||
//_assert_is_closure(&$then); //XXX: There isn't a good way to tell without having to specify some bound on return type...
|
||||
if let Some(result) = parsers::try_parse_with::<$parser>(&mut arg, &mut args) {
|
||||
|
||||
// Result succeeded on visitation, use this parser for this argument and then continue to the next one.
|
||||
$then(result?);
|
||||
continue;
|
||||
}
|
||||
// Result failed on visitation, so continue the control flow to the next `try_parse_for!()` visitation attempt.
|
||||
}
|
||||
};
|
||||
/*($parser:path => $then:expr) => {
|
||||
$then(try_parse_for!(try $parser => std::convert::identity)?)
|
||||
}*/
|
||||
}
|
||||
//TODO: Add `impl TryParse` struct for `--help` and add it at the *top* of the visitation stack (it will most likely appear there.)
|
||||
// This may require a re-work of the `Options` struct, or an enum wrapper around it should be returned instead of options directly, for special modes (like `--help` is, etc.) Perhaps `pub enum Mode { Normal(Options), Help, }` or something should be returned, and `impl From<Options>` for it, with the caller of this closure (below)
|
||||
try_parse_for!(parsers::ExecMode => |result| output.exec.push(result));
|
||||
|
||||
//Note: try_parse_for!(parsers::SomeOtherOption => |result| output.some_other_option.set(result.something)), etc, for any newly added arguments.
|
||||
|
||||
if_trace!(debug!("reached end of parser visitation for argument #{idx} {arg:?}! Failing now with `UnknownOption`"));
|
||||
return Err(ArgParseError::UnknownOption(arg));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
parser()
|
||||
.with_index(idx)
|
||||
.map(move |_| output.into()) //XXX: This is `output.into()`, because when successful result return type is changed from directly `Options` to `enum Mode` (which will `impl From<Options>`), it will allow any `impl Into<Mode>` to be returned. (Boxed dynamic dispatch with a trait `impl FromMode<T: ?Sized> (for Mode) { fn from(val: Box<T>) -> Self { IntoMode::into(val) } }, auto impl trait IntoMode { fn into(self: Box<Self>) -> Mode }` may be required if different types are returned from the closure, this is okay, as argument parsed struct can get rather large.)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArgParseError
|
||||
{
|
||||
/// With an added argument index.
|
||||
WithIndex(usize, Box<ArgParseError>),
|
||||
/// Returned when an invalid or unknown argument is found
|
||||
UnknownOption(OsString),
|
||||
/// Returned when the argument, `argument`, is passed in an invalid context by the user.
|
||||
InvalidUsage { argument: String, message: String, inner: Option<Box<dyn error::Error + Send + Sync + 'static>> },
|
||||
//VisitationFailed,
|
||||
|
||||
}
|
||||
|
||||
trait ArgParseErrorExt<T>: Sized
|
||||
{
|
||||
fn with_index(self, idx: usize) -> Result<T, ArgParseError>;
|
||||
}
|
||||
impl ArgParseError
|
||||
{
|
||||
#[inline]
|
||||
pub fn wrap_index(self, idx: usize) -> Self {
|
||||
Self::WithIndex(idx, Box::new(self))
|
||||
}
|
||||
}
|
||||
impl<T, E: Into<ArgParseError>> ArgParseErrorExt<T> for Result<T, E>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn with_index(self, idx: usize) -> Result<T, ArgParseError> {
|
||||
self.map_err(Into::into)
|
||||
.map_err(move |e| e.wrap_index(idx))
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for ArgParseError
|
||||
{
|
||||
#[inline]
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match self {
|
||||
Self::InvalidUsage { inner, .. } => inner.as_ref().map(|x| -> &(dyn error::Error + 'static) { x.as_ref() }),
|
||||
Self::WithIndex(_, inner) => inner.source(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ArgParseError
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
Self::WithIndex(index, inner) => write!(f, "Argument #{index}: {inner}"),
|
||||
Self::UnknownOption(opt) => {
|
||||
f.write_str("Invalid/unknown argument: `")?;
|
||||
f.write_str(String::from_utf8_lossy(opt.as_bytes()).as_ref())?;
|
||||
f.write_str("`")
|
||||
},
|
||||
Self::InvalidUsage { argument, message, .. } => write!(f, "Invalid usage for argument `{argument}`: {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ArgError: error::Error + Send + Sync + 'static
|
||||
{
|
||||
fn into_invalid_usage(self) -> (String, String, Box<dyn error::Error + Send + Sync + 'static>)
|
||||
where Self: Sized;
|
||||
}
|
||||
|
||||
trait TryParse: Sized
|
||||
{
|
||||
type Error: ArgError;
|
||||
type Output;
|
||||
|
||||
#[inline(always)]
|
||||
fn visit(argument: &OsStr) -> Option<Self> { let _ = argument; None }
|
||||
fn parse<I: ?Sized>(self, argument: OsString, rest: &mut I) -> Result<Self::Output, Self::Error>
|
||||
where I: Iterator<Item = OsString>;
|
||||
}
|
||||
|
||||
impl<E: error::Error + Send + Sync + 'static> From<(String, String, E)> for ArgParseError
|
||||
{
|
||||
#[inline]
|
||||
fn from((argument, message, inner): (String, String, E)) -> Self
|
||||
{
|
||||
Self::InvalidUsage { argument, message, inner: Some(Box::new(inner)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: ArgError> From<E> for ArgParseError
|
||||
{
|
||||
#[inline(always)]
|
||||
fn from(from: E) -> Self
|
||||
{
|
||||
let (argument, message, inner) = from.into_invalid_usage();
|
||||
Self::InvalidUsage { argument, message, inner: Some(inner) }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn extract_last_pathspec<'a>(s: &'a str) -> &'a str
|
||||
{
|
||||
//#[cfg_attr(feature="logging", feature(instrument(ret)))]
|
||||
#[allow(dead_code)]
|
||||
fn string_diff<'a>(a: &'a str, b: &'a str) -> usize
|
||||
{
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn _panic_non_inclusive(swap: bool) -> !
|
||||
{
|
||||
let a = swap.then(|| "b").unwrap_or("a");
|
||||
let b = swap.then(|| "a").unwrap_or("b");
|
||||
panic!("String {a} was not inside string {b}")
|
||||
}
|
||||
let a_addr = a.as_ptr() as usize;
|
||||
let b_addr = b.as_ptr() as usize;
|
||||
let (a_addr, b_addr, sw) =
|
||||
if !(a_addr + a.len() > b_addr + b.len() && b_addr + b.len() < a_addr + a.len()) {
|
||||
(b_addr, a_addr, true)
|
||||
} else {
|
||||
(a_addr, a_addr, false)
|
||||
};
|
||||
|
||||
if b_addr < a_addr /*XXX || (b_addr + b.len()) > (a_addr + a.len())*/ {
|
||||
_panic_non_inclusive(sw)
|
||||
}
|
||||
return a_addr.abs_diff(b_addr);
|
||||
}
|
||||
s.rsplit_once("::")
|
||||
.map(|(_a, b)| /*XXX: This doesn't work...match _a.rsplit_once("::") {
|
||||
Some((_, last)) => &s[string_diff(s, last)..],
|
||||
_ => b
|
||||
}*/ b)
|
||||
.unwrap_or(s)
|
||||
}
|
||||
|
||||
mod parsers {
|
||||
use super::*;
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature="logging", instrument(level="debug", skip(rest), fields(parser = %extract_last_pathspec(type_name::<P>()))))]
|
||||
pub(super) fn try_parse_with<P>(arg: &mut OsString, rest: &mut impl Iterator<Item = OsString>) -> Option<Result<P::Output, ArgParseError>>
|
||||
where P: TryParse
|
||||
{
|
||||
#[cfg(feature="logging")]
|
||||
let _span = tracing::warn_span!("parse", parser= %extract_last_pathspec(type_name::<P>()), ?arg);
|
||||
P::visit(arg.as_os_str()).map(move |parser| {
|
||||
#[cfg(feature="logging")]
|
||||
let _in = _span.enter();
|
||||
parser.parse(/*if_trace!{true arg.clone(); */std::mem::replace(arg, OsString::default()) /*}*/, rest).map_err(Into::into) //This clone is not needed, the argument is captured by `try_parse_with` (in debug) and `parse` (in warning) already.
|
||||
}).map(|res| {
|
||||
#[cfg(feature="logging")]
|
||||
match res.as_ref() {
|
||||
Err(err) => {
|
||||
::tracing::event!(::tracing::Level::ERROR, ?err, "Attempted parse failed with error")
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
res
|
||||
}).or_else(|| {
|
||||
#[cfg(feature="logging")]
|
||||
::tracing::event!(::tracing::Level::TRACE, "no match for this parser with this arg, continuing visitation.");
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Parser for `ExecMode`
|
||||
///
|
||||
/// Parses `-exec` / `-exec{}` modes.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ExecMode {
|
||||
Stdin,
|
||||
Postional,
|
||||
}
|
||||
impl ExecMode {
|
||||
#[inline(always)]
|
||||
fn is_positional(&self) -> bool
|
||||
{
|
||||
match self {
|
||||
Self::Postional => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn command_string(&self) -> &'static str
|
||||
{
|
||||
if self.is_positional() {
|
||||
"-exec{}"
|
||||
} else {
|
||||
"-exec"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExecModeParseError(ExecMode);
|
||||
impl error::Error for ExecModeParseError{}
|
||||
impl fmt::Display for ExecModeParseError
|
||||
{
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "{} needs at least a command", self.0.command_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgError for ExecModeParseError
|
||||
{
|
||||
fn into_invalid_usage(self) -> (String, String, Box<dyn error::Error + Send + Sync + 'static>)
|
||||
where Self: Sized {
|
||||
(self.0.command_string().to_owned(), "Expected a command file-path to execute.".to_owned(), Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryParse for ExecMode
|
||||
{
|
||||
type Error = ExecModeParseError;
|
||||
type Output = super::ExecMode;
|
||||
#[inline(always)]
|
||||
fn visit(argument: &OsStr) -> Option<Self> {
|
||||
|
||||
if argument == OsStr::from_bytes(b"-exec") {
|
||||
Some(Self::Stdin)
|
||||
} else if argument == OsStr::from_bytes(b"-exec{}") {
|
||||
Some(Self::Postional)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse<I: ?Sized>(self, _argument: OsString, rest: &mut I) -> Result<Self::Output, Self::Error>
|
||||
where I: Iterator<Item = OsString> {
|
||||
mod warnings {
|
||||
use super::*;
|
||||
/// Issue a warning when `-exec{}` is provided as an argument, but no positional arguments (`{}`) are specified in the argument list to the command.
|
||||
#[cold]
|
||||
#[cfg_attr(feature="logging", inline(never))]
|
||||
#[cfg_attr(not(feature="logging"), inline(always))]
|
||||
pub fn execp_no_positional_replacements()
|
||||
{
|
||||
if_trace!(warn!("-exec{{}} provided with no positional arguments `{}`, there will be no replacement with the data. Did you mean `-exec`?", POSITIONAL_ARG_STRING));
|
||||
}
|
||||
/// Issue a warning if the user apparently meant to specify two `-exec/{}` arguments to `collect`, but seemingly is accidentally is passing the `-exec/{}` string as an argument to the first.
|
||||
#[cold]
|
||||
#[cfg_attr(feature="logging", inline(never))]
|
||||
#[cfg_attr(not(feature="logging"), inline(always))]
|
||||
pub fn exec_apparent_missing_terminator(first_is_positional: bool, second_is_positional: bool, command: &OsStr, argument_number: usize)
|
||||
{
|
||||
if_trace! {
|
||||
warn!("{} provided, but argument to command {command:?} number #{argument_number} is `{}`. Are you missing the terminator '{}' before this argument?", if first_is_positional {"-exec{}"} else {"-exec"}, if second_is_positional {"-exec{}"} else {"-exec"}, EXEC_MODE_STRING_TERMINATOR)
|
||||
}
|
||||
}
|
||||
|
||||
/// Issue a warning if the user apparently missed a command to `-exec{}`, and has typed `-exec{} {}`...
|
||||
#[cold]
|
||||
#[cfg_attr(feature="logging", inline(never))]
|
||||
#[cfg_attr(not(feature="logging"), inline(always))]
|
||||
//TODO: Do we make this a feature in the future? Being able to `fexecve()` the input received?
|
||||
pub fn execp_command_not_substituted()
|
||||
{
|
||||
if_trace! {
|
||||
warn!("-exec{{}} provided with a command as the positional replacement string `{}`. Commands are not substituted. Are you missing a command? (Note: Currently, `fexecve()`ing the input is not supported.)", POSITIONAL_ARG_STRING)
|
||||
}
|
||||
}
|
||||
|
||||
/// Issue a warning if the user apparently attempted to terminate a `-exec/{}` argument, but instead made the command of the `-exec/{}` itself the terminator.
|
||||
#[cold]
|
||||
#[cfg_attr(feature="logging", inline(never))]
|
||||
#[cfg_attr(not(feature="logging"), inline(always))]
|
||||
pub fn exec_terminator_as_command(exec_arg_str: &str)
|
||||
{
|
||||
if_trace! {
|
||||
warn!("{exec_arg_str} provided with a command that is the -exec/-exec{{}} terminator character `{}`. The sequence is not terminated, and instead the terminator character itself is taken as the command to execute. Did you miss a command before the terminator?", EXEC_MODE_STRING_TERMINATOR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let command = rest.next().ok_or_else(|| ExecModeParseError(self))?;
|
||||
if command == EXEC_MODE_STRING_TERMINATOR {
|
||||
warnings::exec_terminator_as_command(self.command_string());
|
||||
}
|
||||
let test_warn_missing_term = |(idx , string) : (usize, OsString)| {
|
||||
if let Some(val) = Self::visit(&string) {
|
||||
warnings::exec_apparent_missing_terminator(self.is_positional(), val.is_positional(), &command, idx + 1);
|
||||
}
|
||||
string
|
||||
};
|
||||
Ok(match self {
|
||||
Self::Stdin => {
|
||||
super::ExecMode::Stdin {
|
||||
args: rest
|
||||
.take_while(|argument| argument.as_bytes() != EXEC_MODE_STRING_TERMINATOR.as_bytes())
|
||||
.enumerate().map(&test_warn_missing_term)
|
||||
.collect(),
|
||||
command,
|
||||
}
|
||||
},
|
||||
Self::Postional => {
|
||||
let mut repl_warn = true;
|
||||
if command == POSITIONAL_ARG_STRING {
|
||||
warnings::execp_command_not_substituted();
|
||||
}
|
||||
let res = super::ExecMode::Positional {
|
||||
args: rest
|
||||
.take_while(|argument| argument.as_bytes() != EXEC_MODE_STRING_TERMINATOR.as_bytes())
|
||||
.enumerate().map(&test_warn_missing_term)
|
||||
.map(|x| if x.as_bytes() == POSITIONAL_ARG_STRING.as_bytes() {
|
||||
repl_warn = false;
|
||||
None
|
||||
} else {
|
||||
Some(x)
|
||||
})
|
||||
.collect(),
|
||||
command,
|
||||
};
|
||||
if repl_warn { warnings::execp_no_positional_replacements(); }
|
||||
res
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,207 +0,0 @@
|
||||
//! 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";
|
||||
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)]
|
||||
pub enum DispersedVerbosity
|
||||
{
|
||||
Simple = 0,
|
||||
Verbose = 1,
|
||||
}
|
||||
|
||||
impl From<DispersedVerbosity> 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.as_bytes() {
|
||||
b"1" |
|
||||
b"v" |
|
||||
b"verbose" => DispersedVerbosity::Verbose,
|
||||
b"0" |
|
||||
b"s" |
|
||||
b"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<const USE_ENV: bool = DEFAULT_USE_ENV>(eyre::Report);
|
||||
|
||||
impl<const E: bool> From<eyre::Report> for Dispersed<E>
|
||||
{
|
||||
#[inline]
|
||||
fn from(from: eyre::Report) -> Self
|
||||
{
|
||||
Self(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const E: bool> Dispersed<E>
|
||||
{
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> eyre::Report
|
||||
{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispersed<false>
|
||||
{
|
||||
#[inline(always)]
|
||||
pub fn obey_env(self) -> Dispersed<true>
|
||||
{
|
||||
Dispersed(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispersed<true>
|
||||
{
|
||||
#[inline(always)]
|
||||
pub fn ignore_env(self) -> Dispersed<false>
|
||||
{
|
||||
Dispersed(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const E: bool> Dispersed<E>
|
||||
{
|
||||
#[inline(always)]
|
||||
pub fn set_env<const To: bool>(self) -> Dispersed<To>
|
||||
{
|
||||
Dispersed(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Dispersed<true>
|
||||
{
|
||||
#[inline]
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
self.0.source()
|
||||
}
|
||||
}
|
||||
impl error::Error for Dispersed<false>
|
||||
{
|
||||
#[inline]
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
self.0.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Dispersed<false>
|
||||
{
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl fmt::Display for Dispersed<false>
|
||||
{
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl fmt::Debug for Dispersed<true>
|
||||
{
|
||||
#[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<true>
|
||||
{
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,146 +0,0 @@
|
||||
//! 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`.
|
||||
|
||||
#[cfg_attr(feature="logging", instrument(skip_all, fields(fd = ?file.as_raw_fd())))]
|
||||
fn proc_file<F: ?Sized + AsRawFd>(file: &F) -> PathBuf
|
||||
{
|
||||
let fd = file.as_raw_fd();
|
||||
let pid = process::id();
|
||||
//process::Command::new("/bin/ls").arg("-l").arg(format!("/proc/{pid}/fd/")).spawn().unwrap().wait().unwrap();
|
||||
format!("/proc/{pid}/fd/{fd}").into()
|
||||
//format!("/dev/fd/{fd}").into()
|
||||
}
|
||||
|
||||
/// Attempt to `dup()` a file descriptor into a `RawFile`.
|
||||
#[inline]
|
||||
|
||||
#[cfg_attr(feature="logging", instrument(skip_all, err, fields(fd = ?file.as_raw_fd())))]
|
||||
fn dup_file<F: ?Sized + AsRawFd>(file: &F) -> io::Result<memfile::RawFile>
|
||||
{
|
||||
let fd = file.as_raw_fd();
|
||||
debug_assert!(fd >= 0, "Bad input file descriptor from {} (value was {fd})", std::any::type_name::<F>());
|
||||
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))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="logging", instrument(skip_all, fields(has_stdin = ?file.is_some(), filename = ?filename.as_ref())))]
|
||||
fn run_stdin<I>(file: Option<impl Into<fs::File>>, filename: impl AsRef<OsStr>, args: I) -> io::Result<(process::Child, Option<fs::File>)>
|
||||
where I: IntoIterator<Item = OsString>,
|
||||
{
|
||||
let file = {
|
||||
let file: Option<fs::File> = file.map(Into::into);
|
||||
//TODO: Do we need to fcntl() this to make it (the fd) RW?
|
||||
match file {
|
||||
None => None,
|
||||
Some(mut file) => {
|
||||
use std::io::Seek;
|
||||
if let Err(err) = file.seek(io::SeekFrom::Start(0)) {
|
||||
if_trace!(warn!("Failed to seed to start: {err}"));
|
||||
}
|
||||
let _ = try_seal_size(&file);
|
||||
Some(file)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let child = process::Command::new(filename)
|
||||
.args(args)
|
||||
.stdin(file.as_ref().map(|file| process::Stdio::from(fs::File::from(dup_file(file).unwrap()))).unwrap_or_else(|| process::Stdio::null())) //XXX: Maybe change to `piped()` and `io::copy()` from begining (using pread()/send_file()/copy_file_range()?)
|
||||
.stdout(process::Stdio::inherit())
|
||||
.stderr(process::Stdio::inherit())
|
||||
.spawn()?;
|
||||
//TODO: XXX: Why does `/proc/{pid}/fd/{fd}` **and** `/dev/fd/{fd}` not work for -exec{}, and why foes `Stdio::from(file)` not work for stdin even *afer* re-seeking the file???
|
||||
/*
|
||||
if let Some((mut input, mut output)) = file.as_mut().zip(child.stdin.take()) {
|
||||
io::copy(&mut input, &mut output)
|
||||
/*.wrap_err("Failed to pipe file into stdin for child")*/?;
|
||||
}*/
|
||||
|
||||
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, file))
|
||||
}
|
||||
|
||||
/// 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]
|
||||
#[cfg_attr(feature="logging", instrument(skip(file), err))]
|
||||
pub fn run_single<F: ?Sized + AsRawFd>(file: &F, opt: args::ExecMode) -> io::Result<(process::Child, Option<fs::File>)>
|
||||
{
|
||||
let input: std::mem::ManuallyDrop<memfile::RawFile> = std::mem::ManuallyDrop::new(dup_file(file)?);
|
||||
|
||||
match opt {
|
||||
args::ExecMode::Positional { command, args } => {
|
||||
run_stdin(None::<fs::File>, command, args.into_iter().map(|x| x.unwrap_or_else(|| proc_file(&*input).into())))
|
||||
},
|
||||
args::ExecMode::Stdin { command, args } => {
|
||||
run_stdin(Some(std::mem::ManuallyDrop::into_inner(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`.
|
||||
#[cfg_attr(feature="logging", instrument(skip(file)))]
|
||||
pub fn spawn_from<'a, F: ?Sized + AsRawFd>(file: &'a F, opt: Options) -> impl IntoIterator<Item = io::Result<(process::Child, Option<fs::File>)>> + 'a
|
||||
{
|
||||
opt.into_opt_exec().map(|x| run_single(file, x))
|
||||
}
|
||||
|
||||
/// 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 (if one exists)
|
||||
///
|
||||
/// If the child exited via a signal termination, or another method that does not return a status, the iterator's result will be `Ok(None)`
|
||||
#[inline]
|
||||
#[cfg_attr(feature="logging", instrument(skip(file)))]
|
||||
pub fn spawn_from_sync<'a, F: ?Sized + AsRawFd>(file: &'a F, opt: Options) -> impl IntoIterator<Item = eyre::Result<Option<i32>>> + 'a
|
||||
{
|
||||
spawn_from(file, opt).into_iter().zip(0..).map(move |(child, idx)| -> eyre::Result<_> {
|
||||
|
||||
let idx = move || idx.to_string().header("The child index");
|
||||
match child {
|
||||
Ok(mut child) => {
|
||||
Ok(child.0.wait()
|
||||
.wrap_err("Failed to wait on child")
|
||||
.with_note(|| "The child may have detached itself")
|
||||
.with_section(idx)?
|
||||
.code())
|
||||
},
|
||||
Err(err) => {
|
||||
if_trace!(error!("Failed to spawn child: {err}"));
|
||||
Err(err)
|
||||
.wrap_err("Failed to spawn child")
|
||||
}
|
||||
}.with_section(idx)
|
||||
})
|
||||
}
|
@ -1,419 +0,0 @@
|
||||
//! Extensions
|
||||
use super::*;
|
||||
use std::{
|
||||
mem::{
|
||||
self,
|
||||
ManuallyDrop,
|
||||
},
|
||||
marker::PhantomData,
|
||||
ops,
|
||||
iter,
|
||||
};
|
||||
|
||||
/// Essentially equivelant bound as `eyre::StdError` (private trait)
|
||||
///
|
||||
/// Useful for when using traits that convert a generic type into an `eyre::Report`.
|
||||
pub trait EyreError: std::error::Error + Send + Sync + 'static{}
|
||||
impl<T: ?Sized> EyreError for T
|
||||
where T: std::error::Error + Send + Sync + 'static{}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Joiner<I, F>(I, F, bool);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CloneJoiner<T>(T);
|
||||
|
||||
impl<I, F> Joiner<I, F>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn size_calc(low: usize) -> usize
|
||||
{
|
||||
match low {
|
||||
0 | 1 => low,
|
||||
2 => 4,
|
||||
x if x % 2 == 0 => x * 2,
|
||||
odd => (odd * 2) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
type JoinerExt = Joiner<std::convert::Infallible, std::convert::Infallible>;
|
||||
|
||||
impl<I, F> Iterator for Joiner<I, F>
|
||||
where I: Iterator, F: FnMut() -> I::Item
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let val = match self.2 {
|
||||
false => self.0.next(),
|
||||
true => Some(self.1())
|
||||
};
|
||||
if val.is_some() {
|
||||
self.2 ^= true;
|
||||
}
|
||||
val
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (low, high) = self.0.size_hint();
|
||||
(Self::size_calc(low), high.map(Self::size_calc))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> Iterator for Joiner<I, CloneJoiner<T>>
|
||||
where I: Iterator<Item = T>, T: Clone
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let val = match self.2 {
|
||||
false => self.0.next(),
|
||||
true => Some(self.1.0.clone())
|
||||
};
|
||||
if val.is_some() {
|
||||
self.2 ^= true;
|
||||
}
|
||||
val
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (low, high) = self.0.size_hint();
|
||||
(Self::size_calc(low), high.map(Self::size_calc))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, F> iter::FusedIterator for Joiner<I, F>
|
||||
where Joiner<I,F>: Iterator,
|
||||
I: iter::FusedIterator{}
|
||||
impl<I, F> ExactSizeIterator for Joiner<I, F>
|
||||
where Joiner<I,F>: Iterator,
|
||||
I: ExactSizeIterator {}
|
||||
|
||||
pub trait IterJoinExt<T>: Sized
|
||||
{
|
||||
fn join_by<F: FnMut() -> T>(self, joiner: F) -> Joiner<Self, F>;
|
||||
fn join_by_default(self) -> Joiner<Self, fn () -> T>
|
||||
where T: Default;
|
||||
fn join_by_clone(self, value: T) -> Joiner<Self, CloneJoiner<T>>
|
||||
where T: Clone;
|
||||
|
||||
}
|
||||
|
||||
impl<I, T> IterJoinExt<T> for I
|
||||
where I: Iterator<Item = T>
|
||||
{
|
||||
#[inline]
|
||||
fn join_by<F: FnMut() -> T>(self, joiner: F) -> Joiner<Self, F> {
|
||||
Joiner(self, joiner, false)
|
||||
}
|
||||
#[inline]
|
||||
fn join_by_default(self) -> Joiner<Self, fn () -> T>
|
||||
where T: Default
|
||||
{
|
||||
Joiner(self, T::default, false)
|
||||
}
|
||||
#[inline]
|
||||
fn join_by_clone(self, value: T) -> Joiner<Self, CloneJoiner<T>>
|
||||
where T: Clone {
|
||||
Joiner(self, CloneJoiner(value), false)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoEyre<T>
|
||||
{
|
||||
fn into_eyre(self) -> eyre::Result<T>;
|
||||
}
|
||||
|
||||
impl<T, E: EyreError> IntoEyre<T> for Result<T, E>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn into_eyre(self) -> eyre::Result<T> {
|
||||
match self {
|
||||
Err(e) => Err(eyre::Report::from(e)),
|
||||
Ok(y) => Ok(y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FlattenReports<T>
|
||||
{
|
||||
/// Flatten a `eyre::Result<eyre::Result<T>>` into an `eyre::Result<T>`
|
||||
fn flatten(self) -> eyre::Result<T>;
|
||||
}
|
||||
|
||||
pub trait FlattenEyreResult<T, E>
|
||||
where E: EyreError
|
||||
{
|
||||
/// Flatten a `Result<Result<T, IE>, OE>` into an `eyre::Result<E>`
|
||||
fn flatten(self) -> eyre::Result<T>;
|
||||
}
|
||||
|
||||
pub trait FlattenResults<T, E>
|
||||
{
|
||||
/// Flatten a `Result<Result<T, IE>, E>` into a `Result<T, E>`.
|
||||
fn flatten(self) -> Result<T, E>;
|
||||
}
|
||||
|
||||
impl<T, E, IE: Into<E>> FlattenResults<T, E> for Result<Result<T, IE>, E>
|
||||
{
|
||||
/// Flatten a `Result<Result<T, impl Into<E>>, E>` into a `Result<T, E>`
|
||||
///
|
||||
/// This will convert the inner error into the type of the outer error.
|
||||
#[inline]
|
||||
fn flatten(self) -> Result<T, E> {
|
||||
match self {
|
||||
Err(e) => Err(e),
|
||||
Ok(Ok(e)) => Ok(e),
|
||||
Ok(Err(e)) => Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, IE: EyreError, E: EyreError> FlattenEyreResult<T, E> for Result<Result<T, IE>, E>
|
||||
{
|
||||
#[inline]
|
||||
fn flatten(self) -> eyre::Result<T> {
|
||||
match self {
|
||||
Err(e) => Err(e).with_note(|| "Flattened report (outer)"),
|
||||
Ok(Err(e)) => Err(e).with_warning(|| "Flattened report (inner)"),
|
||||
Ok(Ok(a)) => Ok(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FlattenReports<T> for eyre::Result<eyre::Result<T>>
|
||||
{
|
||||
#[inline]
|
||||
fn flatten(self) -> eyre::Result<T> {
|
||||
match self {
|
||||
Err(e) => Err(e.with_note(|| "Flattened report (outer)")),
|
||||
Ok(Err(e)) => Err(e.with_warning(|| "Flattened report (inner)")),
|
||||
Ok(Ok(a)) => Ok(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FlattenReports<T> for eyre::Result<Option<T>>
|
||||
{
|
||||
#[inline]
|
||||
fn flatten(self) -> eyre::Result<T> {
|
||||
match self {
|
||||
Err(e) => Err(e.with_note(|| "Flattened report (outer)")),
|
||||
Ok(None) => Err(eyre!("Value expected, but not found").with_section(|| format!("Option<{}>", std::any::type_name::<T>()).header("Option type was")).with_warning(|| "Flattened report (inner)")),
|
||||
Ok(Some(a)) => Ok(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E: EyreError> FlattenEyreResult<T, E> for Result<Option<T>, E>
|
||||
{
|
||||
#[inline]
|
||||
fn flatten(self) -> eyre::Result<T> {
|
||||
match self {
|
||||
Err(e) => Err(e).with_note(|| "Flattened report (outer)"),
|
||||
Ok(None) => Err(eyre!("Value expected, but not found")
|
||||
.with_section(|| format!("Option<{}>", std::any::type_name::<T>())
|
||||
.header("Option type was"))
|
||||
.with_warning(|| "Flattened report (inner)")),
|
||||
Ok(Some(a)) => Ok(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum RunOnceInternal<F>
|
||||
{
|
||||
Live(ManuallyDrop<F>),
|
||||
Dead,
|
||||
}
|
||||
|
||||
impl<F: Clone> Clone for RunOnceInternal<F>
|
||||
{
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Live(l) => Self::Live(l.clone()),
|
||||
_ => Self::Dead
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> RunOnceInternal<F>
|
||||
{
|
||||
/// Take `F` now, unless it doesn't need to be dropped.
|
||||
///
|
||||
/// # Returns
|
||||
/// * if `!needs_drop::<F>()`, `None` is always returned.
|
||||
/// * if `self` is `Dead`, `None` is returned.
|
||||
/// * if `self` is `Live(f)`, `Some(f)` is returned, and `self` is set to `Dead`.
|
||||
#[inline(always)]
|
||||
fn take_now_for_drop(&mut self) -> Option<F>
|
||||
{
|
||||
if mem::needs_drop::<F>() {
|
||||
self.take_now()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// If `Live`, return the value inside and set to `Dead`.
|
||||
/// Otherwise, return `None`.
|
||||
#[inline(always)]
|
||||
fn take_now(&mut self) -> Option<F>
|
||||
{
|
||||
if let Self::Live(live) = self {
|
||||
let val = unsafe {
|
||||
ManuallyDrop::take(live)
|
||||
};
|
||||
*self = Self::Dead;
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ops::Drop for RunOnceInternal<F>
|
||||
{
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
if mem::needs_drop::<F>() {
|
||||
if let Self::Live(func) = self {
|
||||
unsafe { ManuallyDrop::drop(func) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a 0 argument closure that will only be ran *once*.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RunOnce<F, T>(PhantomData<fn () -> T>, RunOnceInternal<F>);
|
||||
|
||||
unsafe impl<T, F> Send for RunOnce<F, T>
|
||||
where F: FnOnce() -> T + Send {}
|
||||
|
||||
impl<F, T> RunOnce<F, T>
|
||||
where F: FnOnce() -> T
|
||||
{
|
||||
pub const fn new(func: F) -> Self
|
||||
{
|
||||
Self(PhantomData, RunOnceInternal::Live(ManuallyDrop::new(func)))
|
||||
}
|
||||
|
||||
pub const fn never() -> Self
|
||||
{
|
||||
Self(PhantomData, RunOnceInternal::Dead)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_take(&mut self) -> Option<F>
|
||||
{
|
||||
match &mut self.1 {
|
||||
RunOnceInternal::Live(func) => {
|
||||
Some(unsafe { ManuallyDrop::take(func) })
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_run(&mut self) -> Option<T>
|
||||
{
|
||||
self.try_take().map(|func| func())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn run(mut self) -> T
|
||||
{
|
||||
self.try_run().expect("Function has already been consumed")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take(mut self) -> F
|
||||
{
|
||||
self.try_take().expect("Function has already been consumed")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_runnable(&self) -> bool
|
||||
{
|
||||
if let RunOnceInternal::Dead = &self.1 {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in new issue