summaryrefslogtreecommitdiffstats
path: root/third_party/rust/nix/src/pty.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/nix/src/pty.rs')
-rw-r--r--third_party/rust/nix/src/pty.rs371
1 files changed, 371 insertions, 0 deletions
diff --git a/third_party/rust/nix/src/pty.rs b/third_party/rust/nix/src/pty.rs
new file mode 100644
index 0000000000..28ae5e924b
--- /dev/null
+++ b/third_party/rust/nix/src/pty.rs
@@ -0,0 +1,371 @@
+//! 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::errno::Errno;
+use crate::sys::termios::Termios;
+#[cfg(feature = "process")]
+use crate::unistd::{ForkResult, Pid};
+use crate::{fcntl, unistd, Result};
+
+/// 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<usize> {
+ unistd::read(self.0, buf).map_err(io::Error::from)
+ }
+}
+
+impl io::Write for PtyMaster {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ unistd::write(self.0, buf).map_err(io::Error::from)
+ }
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl io::Read for &PtyMaster {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ unistd::read(self.0, buf).map_err(io::Error::from)
+ }
+}
+
+impl io::Write for &PtyMaster {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ 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 pseudoterminal 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<PtyMaster> {
+ 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<String> {
+ 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<String> {
+ let mut name_buf = Vec::<libc::c_char>::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
+/// pseudoterminal.
+#[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<Option<&'a Winsize>>,
+ U: Into<Option<&'b Termios>>,
+>(
+ winsize: T,
+ termios: U,
+) -> Result<OpenptyResult> {
+ use std::ptr;
+
+ let mut slave = mem::MaybeUninit::<libc::c_int>::uninit();
+ let mut master = mem::MaybeUninit::<libc::c_int>::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<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(
+ winsize: T,
+ termios: U,
+) -> Result<ForkptyResult> {
+ use std::ptr;
+
+ let mut master = mem::MaybeUninit::<libc::c_int>::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,
+ })
+}
+}