• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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