• 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 use std::mem;
9 use std::os::unix::prelude::*;
10 
11 use crate::sys::termios::Termios;
12 use crate::unistd::{self, ForkResult, Pid};
13 use crate::{Result, fcntl};
14 use crate::errno::Errno;
15 
16 /// Representation of a master/slave pty pair
17 ///
18 /// This is returned by `openpty`.  Note that this type does *not* implement `Drop`, so the user
19 /// must manually close the file descriptors.
20 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
21 pub struct OpenptyResult {
22     /// The master port in a virtual pty pair
23     pub master: RawFd,
24     /// The slave port in a virtual pty pair
25     pub slave: RawFd,
26 }
27 
28 /// Representation of a master with a forked pty
29 ///
30 /// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user
31 /// must manually close the file descriptors.
32 #[derive(Clone, Copy, Debug)]
33 pub struct ForkptyResult {
34     /// The master port in a virtual pty pair
35     pub master: RawFd,
36     /// Metadata about forked process
37     pub fork_result: ForkResult,
38 }
39 
40 
41 /// Representation of the Master device in a master/slave pty pair
42 ///
43 /// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY
44 /// functions are given the correct file descriptor. Additionally this type implements `Drop`,
45 /// so that when it's consumed or goes out of scope, it's automatically cleaned-up.
46 #[derive(Debug, Eq, Hash, PartialEq)]
47 pub struct PtyMaster(RawFd);
48 
49 impl AsRawFd for PtyMaster {
as_raw_fd(&self) -> RawFd50     fn as_raw_fd(&self) -> RawFd {
51         self.0
52     }
53 }
54 
55 impl IntoRawFd for PtyMaster {
into_raw_fd(self) -> RawFd56     fn into_raw_fd(self) -> RawFd {
57         let fd = self.0;
58         mem::forget(self);
59         fd
60     }
61 }
62 
63 impl Drop for PtyMaster {
drop(&mut self)64     fn drop(&mut self) {
65         // On drop, we ignore errors like EINTR and EIO because there's no clear
66         // way to handle them, we can't return anything, and (on FreeBSD at
67         // least) the file descriptor is deallocated in these cases.  However,
68         // we must panic on EBADF, because it is always an error to close an
69         // invalid file descriptor.  That frequently indicates a double-close
70         // condition, which can cause confusing errors for future I/O
71         // operations.
72         let e = unistd::close(self.0);
73         if e == Err(Errno::EBADF) {
74             panic!("Closing an invalid file descriptor!");
75         };
76     }
77 }
78 
79 impl io::Read for PtyMaster {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>80     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
81         unistd::read(self.0, buf).map_err(io::Error::from)
82     }
83 }
84 
85 impl io::Write for PtyMaster {
write(&mut self, buf: &[u8]) -> io::Result<usize>86     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
87         unistd::write(self.0, buf).map_err(io::Error::from)
88     }
flush(&mut self) -> io::Result<()>89     fn flush(&mut self) -> io::Result<()> {
90         Ok(())
91     }
92 }
93 
94 /// Grant access to a slave pseudoterminal (see
95 /// [`grantpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html))
96 ///
97 /// `grantpt()` changes the mode and owner of the slave pseudoterminal device corresponding to the
98 /// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave.
99 #[inline]
grantpt(fd: &PtyMaster) -> Result<()>100 pub fn grantpt(fd: &PtyMaster) -> Result<()> {
101     if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 {
102         return Err(Errno::last());
103     }
104 
105     Ok(())
106 }
107 
108 /// Open a pseudoterminal device (see
109 /// [`posix_openpt(3)`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html))
110 ///
111 /// `posix_openpt()` returns a file descriptor to an existing unused pseuterminal master device.
112 ///
113 /// # Examples
114 ///
115 /// A common use case with this function is to open both a master and slave PTY pair. This can be
116 /// done as follows:
117 ///
118 /// ```
119 /// use std::path::Path;
120 /// use nix::fcntl::{OFlag, open};
121 /// use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt};
122 /// use nix::sys::stat::Mode;
123 ///
124 /// # #[allow(dead_code)]
125 /// # fn run() -> nix::Result<()> {
126 /// // Open a new PTY master
127 /// let master_fd = posix_openpt(OFlag::O_RDWR)?;
128 ///
129 /// // Allow a slave to be generated for it
130 /// grantpt(&master_fd)?;
131 /// unlockpt(&master_fd)?;
132 ///
133 /// // Get the name of the slave
134 /// let slave_name = unsafe { ptsname(&master_fd) }?;
135 ///
136 /// // Try to open the slave
137 /// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
138 /// # Ok(())
139 /// # }
140 /// ```
141 #[inline]
posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster>142 pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> {
143     let fd = unsafe {
144         libc::posix_openpt(flags.bits())
145     };
146 
147     if fd < 0 {
148         return Err(Errno::last());
149     }
150 
151     Ok(PtyMaster(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 = libc::ptsname(fd.as_raw_fd());
173     if name_ptr.is_null() {
174         return Err(Errno::last());
175     }
176 
177     let name = 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(any(target_os = "android", target_os = "linux"))]
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 /// pseuoterminal.
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 
223 /// Create a new pseudoterminal, returning the slave and master file descriptors
224 /// in `OpenptyResult`
225 /// (see [`openpty`](https://man7.org/linux/man-pages/man3/openpty.3.html)).
226 ///
227 /// If `winsize` is not `None`, the window size of the slave will be set to
228 /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
229 /// terminal settings of the slave will be set to the values in `termios`.
230 #[inline]
openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult>231 pub fn openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult> {
232     use std::ptr;
233 
234     let mut slave = mem::MaybeUninit::<libc::c_int>::uninit();
235     let mut master = mem::MaybeUninit::<libc::c_int>::uninit();
236     let ret = {
237         match (termios.into(), winsize.into()) {
238             (Some(termios), Some(winsize)) => {
239                 let inner_termios = termios.get_libc_termios();
240                 unsafe {
241                     libc::openpty(
242                         master.as_mut_ptr(),
243                         slave.as_mut_ptr(),
244                         ptr::null_mut(),
245                         &*inner_termios as *const libc::termios as *mut _,
246                         winsize as *const Winsize as *mut _,
247                     )
248                 }
249             }
250             (None, Some(winsize)) => {
251                 unsafe {
252                     libc::openpty(
253                         master.as_mut_ptr(),
254                         slave.as_mut_ptr(),
255                         ptr::null_mut(),
256                         ptr::null_mut(),
257                         winsize as *const Winsize as *mut _,
258                     )
259                 }
260             }
261             (Some(termios), None) => {
262                 let inner_termios = termios.get_libc_termios();
263                 unsafe {
264                     libc::openpty(
265                         master.as_mut_ptr(),
266                         slave.as_mut_ptr(),
267                         ptr::null_mut(),
268                         &*inner_termios as *const libc::termios as *mut _,
269                         ptr::null_mut(),
270                     )
271                 }
272             }
273             (None, None) => {
274                 unsafe {
275                     libc::openpty(
276                         master.as_mut_ptr(),
277                         slave.as_mut_ptr(),
278                         ptr::null_mut(),
279                         ptr::null_mut(),
280                         ptr::null_mut(),
281                     )
282                 }
283             }
284         }
285     };
286 
287     Errno::result(ret)?;
288 
289     unsafe {
290         Ok(OpenptyResult {
291             master: master.assume_init(),
292             slave: slave.assume_init(),
293         })
294     }
295 }
296 
297 /// Create a new pseudoterminal, returning the master file descriptor and forked pid.
298 /// in `ForkptyResult`
299 /// (see [`forkpty`](https://man7.org/linux/man-pages/man3/forkpty.3.html)).
300 ///
301 /// If `winsize` is not `None`, the window size of the slave will be set to
302 /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
303 /// terminal settings of the slave will be set to the values in `termios`.
304 ///
305 /// # Safety
306 ///
307 /// In a multithreaded program, only [async-signal-safe] functions like `pause`
308 /// and `_exit` may be called by the child (the parent isn't restricted). Note
309 /// that memory allocation may **not** be async-signal-safe and thus must be
310 /// prevented.
311 ///
312 /// Those functions are only a small subset of your operating system's API, so
313 /// special care must be taken to only invoke code you can control and audit.
314 ///
315 /// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html
forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>( winsize: T, termios: U, ) -> Result<ForkptyResult>316 pub unsafe fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(
317     winsize: T,
318     termios: U,
319 ) -> Result<ForkptyResult> {
320     use std::ptr;
321 
322     let mut master = mem::MaybeUninit::<libc::c_int>::uninit();
323 
324     let term = match termios.into() {
325         Some(termios) => {
326             let inner_termios = termios.get_libc_termios();
327             &*inner_termios as *const libc::termios as *mut _
328         },
329         None => ptr::null_mut(),
330     };
331 
332     let win = winsize
333         .into()
334         .map(|ws| ws as *const Winsize as *mut _)
335         .unwrap_or(ptr::null_mut());
336 
337     let res = libc::forkpty(master.as_mut_ptr(), ptr::null_mut(), term, win);
338 
339     let fork_result = Errno::result(res).map(|res| match res {
340         0 => ForkResult::Child,
341         res => ForkResult::Parent { child: Pid::from_raw(res) },
342     })?;
343 
344     Ok(ForkptyResult {
345         master: master.assume_init(),
346         fork_result,
347     })
348 }
349