aboutsummaryrefslogtreecommitdiffstats
path: root/src/blocking
diff options
context:
space:
mode:
Diffstat (limited to 'src/blocking')
-rw-r--r--src/blocking/command.rs156
-rw-r--r--src/blocking/mod.rs4
-rw-r--r--src/blocking/pty.rs66
3 files changed, 226 insertions, 0 deletions
diff --git a/src/blocking/command.rs b/src/blocking/command.rs
new file mode 100644
index 0000000..3de0f3e
--- /dev/null
+++ b/src/blocking/command.rs
@@ -0,0 +1,156 @@
+use std::os::unix::process::CommandExt as _;
+
+pub struct Command {
+ inner: std::process::Command,
+ stdin: Option<std::process::Stdio>,
+ stdout: Option<std::process::Stdio>,
+ stderr: Option<std::process::Stdio>,
+}
+
+impl Command {
+ pub fn new<S: AsRef<std::ffi::OsStr>>(program: S) -> Self {
+ Self {
+ inner: std::process::Command::new(program),
+ stdin: None,
+ stdout: None,
+ stderr: None,
+ }
+ }
+
+ pub fn arg<S: AsRef<std::ffi::OsStr>>(&mut self, arg: S) -> &mut Self {
+ self.inner.arg(arg);
+ self
+ }
+
+ pub fn args<I, S>(&mut self, args: I) -> &mut Self
+ where
+ I: IntoIterator<Item = S>,
+ S: AsRef<std::ffi::OsStr>,
+ {
+ self.inner.args(args);
+ self
+ }
+
+ pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
+ where
+ K: AsRef<std::ffi::OsStr>,
+ V: AsRef<std::ffi::OsStr>,
+ {
+ self.inner.env(key, val);
+ self
+ }
+
+ pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
+ where
+ I: IntoIterator<Item = (K, V)>,
+ K: AsRef<std::ffi::OsStr>,
+ V: AsRef<std::ffi::OsStr>,
+ {
+ self.inner.envs(vars);
+ self
+ }
+
+ pub fn env_remove<K: AsRef<std::ffi::OsStr>>(
+ &mut self,
+ key: K,
+ ) -> &mut Self {
+ self.inner.env_remove(key);
+ self
+ }
+
+ pub fn env_clear(&mut self) -> &mut Self {
+ self.inner.env_clear();
+ self
+ }
+
+ pub fn current_dir<P: AsRef<std::path::Path>>(
+ &mut self,
+ dir: P,
+ ) -> &mut Self {
+ self.inner.current_dir(dir);
+ self
+ }
+
+ pub fn stdin<T: Into<std::process::Stdio>>(
+ &mut self,
+ cfg: Option<T>,
+ ) -> &mut Self {
+ self.stdin = cfg.map(Into::into);
+ self
+ }
+
+ pub fn stdout<T: Into<std::process::Stdio>>(
+ &mut self,
+ cfg: Option<T>,
+ ) -> &mut Self {
+ self.stdout = cfg.map(Into::into);
+ self
+ }
+
+ pub fn stderr<T: Into<std::process::Stdio>>(
+ &mut self,
+ cfg: Option<T>,
+ ) -> &mut Self {
+ self.stderr = cfg.map(Into::into);
+ self
+ }
+
+ pub fn spawn(
+ &mut self,
+ pty: crate::blocking::Pty,
+ ) -> crate::Result<Child> {
+ let (stdin, stdout, stderr, pre_exec) = crate::sys::setup_subprocess(
+ &pty,
+ pty.pts().map_err(crate::error::spawn)?,
+ )
+ .map_err(crate::error::spawn)?;
+
+ self.inner.stdin(self.stdin.take().unwrap_or(stdin));
+ self.inner.stdout(self.stdout.take().unwrap_or(stdout));
+ self.inner.stderr(self.stderr.take().unwrap_or(stderr));
+
+ // safe because setsid() and close() are async-signal-safe functions
+ // and ioctl() is a raw syscall (which is inherently
+ // async-signal-safe).
+ unsafe { self.inner.pre_exec(pre_exec) };
+
+ let child = self.inner.spawn().map_err(crate::error::spawn)?;
+
+ Ok(Child::new(child, pty))
+ }
+}
+
+pub struct Child {
+ inner: std::process::Child,
+ pty: crate::blocking::Pty,
+}
+
+impl Child {
+ fn new(inner: std::process::Child, pty: crate::blocking::Pty) -> Self {
+ Self { inner, pty }
+ }
+
+ #[must_use]
+ pub fn pty(&self) -> &crate::blocking::Pty {
+ &self.pty
+ }
+
+ #[must_use]
+ pub fn pty_mut(&mut self) -> &mut crate::blocking::Pty {
+ &mut self.pty
+ }
+}
+
+impl std::ops::Deref for Child {
+ type Target = std::process::Child;
+
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+impl std::ops::DerefMut for Child {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.inner
+ }
+}
diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs
new file mode 100644
index 0000000..a45b132
--- /dev/null
+++ b/src/blocking/mod.rs
@@ -0,0 +1,4 @@
+mod command;
+pub use command::{Child, Command};
+mod pty;
+pub use pty::Pty;
diff --git a/src/blocking/pty.rs b/src/blocking/pty.rs
new file mode 100644
index 0000000..7900b50
--- /dev/null
+++ b/src/blocking/pty.rs
@@ -0,0 +1,66 @@
+pub struct Pty {
+ pt: std::fs::File,
+ ptsname: std::path::PathBuf,
+}
+
+impl Pty {
+ pub fn new() -> crate::Result<Self> {
+ let (pt, ptsname) =
+ crate::sys::create_pt().map_err(crate::error::create_pty)?;
+ Ok(Self { pt, ptsname })
+ }
+
+ pub fn resize(&self, size: crate::Size) -> crate::error::Result<()> {
+ crate::sys::set_term_size(self, size)
+ .map_err(crate::error::set_term_size)
+ }
+
+ pub(crate) fn pts(&self) -> std::io::Result<std::fs::File> {
+ let fh = std::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&self.ptsname)?;
+ Ok(fh)
+ }
+}
+
+impl std::ops::Deref for Pty {
+ type Target = std::fs::File;
+
+ fn deref(&self) -> &Self::Target {
+ &self.pt
+ }
+}
+
+impl std::ops::DerefMut for Pty {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.pt
+ }
+}
+
+impl std::os::unix::io::AsRawFd for Pty {
+ fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
+ self.pt.as_raw_fd()
+ }
+}
+
+// there is a Read impl for &std::fs::File, but without this explicit impl,
+// rust finds the Read impl for std::fs::File first, and then complains that
+// it requires &mut self, because method resolution/autoderef doesn't take
+// mutability into account
+impl std::io::Read for &Pty {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ (&self.pt).read(buf)
+ }
+}
+
+// same as above
+impl std::io::Write for &Pty {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ (&self.pt).write(buf)
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ (&self.pt).flush()
+ }
+}