nix/
pty.rs

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}