//! 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(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(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)) } #[cfg_attr(feature="logging", instrument(skip_all, fields(has_stdin = ?file.is_some(), filename = ?filename.as_ref())))] fn run_stdin(file: Option>, filename: impl AsRef, args: I) -> io::Result<(process::Child, Option)> where I: IntoIterator, { let file = { let file: Option = 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(file: &F, opt: args::ExecMode) -> io::Result<(process::Child, Option)> { let input: std::mem::ManuallyDrop = std::mem::ManuallyDrop::new(dup_file(file)?); match opt { args::ExecMode::Positional { command, args } => { run_stdin(None::, 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)>> + '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>> + '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) }) }