1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

chore: fix pty support on Macs (#20037)

Many of the CI tests have been failing on my M2 Pro mac (Ventura 13.4)
when running inside of a vscode terminal (a strange `ENOTTY` error).
This modifies the pty-handling code to use libc directly rather than the
older pty library that appears mostly unmaintained (outside of
@littledivy's fork).

As a bonus, this should allow us to run pty tests on the mac CI runner.

After this PR, the tests now complete with 100% success on my local
machine. Before this PR, I needed to pass `CI=true` to get my local test
suite to pass.
This commit is contained in:
Matt Mastracci 2023-08-03 14:04:37 -06:00 committed by GitHub
parent 6ba245fe25
commit 0f07dc95f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 44 deletions

12
Cargo.lock generated
View file

@ -3805,16 +3805,6 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "pty2"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4461e7f96399674b9112e620e511089bc7c4c0d76545b3cc3e0b46bab72a15d5"
dependencies = [
"errno",
"libc",
]
[[package]] [[package]]
name = "pulldown-cmark" name = "pulldown-cmark"
version = "0.9.3" version = "0.9.3"
@ -5274,13 +5264,13 @@ dependencies = [
"glob", "glob",
"hyper 0.14.26", "hyper 0.14.26",
"lazy-regex", "lazy-regex",
"libc",
"lsp-types", "lsp-types",
"nix", "nix",
"once_cell", "once_cell",
"os_pipe", "os_pipe",
"parking_lot 0.12.1", "parking_lot 0.12.1",
"pretty_assertions", "pretty_assertions",
"pty2",
"regex", "regex",
"reqwest", "reqwest",
"ring", "ring",

View file

@ -24,6 +24,7 @@ futures.workspace = true
glob.workspace = true glob.workspace = true
hyper = { workspace = true, features = ["server", "http1", "http2", "runtime"] } hyper = { workspace = true, features = ["server", "http1", "http2", "runtime"] }
lazy-regex.workspace = true lazy-regex.workspace = true
libc.workspace = true
lsp-types.workspace = true lsp-types.workspace = true
nix.workspace = true nix.workspace = true
once_cell.workspace = true once_cell.workspace = true
@ -43,8 +44,5 @@ tokio.workspace = true
tokio-rustls.workspace = true tokio-rustls.workspace = true
url.workspace = true url.workspace = true
[target.'cfg(unix)'.dependencies]
pty2 = "0.1.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { workspace = true, features = ["consoleapi", "synchapi", "handleapi", "namedpipeapi", "winbase", "winerror"] } winapi = { workspace = true, features = ["consoleapi", "synchapi", "handleapi", "namedpipeapi", "winbase", "winerror"] }

View file

@ -44,10 +44,10 @@ impl Pty {
} }
pub fn is_supported() -> bool { pub fn is_supported() -> bool {
let is_mac_or_windows = cfg!(target_os = "macos") || cfg!(windows); let is_windows = cfg!(windows);
if is_mac_or_windows && std::env::var("CI").is_ok() { if is_windows && std::env::var("CI").is_ok() {
// the pty tests give a ENOTTY error for Mac and don't really start up // the pty tests don't really start up on the windows CI for some reason
// on the windows CI for some reason so ignore them for now // so ignore them for now
eprintln!("Ignoring windows CI."); eprintln!("Ignoring windows CI.");
false false
} else { } else {
@ -216,8 +216,10 @@ impl Pty {
trait SystemPty: Read + Write {} trait SystemPty: Read + Write {}
impl SystemPty for std::fs::File {}
#[cfg(unix)] #[cfg(unix)]
fn setup_pty(master: &pty2::fork::Master) { fn setup_pty(fd: i32) {
use nix::fcntl::fcntl; use nix::fcntl::fcntl;
use nix::fcntl::FcntlArg; use nix::fcntl::FcntlArg;
use nix::fcntl::OFlag; use nix::fcntl::OFlag;
@ -225,9 +227,7 @@ fn setup_pty(master: &pty2::fork::Master) {
use nix::sys::termios::tcgetattr; use nix::sys::termios::tcgetattr;
use nix::sys::termios::tcsetattr; use nix::sys::termios::tcsetattr;
use nix::sys::termios::SetArg; use nix::sys::termios::SetArg;
use std::os::fd::AsRawFd;
let fd = master.as_raw_fd();
let mut term = tcgetattr(fd).unwrap(); let mut term = tcgetattr(fd).unwrap();
// disable cooked mode // disable cooked mode
term.local_flags.remove(termios::LocalFlags::ICANON); term.local_flags.remove(termios::LocalFlags::ICANON);
@ -246,21 +246,60 @@ fn create_pty(
cwd: &Path, cwd: &Path,
env_vars: Option<HashMap<String, String>>, env_vars: Option<HashMap<String, String>>,
) -> Box<dyn SystemPty> { ) -> Box<dyn SystemPty> {
let fork = pty2::fork::Fork::from_ptmx().unwrap(); use crate::pty::unix::UnixPty;
if fork.is_parent().is_ok() { use std::os::unix::process::CommandExt;
let master = fork.is_parent().unwrap();
setup_pty(&master); // Manually open pty main/secondary sides in the test process. Since we're not actually
Box::new(unix::UnixPty { fork }) // changing uid/gid here, this is the easiest way to do it.
} else {
std::process::Command::new(program) // SAFETY: Posix APIs
let (fdm, fds) = unsafe {
let fdm = libc::posix_openpt(libc::O_RDWR);
if fdm < 0 {
panic!("posix_openpt failed");
}
let res = libc::grantpt(fdm);
if res != 0 {
panic!("grantpt failed");
}
let res = libc::unlockpt(fdm);
if res != 0 {
panic!("unlockpt failed");
}
let fds = libc::open(libc::ptsname(fdm), libc::O_RDWR);
if fdm < 0 {
panic!("open(ptsname) failed");
}
(fdm, fds)
};
// SAFETY: Posix APIs
unsafe {
let cmd = std::process::Command::new(program)
.current_dir(cwd) .current_dir(cwd)
.args(args) .args(args)
.envs(env_vars.unwrap_or_default()) .envs(env_vars.unwrap_or_default())
.pre_exec(move || {
// Close parent's main handle
libc::close(fdm);
libc::dup2(fds, 0);
libc::dup2(fds, 1);
libc::dup2(fds, 2);
// Note that we could close `fds` here as well, but this is a short-lived process and
// we're just not going to worry about "leaking" it
Ok(())
})
.spawn() .spawn()
.unwrap()
.wait()
.unwrap(); .unwrap();
std::process::exit(0);
// Close child's secondary handle
libc::close(fds);
setup_pty(fdm);
use std::os::fd::FromRawFd;
let pid = nix::unistd::Pid::from_raw(cmd.id() as _);
let file = std::fs::File::from_raw_fd(fdm);
Box::new(UnixPty { pid, file })
} }
} }
@ -272,19 +311,15 @@ mod unix {
use super::SystemPty; use super::SystemPty;
pub struct UnixPty { pub struct UnixPty {
pub fork: pty2::fork::Fork, pub pid: nix::unistd::Pid,
pub file: std::fs::File,
} }
impl Drop for UnixPty { impl Drop for UnixPty {
fn drop(&mut self) { fn drop(&mut self) {
use nix::sys::signal::kill; use nix::sys::signal::kill;
use nix::sys::signal::Signal; use nix::sys::signal::Signal;
use nix::unistd::Pid; kill(self.pid, Signal::SIGTERM).unwrap()
if let pty2::fork::Fork::Parent(child_pid, _) = self.fork {
let pid = Pid::from_raw(child_pid);
kill(pid, Signal::SIGTERM).unwrap()
}
} }
} }
@ -292,20 +327,17 @@ mod unix {
impl Read for UnixPty { impl Read for UnixPty {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let mut master = self.fork.is_parent().unwrap(); self.file.read(buf)
master.read(buf)
} }
} }
impl Write for UnixPty { impl Write for UnixPty {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut master = self.fork.is_parent().unwrap(); self.file.write(buf)
master.write(buf)
} }
fn flush(&mut self) -> std::io::Result<()> { fn flush(&mut self) -> std::io::Result<()> {
let mut master = self.fork.is_parent().unwrap(); self.file.flush()
master.flush()
} }
} }
} }