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