1use crate::{
4 Child, CommandBuilder, MasterPty, PtyPair, PtySize, PtySystem, SlavePty,
5};
6use anyhow::{bail, Error};
7use filedescriptor::FileDescriptor;
8use libc::{self, winsize};
9use std::io::{Read, Write};
10use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
11use std::os::unix::process::CommandExt;
12use std::{io, mem, ptr};
13
14#[derive(Default)]
15pub struct UnixPtySystem {}
16
17fn openpty(size: PtySize) -> anyhow::Result<(UnixMasterPty, UnixSlavePty)> {
18 let mut master: RawFd = -1;
19 let mut slave: RawFd = -1;
20
21 let mut size = winsize {
22 ws_row: size.rows,
23 ws_col: size.cols,
24 ws_xpixel: size.pixel_width,
25 ws_ypixel: size.pixel_height,
26 };
27
28 let result = unsafe {
29 #[cfg_attr(feature = "cargo-clippy", allow(clippy::unnecessary_mut_passed))]
31 libc::openpty(
32 &mut master,
33 &mut slave,
34 ptr::null_mut(),
35 ptr::null_mut(),
36 &mut size,
37 )
38 };
39
40 if result != 0 {
41 bail!("failed to openpty: {:?}", io::Error::last_os_error());
42 }
43
44 let master = UnixMasterPty {
45 fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(master) }),
46 };
47 let slave = UnixSlavePty {
48 fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(slave) }),
49 };
50
51 cloexec(master.fd.as_raw_fd())?;
56 cloexec(slave.fd.as_raw_fd())?;
57
58 Ok((master, slave))
59}
60
61impl PtySystem for UnixPtySystem {
62 fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair> {
63 let (master, slave) = openpty(size)?;
64 Ok(PtyPair {
65 master: Box::new(master),
66 slave: Box::new(slave),
67 })
68 }
69}
70
71struct PtyFd(pub FileDescriptor);
72impl std::ops::Deref for PtyFd {
73 type Target = FileDescriptor;
74 fn deref(&self) -> &FileDescriptor {
75 &self.0
76 }
77}
78impl std::ops::DerefMut for PtyFd {
79 fn deref_mut(&mut self) -> &mut FileDescriptor {
80 &mut self.0
81 }
82}
83
84impl Read for PtyFd {
85 fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
86 match self.0.read(buf) {
87 Err(ref e) if e.raw_os_error() == Some(libc::EIO) => {
88 Ok(0)
93 }
94 x => x,
95 }
96 }
97}
98
99pub fn close_random_fds() {
117 if let Ok(dir) = std::fs::read_dir("/dev/fd") {
122 let mut fds = vec![];
123 for entry in dir {
124 if let Some(num) = entry
125 .ok()
126 .map(|e| e.file_name())
127 .and_then(|s| s.into_string().ok())
128 .and_then(|n| n.parse::<libc::c_int>().ok())
129 {
130 if num > 2 {
131 fds.push(num);
132 }
133 }
134 }
135 for fd in fds {
136 unsafe {
137 libc::close(fd);
138 }
139 }
140 }
141}
142
143impl PtyFd {
144 fn resize(&self, size: PtySize) -> Result<(), Error> {
145 let ws_size = winsize {
146 ws_row: size.rows,
147 ws_col: size.cols,
148 ws_xpixel: size.pixel_width,
149 ws_ypixel: size.pixel_height,
150 };
151
152 if unsafe {
153 libc::ioctl(
154 self.0.as_raw_fd(),
155 libc::TIOCSWINSZ as _,
156 &ws_size as *const _,
157 )
158 } != 0
159 {
160 bail!(
161 "failed to ioctl(TIOCSWINSZ): {:?}",
162 io::Error::last_os_error()
163 );
164 }
165
166 Ok(())
167 }
168
169 fn get_size(&self) -> Result<PtySize, Error> {
170 let mut size: winsize = unsafe { mem::zeroed() };
171 if unsafe {
172 libc::ioctl(
173 self.0.as_raw_fd(),
174 libc::TIOCGWINSZ as _,
175 &mut size as *mut _,
176 )
177 } != 0
178 {
179 bail!(
180 "failed to ioctl(TIOCGWINSZ): {:?}",
181 io::Error::last_os_error()
182 );
183 }
184 Ok(PtySize {
185 rows: size.ws_row,
186 cols: size.ws_col,
187 pixel_width: size.ws_xpixel,
188 pixel_height: size.ws_ypixel,
189 })
190 }
191
192 fn spawn_command(
193 &self,
194 builder: CommandBuilder,
195 ) -> anyhow::Result<std::process::Child> {
196 let configured_umask = builder.umask;
197
198 let mut cmd = builder.as_command()?;
199 let controlling_tty = builder.get_controlling_tty();
200
201 unsafe {
202 cmd
203 .stdin(self.as_stdio()?)
204 .stdout(self.as_stdio()?)
205 .stderr(self.as_stdio()?)
206 .pre_exec(move || {
207 for signo in &[
211 libc::SIGCHLD,
212 libc::SIGHUP,
213 libc::SIGINT,
214 libc::SIGQUIT,
215 libc::SIGTERM,
216 libc::SIGALRM,
217 ] {
218 libc::signal(*signo, libc::SIG_DFL);
219 }
220
221 if libc::setsid() == -1 {
223 return Err(io::Error::last_os_error());
224 }
225
226 #[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
231 if controlling_tty {
232 if libc::ioctl(0, libc::TIOCSCTTY as _, 0) == -1 {
237 return Err(io::Error::last_os_error());
238 }
239 }
240
241 close_random_fds();
242
243 if let Some(mask) = configured_umask {
244 libc::umask(mask);
245 }
246
247 Ok(())
248 })
249 };
250
251 let mut child = cmd.spawn()?;
252
253 child.stdin.take();
259 child.stdout.take();
260 child.stderr.take();
261
262 Ok(child)
263 }
264}
265
266struct UnixMasterPty {
269 fd: PtyFd,
270}
271
272struct UnixSlavePty {
275 fd: PtyFd,
276}
277
278fn cloexec(fd: RawFd) -> Result<(), Error> {
280 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
281 if flags == -1 {
282 bail!(
283 "fcntl to read flags failed: {:?}",
284 io::Error::last_os_error()
285 );
286 }
287 let result =
288 unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
289 if result == -1 {
290 bail!(
291 "fcntl to set CLOEXEC failed: {:?}",
292 io::Error::last_os_error()
293 );
294 }
295 Ok(())
296}
297
298impl SlavePty for UnixSlavePty {
299 fn spawn_command(
300 &self,
301 builder: CommandBuilder,
302 ) -> Result<Box<dyn Child + Send + Sync>, Error> {
303 Ok(Box::new(self.fd.spawn_command(builder)?))
304 }
305}
306
307impl MasterPty for UnixMasterPty {
308 fn resize(&self, size: PtySize) -> Result<(), Error> {
309 self.fd.resize(size)
310 }
311
312 fn get_size(&self) -> Result<PtySize, Error> {
313 self.fd.get_size()
314 }
315
316 fn try_clone_reader(&self) -> Result<Box<dyn Read + Send>, Error> {
317 let fd = PtyFd(self.fd.try_clone()?);
318 Ok(Box::new(fd))
319 }
320
321 fn try_clone_writer(&self) -> Result<Box<dyn Write + Send>, Error> {
322 let fd = PtyFd(self.fd.try_clone()?);
323 Ok(Box::new(UnixMasterPty { fd }))
324 }
325
326 fn process_group_leader(&self) -> Option<libc::pid_t> {
327 match unsafe { libc::tcgetpgrp(self.fd.0.as_raw_fd()) } {
328 pid if pid > 0 => Some(pid),
329 _ => None,
330 }
331 }
332}
333
334impl Write for UnixMasterPty {
335 fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
336 self.fd.write(buf)
337 }
338 fn flush(&mut self) -> Result<(), io::Error> {
339 self.fd.flush()
340 }
341}