1#![allow(unused)]
27
28use color_eyre::eyre::{Error, bail};
29use filedescriptor::FileDescriptor;
30use nix::libc::{self, pid_t, winsize};
31use nix::unistd::{Pid, dup2, execv, fork};
32use std::cell::RefCell;
33use std::ffi::{CStr, CString, OsStr};
34
35use std::fs::File;
36use std::io::{Read, Write};
37use std::os::fd::{AsFd, OwnedFd};
38use std::os::unix::ffi::{OsStrExt, OsStringExt};
39use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
40use std::os::unix::process::CommandExt;
41use std::path::{Path, PathBuf};
42use std::process::Command;
43use std::{io, mem, ptr};
44
45use std::io::Result as IoResult;
48
49use crate::cmdbuilder::CommandBuilder;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct PtySize {
54 pub rows: u16,
56 pub cols: u16,
58 pub pixel_width: u16,
61 pub pixel_height: u16,
64}
65
66impl Default for PtySize {
67 fn default() -> Self {
68 Self {
69 rows: 24,
70 cols: 80,
71 pixel_width: 0,
72 pixel_height: 0,
73 }
74 }
75}
76
77pub trait MasterPty: Send {
79 fn resize(&self, size: PtySize) -> Result<(), Error>;
83 fn get_size(&self) -> Result<PtySize, Error>;
85 fn try_clone_reader(&self) -> Result<Box<dyn std::io::Read + Send>, Error>;
88 fn take_writer(&self) -> Result<Box<dyn std::io::Write + Send>, Error>;
93
94 #[cfg(unix)]
97 fn process_group_leader(&self) -> Option<libc::pid_t>;
98
99 #[cfg(unix)]
104 fn as_raw_fd(&self) -> Option<RawFd>;
105
106 #[cfg(unix)]
107 fn tty_name(&self) -> Option<std::path::PathBuf>;
108
109 #[cfg(unix)]
112 fn get_termios(&self) -> Option<nix::sys::termios::Termios> {
113 None
114 }
115}
116
117pub trait Child: std::fmt::Debug + ChildKiller + Send {
120 fn try_wait(&mut self) -> IoResult<Option<ExitStatus>>;
125 fn wait(&mut self) -> IoResult<ExitStatus>;
128 fn process_id(&self) -> Pid;
131}
132
133pub trait ChildKiller: std::fmt::Debug + Send {
135 fn kill(&mut self) -> IoResult<()>;
137
138 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync>;
142}
143
144#[derive(Debug, Clone)]
146pub struct ExitStatus {
147 code: u32,
148 signal: Option<String>,
149}
150
151impl ExitStatus {
152 pub fn with_exit_code(code: u32) -> Self {
154 Self { code, signal: None }
155 }
156
157 pub fn with_signal(signal: &str) -> Self {
159 Self {
160 code: 1,
161 signal: Some(signal.to_string()),
162 }
163 }
164
165 pub fn success(&self) -> bool {
167 match self.signal {
168 None => self.code == 0,
169 Some(_) => false,
170 }
171 }
172
173 pub fn exit_code(&self) -> u32 {
175 self.code
176 }
177
178 pub fn signal(&self) -> Option<&str> {
180 self.signal.as_deref()
181 }
182}
183
184impl From<std::process::ExitStatus> for ExitStatus {
185 fn from(status: std::process::ExitStatus) -> Self {
186 #[cfg(unix)]
187 {
188 use std::os::unix::process::ExitStatusExt;
189
190 if let Some(signal) = status.signal() {
191 let signame = unsafe { libc::strsignal(signal) };
192 let signal = if signame.is_null() {
193 format!("Signal {signal}")
194 } else {
195 let signame = unsafe { std::ffi::CStr::from_ptr(signame) };
196 signame.to_string_lossy().to_string()
197 };
198
199 return Self {
200 code: status.code().map(|c| c as u32).unwrap_or(1),
201 signal: Some(signal),
202 };
203 }
204 }
205
206 let code = status
207 .code()
208 .map(|c| c as u32)
209 .unwrap_or_else(|| if status.success() { 0 } else { 1 });
210
211 Self { code, signal: None }
212 }
213}
214
215impl std::fmt::Display for ExitStatus {
216 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
217 if self.success() {
218 write!(fmt, "Success")
219 } else {
220 match &self.signal {
221 Some(sig) => write!(fmt, "Terminated by {sig}"),
222 None => write!(fmt, "Exited with code {}", self.code),
223 }
224 }
225 }
226}
227
228pub struct PtyPair {
229 pub slave: UnixSlavePty,
232 pub master: UnixMasterPty,
233}
234
235pub trait PtySystem {
239 fn openpty(&self, size: PtySize) -> color_eyre::Result<PtyPair>;
243}
244
245impl Child for std::process::Child {
246 fn try_wait(&mut self) -> IoResult<Option<ExitStatus>> {
247 Self::try_wait(self).map(|s| s.map(Into::into))
248 }
249
250 fn wait(&mut self) -> IoResult<ExitStatus> {
251 Self::wait(self).map(Into::into)
252 }
253
254 fn process_id(&self) -> Pid {
255 Pid::from_raw(self.id() as pid_t)
256 }
257}
258
259#[derive(Debug)]
260struct ProcessSignaller {
261 pid: Option<Pid>,
262}
263
264impl ChildKiller for ProcessSignaller {
265 fn kill(&mut self) -> IoResult<()> {
266 if let Some(pid) = self.pid {
267 let result = unsafe { libc::kill(pid.as_raw(), libc::SIGHUP) };
268 if result != 0 {
269 return Err(std::io::Error::last_os_error());
270 }
271 }
272 Ok(())
273 }
274
275 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
276 Box::new(Self { pid: self.pid })
277 }
278}
279
280impl ChildKiller for std::process::Child {
281 fn kill(&mut self) -> IoResult<()> {
282 #[cfg(unix)]
283 {
284 let result = unsafe { libc::kill(self.id() as i32, libc::SIGHUP) };
288 if result != 0 {
289 return Err(std::io::Error::last_os_error());
290 }
291
292 for attempt in 0..5 {
299 if attempt > 0 {
300 std::thread::sleep(std::time::Duration::from_millis(50));
301 }
302
303 if let Ok(Some(_)) = self.try_wait() {
304 return Ok(());
306 }
307 }
308
309 }
311
312 Self::kill(self)
313 }
314
315 fn clone_killer(&self) -> Box<dyn ChildKiller + Send + Sync> {
316 Box::new(ProcessSignaller {
317 pid: Some(self.process_id()),
318 })
319 }
320}
321
322pub fn native_pty_system() -> NativePtySystem {
323 NativePtySystem::default()
324}
325
326pub type NativePtySystem = UnixPtySystem;
327
328#[derive(Default)]
329pub struct UnixPtySystem {}
330
331fn openpty(size: PtySize) -> color_eyre::Result<(UnixMasterPty, UnixSlavePty)> {
332 let mut master: RawFd = -1;
333 let mut slave: RawFd = -1;
334
335 let mut size = winsize {
336 ws_row: size.rows,
337 ws_col: size.cols,
338 ws_xpixel: size.pixel_width,
339 ws_ypixel: size.pixel_height,
340 };
341
342 let result = unsafe {
343 libc::openpty(
344 &mut master,
345 &mut slave,
346 ptr::null_mut(),
347 ptr::null_mut(),
348 &size,
349 )
350 };
351
352 if result != 0 {
353 bail!("failed to openpty: {:?}", io::Error::last_os_error());
354 }
355
356 let tty_name = tty_name(slave);
357
358 let master = UnixMasterPty {
359 fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(master) }),
360 took_writer: RefCell::new(false),
361 tty_name,
362 };
363 let slave = UnixSlavePty {
364 fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(slave) }),
365 };
366
367 cloexec(master.fd.as_raw_fd())?;
372 cloexec(slave.fd.as_raw_fd())?;
373
374 Ok((master, slave))
375}
376
377impl PtySystem for UnixPtySystem {
378 fn openpty(&self, size: PtySize) -> color_eyre::Result<PtyPair> {
379 let (master, slave) = openpty(size)?;
380 Ok(PtyPair { master, slave })
381 }
382}
383
384pub struct PtyFd(pub FileDescriptor);
385impl std::ops::Deref for PtyFd {
386 type Target = FileDescriptor;
387 fn deref(&self) -> &FileDescriptor {
388 &self.0
389 }
390}
391impl std::ops::DerefMut for PtyFd {
392 fn deref_mut(&mut self) -> &mut FileDescriptor {
393 &mut self.0
394 }
395}
396
397impl Read for PtyFd {
398 fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
399 match self.0.read(buf) {
400 Err(ref e) if e.raw_os_error() == Some(libc::EIO) => {
401 Ok(0)
406 }
407 x => x,
408 }
409 }
410}
411
412fn tty_name(fd: RawFd) -> Option<PathBuf> {
413 let mut buf = vec![0 as std::ffi::c_char; 128];
414
415 loop {
416 let res = unsafe { libc::ttyname_r(fd, buf.as_mut_ptr(), buf.len()) };
417
418 if res == libc::ERANGE {
419 if buf.len() > 64 * 1024 {
420 return None;
424 }
425 buf.resize(buf.len() * 2, 0 as std::ffi::c_char);
426 continue;
427 }
428
429 return if res == 0 {
430 let cstr = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
431 let osstr = OsStr::from_bytes(cstr.to_bytes());
432 Some(PathBuf::from(osstr))
433 } else {
434 None
435 };
436 }
437}
438
439fn close_random_fds() {
457 if let Ok(dir) = std::fs::read_dir("/proc/self/fd").or_else(|_| std::fs::read_dir("/dev/fd")) {
462 let mut fds = vec![];
463 for entry in dir {
464 if let Some(num) = entry
465 .ok()
466 .map(|e| e.file_name())
467 .and_then(|s| s.into_string().ok())
468 .and_then(|n| n.parse::<libc::c_int>().ok())
469 && num > 2
470 {
471 fds.push(num);
472 }
473 }
474 for fd in fds {
475 let _ = nix::unistd::close(fd);
476 }
477 }
478}
479
480impl PtyFd {
481 fn resize(&self, size: PtySize) -> Result<(), Error> {
482 let ws_size = winsize {
483 ws_row: size.rows,
484 ws_col: size.cols,
485 ws_xpixel: size.pixel_width,
486 ws_ypixel: size.pixel_height,
487 };
488
489 if unsafe {
490 libc::ioctl(
491 self.0.as_raw_fd(),
492 libc::TIOCSWINSZ as _,
493 &ws_size as *const _,
494 )
495 } != 0
496 {
497 bail!(
498 "failed to ioctl(TIOCSWINSZ): {:?}",
499 io::Error::last_os_error()
500 );
501 }
502
503 Ok(())
504 }
505
506 fn get_size(&self) -> Result<PtySize, Error> {
507 let mut size: winsize = unsafe { mem::zeroed() };
508 if unsafe {
509 libc::ioctl(
510 self.0.as_raw_fd(),
511 libc::TIOCGWINSZ as _,
512 &mut size as *mut _,
513 )
514 } != 0
515 {
516 bail!(
517 "failed to ioctl(TIOCGWINSZ): {:?}",
518 io::Error::last_os_error()
519 );
520 }
521 Ok(PtySize {
522 rows: size.ws_row,
523 cols: size.ws_col,
524 pixel_width: size.ws_xpixel,
525 pixel_height: size.ws_ypixel,
526 })
527 }
528
529 fn spawn_command(
530 &self,
531 command: CommandBuilder,
532 pre_exec: impl FnOnce(&Path) -> color_eyre::Result<()> + Send + Sync + 'static,
533 ) -> color_eyre::Result<Pid> {
534 spawn_command_from_pty_fd(Some(self), command, pre_exec)
535 }
536}
537
538pub fn spawn_command(
539 pts: Option<&UnixSlavePty>,
540 command: CommandBuilder,
541 pre_exec: impl FnOnce(&Path) -> color_eyre::Result<()> + Send + Sync + 'static,
542) -> color_eyre::Result<Pid> {
543 if let Some(pts) = pts {
544 pts.spawn_command(command, pre_exec)
545 } else {
546 spawn_command_from_pty_fd(None, command, pre_exec)
547 }
548}
549
550fn spawn_command_from_pty_fd(
551 pty: Option<&PtyFd>,
552 command: CommandBuilder,
553 pre_exec: impl FnOnce(&Path) -> color_eyre::Result<()> + Send + Sync + 'static,
554) -> color_eyre::Result<Pid> {
555 let configured_umask = command.umask;
556
557 let mut cmd = command.build()?;
558
559 match unsafe { fork()? } {
560 nix::unistd::ForkResult::Parent { child } => Ok(child),
561 nix::unistd::ForkResult::Child => {
562 if let Some(pty) = pty {
563 let mut stdin = unsafe { OwnedFd::from_raw_fd(0) };
564 let mut stdout = unsafe { OwnedFd::from_raw_fd(1) };
565 let mut stderr = unsafe { OwnedFd::from_raw_fd(2) };
566 dup2(pty.as_fd(), &mut stdin).unwrap();
567 dup2(pty.as_fd(), &mut stdout).unwrap();
568 dup2(pty.as_fd(), &mut stderr).unwrap();
569 std::mem::forget(stdin);
570 std::mem::forget(stdout);
571 std::mem::forget(stderr);
572 }
573
574 for signo in &[
578 libc::SIGCHLD,
579 libc::SIGHUP,
580 libc::SIGINT,
581 libc::SIGQUIT,
582 libc::SIGTERM,
583 libc::SIGALRM,
584 ] {
585 unsafe {
586 _ = libc::signal(*signo, libc::SIG_DFL);
587 }
588 }
589
590 unsafe {
591 let empty_set: libc::sigset_t = std::mem::zeroed();
592 _ = libc::sigprocmask(libc::SIG_SETMASK, &empty_set, std::ptr::null_mut());
593 }
594
595 pre_exec(&cmd.program).unwrap();
596
597 close_random_fds();
598
599 if let Some(mask) = configured_umask {
600 _ = unsafe { libc::umask(mask) };
601 }
602
603 execv(
604 &CString::new(cmd.program.into_os_string().into_vec()).unwrap(),
605 &cmd.args,
606 )
607 .unwrap();
608 unreachable!()
609 }
610 }
611}
612
613pub struct UnixMasterPty {
616 fd: PtyFd,
617 took_writer: RefCell<bool>,
618 tty_name: Option<PathBuf>,
619}
620
621pub struct UnixSlavePty {
624 pub fd: PtyFd,
625}
626
627impl UnixSlavePty {
628 pub fn spawn_command(
629 &self,
630 command: CommandBuilder,
631 pre_exec: impl FnOnce(&Path) -> color_eyre::Result<()> + Send + Sync + 'static,
632 ) -> color_eyre::Result<Pid> {
633 self.fd.spawn_command(command, pre_exec)
634 }
635}
636
637fn cloexec(fd: RawFd) -> Result<(), Error> {
639 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
640 if flags == -1 {
641 bail!(
642 "fcntl to read flags failed: {:?}",
643 io::Error::last_os_error()
644 );
645 }
646 let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
647 if result == -1 {
648 bail!(
649 "fcntl to set CLOEXEC failed: {:?}",
650 io::Error::last_os_error()
651 );
652 }
653 Ok(())
654}
655
656impl MasterPty for UnixMasterPty {
657 fn resize(&self, size: PtySize) -> Result<(), Error> {
658 self.fd.resize(size)
659 }
660
661 fn get_size(&self) -> Result<PtySize, Error> {
662 self.fd.get_size()
663 }
664
665 fn try_clone_reader(&self) -> Result<Box<dyn Read + Send>, Error> {
666 let fd = PtyFd(self.fd.try_clone()?);
667 Ok(Box::new(fd))
668 }
669
670 fn take_writer(&self) -> Result<Box<dyn Write + Send>, Error> {
671 if *self.took_writer.borrow() {
672 bail!("cannot take writer more than once");
673 }
674 *self.took_writer.borrow_mut() = true;
675 let fd = PtyFd(self.fd.try_clone()?);
676 Ok(Box::new(UnixMasterWriter { fd }))
677 }
678
679 fn as_raw_fd(&self) -> Option<RawFd> {
680 Some(self.fd.0.as_raw_fd())
681 }
682
683 fn tty_name(&self) -> Option<PathBuf> {
684 self.tty_name.clone()
685 }
686
687 fn process_group_leader(&self) -> Option<libc::pid_t> {
688 match unsafe { libc::tcgetpgrp(self.fd.0.as_raw_fd()) } {
689 pid if pid > 0 => Some(pid),
690 _ => None,
691 }
692 }
693
694 fn get_termios(&self) -> Option<nix::sys::termios::Termios> {
695 nix::sys::termios::tcgetattr(unsafe { File::from_raw_fd(self.fd.0.as_raw_fd()) }).ok()
696 }
697}
698
699struct UnixMasterWriter {
703 fd: PtyFd,
704}
705
706impl Drop for UnixMasterWriter {
707 fn drop(&mut self) {
708 let mut t: libc::termios = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
709 if unsafe { libc::tcgetattr(self.fd.0.as_raw_fd(), &mut t) } == 0 {
710 let eot = t.c_cc[libc::VEOF];
713 if eot != 0 {
714 let _ = self.fd.0.write_all(&[b'\n', eot]);
715 }
716 }
717 }
718}
719
720impl Write for UnixMasterWriter {
721 fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
722 self.fd.write(buf)
723 }
724 fn flush(&mut self) -> Result<(), io::Error> {
725 self.fd.flush()
726 }
727}