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