]> git.proxmox.com Git - pve-xtermjs.git/blame - termproxy/src/pty.rs
termproxy: avoid expensive proxmox-sys crate, copy over PTY module
[pve-xtermjs.git] / termproxy / src / pty.rs
CommitLineData
a69ef0b6
TL
1//! Helper for creating a pseudo-terminal
2//!
3//! see [PTY](struct.PTY.html) for an example on how to use it
4
5use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
6
7use nix::fcntl::OFlag;
8use nix::pty::{grantpt, posix_openpt, ptsname_r, unlockpt, PtyMaster};
9use nix::sys::stat::Mode;
10use nix::unistd::{dup2, setsid};
11use nix::{ioctl_write_int_bad, ioctl_write_ptr_bad, Result};
12
13ioctl_write_int_bad!(set_controlling_tty, libc::TIOCSCTTY);
14ioctl_write_ptr_bad!(set_size, libc::TIOCSWINSZ, nix::pty::Winsize);
15
16/// Represents a PTY
17///
18/// Implements Read and Write (from std::io) so one can simply use it
19/// to read and write the terminal of a child process
20///
21/// Example:
22/// ```
23/// # use proxmox_sys::linux::pty::*;
24/// # use std::process::Command;
25/// # use nix::Result;
26/// fn fork() -> Result<u64> {
27/// // Code that forks and returs the pid/0
28/// # Ok(1)
29/// }
30///
31/// fn exec(cmd: &str) -> Result<()> {
32/// // Code that execs the cmd
33/// # Ok(())
34/// }
35///
36/// fn main() -> Result<()> {
37/// let (mut pty, secondary) = PTY::new()?;
38///
39/// let child = fork()?;
40/// if child == 0 {
41/// make_controlling_terminal(&secondary)?;
42/// exec("/some/binary")?;
43/// }
44///
45/// // read/write or set size of the terminal
46/// pty.set_size(80, 20);
47///
48/// Ok(())
49/// }
50/// ```
51pub struct PTY {
52 primary: PtyMaster,
53}
54
55/// Used to make a new process group of the current process,
56/// and make the given terminal its controlling terminal
57pub fn make_controlling_terminal(terminal: &str) -> Result<()> {
58 setsid()?; // make new process group
59 let mode = Mode::S_IRUSR
60 | Mode::S_IWUSR
61 | Mode::S_IRGRP
62 | Mode::S_IWGRP
63 | Mode::S_IROTH
64 | Mode::S_IWOTH; // 0666
65 let secondary_fd = nix::fcntl::open(terminal, OFlag::O_RDWR | OFlag::O_NOCTTY, mode)
66 .map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })?;
67 let s_raw_fd = secondary_fd.as_raw_fd();
68 unsafe { set_controlling_tty(s_raw_fd, 0) }?;
69 dup2(s_raw_fd, 0)?;
70 dup2(s_raw_fd, 1)?;
71 dup2(s_raw_fd, 2)?;
72
73 if s_raw_fd <= 2 {
74 std::mem::forget(secondary_fd); // don't call drop handler
75 }
76
77 Ok(())
78}
79
80impl PTY {
81 /// Creates a new PTY by opening /dev/ptmx and returns
82 /// a new PTY and the path to the secondary terminal on success.
83 pub fn new() -> Result<(Self, String)> {
84 let primary =
85 posix_openpt(OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC)?;
86 grantpt(&primary)?;
87 unlockpt(&primary)?;
88 let secondary = ptsname_r(&primary)?; // linux specific
89 Ok((Self { primary }, secondary))
90 }
91
92 /// Uses the ioctl 'TIOCSWINSZ' on the terminal fd to set the terminals
93 /// columns and rows
94 pub fn set_size(&mut self, col: u16, row: u16) -> Result<()> {
95 let size = nix::pty::Winsize {
96 ws_row: row,
97 ws_col: col,
98 ws_xpixel: 0,
99 ws_ypixel: 0,
100 };
101
102 unsafe { set_size(self.primary.as_raw_fd(), &size) }?;
103
104 Ok(())
105 }
106}
107
108impl std::io::Read for PTY {
109 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
110 Ok(nix::unistd::read(self.primary.as_raw_fd(), buf)?)
111 }
112}
113
114impl std::io::Write for PTY {
115 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
116 Ok(nix::unistd::write(self.primary.as_raw_fd(), buf)?)
117 }
118
119 fn flush(&mut self) -> std::io::Result<()> {
120 Ok(())
121 }
122}
123
124impl AsRawFd for PTY {
125 fn as_raw_fd(&self) -> RawFd {
126 self.primary.as_raw_fd()
127 }
128}
129
130impl AsFd for PTY {
131 fn as_fd(&self) -> BorrowedFd<'_> {
132 unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
133 }
134}