chiark / gitweb /
childio: new facility, will be used for ssh child
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 31 May 2021 12:03:07 +0000 (13:03 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 31 May 2021 12:54:07 +0000 (13:54 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/childio.rs [new file with mode: 0644]
src/lib.rs

diff --git a/src/childio.rs b/src/childio.rs
new file mode 100644 (file)
index 0000000..35446b2
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright 2020-2021 Ian Jackson and contributors to Otter
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// There is NO WARRANTY.
+
+use crate::prelude::*;
+
+use std::process::{self, ChildStdin, ChildStdout};
+
+#[derive(Debug)]
+pub struct ChildIo<RW> {
+  rw: RW,
+  child: Arc<Mutex<ChildWrapper>>,
+}
+
+#[derive(Debug)]
+struct ChildWrapper {
+  reported: bool,
+  desc: String,
+  child: process::Child,
+}
+
+impl Display for ChildWrapper {
+  #[throws(fmt::Error)]
+  fn fmt(&self, f: &mut fmt::Formatter) {
+    write!(f, "subprocess {} [{}]", &self.desc, self.child.id())?
+  }
+}
+
+impl<RW> ChildIo<RW> {
+  fn rw_result(&self, r: io::Result<usize>) -> io::Result<usize> {
+    if r.is_ok() && *r.as_ref().unwrap() > 0 { return r }
+    let status = self.child.lock().child.try_wait()?;
+    match status {
+      None => r,
+      Some(es) if es.success() => r,
+      Some(es) => {
+        let mut child = self.child.lock();
+        child.reported = true;
+        let ae = anyhow!("{} failed: {}", &child, es);
+        Err(io::Error::new(ErrorKind::Other, ae))
+      },
+    }
+  }
+}
+
+pub fn new_pair(mut input: process::Child, desc: String)
+                -> (ChildIo<ChildStdin>, ChildIo<ChildStdout>) {
+  let stdin  = input.stdin .take().expect("ChildIo::pair, no stdin¬");
+  let stdout = input.stdout.take().expect("ChildIo::pair, no stdout¬");
+  let wrapper = Arc::new(Mutex::new(ChildWrapper {
+    reported: false,
+    desc,
+    child: input,
+  }));
+  (ChildIo { rw: stdin,  child: wrapper.clone() },
+   ChildIo { rw: stdout, child: wrapper         })
+}
+
+#[throws(io::Error)]
+pub fn run_pair(mut cmd: process::Command, desc: String)
+                -> (ChildIo<ChildStdin>, ChildIo<ChildStdout>) {
+  cmd.stdin (Stdio::piped());
+  cmd.stdout(Stdio::piped());
+  new_pair(cmd.spawn()?, desc)
+}
+
+impl Drop for ChildWrapper {
+  fn drop(&mut self) {
+    use nix::sys::signal::{self, Signal::*};
+    use nix::unistd::Pid;
+
+    if let Err(e) = (||{
+      let es = match self.child.try_wait().context("wait")? {
+        Some(es) => es,
+        None => {
+          let pid = self.child.id();
+          let pid = pid.try_into()
+            .map_err(|_| anyhow!("pid {:?} out of range!", pid))?;
+          let pid = Pid::from_raw(pid);
+          signal::kill(pid, SIGTERM).context("kill")?;
+          self.child.wait().context("wait after kill")?
+        },
+      };
+      if ! self.reported && ! es.success()
+      && es.signal() != Some(SIGPIPE as _) {
+        warn!("{} failed: {}", &self, es);
+      }
+      Ok::<_,AE>(())
+    })() {
+      warn!("{} cleanup failed: {}", &self, e);
+    }
+  }
+}
+
+impl<R> Read for ChildIo<R> where R: Read {
+  fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+    let r = self.rw.read(buf);
+    self.rw_result(r)
+  }
+}
+
+impl<W> Write for ChildIo<W> where W: Write {
+  fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+    let r = self.rw.write(buf);
+    self.rw_result(r)
+  }
+  fn flush(&mut self) -> io::Result<()> {
+    let r = self.rw.flush();
+    self.rw_result(r.map(|()|0)).map(|_|())
+  }
+}
index ef9209ffd6136efaf97690e40ac5b1fb5848c46d..14363c9f08d147912f26e6325f2d5391b8450937 100644 (file)
@@ -13,6 +13,7 @@ pub mod accounts;
 pub mod asseturl;
 pub mod authproofs;
 pub mod bundles;
+pub mod childio;
 pub mod clock;
 pub mod commands;
 pub mod config;