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