From 14f32d62625e9683afe1540dc2db586b7331f2c7 Mon Sep 17 00:00:00 2001 From: Avril Date: Sat, 4 Jun 2022 06:08:09 +0100 Subject: [PATCH] Explicitly closes `stdout` before process exits. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version bump: 1.1.0 Fortune for collect's current commit: Half curse − 半凶 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e59990..a63465e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,7 +84,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "collect" -version = "1.0.3" +version = "1.1.0" dependencies = [ "bitflags", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 13554a6..4e117dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "collect" -version = "1.0.3" +version = "1.1.0" description = "collect all of stdin until it is closed, then output it all to stdout" keywords = ["shell", "pipe", "utility", "unix", "linux"] authors = ["Avril "] diff --git a/src/main.rs b/src/main.rs index df51799..7ce2040 100644 --- a/src/main.rs +++ b/src/main.rs @@ -449,12 +449,41 @@ mod work { } } + +#[cfg_attr(feature="logging", instrument(err))] +#[inline(always)] +unsafe fn close_raw_fileno(fd: RawFd) -> io::Result<()> +{ + match libc::close(fd) { + 0 => Ok(()), + _ => Err(io::Error::last_os_error()), + } +} + +#[inline] +#[cfg_attr(feature="logging", instrument(skip_all, fields(T = ?std::any::type_name::())))] +fn close_fileno(fd: T) -> eyre::Result<()> +{ + let fd = fd.into_raw_fd(); + if fd < 0 { + return Err(eyre!("Invalid fd").with_note(|| format!("fds begin at 0 and end at {}", RawFd::MAX))); + } else { + if_trace!(debug!("closing consumed fd {fd}")); + unsafe { + close_raw_fileno(fd) + }.wrap_err("Failed to close fd") + .with_section(move || fd.header("Fileno was")) + .with_section(|| std::any::type_name::().header("")) + } +} + #[cfg_attr(feature="logging", instrument(err))] fn main() -> eyre::Result<()> { init()?; feature_check()?; if_trace!(debug!("initialised")); + //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? cfg_if!{ if #[cfg(feature="memfile")] { work::memfd() @@ -464,6 +493,17 @@ fn main() -> eyre::Result<()> { .wrap_err("Operation failed").with_note(|| "Strategy was `buffered`")?; } } + // Transfer complete + + // Now that transfer is complete from buffer to `stdout`, close `stdout` pipe before exiting process. + if_trace!(info!("Transfer complete, closing `stdout` pipe")); + { + let stdout_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")) + }.wrap_err(eyre!("Failed to close stdout"))?; Ok(()) }