1use crate::{Child, CommandBuilder, MasterPty, PtyPair, PtySize, PtySystem, SlavePty};
4use anyhow::{bail, Error};
5use filedescriptor::FileDescriptor;
6use libc::{self, winsize};
7use std::cell::RefCell;
8use std::ffi::OsStr;
9use std::io::{Read, Write};
10use std::os::fd::AsFd;
11use std::os::unix::ffi::OsStrExt;
12use std::os::unix::io::{AsRawFd, FromRawFd};
13use std::os::unix::process::CommandExt;
14use std::path::PathBuf;
15use std::{io, mem, ptr};
16
17pub use std::os::unix::io::RawFd;
18
19#[derive(Default)]
20pub struct UnixPtySystem {}
21
22fn openpty(size: PtySize) -> anyhow::Result<(UnixMasterPty, UnixSlavePty)> {
23 let mut master: RawFd = -1;
24 let mut slave: RawFd = -1;
25
26 let mut size = winsize {
27 ws_row: size.rows,
28 ws_col: size.cols,
29 ws_xpixel: size.pixel_width,
30 ws_ypixel: size.pixel_height,
31 };
32
33 let result = unsafe {
34 #[allow(clippy::unnecessary_mut_passed)]
36 libc::openpty(
37 &mut master,
38 &mut slave,
39 ptr::null_mut(),
40 ptr::null_mut(),
41 &mut size,
42 )
43 };
44
45 if result != 0 {
46 bail!("failed to openpty: {:?}", io::Error::last_os_error());
47 }
48
49 let tty_name = tty_name(slave);
50
51 let master = UnixMasterPty {
52 fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(master) }),
53 took_writer: RefCell::new(false),
54 tty_name,
55 };
56 let slave = UnixSlavePty {
57 fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(slave) }),
58 };
59
60 cloexec(master.fd.as_raw_fd())?;
65 cloexec(slave.fd.as_raw_fd())?;
66
67 Ok((master, slave))
68}
69
70impl PtySystem for UnixPtySystem {
71 fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair> {
72 let (master, slave) = openpty(size)?;
73 Ok(PtyPair {
74 master: Box::new(master),
75 slave: Box::new(slave),
76 })
77 }
78}
79
80struct PtyFd(pub FileDescriptor);
81impl std::ops::Deref for PtyFd {
82 type Target = FileDescriptor;
83 fn deref(&self) -> &FileDescriptor {
84 &self.0
85 }
86}
87impl std::ops::DerefMut for PtyFd {
88 fn deref_mut(&mut self) -> &mut FileDescriptor {
89 &mut self.0
90 }
91}
92
93impl Read for PtyFd {
94 fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
95 match self.0.read(buf) {
96 Err(ref e) if e.raw_os_error() == Some(libc::EIO) => {
97 Ok(0)
102 }
103 x => x,
104 }
105 }
106}
107
108fn tty_name(fd: RawFd) -> Option<PathBuf> {
109 let mut buf = vec![0 as std::ffi::c_char; 128];
110
111 loop {
112 let res = unsafe { libc::ttyname_r(fd, buf.as_mut_ptr(), buf.len()) };
113
114 if res == libc::ERANGE {
115 if buf.len() > 64 * 1024 {
116 return None;
120 }
121 buf.resize(buf.len() * 2, 0 as std::ffi::c_char);
122 continue;
123 }
124
125 return if res == 0 {
126 let cstr = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
127 let osstr = OsStr::from_bytes(cstr.to_bytes());
128 Some(PathBuf::from(osstr))
129 } else {
130 None
131 };
132 }
133}
134
135pub fn close_random_fds() {
153 if let Ok(dir) = std::fs::read_dir("/dev/fd") {
158 let mut fds = vec![];
159 for entry in dir {
160 if let Some(num) = entry
161 .ok()
162 .map(|e| e.file_name())
163 .and_then(|s| s.into_string().ok())
164 .and_then(|n| n.parse::<libc::c_int>().ok())
165 {
166 if num > 2 {
167 fds.push(num);
168 }
169 }
170 }
171 for fd in fds {
172 unsafe {
173 libc::close(fd);
174 }
175 }
176 }
177}
178
179impl PtyFd {
180 fn resize(&self, size: PtySize) -> Result<(), Error> {
181 let ws_size = winsize {
182 ws_row: size.rows,
183 ws_col: size.cols,
184 ws_xpixel: size.pixel_width,
185 ws_ypixel: size.pixel_height,
186 };
187
188 if unsafe {
189 libc::ioctl(
190 self.0.as_raw_fd(),
191 libc::TIOCSWINSZ as _,
192 &ws_size as *const _,
193 )
194 } != 0
195 {
196 bail!(
197 "failed to ioctl(TIOCSWINSZ): {:?}",
198 io::Error::last_os_error()
199 );
200 }
201
202 Ok(())
203 }
204
205 fn get_size(&self) -> Result<PtySize, Error> {
206 let mut size: winsize = unsafe { mem::zeroed() };
207 if unsafe {
208 libc::ioctl(
209 self.0.as_raw_fd(),
210 libc::TIOCGWINSZ as _,
211 &mut size as *mut _,
212 )
213 } != 0
214 {
215 bail!(
216 "failed to ioctl(TIOCGWINSZ): {:?}",
217 io::Error::last_os_error()
218 );
219 }
220 Ok(PtySize {
221 rows: size.ws_row,
222 cols: size.ws_col,
223 pixel_width: size.ws_xpixel,
224 pixel_height: size.ws_ypixel,
225 })
226 }
227
228 fn spawn_command(&self, builder: CommandBuilder) -> anyhow::Result<std::process::Child> {
229 let configured_umask = builder.umask;
230
231 let mut cmd = builder.as_command()?;
232 let controlling_tty = builder.get_controlling_tty();
233
234 unsafe {
235 cmd.stdin(self.as_stdio()?)
236 .stdout(self.as_stdio()?)
237 .stderr(self.as_stdio()?)
238 .pre_exec(move || {
239 for signo in &[
243 libc::SIGCHLD,
244 libc::SIGHUP,
245 libc::SIGINT,
246 libc::SIGQUIT,
247 libc::SIGTERM,
248 libc::SIGALRM,
249 ] {
250 libc::signal(*signo, libc::SIG_DFL);
251 }
252
253 let empty_set: libc::sigset_t = std::mem::zeroed();
254 libc::sigprocmask(libc::SIG_SETMASK, &empty_set, std::ptr::null_mut());
255
256 if libc::setsid() == -1 {
258 return Err(io::Error::last_os_error());
259 }
260
261 #[allow(clippy::cast_lossless)]
266 if controlling_tty {
267 if libc::ioctl(0, libc::TIOCSCTTY as _, 0) == -1 {
272 return Err(io::Error::last_os_error());
273 }
274 }
275
276 close_random_fds();
277
278 if let Some(mask) = configured_umask {
279 libc::umask(mask);
280 }
281
282 Ok(())
283 })
284 };
285
286 let mut child = cmd.spawn()?;
287
288 child.stdin.take();
294 child.stdout.take();
295 child.stderr.take();
296
297 Ok(child)
298 }
299}
300
301struct UnixMasterPty {
304 fd: PtyFd,
305 took_writer: RefCell<bool>,
306 tty_name: Option<PathBuf>,
307}
308
309struct UnixSlavePty {
312 fd: PtyFd,
313}
314
315fn cloexec(fd: RawFd) -> Result<(), Error> {
317 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
318 if flags == -1 {
319 bail!(
320 "fcntl to read flags failed: {:?}",
321 io::Error::last_os_error()
322 );
323 }
324 let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
325 if result == -1 {
326 bail!(
327 "fcntl to set CLOEXEC failed: {:?}",
328 io::Error::last_os_error()
329 );
330 }
331 Ok(())
332}
333
334impl SlavePty for UnixSlavePty {
335 fn spawn_command(
336 &self,
337 builder: CommandBuilder,
338 ) -> Result<Box<dyn Child + Send + Sync>, Error> {
339 Ok(Box::new(self.fd.spawn_command(builder)?))
340 }
341}
342
343impl MasterPty for UnixMasterPty {
344 fn resize(&self, size: PtySize) -> Result<(), Error> {
345 self.fd.resize(size)
346 }
347
348 fn get_size(&self) -> Result<PtySize, Error> {
349 self.fd.get_size()
350 }
351
352 fn try_clone_reader(&self) -> Result<Box<dyn Read + Send>, Error> {
353 let fd = PtyFd(self.fd.try_clone()?);
354 Ok(Box::new(fd))
355 }
356
357 fn take_writer(&self) -> Result<Box<dyn Write + Send>, Error> {
358 if *self.took_writer.borrow() {
359 anyhow::bail!("cannot take writer more than once");
360 }
361 *self.took_writer.borrow_mut() = true;
362 let fd = PtyFd(self.fd.try_clone()?);
363 Ok(Box::new(UnixMasterWriter { fd }))
364 }
365
366 fn as_raw_fd(&self) -> Option<RawFd> {
367 Some(self.fd.0.as_raw_fd())
368 }
369
370 fn tty_name(&self) -> Option<PathBuf> {
371 self.tty_name.clone()
372 }
373
374 fn process_group_leader(&self) -> Option<libc::pid_t> {
375 match unsafe { libc::tcgetpgrp(self.fd.0.as_raw_fd()) } {
376 pid if pid > 0 => Some(pid),
377 _ => None,
378 }
379 }
380
381 fn get_termios(&self) -> Option<nix::sys::termios::Termios> {
382 nix::sys::termios::tcgetattr(self.fd.0.as_fd()).ok()
383 }
384}
385
386struct UnixMasterWriter {
390 fd: PtyFd,
391}
392
393impl Drop for UnixMasterWriter {
394 fn drop(&mut self) {
395 let mut t: libc::termios = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
396 if unsafe { libc::tcgetattr(self.fd.0.as_raw_fd(), &mut t) } == 0 {
397 let eot = t.c_cc[libc::VEOF];
400 if eot != 0 {
401 let _ = self.fd.0.write_all(&[b'\n', eot]);
402 }
403 }
404 }
405}
406
407impl Write for UnixMasterWriter {
408 fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
409 self.fd.write(buf)
410 }
411 fn flush(&mut self) -> Result<(), io::Error> {
412 self.fd.flush()
413 }
414}