]> git.proxmox.com Git - pve-xtermjs.git/blob - termproxy/src/pty.rs
d/control: fix build-dependencies
[pve-xtermjs.git] / termproxy / src / pty.rs
1 //! Helper for creating a pseudo-terminal
2 //!
3 //! see [PTY](struct.PTY.html) for an example on how to use it
4
5 use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
6
7 use nix::fcntl::OFlag;
8 use nix::pty::{grantpt, posix_openpt, ptsname_r, unlockpt, PtyMaster};
9 use nix::sys::stat::Mode;
10 use nix::unistd::{dup2, setsid};
11 use nix::{ioctl_write_int_bad, ioctl_write_ptr_bad, Result};
12
13 ioctl_write_int_bad!(set_controlling_tty, libc::TIOCSCTTY);
14 ioctl_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 /// ```
51 pub 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
57 pub 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
80 impl 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
108 impl 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
114 impl 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
124 impl AsRawFd for PTY {
125 fn as_raw_fd(&self) -> RawFd {
126 self.primary.as_raw_fd()
127 }
128 }
129
130 impl AsFd for PTY {
131 fn as_fd(&self) -> BorrowedFd<'_> {
132 unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
133 }
134 }