]> git.proxmox.com Git - pve-xtermjs.git/blobdiff - termproxy/src/pty.rs
termproxy: avoid expensive proxmox-sys crate, copy over PTY module
[pve-xtermjs.git] / termproxy / src / pty.rs
diff --git a/termproxy/src/pty.rs b/termproxy/src/pty.rs
new file mode 100644 (file)
index 0000000..660d3ab
--- /dev/null
@@ -0,0 +1,134 @@
+//! Helper for creating a pseudo-terminal
+//!
+//! see [PTY](struct.PTY.html) for an example on how to use it
+
+use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
+
+use nix::fcntl::OFlag;
+use nix::pty::{grantpt, posix_openpt, ptsname_r, unlockpt, PtyMaster};
+use nix::sys::stat::Mode;
+use nix::unistd::{dup2, setsid};
+use nix::{ioctl_write_int_bad, ioctl_write_ptr_bad, Result};
+
+ioctl_write_int_bad!(set_controlling_tty, libc::TIOCSCTTY);
+ioctl_write_ptr_bad!(set_size, libc::TIOCSWINSZ, nix::pty::Winsize);
+
+/// Represents a PTY
+///
+/// Implements Read and Write (from std::io) so one can simply use it
+/// to read and write the terminal of a child process
+///
+/// Example:
+/// ```
+/// # use proxmox_sys::linux::pty::*;
+/// # use std::process::Command;
+/// # use nix::Result;
+/// fn fork() -> Result<u64> {
+///     // Code that forks and returs the pid/0
+/// # Ok(1)
+/// }
+///
+/// fn exec(cmd: &str) -> Result<()> {
+///     // Code that execs the cmd
+/// #    Ok(())
+/// }
+///
+/// fn main() -> Result<()> {
+///     let (mut pty, secondary) = PTY::new()?;
+///
+///     let child = fork()?;
+///     if child == 0 {
+///         make_controlling_terminal(&secondary)?;
+///         exec("/some/binary")?;
+///     }
+///
+///     // read/write or set size of the terminal
+///     pty.set_size(80, 20);
+///
+///     Ok(())
+///  }
+/// ```
+pub struct PTY {
+    primary: PtyMaster,
+}
+
+/// Used to make a new process group of the current process,
+/// and make the given terminal its controlling terminal
+pub fn make_controlling_terminal(terminal: &str) -> Result<()> {
+    setsid()?; // make new process group
+    let mode = Mode::S_IRUSR
+        | Mode::S_IWUSR
+        | Mode::S_IRGRP
+        | Mode::S_IWGRP
+        | Mode::S_IROTH
+        | Mode::S_IWOTH; // 0666
+    let secondary_fd = nix::fcntl::open(terminal, OFlag::O_RDWR | OFlag::O_NOCTTY, mode)
+        .map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })?;
+    let s_raw_fd = secondary_fd.as_raw_fd();
+    unsafe { set_controlling_tty(s_raw_fd, 0) }?;
+    dup2(s_raw_fd, 0)?;
+    dup2(s_raw_fd, 1)?;
+    dup2(s_raw_fd, 2)?;
+
+    if s_raw_fd <= 2 {
+        std::mem::forget(secondary_fd); // don't call drop handler
+    }
+
+    Ok(())
+}
+
+impl PTY {
+    /// Creates a new PTY by opening /dev/ptmx and returns
+    /// a new PTY and the path to the secondary terminal on success.
+    pub fn new() -> Result<(Self, String)> {
+        let primary =
+            posix_openpt(OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC)?;
+        grantpt(&primary)?;
+        unlockpt(&primary)?;
+        let secondary = ptsname_r(&primary)?; // linux specific
+        Ok((Self { primary }, secondary))
+    }
+
+    /// Uses the ioctl 'TIOCSWINSZ' on the terminal fd to set the terminals
+    /// columns and rows
+    pub fn set_size(&mut self, col: u16, row: u16) -> Result<()> {
+        let size = nix::pty::Winsize {
+            ws_row: row,
+            ws_col: col,
+            ws_xpixel: 0,
+            ws_ypixel: 0,
+        };
+
+        unsafe { set_size(self.primary.as_raw_fd(), &size) }?;
+
+        Ok(())
+    }
+}
+
+impl std::io::Read for PTY {
+    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+        Ok(nix::unistd::read(self.primary.as_raw_fd(), buf)?)
+    }
+}
+
+impl std::io::Write for PTY {
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+        Ok(nix::unistd::write(self.primary.as_raw_fd(), buf)?)
+    }
+
+    fn flush(&mut self) -> std::io::Result<()> {
+        Ok(())
+    }
+}
+
+impl AsRawFd for PTY {
+    fn as_raw_fd(&self) -> RawFd {
+        self.primary.as_raw_fd()
+    }
+}
+
+impl AsFd for PTY {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
+    }
+}