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::sys::termios::Termios;
12#[cfg(feature = "process")]
13use crate::unistd::{ForkResult, Pid};
14use crate::{Result, fcntl, unistd};
15use crate::errno::Errno;
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
45/// Representation of the Master device in a master/slave pty pair
46///
47/// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY
48/// functions are given the correct file descriptor. Additionally this type implements `Drop`,
49/// so that when it's consumed or goes out of scope, it's automatically cleaned-up.
50#[derive(Debug, Eq, Hash, PartialEq)]
51pub struct PtyMaster(RawFd);
52
53impl AsRawFd for PtyMaster {
54 fn as_raw_fd(&self) -> RawFd {
55 self.0
56 }
57}
58
59impl IntoRawFd for PtyMaster {
60 fn into_raw_fd(self) -> RawFd {
61 let fd = self.0;
62 mem::forget(self);
63 fd
64 }
65}
66
67impl Drop for PtyMaster {
68 fn drop(&mut self) {
69 // On drop, we ignore errors like EINTR and EIO because there's no clear
70 // way to handle them, we can't return anything, and (on FreeBSD at
71 // least) the file descriptor is deallocated in these cases. However,
72 // we must panic on EBADF, because it is always an error to close an
73 // invalid file descriptor. That frequently indicates a double-close
74 // condition, which can cause confusing errors for future I/O
75 // operations.
76 let e = unistd::close(self.0);
77 if e == Err(Errno::EBADF) {
78 panic!("Closing an invalid file descriptor!");
79 };
80 }
81}
82
83impl io::Read for PtyMaster {
84 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
85 unistd::read(self.0, buf).map_err(io::Error::from)
86 }
87}
88
89impl io::Write for PtyMaster {
90 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
91 unistd::write(self.0, buf).map_err(io::Error::from)
92 }
93 fn flush(&mut self) -> io::Result<()> {
94 Ok(())
95 }
96}
97
98impl io::Read for &PtyMaster {
99 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
100 unistd::read(self.0, buf).map_err(io::Error::from)
101 }
102}
103
104impl io::Write for &PtyMaster {
105 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
106 unistd::write(self.0, buf).map_err(io::Error::from)
107 }
108 fn flush(&mut self) -> io::Result<()> {
109 Ok(())
110 }
111}
112
113/// Grant access to a slave pseudoterminal (see
114/// [`grantpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html))
115///
116/// `grantpt()` changes the mode and owner of the slave pseudoterminal device corresponding to the
117/// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave.
118#[inline]
119pub fn grantpt(fd: &PtyMaster) -> Result<()> {
120 if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 {
121 return Err(Errno::last());
122 }
123
124 Ok(())
125}
126
127/// Open a pseudoterminal device (see
128/// [`posix_openpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html))
129///
130/// `posix_openpt()` returns a file descriptor to an existing unused pseudoterminal master device.
131///
132/// # Examples
133///
134/// A common use case with this function is to open both a master and slave PTY pair. This can be
135/// done as follows:
136///
137/// ```
138/// use std::path::Path;
139/// use nix::fcntl::{OFlag, open};
140/// use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt};
141/// use nix::sys::stat::Mode;
142///
143/// # #[allow(dead_code)]
144/// # fn run() -> nix::Result<()> {
145/// // Open a new PTY master
146/// let master_fd = posix_openpt(OFlag::O_RDWR)?;
147///
148/// // Allow a slave to be generated for it
149/// grantpt(&master_fd)?;
150/// unlockpt(&master_fd)?;
151///
152/// // Get the name of the slave
153/// let slave_name = unsafe { ptsname(&master_fd) }?;
154///
155/// // Try to open the slave
156/// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
157/// # Ok(())
158/// # }
159/// ```
160#[inline]
161pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> {
162 let fd = unsafe {
163 libc::posix_openpt(flags.bits())
164 };
165
166 if fd < 0 {
167 return Err(Errno::last());
168 }
169
170 Ok(PtyMaster(fd))
171}
172
173/// Get the name of the slave pseudoterminal (see
174/// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html))
175///
176/// `ptsname()` returns the name of the slave pseudoterminal device corresponding to the master
177/// referred to by `fd`.
178///
179/// This value is useful for opening the slave pty once the master has already been opened with
180/// `posix_openpt()`.
181///
182/// # Safety
183///
184/// `ptsname()` mutates global variables and is *not* threadsafe.
185/// Mutating global variables is always considered `unsafe` by Rust and this
186/// function is marked as `unsafe` to reflect that.
187///
188/// For a threadsafe and non-`unsafe` alternative on Linux, see `ptsname_r()`.
189#[inline]
190pub unsafe fn ptsname(fd: &PtyMaster) -> Result<String> {
191 let name_ptr = libc::ptsname(fd.as_raw_fd());
192 if name_ptr.is_null() {
193 return Err(Errno::last());
194 }
195
196 let name = CStr::from_ptr(name_ptr);
197 Ok(name.to_string_lossy().into_owned())
198}
199
200/// Get the name of the slave pseudoterminal (see
201/// [`ptsname(3)`](https://man7.org/linux/man-pages/man3/ptsname.3.html))
202///
203/// `ptsname_r()` returns the name of the slave pseudoterminal device corresponding to the master
204/// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the
205/// POSIX standard and is instead a Linux-specific extension.
206///
207/// This value is useful for opening the slave ptty once the master has already been opened with
208/// `posix_openpt()`.
209#[cfg(any(target_os = "android", target_os = "linux"))]
210#[cfg_attr(docsrs, doc(cfg(all())))]
211#[inline]
212pub fn ptsname_r(fd: &PtyMaster) -> Result<String> {
213 let mut name_buf = Vec::<libc::c_char>::with_capacity(64);
214 let name_buf_ptr = name_buf.as_mut_ptr();
215 let cname = unsafe {
216 let cap = name_buf.capacity();
217 if libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, cap) != 0 {
218 return Err(crate::Error::last());
219 }
220 CStr::from_ptr(name_buf.as_ptr())
221 };
222
223 let name = cname.to_string_lossy().into_owned();
224 Ok(name)
225}
226
227/// Unlock a pseudoterminal master/slave pseudoterminal pair (see
228/// [`unlockpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html))
229///
230/// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal
231/// referred to by `fd`. This must be called before trying to open the slave side of a
232/// pseudoterminal.
233#[inline]
234pub fn unlockpt(fd: &PtyMaster) -> Result<()> {
235 if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 {
236 return Err(Errno::last());
237 }
238
239 Ok(())
240}
241
242
243/// Create a new pseudoterminal, returning the slave and master file descriptors
244/// in `OpenptyResult`
245/// (see [`openpty`](https://man7.org/linux/man-pages/man3/openpty.3.html)).
246///
247/// If `winsize` is not `None`, the window size of the slave will be set to
248/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
249/// terminal settings of the slave will be set to the values in `termios`.
250#[inline]
251pub fn openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult> {
252 use std::ptr;
253
254 let mut slave = mem::MaybeUninit::<libc::c_int>::uninit();
255 let mut master = mem::MaybeUninit::<libc::c_int>::uninit();
256 let ret = {
257 match (termios.into(), winsize.into()) {
258 (Some(termios), Some(winsize)) => {
259 let inner_termios = termios.get_libc_termios();
260 unsafe {
261 libc::openpty(
262 master.as_mut_ptr(),
263 slave.as_mut_ptr(),
264 ptr::null_mut(),
265 &*inner_termios as *const libc::termios as *mut _,
266 winsize as *const Winsize as *mut _,
267 )
268 }
269 }
270 (None, Some(winsize)) => {
271 unsafe {
272 libc::openpty(
273 master.as_mut_ptr(),
274 slave.as_mut_ptr(),
275 ptr::null_mut(),
276 ptr::null_mut(),
277 winsize as *const Winsize as *mut _,
278 )
279 }
280 }
281 (Some(termios), None) => {
282 let inner_termios = termios.get_libc_termios();
283 unsafe {
284 libc::openpty(
285 master.as_mut_ptr(),
286 slave.as_mut_ptr(),
287 ptr::null_mut(),
288 &*inner_termios as *const libc::termios as *mut _,
289 ptr::null_mut(),
290 )
291 }
292 }
293 (None, None) => {
294 unsafe {
295 libc::openpty(
296 master.as_mut_ptr(),
297 slave.as_mut_ptr(),
298 ptr::null_mut(),
299 ptr::null_mut(),
300 ptr::null_mut(),
301 )
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}