1 //! Create master and slave virtual pseudo-terminals (PTYs)
2
3 pub use libc::pid_t as SessionId;
4 pub use libc::winsize as Winsize;
5
6 use std::ffi::CStr;
7 use std::io;
8 #[cfg(not(target_os = "aix"))]
9 use std::mem;
10 use std::os::unix::prelude::*;
11
12 use crate::errno::Errno;
13 #[cfg(not(target_os = "aix"))]
14 use crate::sys::termios::Termios;
15 #[cfg(feature = "process")]
16 use crate::unistd::ForkResult;
17 #[cfg(all(feature = "process", not(target_os = "aix")))]
18 use crate::unistd::Pid;
19 use crate::{fcntl, unistd, Result};
20
21 /// Representation of a master/slave pty pair
22 ///
23 /// This is returned by [`openpty`].
24 #[derive(Debug)]
25 pub 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
32 feature! {
33 #![feature = "process"]
34 /// Representation of a master with a forked pty
35 ///
36 /// This is returned by [`forkpty`].
37 #[derive(Debug)]
38 pub 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)]
51 pub struct PtyMaster(OwnedFd);
52
53 impl AsRawFd for PtyMaster {
as_raw_fd(&self) -> RawFd54 fn as_raw_fd(&self) -> RawFd {
55 self.0.as_raw_fd()
56 }
57 }
58
59 impl IntoRawFd for PtyMaster {
into_raw_fd(self) -> RawFd60 fn into_raw_fd(self) -> RawFd {
61 let fd = self.0;
62 fd.into_raw_fd()
63 }
64 }
65
66 impl io::Read for PtyMaster {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>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
72 impl io::Write for PtyMaster {
write(&mut self, buf: &[u8]) -> io::Result<usize>73 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
74 unistd::write(&self.0, buf).map_err(io::Error::from)
75 }
flush(&mut self) -> io::Result<()>76 fn flush(&mut self) -> io::Result<()> {
77 Ok(())
78 }
79 }
80
81 impl io::Read for &PtyMaster {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>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
87 impl io::Write for &PtyMaster {
write(&mut self, buf: &[u8]) -> io::Result<usize>88 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
89 unistd::write(&self.0, buf).map_err(io::Error::from)
90 }
flush(&mut self) -> io::Result<()>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]
grantpt(fd: &PtyMaster) -> Result<()>102 pub 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]
posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster>144 pub 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]
ptsname(fd: &PtyMaster) -> Result<String>171 pub 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]
ptsname_r(fd: &PtyMaster) -> Result<String>192 pub 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]
unlockpt(fd: &PtyMaster) -> Result<()>214 pub 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"))]
openpty< 'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>, >( winsize: T, termios: U, ) -> Result<OpenptyResult>231 pub 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
301 feature! {
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"))]
323 pub 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 }
357