//! Create master and slave virtual pseudo-terminals (PTYs) pub use libc::pid_t as SessionId; pub use libc::winsize as Winsize; use std::ffi::CStr; use std::io; use std::mem; use std::os::unix::prelude::*; use crate::sys::termios::Termios; #[cfg(feature = "process")] use crate::unistd::{ForkResult, Pid}; use crate::{Result, fcntl, unistd}; use crate::errno::Errno; /// Representation of a master/slave pty pair /// /// This is returned by `openpty`. Note that this type does *not* implement `Drop`, so the user /// must manually close the file descriptors. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct OpenptyResult { /// The master port in a virtual pty pair pub master: RawFd, /// The slave port in a virtual pty pair pub slave: RawFd, } feature! { #![feature = "process"] /// Representation of a master with a forked pty /// /// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user /// must manually close the file descriptors. #[derive(Clone, Copy, Debug)] pub struct ForkptyResult { /// The master port in a virtual pty pair pub master: RawFd, /// Metadata about forked process pub fork_result: ForkResult, } } /// Representation of the Master device in a master/slave pty pair /// /// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY /// functions are given the correct file descriptor. Additionally this type implements `Drop`, /// so that when it's consumed or goes out of scope, it's automatically cleaned-up. #[derive(Debug, Eq, Hash, PartialEq)] pub struct PtyMaster(RawFd); impl AsRawFd for PtyMaster { fn as_raw_fd(&self) -> RawFd { self.0 } } impl IntoRawFd for PtyMaster { fn into_raw_fd(self) -> RawFd { let fd = self.0; mem::forget(self); fd } } impl Drop for PtyMaster { fn drop(&mut self) { // On drop, we ignore errors like EINTR and EIO because there's no clear // way to handle them, we can't return anything, and (on FreeBSD at // least) the file descriptor is deallocated in these cases. However, // we must panic on EBADF, because it is always an error to close an // invalid file descriptor. That frequently indicates a double-close // condition, which can cause confusing errors for future I/O // operations. let e = unistd::close(self.0); if e == Err(Errno::EBADF) { panic!("Closing an invalid file descriptor!"); }; } } impl io::Read for PtyMaster { fn read(&mut self, buf: &mut [u8]) -> io::Result { unistd::read(self.0, buf).map_err(io::Error::from) } } impl io::Write for PtyMaster { fn write(&mut self, buf: &[u8]) -> io::Result { unistd::write(self.0, buf).map_err(io::Error::from) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } /// Grant access to a slave pseudoterminal (see /// [`grantpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html)) /// /// `grantpt()` changes the mode and owner of the slave pseudoterminal device corresponding to the /// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave. #[inline] pub fn grantpt(fd: &PtyMaster) -> Result<()> { if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 { return Err(Errno::last()); } Ok(()) } /// Open a pseudoterminal device (see /// [`posix_openpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html)) /// /// `posix_openpt()` returns a file descriptor to an existing unused pseuterminal master device. /// /// # Examples /// /// A common use case with this function is to open both a master and slave PTY pair. This can be /// done as follows: /// /// ``` /// use std::path::Path; /// use nix::fcntl::{OFlag, open}; /// use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt}; /// use nix::sys::stat::Mode; /// /// # #[allow(dead_code)] /// # fn run() -> nix::Result<()> { /// // Open a new PTY master /// let master_fd = posix_openpt(OFlag::O_RDWR)?; /// /// // Allow a slave to be generated for it /// grantpt(&master_fd)?; /// unlockpt(&master_fd)?; /// /// // Get the name of the slave /// let slave_name = unsafe { ptsname(&master_fd) }?; /// /// // Try to open the slave /// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?; /// # Ok(()) /// # } /// ``` #[inline] pub fn posix_openpt(flags: fcntl::OFlag) -> Result { let fd = unsafe { libc::posix_openpt(flags.bits()) }; if fd < 0 { return Err(Errno::last()); } Ok(PtyMaster(fd)) } /// Get the name of the slave pseudoterminal (see /// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html)) /// /// `ptsname()` returns the name of the slave pseudoterminal device corresponding to the master /// referred to by `fd`. /// /// This value is useful for opening the slave pty once the master has already been opened with /// `posix_openpt()`. /// /// # Safety /// /// `ptsname()` mutates global variables and is *not* threadsafe. /// Mutating global variables is always considered `unsafe` by Rust and this /// function is marked as `unsafe` to reflect that. /// /// For a threadsafe and non-`unsafe` alternative on Linux, see `ptsname_r()`. #[inline] pub unsafe fn ptsname(fd: &PtyMaster) -> Result { let name_ptr = libc::ptsname(fd.as_raw_fd()); if name_ptr.is_null() { return Err(Errno::last()); } let name = CStr::from_ptr(name_ptr); Ok(name.to_string_lossy().into_owned()) } /// Get the name of the slave pseudoterminal (see /// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html)) /// /// `ptsname_r()` returns the name of the slave pseudoterminal device corresponding to the master /// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the /// POSIX standard and is instead a Linux-specific extension. /// /// This value is useful for opening the slave ptty once the master has already been opened with /// `posix_openpt()`. #[cfg(any(target_os = "android", target_os = "linux"))] #[cfg_attr(docsrs, doc(cfg(all())))] #[inline] pub fn ptsname_r(fd: &PtyMaster) -> Result { let mut name_buf = Vec::::with_capacity(64); let name_buf_ptr = name_buf.as_mut_ptr(); let cname = unsafe { let cap = name_buf.capacity(); if libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, cap) != 0 { return Err(crate::Error::last()); } CStr::from_ptr(name_buf.as_ptr()) }; let name = cname.to_string_lossy().into_owned(); Ok(name) } /// Unlock a pseudoterminal master/slave pseudoterminal pair (see /// [`unlockpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html)) /// /// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal /// referred to by `fd`. This must be called before trying to open the slave side of a /// pseuoterminal. #[inline] pub fn unlockpt(fd: &PtyMaster) -> Result<()> { if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 { return Err(Errno::last()); } Ok(()) } /// Create a new pseudoterminal, returning the slave and master file descriptors /// in `OpenptyResult` /// (see [`openpty`](https://man7.org/linux/man-pages/man3/openpty.3.html)). /// /// If `winsize` is not `None`, the window size of the slave will be set to /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's /// terminal settings of the slave will be set to the values in `termios`. #[inline] pub fn openpty<'a, 'b, T: Into>, U: Into>>(winsize: T, termios: U) -> Result { use std::ptr; let mut slave = mem::MaybeUninit::::uninit(); let mut master = mem::MaybeUninit::::uninit(); let ret = { match (termios.into(), winsize.into()) { (Some(termios), Some(winsize)) => { let inner_termios = termios.get_libc_termios(); unsafe { libc::openpty( master.as_mut_ptr(), slave.as_mut_ptr(), ptr::null_mut(), &*inner_termios as *const libc::termios as *mut _, winsize as *const Winsize as *mut _, ) } } (None, Some(winsize)) => { unsafe { libc::openpty( master.as_mut_ptr(), slave.as_mut_ptr(), ptr::null_mut(), ptr::null_mut(), winsize as *const Winsize as *mut _, ) } } (Some(termios), None) => { let inner_termios = termios.get_libc_termios(); unsafe { libc::openpty( master.as_mut_ptr(), slave.as_mut_ptr(), ptr::null_mut(), &*inner_termios as *const libc::termios as *mut _, ptr::null_mut(), ) } } (None, None) => { unsafe { libc::openpty( master.as_mut_ptr(), slave.as_mut_ptr(), ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), ) } } } }; Errno::result(ret)?; unsafe { Ok(OpenptyResult { master: master.assume_init(), slave: slave.assume_init(), }) } } feature! { #![feature = "process"] /// Create a new pseudoterminal, returning the master file descriptor and forked pid. /// in `ForkptyResult` /// (see [`forkpty`](https://man7.org/linux/man-pages/man3/forkpty.3.html)). /// /// If `winsize` is not `None`, the window size of the slave will be set to /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's /// terminal settings of the slave will be set to the values in `termios`. /// /// # Safety /// /// In a multithreaded program, only [async-signal-safe] functions like `pause` /// and `_exit` may be called by the child (the parent isn't restricted). Note /// that memory allocation may **not** be async-signal-safe and thus must be /// prevented. /// /// Those functions are only a small subset of your operating system's API, so /// special care must be taken to only invoke code you can control and audit. /// /// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html pub unsafe fn forkpty<'a, 'b, T: Into>, U: Into>>( winsize: T, termios: U, ) -> Result { use std::ptr; let mut master = mem::MaybeUninit::::uninit(); let term = match termios.into() { Some(termios) => { let inner_termios = termios.get_libc_termios(); &*inner_termios as *const libc::termios as *mut _ }, None => ptr::null_mut(), }; let win = winsize .into() .map(|ws| ws as *const Winsize as *mut _) .unwrap_or(ptr::null_mut()); let res = libc::forkpty(master.as_mut_ptr(), ptr::null_mut(), term, win); let fork_result = Errno::result(res).map(|res| match res { 0 => ForkResult::Child, res => ForkResult::Parent { child: Pid::from_raw(res) }, })?; Ok(ForkptyResult { master: master.assume_init(), fork_result, }) } }