diff options
Diffstat (limited to 'src/blocking')
-rw-r--r-- | src/blocking/command.rs | 156 | ||||
-rw-r--r-- | src/blocking/mod.rs | 4 | ||||
-rw-r--r-- | src/blocking/pty.rs | 66 |
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() + } +} |