akv_cli/pty/
posix.rs

1// Copyright 2024 Heath Stewart.
2// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
3
4// cspell:disable
5use 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/// A pseudoterminal.
54#[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/// The child end of a pseudoterminal.
101#[derive(Debug)]
102pub struct Pts(OwnedFd);