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