Made -exec (stdin ver) work via piping hack (XXX: We need to find how to pass file directly.) (TODO: -exec{} still doesn"t work: `No such file or directory` error when accessing /dev/fd/{fd} *and* /proc/{pid}/{fd}?? Idk why...

Fortune for collect's current commit: Future small blessing − 末小吉
exec
Avril 2 years ago
parent bc121420b8
commit 582bfc0dad
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -18,8 +18,9 @@ use std::{
fn proc_file<F: ?Sized + AsRawFd>(file: &F) -> PathBuf fn proc_file<F: ?Sized + AsRawFd>(file: &F) -> PathBuf
{ {
let fd = file.as_raw_fd(); let fd = file.as_raw_fd();
let pid = process::id(); //let pid = process::id();
format!("/proc/{pid}/fd/{fd}").into() //format!("/proc/{pid}/fd/{fd}").into()
format!("/dev/fd/{fd}").into()
} }
/// Attempt to `dup()` a file descriptor into a `RawFile`. /// Attempt to `dup()` a file descriptor into a `RawFile`.
@ -43,15 +44,31 @@ fn run_stdin<I>(file: Option<impl Into<fs::File>>, filename: impl AsRef<OsStr>,
where I: IntoIterator<Item = OsString>, where I: IntoIterator<Item = OsString>,
{ {
let file = { let file = {
let mut file: Option<fs::File> = file.map(Into::into); let file: Option<fs::File> = file.map(Into::into);
//TODO: Do we need to fcntl() this to make it (the fd) RW? //TODO: Do we need to fcntl() this to make it (the fd) RW?
file 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)
let mut child = process::Command::new(filename)
.args(args) .args(args)
.stdin(file.map(|file| process::Stdio::from(file)).unwrap_or_else(|| process::Stdio::inherit())) .stdin(file.as_ref().map(|file| process::Stdio::piped()/*::from(file)*/).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()) .stdout(process::Stdio::inherit())
.stderr(process::Stdio::inherit())
.spawn()?; .spawn()?;
if let Some((mut input, mut output)) = file.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())); if_trace!(info!("Spawned child process: {}", child.id()));
/*Ok(child.wait()? /*Ok(child.wait()?

@ -117,8 +117,44 @@ impl ModeReturn for BufferedReturn { fn get_fd_str(&self) -> &OsStr{ static_asse
mod args; mod args;
#[derive(Debug)]
pub struct NoFile(std::convert::Infallible);
impl AsRawFd for NoFile
{
#[inline]
fn as_raw_fd(&self) -> RawFd {
match self.0{}
}
}
trait ModeReturn: Send { trait ModeReturn: Send {
type ExecFile: AsRawFd;
fn get_exec_file(self) -> Option<Self::ExecFile>;
}
impl ModeReturn for () {
type ExecFile = NoFile;
#[inline(always)]
fn get_exec_file(self) -> Option<Self::ExecFile> {
None
}
}
impl ModeReturn for io::Stdout {
type ExecFile = Self;
#[inline(always)]
fn get_exec_file(self) -> Option<Self::ExecFile> {
Some(self)
}
}
impl ModeReturn for std::fs::File {
type ExecFile = Self;
#[inline(always)]
fn get_exec_file(self) -> Option<Self::ExecFile> {
Some(self)
}
} }
fn init() -> eyre::Result<()> fn init() -> eyre::Result<()>
@ -194,7 +230,7 @@ mod work {
use super::*; use super::*;
#[cfg_attr(feature="logging", instrument(err))] #[cfg_attr(feature="logging", instrument(err))]
#[inline] #[inline]
pub(super) fn buffered() -> eyre::Result<()> pub(super) fn buffered() -> eyre::Result<impl ModeReturn>
{ {
if_trace!(info!("strategy: allocated buffer")); if_trace!(info!("strategy: allocated buffer"));
@ -211,8 +247,9 @@ mod work {
}; };
if_trace!(info!("collected {read} from stdin. starting write.")); if_trace!(info!("collected {read} from stdin. starting write."));
let stdout = io::stdout();
let written = let written =
io::copy(&mut (&bytes[..read]).reader() , &mut io::stdout().lock()) io::copy(&mut (&bytes[..read]).reader() , &mut stdout.lock())
.with_section(|| read.header("Bytes read")) .with_section(|| read.header("Bytes read"))
.with_section(|| bytes.len().header("Buffer length (frozen)")) .with_section(|| bytes.len().header("Buffer length (frozen)"))
.with_section(|| format!("{:?}", &bytes[..read]).header("Read Buffer")) .with_section(|| format!("{:?}", &bytes[..read]).header("Read Buffer"))
@ -225,14 +262,14 @@ mod work {
.wrap_err("Writing failed: size mismatch"); .wrap_err("Writing failed: size mismatch");
} }
Ok(()) Ok(stdout)
} }
#[cfg_attr(feature="logging", instrument(err))] #[cfg_attr(feature="logging", instrument(err))]
#[inline] #[inline]
#[cfg(feature="memfile")] #[cfg(feature="memfile")]
//TODO: We should establish a max memory threshold for this to prevent full system OOM: Output a warning message if it exceeeds, say, 70-80% of free memory (not including used by this program (TODO: How do we calculate this efficiently?)), and fail with an error if it exceeds 90% of memory... Or, instead of using free memory as basis of the requirement levels on the max size of the memory file, use max memory? Or just total free memory at the start of program? Or check free memory each time (slow!! probably not this one...). Umm... I think basing it off total memory would be best; perhaps make the percentage levels user-configurable at compile time (and allow the user to set the memory value as opposed to using the total system memory at runtime.) or runtime (compile-time preffered; use that crate that lets us use TOML config files at comptime (find it pretty easy by looking through ~/work's rust projects, I've used it before.)) //TODO: We should establish a max memory threshold for this to prevent full system OOM: Output a warning message if it exceeeds, say, 70-80% of free memory (not including used by this program (TODO: How do we calculate this efficiently?)), and fail with an error if it exceeds 90% of memory... Or, instead of using free memory as basis of the requirement levels on the max size of the memory file, use max memory? Or just total free memory at the start of program? Or check free memory each time (slow!! probably not this one...). Umm... I think basing it off total memory would be best; perhaps make the percentage levels user-configurable at compile time (and allow the user to set the memory value as opposed to using the total system memory at runtime.) or runtime (compile-time preffered; use that crate that lets us use TOML config files at comptime (find it pretty easy by looking through ~/work's rust projects, I've used it before.))
pub(super) fn memfd() -> eyre::Result<()> pub(super) fn memfd() -> eyre::Result<impl ModeReturn>
{ {
const DEFAULT_BUFFER_SIZE: fn () -> Option<std::num::NonZeroUsize> = || { const DEFAULT_BUFFER_SIZE: fn () -> Option<std::num::NonZeroUsize> = || {
cfg_if!{ cfg_if!{
@ -490,6 +527,10 @@ mod work {
// Seal memfile // Seal memfile
let _ = try_seal_size(&file); let _ = try_seal_size(&file);
// Now copy memfile to stdout
// 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.)) // 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) set_stdout_len(read)
.wrap_err(eyre!("Failed to `ftruncate()` stdout after collection of {read} bytes")) .wrap_err(eyre!("Failed to `ftruncate()` stdout after collection of {read} bytes"))
@ -507,7 +548,7 @@ mod work {
.wrap_err("Writing failed: size mismatch"); .wrap_err("Writing failed: size mismatch");
} }
Ok(()) Ok(file)
} }
} }
@ -570,16 +611,27 @@ fn main() -> errors::DispersedResult<()> {
}; };
//TODO: maybe look into fd SEALing? Maybe we can prevent a consumer process from reading from stdout until we've finished the transfer. The name SEAL sounds like it might have something to do with that? //TODO: maybe look into fd SEALing? Maybe we can prevent a consumer process from reading from stdout until we've finished the transfer. The name SEAL sounds like it might have something to do with that?
let execfile;
cfg_if!{ cfg_if!{
if #[cfg(feature="memfile")] { if #[cfg(feature="memfile")] {
work::memfd() execfile = work::memfd()
.wrap_err("Operation failed").with_note(|| "Stragery was `memfd`")?; .wrap_err("Operation failed").with_note(|| "Stragery was `memfd`")?;
} else { } else {
work::buffered() execfile = work::buffered()
.wrap_err("Operation failed").with_note(|| "Strategy was `buffered`")?; .wrap_err("Operation failed").with_note(|| "Strategy was `buffered`")?;
} }
} }
// Transfer complete // Transfer complete, run exec.
let rc = if let Some(file) = execfile.get_exec_file() {
exec::spawn_from_sync(&file, opt).into_iter().try_fold(0i32, |opt, res| res.map(|x| opt | x.unwrap_or(0)))
} else {
if_trace!(debug!("there is no file to apply potential -exec/{{}} to"));
Ok(0i32)
}.wrap_err("-exec/{} operations failed")?;
if_trace!(match rc {
0 => trace!("-exec/{{}} operation(s all) returned 0 exit status"),
n => error!("-exec/{{}} operation(s) returned non-zero exit code (total: {}) or were killed by signal", n),
});
// Now that transfer is complete from buffer to `stdout`, close `stdout` pipe before exiting process. // Now that transfer is complete from buffer to `stdout`, close `stdout` pipe before exiting process.
if_trace!(info!("Transfer complete, closing `stdout` pipe")); if_trace!(info!("Transfer complete, closing `stdout` pipe"));
@ -591,5 +643,9 @@ fn main() -> errors::DispersedResult<()> {
.with_warning(|| format!("It is possible fd {} (STDOUT_FILENO) has already been closed; if so, look for where that happens and prevent it. `stdout` should be closed here.", stdout_fd).header("Possible bug")) .with_warning(|| format!("It is possible fd {} (STDOUT_FILENO) has already been closed; if so, look for where that happens and prevent it. `stdout` should be closed here.", stdout_fd).header("Possible bug"))
}.wrap_err(eyre!("Failed to close stdout"))?; }.wrap_err(eyre!("Failed to close stdout"))?;
if rc != 0 {
std::process::exit(rc);
}
Ok(()) Ok(())
} }

Loading…
Cancel
Save