1 //! Create master and slave virtual pseudo-terminals (PTYs)
2
3 use libc;
4
5 pub use libc::pid_t as SessionId;
6 pub use libc::winsize as Winsize;
7
8 use std::ffi::CStr;
9 use std::mem;
10 use std::os::unix::prelude::*;
11
12 use sys::termios::Termios;
13 use unistd::ForkResult;
14 use {Result, Error, fcntl};
15 use errno::Errno;
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 /// Representation of a master with a forked pty
30 ///
31 /// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user
32 /// must manually close the file descriptors.
33 #[derive(Clone, Copy, Debug)]
34 pub struct ForkptyResult {
35 /// The master port in a virtual pty pair
36 pub master: RawFd,
37 /// Metadata about forked process
38 pub fork_result: ForkResult,
39 }
40
41
42 /// Representation of the Master device in a master/slave pty pair
43 ///
44 /// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY
45 /// functions are given the correct file descriptor. Additionally this type implements `Drop`,
46 /// so that when it's consumed or goes out of scope, it's automatically cleaned-up.
47 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
48 pub struct PtyMaster(RawFd);
49
50 impl AsRawFd for PtyMaster {
as_raw_fd(&self) -> RawFd51 fn as_raw_fd(&self) -> RawFd {
52 self.0
53 }
54 }
55
56 impl IntoRawFd for PtyMaster {
into_raw_fd(self) -> RawFd57 fn into_raw_fd(self) -> RawFd {
58 let fd = self.0;
59 mem::forget(self);
60 fd
61 }
62 }
63
64 impl Drop for PtyMaster {
drop(&mut self)65 fn drop(&mut self) {
66 // On drop, we ignore errors like EINTR and EIO because there's no clear
67 // way to handle them, we can't return anything, and (on FreeBSD at
68 // least) the file descriptor is deallocated in these cases. However,
69 // we must panic on EBADF, because it is always an error to close an
70 // invalid file descriptor. That frequently indicates a double-close
71 // condition, which can cause confusing errors for future I/O
72 // operations.
73 let e = ::unistd::close(self.0);
74 if e == Err(Error::Sys(Errno::EBADF)) {
75 panic!("Closing an invalid file descriptor!");
76 };
77 }
78 }
79
80 /// Grant access to a slave pseudoterminal (see
81 /// [`grantpt(3)`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html))
82 ///
83 /// `grantpt()` changes the mode and owner of the slave pseudoterminal device corresponding to the
84 /// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave.
85 #[inline]
grantpt(fd: &PtyMaster) -> Result<()>86 pub fn grantpt(fd: &PtyMaster) -> Result<()> {
87 if unsafe { libc::grantpt(fd.as_raw_fd()) } < 0 {
88 return Err(Error::last());
89 }
90
91 Ok(())
92 }
93
94 /// Open a pseudoterminal device (see
95 /// [`posix_openpt(3)`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html))
96 ///
97 /// `posix_openpt()` returns a file descriptor to an existing unused pseuterminal master device.
98 ///
99 /// # Examples
100 ///
101 /// A common use case with this function is to open both a master and slave PTY pair. This can be
102 /// done as follows:
103 ///
104 /// ```
105 /// use std::path::Path;
106 /// use nix::fcntl::{OFlag, open};
107 /// use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt};
108 /// use nix::sys::stat::Mode;
109 ///
110 /// # #[allow(dead_code)]
111 /// # fn run() -> nix::Result<()> {
112 /// // Open a new PTY master
113 /// let master_fd = posix_openpt(OFlag::O_RDWR)?;
114 ///
115 /// // Allow a slave to be generated for it
116 /// grantpt(&master_fd)?;
117 /// unlockpt(&master_fd)?;
118 ///
119 /// // Get the name of the slave
120 /// let slave_name = unsafe { ptsname(&master_fd) }?;
121 ///
122 /// // Try to open the slave
123 /// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
124 /// # Ok(())
125 /// # }
126 /// ```
127 #[inline]
posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster>128 pub fn posix_openpt(flags: fcntl::OFlag) -> Result<PtyMaster> {
129 let fd = unsafe {
130 libc::posix_openpt(flags.bits())
131 };
132
133 if fd < 0 {
134 return Err(Error::last());
135 }
136
137 Ok(PtyMaster(fd))
138 }
139
140 /// Get the name of the slave pseudoterminal (see
141 /// [`ptsname(3)`](http://man7.org/linux/man-pages/man3/ptsname.3.html))
142 ///
143 /// `ptsname()` returns the name of the slave pseudoterminal device corresponding to the master
144 /// referred to by `fd`.
145 ///
146 /// This value is useful for opening the slave pty once the master has already been opened with
147 /// `posix_openpt()`.
148 ///
149 /// # Safety
150 ///
151 /// `ptsname()` mutates global variables and is *not* threadsafe.
152 /// Mutating global variables is always considered `unsafe` by Rust and this
153 /// function is marked as `unsafe` to reflect that.
154 ///
155 /// For a threadsafe and non-`unsafe` alternative on Linux, see `ptsname_r()`.
156 #[inline]
ptsname(fd: &PtyMaster) -> Result<String>157 pub unsafe fn ptsname(fd: &PtyMaster) -> Result<String> {
158 let name_ptr = libc::ptsname(fd.as_raw_fd());
159 if name_ptr.is_null() {
160 return Err(Error::last());
161 }
162
163 let name = CStr::from_ptr(name_ptr);
164 Ok(name.to_string_lossy().into_owned())
165 }
166
167 /// Get the name of the slave pseudoterminal (see
168 /// [`ptsname(3)`](http://man7.org/linux/man-pages/man3/ptsname.3.html))
169 ///
170 /// `ptsname_r()` returns the name of the slave pseudoterminal device corresponding to the master
171 /// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the
172 /// POSIX standard and is instead a Linux-specific extension.
173 ///
174 /// This value is useful for opening the slave ptty once the master has already been opened with
175 /// `posix_openpt()`.
176 #[cfg(any(target_os = "android", target_os = "linux"))]
177 #[inline]
ptsname_r(fd: &PtyMaster) -> Result<String>178 pub fn ptsname_r(fd: &PtyMaster) -> Result<String> {
179 let mut name_buf = vec![0u8; 64];
180 let name_buf_ptr = name_buf.as_mut_ptr() as *mut libc::c_char;
181 if unsafe { libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, name_buf.capacity()) } != 0 {
182 return Err(Error::last());
183 }
184
185 // Find the first null-character terminating this string. This is guaranteed to succeed if the
186 // return value of `libc::ptsname_r` is 0.
187 let null_index = name_buf.iter().position(|c| *c == b'\0').unwrap();
188 name_buf.truncate(null_index);
189
190 let name = String::from_utf8(name_buf)?;
191 Ok(name)
192 }
193
194 /// Unlock a pseudoterminal master/slave pseudoterminal pair (see
195 /// [`unlockpt(3)`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html))
196 ///
197 /// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal
198 /// referred to by `fd`. This must be called before trying to open the slave side of a
199 /// pseuoterminal.
200 #[inline]
unlockpt(fd: &PtyMaster) -> Result<()>201 pub fn unlockpt(fd: &PtyMaster) -> Result<()> {
202 if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 {
203 return Err(Error::last());
204 }
205
206 Ok(())
207 }
208
209
210 /// Create a new pseudoterminal, returning the slave and master file descriptors
211 /// in `OpenptyResult`
212 /// (see [`openpty`](http://man7.org/linux/man-pages/man3/openpty.3.html)).
213 ///
214 /// If `winsize` is not `None`, the window size of the slave will be set to
215 /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
216 /// terminal settings of the slave will be set to the values in `termios`.
217 #[inline]
openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult>218 pub fn openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult> {
219 use std::ptr;
220
221 let mut slave = mem::MaybeUninit::<libc::c_int>::uninit();
222 let mut master = mem::MaybeUninit::<libc::c_int>::uninit();
223 let ret = {
224 match (termios.into(), winsize.into()) {
225 (Some(termios), Some(winsize)) => {
226 let inner_termios = termios.get_libc_termios();
227 unsafe {
228 libc::openpty(
229 master.as_mut_ptr(),
230 slave.as_mut_ptr(),
231 ptr::null_mut(),
232 &*inner_termios as *const libc::termios as *mut _,
233 winsize as *const Winsize as *mut _,
234 )
235 }
236 }
237 (None, Some(winsize)) => {
238 unsafe {
239 libc::openpty(
240 master.as_mut_ptr(),
241 slave.as_mut_ptr(),
242 ptr::null_mut(),
243 ptr::null_mut(),
244 winsize as *const Winsize as *mut _,
245 )
246 }
247 }
248 (Some(termios), None) => {
249 let inner_termios = termios.get_libc_termios();
250 unsafe {
251 libc::openpty(
252 master.as_mut_ptr(),
253 slave.as_mut_ptr(),
254 ptr::null_mut(),
255 &*inner_termios as *const libc::termios as *mut _,
256 ptr::null_mut(),
257 )
258 }
259 }
260 (None, None) => {
261 unsafe {
262 libc::openpty(
263 master.as_mut_ptr(),
264 slave.as_mut_ptr(),
265 ptr::null_mut(),
266 ptr::null_mut(),
267 ptr::null_mut(),
268 )
269 }
270 }
271 }
272 };
273
274 Errno::result(ret)?;
275
276 unsafe {
277 Ok(OpenptyResult {
278 master: master.assume_init(),
279 slave: slave.assume_init(),
280 })
281 }
282 }
283
284 /// Create a new pseudoterminal, returning the master file descriptor and forked pid.
285 /// in `ForkptyResult`
286 /// (see [`forkpty`](http://man7.org/linux/man-pages/man3/forkpty.3.html)).
287 ///
288 /// If `winsize` is not `None`, the window size of the slave will be set to
289 /// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
290 /// terminal settings of the slave will be set to the values in `termios`.
forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>( winsize: T, termios: U, ) -> Result<ForkptyResult>291 pub fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(
292 winsize: T,
293 termios: U,
294 ) -> Result<ForkptyResult> {
295 use std::ptr;
296 use unistd::Pid;
297 use unistd::ForkResult::*;
298
299 let mut master = mem::MaybeUninit::<libc::c_int>::uninit();
300
301 let term = match termios.into() {
302 Some(termios) => {
303 let inner_termios = termios.get_libc_termios();
304 &*inner_termios as *const libc::termios as *mut _
305 },
306 None => ptr::null_mut(),
307 };
308
309 let win = winsize
310 .into()
311 .map(|ws| ws as *const Winsize as *mut _)
312 .unwrap_or(ptr::null_mut());
313
314 let res = unsafe {
315 libc::forkpty(master.as_mut_ptr(), ptr::null_mut(), term, win)
316 };
317
318 let fork_result = Errno::result(res).map(|res| match res {
319 0 => Child,
320 res => Parent { child: Pid::from_raw(res) },
321 })?;
322
323 unsafe {
324 Ok(ForkptyResult {
325 master: master.assume_init(),
326 fork_result,
327 })
328 }
329 }
330
331