1use libc::{fcntl, openpty, F_GETFL, F_SETFL, O_NONBLOCK};
6use std::{
7 io::{self, IsTerminal as _, Read},
8 os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
9 process::{Child, Command, Stdio},
10 ptr,
11};
12
13impl super::CommandExt for Command {
14 type Output = Child;
15
16 fn spawn_pty<'a>(&mut self) -> crate::Result<(Self::Output, super::Pty<'a>)> {
17 let mut pty = 0;
18 let mut pts = 0;
19 unsafe {
20 let ret = openpty(
21 &mut pty,
22 &mut pts,
23 ptr::null_mut(),
24 ptr::null_mut(),
25 ptr::null_mut(),
26 );
27 if ret != 0 {
28 Err(io::Error::last_os_error())?;
29 }
30
31 let mut flags = fcntl(pty, F_GETFL);
32 flags |= O_NONBLOCK;
33 let ret = fcntl(pty, F_SETFL, flags);
34 if ret != 0 {
35 Err(io::Error::last_os_error())?;
36 }
37
38 let pty = Pty::Owned(OwnedFd::from_raw_fd(pty));
39 let pts = Pts(OwnedFd::from_raw_fd(pts));
40
41 if io::stdout().is_terminal() {
42 self.stdout::<Stdio>(pts.0.try_clone()?.into());
43 }
44 if io::stderr().is_terminal() {
45 self.stderr::<Stdio>(pts.0.try_clone()?.into());
46 }
47
48 Ok((self.spawn()?, super::Pty(pty)))
49 }
50 }
51}
52
53#[derive(Debug)]
55pub enum Pty<'a> {
56 Owned(OwnedFd),
57 Borrowed(BorrowedFd<'a>),
58}
59
60impl AsRawFd for Pty<'_> {
61 fn as_raw_fd(&self) -> std::os::fd::RawFd {
62 match self {
63 Self::Owned(fd) => fd.as_raw_fd(),
64 Self::Borrowed(fd) => fd.as_raw_fd(),
65 }
66 }
67}
68
69impl Clone for Pty<'_> {
70 fn clone(&self) -> Self {
71 unsafe { Self::Borrowed(BorrowedFd::borrow_raw(self.as_raw_fd())) }
72 }
73}
74
75impl Read for Pty<'_> {
76 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
77 let len = unsafe {
78 libc::read(
79 self.as_raw_fd(),
80 buf.as_mut_ptr() as *mut libc::c_void,
81 buf.len(),
82 )
83 };
84
85 if len == -1 {
86 let err = io::Error::last_os_error();
87 tracing::trace!("read error {err:?}");
88
89 if let Some(libc::EBADF) = err.raw_os_error() {
90 return Ok(0);
91 }
92
93 return Err(err);
94 }
95
96 Ok(len as usize)
97 }
98}
99
100#[derive(Debug)]
102pub struct Pts(OwnedFd);