| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 1 | //! Create master and slave virtual pseudo-terminals (PTYs) |
| 2 | |
| 3 | pub use libc::pid_t as SessionId; |
| 4 | pub use libc::winsize as Winsize; |
| 5 | |
| 6 | use std::ffi::CStr; |
| 7 | use std::io; |
| 8 | use std::mem; |
| 9 | use std::os::unix::prelude::*; |
| 10 | |
| 11 | use crate::sys::termios::Termios; |
| 12 | use crate::unistd::{self, ForkResult, Pid}; |
| 13 | use crate::{Result, Error, fcntl}; |
| 14 | use crate::errno::Errno; |
| 15 | |
| 16 | /// Representation of a master/slave pty pair |
| 17 | /// |
| 18 | /// This is returned by `openpty`. Note that this type does *not* implement `Drop`, so the user |
| 19 | /// must manually close the file descriptors. |
| 20 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
| 21 | pub struct OpenptyResult { |
| 22 | /// The master port in a virtual pty pair |
| 23 | pub master: RawFd, |
| 24 | /// The slave port in a virtual pty pair |
| 25 | pub slave: RawFd, |
| 26 | } |
| 27 | |
| 28 | /// Representation of a master with a forked pty |
| 29 | /// |
| 30 | /// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user |
| 31 | /// must manually close the file descriptors. |
| 32 | #[derive(Clone, Copy, Debug)] |
| 33 | pub struct ForkptyResult { |
| 34 | /// The master port in a virtual pty pair |
| 35 | pub master: RawFd, |
| 36 | /// Metadata about forked process |
| 37 | pub fork_result: ForkResult, |
| 38 | } |
| 39 | |
| 40 | |
| 41 | /// Representation of the Master device in a master/slave pty pair |
| 42 | /// |
| 43 | /// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY |
| 44 | /// functions are given the correct file descriptor. Additionally this type implements `Drop`, |
| 45 | /// so that when it's consumed or goes out of scope, it's automatically cleaned-up. |
| Joel Galenson | 4727c11 | 2021-04-02 12:22:24 -0700 | [diff] [blame] | 46 | #[derive(Debug, Eq, Hash, PartialEq)] |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 47 | pub struct PtyMaster(RawFd); |
| 48 | |
| 49 | impl AsRawFd for PtyMaster { |
| 50 | fn as_raw_fd(&self) -> RawFd { |
| 51 | self.0 |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | impl IntoRawFd for PtyMaster { |
| 56 | fn into_raw_fd(self) -> RawFd { |
| 57 | let fd = self.0; |
| 58 | mem::forget(self); |
| 59 | fd |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | impl Drop for PtyMaster { |
| 64 | fn drop(&mut self) { |
| 65 | // On drop, we ignore errors like EINTR and EIO because there's no clear |
| 66 | // way to handle them, we can't return anything, and (on FreeBSD at |
| 67 | // least) the file descriptor is deallocated in these cases. However, |
| 68 | // we must panic on EBADF, because it is always an error to close an |
| 69 | // invalid file descriptor. That frequently indicates a double-close |
| 70 | // condition, which can cause confusing errors for future I/O |
| 71 | // operations. |
| 72 | let e = unistd::close(self.0); |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 73 | if e == Err(Errno::EBADF) { |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 74 | panic!("Closing an invalid file descriptor!"); |
| 75 | }; |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | impl io::Read for PtyMaster { |
| 80 | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 81 | unistd::read(self.0, buf).map_err(io::Error::from) |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 82 | } |
| 83 | } |
| 84 | |
| 85 | impl io::Write for PtyMaster { |
| 86 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 87 | unistd::write(self.0, buf).map_err(io::Error::from) |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 88 | } |
| 89 | fn flush(&mut self) -> io::Result<()> { |
| 90 | Ok(()) |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | /// Grant access to a slave pseudoterminal (see |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 95 | /// [`grantpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html)) |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 96 | /// |
| 97 | /// `grantpt()` changes the mode and owner of the slave pseudoterminal device corresponding to the |
| 98 | /// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave. |
| 99 | #[inline] |
| 100 | pub fn grantpt(fd: &PtyMaster) -> Result<()> { |
| 101 | if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 { |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 102 | return Err(Error::from(Errno::last())); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 103 | } |
| 104 | |
| 105 | Ok(()) |
| 106 | } |
| 107 | |
| 108 | /// Open a pseudoterminal device (see |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 109 | /// [`posix_openpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html)) |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 110 | /// |
| 111 | /// `posix_openpt()` returns a file descriptor to an existing unused pseuterminal master device. |
| 112 | /// |
| 113 | /// # Examples |
| 114 | /// |
| 115 | /// A common use case with this function is to open both a master and slave PTY pair. This can be |
| 116 | /// done as follows: |
| 117 | /// |
| 118 | /// ``` |
| 119 | /// use std::path::Path; |
| 120 | /// use nix::fcntl::{OFlag, open}; |
| 121 | /// use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt}; |
| 122 | /// use nix::sys::stat::Mode; |
| 123 | /// |
| 124 | /// # #[allow(dead_code)] |
| 125 | /// # fn run() -> nix::Result<()> { |
| 126 | /// // Open a new PTY master |
| 127 | /// let master_fd = posix_openpt(OFlag::O_RDWR)?; |
| 128 | /// |
| 129 | /// // Allow a slave to be generated for it |
| 130 | /// grantpt(&master_fd)?; |
| 131 | /// unlockpt(&master_fd)?; |
| 132 | /// |
| 133 | /// // Get the name of the slave |
| 134 | /// let slave_name = unsafe { ptsname(&master_fd) }?; |
| 135 | /// |
| 136 | /// // Try to open the slave |
| 137 | /// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?; |
| 138 | /// # Ok(()) |
| 139 | /// # } |
| 140 | /// ``` |
| 141 | #[inline] |
| 142 | pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> { |
| 143 | let fd = unsafe { |
| 144 | libc::posix_openpt(flags.bits()) |
| 145 | }; |
| 146 | |
| 147 | if fd < 0 { |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 148 | return Err(Error::from(Errno::last())); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 149 | } |
| 150 | |
| 151 | Ok(PtyMaster(fd)) |
| 152 | } |
| 153 | |
| 154 | /// Get the name of the slave pseudoterminal (see |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 155 | /// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html)) |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 156 | /// |
| 157 | /// `ptsname()` returns the name of the slave pseudoterminal device corresponding to the master |
| 158 | /// referred to by `fd`. |
| 159 | /// |
| 160 | /// This value is useful for opening the slave pty once the master has already been opened with |
| 161 | /// `posix_openpt()`. |
| 162 | /// |
| 163 | /// # Safety |
| 164 | /// |
| 165 | /// `ptsname()` mutates global variables and is *not* threadsafe. |
| 166 | /// Mutating global variables is always considered `unsafe` by Rust and this |
| 167 | /// function is marked as `unsafe` to reflect that. |
| 168 | /// |
| 169 | /// For a threadsafe and non-`unsafe` alternative on Linux, see `ptsname_r()`. |
| 170 | #[inline] |
| 171 | pub unsafe fn ptsname(fd: &PtyMaster) -> Result<String> { |
| 172 | let name_ptr = libc::ptsname(fd.as_raw_fd()); |
| 173 | if name_ptr.is_null() { |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 174 | return Err(Error::from(Errno::last())); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | let name = CStr::from_ptr(name_ptr); |
| 178 | Ok(name.to_string_lossy().into_owned()) |
| 179 | } |
| 180 | |
| 181 | /// Get the name of the slave pseudoterminal (see |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 182 | /// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html)) |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 183 | /// |
| 184 | /// `ptsname_r()` returns the name of the slave pseudoterminal device corresponding to the master |
| 185 | /// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the |
| 186 | /// POSIX standard and is instead a Linux-specific extension. |
| 187 | /// |
| 188 | /// This value is useful for opening the slave ptty once the master has already been opened with |
| 189 | /// `posix_openpt()`. |
| 190 | #[cfg(any(target_os = "android", target_os = "linux"))] |
| 191 | #[inline] |
| 192 | pub fn ptsname_r(fd: &PtyMaster) -> Result<String> { |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 193 | let mut name_buf = Vec::<libc::c_char>::with_capacity(64); |
| 194 | let name_buf_ptr = name_buf.as_mut_ptr(); |
| 195 | let cname = unsafe { |
| 196 | let cap = name_buf.capacity(); |
| 197 | if libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, cap) != 0 { |
| 198 | return Err(Error::last()); |
| 199 | } |
| 200 | CStr::from_ptr(name_buf.as_ptr()) |
| 201 | }; |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 202 | |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 203 | let name = cname.to_string_lossy().into_owned(); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 204 | Ok(name) |
| 205 | } |
| 206 | |
| 207 | /// Unlock a pseudoterminal master/slave pseudoterminal pair (see |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 208 | /// [`unlockpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html)) |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 209 | /// |
| 210 | /// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal |
| 211 | /// referred to by `fd`. This must be called before trying to open the slave side of a |
| 212 | /// pseuoterminal. |
| 213 | #[inline] |
| 214 | pub fn unlockpt(fd: &PtyMaster) -> Result<()> { |
| 215 | if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 { |
| Joel Galenson | 981b40a | 2021-08-09 10:34:35 -0700 | [diff] [blame] | 216 | return Err(Error::from(Errno::last())); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 217 | } |
| 218 | |
| 219 | Ok(()) |
| 220 | } |
| 221 | |
| 222 | |
| 223 | /// Create a new pseudoterminal, returning the slave and master file descriptors |
| 224 | /// in `OpenptyResult` |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 225 | /// (see [`openpty`](https://man7.org/linux/man-pages/man3/openpty.3.html)). |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 226 | /// |
| 227 | /// If `winsize` is not `None`, the window size of the slave will be set to |
| 228 | /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's |
| 229 | /// terminal settings of the slave will be set to the values in `termios`. |
| 230 | #[inline] |
| 231 | pub fn openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult> { |
| 232 | use std::ptr; |
| 233 | |
| 234 | let mut slave = mem::MaybeUninit::<libc::c_int>::uninit(); |
| 235 | let mut master = mem::MaybeUninit::<libc::c_int>::uninit(); |
| 236 | let ret = { |
| 237 | match (termios.into(), winsize.into()) { |
| 238 | (Some(termios), Some(winsize)) => { |
| 239 | let inner_termios = termios.get_libc_termios(); |
| 240 | unsafe { |
| 241 | libc::openpty( |
| 242 | master.as_mut_ptr(), |
| 243 | slave.as_mut_ptr(), |
| 244 | ptr::null_mut(), |
| 245 | &*inner_termios as *const libc::termios as *mut _, |
| 246 | winsize as *const Winsize as *mut _, |
| 247 | ) |
| 248 | } |
| 249 | } |
| 250 | (None, Some(winsize)) => { |
| 251 | unsafe { |
| 252 | libc::openpty( |
| 253 | master.as_mut_ptr(), |
| 254 | slave.as_mut_ptr(), |
| 255 | ptr::null_mut(), |
| 256 | ptr::null_mut(), |
| 257 | winsize as *const Winsize as *mut _, |
| 258 | ) |
| 259 | } |
| 260 | } |
| 261 | (Some(termios), None) => { |
| 262 | let inner_termios = termios.get_libc_termios(); |
| 263 | unsafe { |
| 264 | libc::openpty( |
| 265 | master.as_mut_ptr(), |
| 266 | slave.as_mut_ptr(), |
| 267 | ptr::null_mut(), |
| 268 | &*inner_termios as *const libc::termios as *mut _, |
| 269 | ptr::null_mut(), |
| 270 | ) |
| 271 | } |
| 272 | } |
| 273 | (None, None) => { |
| 274 | unsafe { |
| 275 | libc::openpty( |
| 276 | master.as_mut_ptr(), |
| 277 | slave.as_mut_ptr(), |
| 278 | ptr::null_mut(), |
| 279 | ptr::null_mut(), |
| 280 | ptr::null_mut(), |
| 281 | ) |
| 282 | } |
| 283 | } |
| 284 | } |
| 285 | }; |
| 286 | |
| 287 | Errno::result(ret)?; |
| 288 | |
| 289 | unsafe { |
| 290 | Ok(OpenptyResult { |
| 291 | master: master.assume_init(), |
| 292 | slave: slave.assume_init(), |
| 293 | }) |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | /// Create a new pseudoterminal, returning the master file descriptor and forked pid. |
| 298 | /// in `ForkptyResult` |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 299 | /// (see [`forkpty`](https://man7.org/linux/man-pages/man3/forkpty.3.html)). |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 300 | /// |
| 301 | /// If `winsize` is not `None`, the window size of the slave will be set to |
| 302 | /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's |
| 303 | /// terminal settings of the slave will be set to the values in `termios`. |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 304 | /// |
| 305 | /// # Safety |
| 306 | /// |
| 307 | /// In a multithreaded program, only [async-signal-safe] functions like `pause` |
| 308 | /// and `_exit` may be called by the child (the parent isn't restricted). Note |
| 309 | /// that memory allocation may **not** be async-signal-safe and thus must be |
| 310 | /// prevented. |
| 311 | /// |
| 312 | /// Those functions are only a small subset of your operating system's API, so |
| 313 | /// special care must be taken to only invoke code you can control and audit. |
| 314 | /// |
| 315 | /// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html |
| 316 | pub unsafe fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>( |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 317 | winsize: T, |
| 318 | termios: U, |
| 319 | ) -> Result<ForkptyResult> { |
| 320 | use std::ptr; |
| 321 | |
| 322 | let mut master = mem::MaybeUninit::<libc::c_int>::uninit(); |
| 323 | |
| 324 | let term = match termios.into() { |
| 325 | Some(termios) => { |
| 326 | let inner_termios = termios.get_libc_termios(); |
| 327 | &*inner_termios as *const libc::termios as *mut _ |
| 328 | }, |
| 329 | None => ptr::null_mut(), |
| 330 | }; |
| 331 | |
| 332 | let win = winsize |
| 333 | .into() |
| 334 | .map(|ws| ws as *const Winsize as *mut _) |
| 335 | .unwrap_or(ptr::null_mut()); |
| 336 | |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 337 | let res = libc::forkpty(master.as_mut_ptr(), ptr::null_mut(), term, win); |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 338 | |
| 339 | let fork_result = Errno::result(res).map(|res| match res { |
| 340 | 0 => ForkResult::Child, |
| 341 | res => ForkResult::Parent { child: Pid::from_raw(res) }, |
| 342 | })?; |
| 343 | |
| Joel Galenson | e7950d9 | 2021-06-21 14:41:02 -0700 | [diff] [blame] | 344 | Ok(ForkptyResult { |
| 345 | master: master.assume_init(), |
| 346 | fork_result, |
| 347 | }) |
| Andrew Walbran | 12f6140 | 2020-10-14 11:10:53 +0100 | [diff] [blame] | 348 | } |