/* TODO: Allow `collect -exec <command>` /proc/self/fds/<memfd OR STDOUT_FILENO>, `collect -exec{} <command> {/proc/self/fds/<memfd OR STDOUT_FILENO>} <other args>`
structOptions{
/// If the arguments vector contains `None`, that `None` shall be replaced with the string referring to: If in memfd mode: The `memfd_create()` buffer fd, set to RW, truncated to size, seeked to 0. In this mode, the file will remain open when `ecec` is not `None`, and will instead be returned below, as the mode methods will all be modified to return `Option<Box<dyn ModeReturn + 'static>>::Some(<private struct: impl ModeReturn>)`, which will contain the memfd object so it is dropped *after* the child process has exited. If the mode is *not* memfd, then `STDOUT_FILENO` itself will be used; also set to RW, truncated correctly, and seeked to 0. The return code of this process shall be the return code of the child process once it has terminated.
/// Execution of commands (if passed) **always** happens *after* the copy to `stdout`, but *before* the **close** of `stdout`. If the copy to `stdout` fails, the exec will not be executed regardless of if the mode required is actually using `stdout`.
/// The process shall always wait for the child to terminate before exiting. If the child daemon forks, that fork is not followed, and the process exists anyway.
/// Ideally, A `SIGHUP` handler should be registered, which tells the parent to stop waiting on the child and exit now. TODO: The behaviour of the child is unspecified if this happens. It may be killed, or re-attached to `init`. But the return code of the parent should always be `0` in this case.
implModeReturnforBufferedReturn{fnget_fd_str(&self)-> &OsStr{static_assert(STDOUT_FILENO==1);"/proc/self/fds/1"}}/* XXX: In the case where the (compile time) check of STDOUT_FILENO == 0 fails, another boxed struct containing the OSString with the correct path that `impl ModeReturn` can be returned, this path will be removed by the compiler if `STDOUT_FILENO != 1`, allowing for better unboxing analysis. */
if_trace!(warn!("This is an incorrectly compiled binary! Compiled with `mode: buffered` and the `memfile` feature; `memfile` stragery will be used and the mode selection will be ignored."));
//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.))
error!("Size too large (over max by {}) (max {})",to-(i64::MAXasu64),i64::MAX);
}else{
trace!("Setting {fd} size to {to}");
}
}
ifcfg!(debug_assertions){
i64::try_from(to).map_err(|_|io::Error::new(io::ErrorKind::InvalidInput,"Size too large for ftruncate() offset"))?
}else{
toasi64
}
};
matchunsafe{ftruncate(fd,to)}{
-1=>Err(io::Error::last_os_error()),
_=>Ok(())
}
}
//TODO: How to `ftruncate()` stdout only once... If try_get_size succeeds, we want to do it then. If it doesn't, we want to do it when `stdin` as been consumed an we know the size of the memory-file... `RunOnce` won't work unless we can give it an argument....
#[allow(unused_mut)]
letmutset_stdout_len={
cfg_if!{
if#[cfg(feature="memfile-size-output")]{
if_trace!(warn!("Feature `memfile-size-output` is not yet stable and will cause crash."));
constSTDOUT: memfile::fd::RawFileDescriptor=unsafe{memfile::fd::RawFileDescriptor::new_unchecked(libc::STDOUT_FILENO)};//TODO: Get this from `std::io::Stdout.as_raw_fd()` instead.
//TODO: XXX: Even if this actually works, is it safe to do this? Won't the consumer try to read `value` bytes before we've written them? Perhaps remove pre-setting entirely...
matchbuffsz{
y@Some(refvalue)=>{
letvalue=value.get();
set_stdout_len(value).wrap_err("Failed to set stdout len to that of stdin")
.with_section(||value.header("Stdin len was calculated as"))
.wrap_err(eyre!("Failed to convert read bytes to `usize`")
.with_section(||read.header("Number of bytes was"))
.with_section(||u128::abs_diff(read.into(),usize::MAXasu128).header("Difference between `read` and `usize::MAX` is"))
.with_suggestion(||"It is likely you are running on a 32-bit ptr width machine and this input exceeds that of the maximum 32-bit unsigned integer value")
.with_note(||usize::MAX.header("Maximum value of `usize`")))?)
//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?
letstdout_fd=libc::STDOUT_FILENO;// (io::Stdout does not impl `IntoRawFd`, just use the raw fd directly; using the constant from libc may help in weird cases where STDOUT_FILENO is not 1...)
debug_assert_eq!(stdout_fd,std::io::stdout().as_raw_fd(),"STDOUT_FILENO and io::stdout().as_raw_fd() are not returning the same value.");
close_fileno(/*std::io::stdout().as_raw_fd()*/stdout_fd)// SAFETY: We just assume fd 1 is still open. If it's not (i.e. already been closed), this will return error.
.with_section(move||stdout_fd.header("Attempted to close this fd (STDOUT_FILENO)"))
.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"))
if_trace!(error!("Exiting with non-zero code due to child(s) returning non-zero exit status"));//TODO: A runtime flag to disable this? TODO: Also, a flag to stop printing to stdout so consumers of output can use just `-exec/{}` child process `stdout`s is enabled