mirror of
https://gitlab.redox-os.org/redox-os/redox.git
synced 2026-06-28 07:44:18 +08:00
Try use pty
This commit is contained in:
parent
66d7a520e1
commit
a943426bde
99
Cargo.lock
generated
99
Cargo.lock
generated
@ -416,6 +416,12 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
@ -753,6 +759,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dryoc"
|
||||
version = "0.6.2"
|
||||
@ -854,6 +866,17 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"thiserror 1.0.69",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.2"
|
||||
@ -1537,6 +1560,18 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if 1.0.1",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
@ -1748,6 +1783,27 @@ version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "portable-pty"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 1.3.2",
|
||||
"downcast-rs",
|
||||
"filedescriptor",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"nix",
|
||||
"serial2",
|
||||
"shared_library",
|
||||
"shell-words",
|
||||
"winapi",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.2"
|
||||
@ -1797,7 +1853,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"cfg_aliases 0.2.1",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
@ -1837,7 +1893,7 @@ version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
@ -2007,12 +2063,15 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"blake3 1.5.3",
|
||||
"filedescriptor",
|
||||
"ignore",
|
||||
"libc",
|
||||
"object",
|
||||
"pbr",
|
||||
"pkgar 0.1.19",
|
||||
"pkgar-core 0.1.19",
|
||||
"pkgar-keys 0.1.19",
|
||||
"portable-pty",
|
||||
"ratatui",
|
||||
"redox-pkg",
|
||||
"redoxer",
|
||||
@ -2434,6 +2493,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial2"
|
||||
version = "0.2.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cc76fa68e25e771492ca1e3c53d447ef0be3093e05cd3b47f4b712ba10c6f3c"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.1",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
@ -2445,6 +2515,22 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_library"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@ -3445,6 +3531,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
|
||||
@ -23,12 +23,14 @@ doctest = false
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
blake3 = "=1.5.3" # 1.5.4 is incompatible with blake3 0.3 dependency from pkgar
|
||||
libc = "0.2"
|
||||
ignore = "0.4"
|
||||
object = { version = "0.36", features = ["build_core"] }
|
||||
pbr = "1.0.2"
|
||||
pkgar = { path = "pkgar/pkgar" }
|
||||
pkgar-core = { path = "pkgar/pkgar-core" }
|
||||
pkgar-keys = { path = "pkgar/pkgar-keys" }
|
||||
portable-pty = "0.9.0"
|
||||
redox-pkg = "0.2.8"
|
||||
redoxer = "0.2"
|
||||
regex = "1.11"
|
||||
@ -36,6 +38,7 @@ serde = { version = "=1.0.197", features = ["derive"] }
|
||||
termion = "4"
|
||||
toml = "0.8"
|
||||
walkdir = "2.3.1"
|
||||
filedescriptor = "0.8.3"
|
||||
|
||||
[dependencies.ratatui]
|
||||
version = "0.29.0"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::io::{BufRead, BufReader, PipeReader, Write, stderr, stdin, stdout};
|
||||
use std::io::{BufRead, BufReader, Read, Write, stderr, stdin, stdout};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
@ -14,8 +14,9 @@ use cookbook::WALK_DEPTH;
|
||||
use cookbook::config::{CookConfig, get_config, init_config};
|
||||
use cookbook::cook::cook_build::build;
|
||||
use cookbook::cook::fetch::{fetch, fetch_offline};
|
||||
use cookbook::cook::fs::{Stdout, create_target_dir};
|
||||
use cookbook::cook::fs::create_target_dir;
|
||||
use cookbook::cook::package::package;
|
||||
use cookbook::cook::pty::{setup_pty, PtyOut, UnixSlavePty};
|
||||
use cookbook::recipe::CookRecipe;
|
||||
use pkg::PackageName;
|
||||
use pkg::package::PackageError;
|
||||
@ -355,7 +356,7 @@ fn parse_args(args: Vec<String>) -> anyhow::Result<(CliConfig, CliCommand, Vec<C
|
||||
fn handle_fetch(
|
||||
recipe: &CookRecipe,
|
||||
config: &CliConfig,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let recipe_dir = &recipe.dir;
|
||||
let source_dir = match config.cook.offline {
|
||||
@ -372,7 +373,7 @@ fn handle_cook(
|
||||
config: &CliConfig,
|
||||
source_dir: PathBuf,
|
||||
is_deps: bool,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> anyhow::Result<()> {
|
||||
let recipe_dir = &recipe.dir;
|
||||
let target_dir = create_target_dir(recipe_dir).map_err(|e| anyhow!(e))?;
|
||||
@ -1212,13 +1213,15 @@ fn draw_prompt(f: &mut ratatui::Frame, prompt: &FailurePrompt) {
|
||||
f.render_widget(paragraph, popup_area);
|
||||
}
|
||||
|
||||
fn spawn_log_reader(
|
||||
mut pipe_reader: PipeReader,
|
||||
fn spawn_log_reader<R>(
|
||||
mut reader: R,
|
||||
package_name: PackageName,
|
||||
status_tx: mpsc::Sender<StatusUpdate>,
|
||||
) {
|
||||
) where
|
||||
R: Read + Send + 'static,
|
||||
{
|
||||
thread::spawn(move || {
|
||||
let reader = BufReader::new(&mut pipe_reader);
|
||||
let reader = BufReader::new(&mut reader);
|
||||
for line in reader.lines() {
|
||||
let line_str = line.unwrap_or_else(|e| format!("[IO Error] {}", e));
|
||||
if status_tx
|
||||
@ -1235,12 +1238,12 @@ fn spawn_log_reader(
|
||||
fn setup_logger(
|
||||
status_tx: &mpsc::Sender<StatusUpdate>,
|
||||
name: &PackageName,
|
||||
) -> (std::io::PipeWriter, std::io::PipeWriter) {
|
||||
let (stdout_reader, stdout_writer) = std::io::pipe().expect("Failed to create stdout pipe");
|
||||
let (stderr_reader, stderr_writer) = std::io::pipe().expect("Failed to create stderr pipe");
|
||||
spawn_log_reader(stdout_reader, name.clone(), status_tx.clone());
|
||||
spawn_log_reader(stderr_reader, name.clone(), status_tx.clone());
|
||||
(stdout_writer, stderr_writer)
|
||||
) -> (UnixSlavePty, std::io::PipeWriter) {
|
||||
let (pty_reader, log_reader, pipes) = setup_pty();
|
||||
|
||||
spawn_log_reader(pty_reader, name.clone(), status_tx.clone());
|
||||
spawn_log_reader(log_reader, name.clone(), status_tx.clone());
|
||||
pipes
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
|
||||
@ -3,4 +3,5 @@ pub mod cook_build;
|
||||
pub mod fetch;
|
||||
pub mod fs;
|
||||
pub mod package;
|
||||
pub mod pty;
|
||||
pub mod script;
|
||||
|
||||
@ -3,6 +3,7 @@ use pkg::{Package, PackageName};
|
||||
use redoxer::target;
|
||||
|
||||
use crate::cook::fs::*;
|
||||
use crate::cook::pty::PtyOut;
|
||||
use crate::cook::script::*;
|
||||
use crate::recipe::AutoDeps;
|
||||
use crate::recipe::BuildKind;
|
||||
@ -39,7 +40,7 @@ macro_rules! log_warn {
|
||||
fn auto_deps(
|
||||
stage_dir: &Path,
|
||||
dep_pkgars: &BTreeSet<(PackageName, PathBuf)>,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> BTreeSet<PackageName> {
|
||||
let mut paths = BTreeSet::new();
|
||||
let mut visited = BTreeSet::new();
|
||||
@ -167,7 +168,7 @@ pub fn build(
|
||||
recipe: &Recipe,
|
||||
offline_mode: bool,
|
||||
check_source: bool,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> Result<(PathBuf, BTreeSet<PackageName>), String> {
|
||||
let sysroot_dir = target_dir.join("sysroot");
|
||||
let stage_dir = target_dir.join("stage");
|
||||
@ -379,7 +380,7 @@ fn build_auto_deps(
|
||||
target_dir: &Path,
|
||||
stage_dir: &PathBuf,
|
||||
dep_pkgars: BTreeSet<(PackageName, PathBuf)>,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> Result<BTreeSet<PackageName>, String> {
|
||||
let auto_deps_path = target_dir.join("auto_deps.toml");
|
||||
if auto_deps_path.is_file() && modified(&auto_deps_path)? < modified(stage_dir)? {
|
||||
@ -412,7 +413,7 @@ pub fn build_remote(
|
||||
target_dir: &Path,
|
||||
name: &PackageName,
|
||||
offline_mode: bool,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> Result<(PathBuf, BTreeSet<PackageName>), String> {
|
||||
// download straight from remote source then declare pkg dependencies as autodeps dependency
|
||||
let stage_dir = target_dir.join("stage");
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use crate::config::translate_mirror;
|
||||
use crate::cook::fs::*;
|
||||
use crate::cook::pty::PtyOut;
|
||||
use crate::cook::script::*;
|
||||
use crate::is_redox;
|
||||
use crate::recipe::BuildKind;
|
||||
@ -43,7 +44,7 @@ pub(crate) fn get_blake3(path: &PathBuf, show_progress: bool) -> Result<String,
|
||||
pub fn fetch_offline(
|
||||
recipe_dir: &Path,
|
||||
recipe: &Recipe,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> Result<PathBuf, String> {
|
||||
let source_dir = recipe_dir.join("source");
|
||||
if recipe.build.kind == BuildKind::None || recipe.build.kind == BuildKind::Remote {
|
||||
@ -103,7 +104,7 @@ pub fn fetch_offline(
|
||||
Ok(source_dir)
|
||||
}
|
||||
|
||||
pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &Stdout) -> Result<PathBuf, String> {
|
||||
pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<PathBuf, String> {
|
||||
let source_dir = recipe_dir.join("source");
|
||||
if recipe.build.kind == BuildKind::None || recipe.build.kind == BuildKind::Remote {
|
||||
// the build function doesn't need source dir exists
|
||||
@ -374,7 +375,7 @@ pub(crate) fn fetch_resolve_canon(
|
||||
pub(crate) fn fetch_extract_tar(
|
||||
source_tar: PathBuf,
|
||||
source_dir_tmp: &PathBuf,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> Result<(), String> {
|
||||
let mut command = Command::new("tar");
|
||||
if is_redox() {
|
||||
@ -420,7 +421,7 @@ pub(crate) fn fetch_apply_patches(
|
||||
patches: &Vec<String>,
|
||||
script: &Option<String>,
|
||||
source_dir_tmp: &PathBuf,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> Result<(), String> {
|
||||
for patch_name in patches {
|
||||
let patch_file = recipe_dir.join(patch_name);
|
||||
|
||||
@ -8,7 +8,10 @@ use std::{
|
||||
};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::config::translate_mirror;
|
||||
use crate::{
|
||||
config::translate_mirror,
|
||||
cook::pty::{PtyOut, spawn_to_pipe},
|
||||
};
|
||||
|
||||
//TODO: pub(crate) for all of these functions
|
||||
|
||||
@ -146,27 +149,10 @@ pub fn rename(src: &Path, dst: &Path) -> Result<(), String> {
|
||||
})
|
||||
}
|
||||
|
||||
pub type Stdout<'a> = Option<(&'a mut PipeWriter, &'a mut PipeWriter)>;
|
||||
|
||||
fn pipe_to_cmd(command: &mut Command, stdout_pipe: &Stdout) -> Result<(), String> {
|
||||
Ok(if let Some((stdout, stderr)) = stdout_pipe {
|
||||
command.stdout::<PipeWriter>(
|
||||
stdout
|
||||
.try_clone()
|
||||
.map_err(|e| format!("unable to clone stdout fd: {:?}", e))?,
|
||||
);
|
||||
command.stderr(
|
||||
stderr
|
||||
.try_clone()
|
||||
.map_err(|e| format!("unable to clone stderr fd: {:?}", e))?,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run_command(mut command: process::Command, stdout_pipe: &Stdout) -> Result<(), String> {
|
||||
pipe_to_cmd(&mut command, stdout_pipe)?;
|
||||
let status = command
|
||||
.status()
|
||||
pub fn run_command(mut command: process::Command, stdout_pipe: &PtyOut) -> Result<(), String> {
|
||||
let status = spawn_to_pipe(&mut command, stdout_pipe)
|
||||
.map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?
|
||||
.wait()
|
||||
.map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?;
|
||||
|
||||
if !status.success() {
|
||||
@ -182,13 +168,10 @@ pub fn run_command(mut command: process::Command, stdout_pipe: &Stdout) -> Resul
|
||||
pub fn run_command_stdin(
|
||||
mut command: process::Command,
|
||||
stdin_data: &[u8],
|
||||
stdout_pipe: &Stdout,
|
||||
stdout_pipe: &PtyOut,
|
||||
) -> Result<(), String> {
|
||||
command.stdin(Stdio::piped());
|
||||
pipe_to_cmd(&mut command, stdout_pipe)?;
|
||||
|
||||
let mut child = command
|
||||
.spawn()
|
||||
let mut child = spawn_to_pipe(&mut command, stdout_pipe)
|
||||
.map_err(|err| format!("failed to spawn {:?}: {}\n{:#?}", command, err, err))?;
|
||||
|
||||
if let Some(ref mut stdin) = child.stdin {
|
||||
@ -240,7 +223,7 @@ pub fn offline_check_exists(path: &PathBuf) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn download_wget(url: &str, dest: &PathBuf, logger: &Stdout) -> Result<(), String> {
|
||||
pub fn download_wget(url: &str, dest: &PathBuf, logger: &PtyOut) -> Result<(), String> {
|
||||
if !dest.is_file() {
|
||||
let dest_tmp = PathBuf::from(format!("{}.tmp", dest.display()));
|
||||
let mut command = Command::new("wget");
|
||||
|
||||
@ -3,32 +3,18 @@ use std::{collections::BTreeSet, env, path::Path};
|
||||
use pkg::{Package, PackageName};
|
||||
|
||||
use crate::{
|
||||
cook::fs::*,
|
||||
cook::{fs::*, pty::PtyOut},
|
||||
log_to_pty,
|
||||
recipe::{BuildKind, Recipe},
|
||||
};
|
||||
|
||||
macro_rules! log_warn {
|
||||
($logger:expr, $($arg:tt)+) => {
|
||||
use std::io::Write;
|
||||
|
||||
if $logger.is_some() {
|
||||
let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write(
|
||||
format!($($arg)+)
|
||||
.as_bytes(),
|
||||
);
|
||||
} else {
|
||||
eprintln!($($arg)+);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn package(
|
||||
stage_dir: &Path,
|
||||
target_dir: &Path,
|
||||
name: &PackageName,
|
||||
recipe: &Recipe,
|
||||
auto_deps: &BTreeSet<PackageName>,
|
||||
logger: &Stdout,
|
||||
logger: &PtyOut,
|
||||
) -> Result<(), String> {
|
||||
if recipe.build.kind == BuildKind::None {
|
||||
// metapackages don't have stage dir
|
||||
@ -58,7 +44,7 @@ pub fn package(
|
||||
if package_file.is_file() {
|
||||
let stage_modified = modified_dir(stage_dir)?;
|
||||
if modified(&package_file)? < stage_modified {
|
||||
log_warn!(
|
||||
log_to_pty!(
|
||||
logger,
|
||||
"DEBUG: '{}' newer than '{}'",
|
||||
stage_dir.display(),
|
||||
|
||||
403
src/cook/pty.rs
Normal file
403
src/cook/pty.rs
Normal file
@ -0,0 +1,403 @@
|
||||
use anyhow::{Error, bail};
|
||||
use filedescriptor::FileDescriptor;
|
||||
use libc::{self, winsize};
|
||||
use portable_pty::PtySize;
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::fd::FromRawFd;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Child;
|
||||
use std::{io, mem, ptr};
|
||||
use std::{
|
||||
io::{PipeReader, PipeWriter},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
pub use std::os::unix::io::RawFd;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_to_pty {
|
||||
($logger:expr, $($arg:tt)+) => {
|
||||
use std::io::Write;
|
||||
|
||||
if $logger.is_some() {
|
||||
let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write(
|
||||
format!($($arg)+)
|
||||
.as_bytes(),
|
||||
);
|
||||
} else {
|
||||
eprintln!($($arg)+);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub type PtyOut<'a> = Option<(&'a mut UnixSlavePty, &'a mut PipeWriter)>;
|
||||
|
||||
pub fn setup_pty() -> (
|
||||
Box<dyn Read + Send>,
|
||||
PipeReader,
|
||||
(UnixSlavePty, std::io::PipeWriter),
|
||||
) {
|
||||
let pty_system = UnixPtySystem::default();
|
||||
let pair = pty_system
|
||||
.openpty(PtySize {
|
||||
rows: 24, // Standard terminal size
|
||||
cols: 80, // Standard terminal size
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Unable to open pty");
|
||||
|
||||
// TODO: There's no way to handle stdin
|
||||
let pty_reader = pair
|
||||
.master
|
||||
.try_clone_reader()
|
||||
.expect("Unable to clone pty reader");
|
||||
|
||||
let (log_reader, log_writer) = std::io::pipe().expect("Failed to create log pipe");
|
||||
let pipes = (pair.slave, log_writer);
|
||||
(pty_reader, log_reader, pipes)
|
||||
}
|
||||
|
||||
pub fn spawn_to_pipe(command: &mut Command, stdout_pipe: &PtyOut) -> Result<Child, Error> {
|
||||
match stdout_pipe {
|
||||
Some(stdout) => stdout.0.spawn_command(command.into()),
|
||||
None => Ok(command.spawn()?),
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// based on portable-pty crate
|
||||
// copied here since it isn't flexible enough
|
||||
//
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UnixPtySystem {}
|
||||
|
||||
fn openpty(size: PtySize) -> anyhow::Result<(UnixMasterPty, UnixSlavePty)> {
|
||||
let mut master: RawFd = -1;
|
||||
let mut slave: RawFd = -1;
|
||||
|
||||
let mut size = winsize {
|
||||
ws_row: size.rows,
|
||||
ws_col: size.cols,
|
||||
ws_xpixel: size.pixel_width,
|
||||
ws_ypixel: size.pixel_height,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
// BSDish systems may require mut pointers to some args
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
libc::openpty(
|
||||
&mut master,
|
||||
&mut slave,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
&mut size,
|
||||
)
|
||||
};
|
||||
|
||||
if result != 0 {
|
||||
bail!("failed to openpty: {:?}", io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let tty_name = tty_name(slave);
|
||||
|
||||
let master = UnixMasterPty {
|
||||
fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(master) }),
|
||||
took_writer: RefCell::new(false),
|
||||
tty_name,
|
||||
};
|
||||
let slave = UnixSlavePty {
|
||||
fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(slave) }),
|
||||
};
|
||||
|
||||
// Ensure that these descriptors will get closed when we execute
|
||||
// the child process. This is done after constructing the Pty
|
||||
// instances so that we ensure that the Ptys get drop()'d if
|
||||
// the cloexec() functions fail (unlikely!).
|
||||
cloexec(master.fd.as_raw_fd())?;
|
||||
cloexec(slave.fd.as_raw_fd())?;
|
||||
|
||||
Ok((master, slave))
|
||||
}
|
||||
|
||||
pub struct PtyPair {
|
||||
// slave is listed first so that it is dropped first.
|
||||
// The drop order is stable and specified by rust rfc 1857
|
||||
pub slave: UnixSlavePty,
|
||||
pub master: UnixMasterPty,
|
||||
}
|
||||
|
||||
impl UnixPtySystem {
|
||||
fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair> {
|
||||
let (master, slave) = openpty(size)?;
|
||||
Ok(PtyPair {
|
||||
master: master,
|
||||
slave: slave,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct PtyFd(pub FileDescriptor);
|
||||
impl std::ops::Deref for PtyFd {
|
||||
type Target = FileDescriptor;
|
||||
fn deref(&self) -> &FileDescriptor {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for PtyFd {
|
||||
fn deref_mut(&mut self) -> &mut FileDescriptor {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for PtyFd {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
|
||||
match self.0.read(buf) {
|
||||
Err(ref e) if e.raw_os_error() == Some(libc::EIO) => {
|
||||
// EIO indicates that the slave pty has been closed.
|
||||
// Treat this as EOF so that std::io::Read::read_to_string
|
||||
// and similar functions gracefully terminate when they
|
||||
// encounter this condition
|
||||
Ok(0)
|
||||
}
|
||||
x => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tty_name(fd: RawFd) -> Option<PathBuf> {
|
||||
let mut buf = vec![0 as std::ffi::c_char; 128];
|
||||
|
||||
loop {
|
||||
let res = unsafe { libc::ttyname_r(fd, buf.as_mut_ptr(), buf.len()) };
|
||||
|
||||
if res == libc::ERANGE {
|
||||
if buf.len() > 64 * 1024 {
|
||||
// on macOS, if the buf is "too big", ttyname_r can
|
||||
// return ERANGE, even though that is supposed to
|
||||
// indicate buf is "too small".
|
||||
return None;
|
||||
}
|
||||
buf.resize(buf.len() * 2, 0 as std::ffi::c_char);
|
||||
continue;
|
||||
}
|
||||
|
||||
return if res == 0 {
|
||||
let cstr = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
|
||||
let osstr = OsStr::from_bytes(cstr.to_bytes());
|
||||
Some(PathBuf::from(osstr))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl PtyFd {
|
||||
fn resize(&self, size: PtySize) -> Result<(), Error> {
|
||||
let ws_size = winsize {
|
||||
ws_row: size.rows,
|
||||
ws_col: size.cols,
|
||||
ws_xpixel: size.pixel_width,
|
||||
ws_ypixel: size.pixel_height,
|
||||
};
|
||||
|
||||
if unsafe {
|
||||
libc::ioctl(
|
||||
self.0.as_raw_fd(),
|
||||
libc::TIOCSWINSZ as _,
|
||||
&ws_size as *const _,
|
||||
)
|
||||
} != 0
|
||||
{
|
||||
bail!(
|
||||
"failed to ioctl(TIOCSWINSZ): {:?}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_size(&self) -> Result<PtySize, Error> {
|
||||
let mut size: winsize = unsafe { mem::zeroed() };
|
||||
if unsafe {
|
||||
libc::ioctl(
|
||||
self.0.as_raw_fd(),
|
||||
libc::TIOCGWINSZ as _,
|
||||
&mut size as *mut _,
|
||||
)
|
||||
} != 0
|
||||
{
|
||||
bail!(
|
||||
"failed to ioctl(TIOCGWINSZ): {:?}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
Ok(PtySize {
|
||||
rows: size.ws_row,
|
||||
cols: size.ws_col,
|
||||
pixel_width: size.ws_xpixel,
|
||||
pixel_height: size.ws_ypixel,
|
||||
})
|
||||
}
|
||||
|
||||
fn spawn_command(&self, cmd: &mut Command) -> anyhow::Result<std::process::Child> {
|
||||
unsafe {
|
||||
cmd
|
||||
// .stdin(self.as_stdio()?)
|
||||
.stdout(self.as_stdio()?)
|
||||
.stderr(self.as_stdio()?)
|
||||
.pre_exec(move || {
|
||||
// Clean up a few things before we exec the program
|
||||
// Clear out any potentially problematic signal
|
||||
// dispositions that we might have inherited
|
||||
for signo in &[
|
||||
libc::SIGCHLD,
|
||||
libc::SIGHUP,
|
||||
libc::SIGINT,
|
||||
libc::SIGQUIT,
|
||||
libc::SIGTERM,
|
||||
libc::SIGALRM,
|
||||
] {
|
||||
libc::signal(*signo, libc::SIG_DFL);
|
||||
}
|
||||
|
||||
let empty_set: libc::sigset_t = std::mem::zeroed();
|
||||
libc::sigprocmask(libc::SIG_SETMASK, &empty_set, std::ptr::null_mut());
|
||||
|
||||
// Establish ourselves as a session leader.
|
||||
if libc::setsid() == -1 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
|
||||
let mut child = cmd.spawn()?;
|
||||
|
||||
// Ensure that we close out the slave fds that Child retains;
|
||||
// they are not what we need (we need the master side to reference
|
||||
// them) and won't work in the usual way anyway.
|
||||
// In practice these are None, but it seems best to be move them
|
||||
// out in case the behavior of Command changes in the future.
|
||||
child.stdin.take();
|
||||
child.stdout.take();
|
||||
child.stderr.take();
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the master end of a pty.
|
||||
/// The file descriptor will be closed when the Pty is dropped.
|
||||
pub struct UnixMasterPty {
|
||||
fd: PtyFd,
|
||||
took_writer: RefCell<bool>,
|
||||
tty_name: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Represents the slave end of a pty.
|
||||
/// The file descriptor will be closed when the Pty is dropped.
|
||||
pub struct UnixSlavePty {
|
||||
fd: PtyFd,
|
||||
}
|
||||
|
||||
/// Helper function to set the close-on-exec flag for a raw descriptor
|
||||
fn cloexec(fd: RawFd) -> Result<(), Error> {
|
||||
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
|
||||
if flags == -1 {
|
||||
bail!(
|
||||
"fcntl to read flags failed: {:?}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
|
||||
if result == -1 {
|
||||
bail!(
|
||||
"fcntl to set CLOEXEC failed: {:?}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl UnixSlavePty {
|
||||
fn spawn_command(&self, builder: &mut Command) -> Result<std::process::Child, Error> {
|
||||
Ok(self.fd.spawn_command(builder)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl UnixMasterPty {
|
||||
fn resize(&self, size: PtySize) -> Result<(), Error> {
|
||||
self.fd.resize(size)
|
||||
}
|
||||
|
||||
fn get_size(&self) -> Result<PtySize, Error> {
|
||||
self.fd.get_size()
|
||||
}
|
||||
|
||||
fn try_clone_reader(&self) -> Result<Box<dyn Read + Send>, Error> {
|
||||
let fd = PtyFd(self.fd.try_clone()?);
|
||||
Ok(Box::new(fd))
|
||||
}
|
||||
|
||||
fn take_writer(&self) -> Result<Box<dyn Write + Send>, Error> {
|
||||
if *self.took_writer.borrow() {
|
||||
anyhow::bail!("cannot take writer more than once");
|
||||
}
|
||||
*self.took_writer.borrow_mut() = true;
|
||||
let fd = PtyFd(self.fd.try_clone()?);
|
||||
Ok(Box::new(UnixMasterWriter { fd }))
|
||||
}
|
||||
|
||||
fn as_raw_fd(&self) -> Option<RawFd> {
|
||||
Some(self.fd.0.as_raw_fd())
|
||||
}
|
||||
|
||||
fn tty_name(&self) -> Option<PathBuf> {
|
||||
self.tty_name.clone()
|
||||
}
|
||||
|
||||
fn process_group_leader(&self) -> Option<libc::pid_t> {
|
||||
match unsafe { libc::tcgetpgrp(self.fd.0.as_raw_fd()) } {
|
||||
pid if pid > 0 => Some(pid),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the master end of a pty.
|
||||
/// EOT will be sent, and then the file descriptor will be closed when
|
||||
/// the Pty is dropped.
|
||||
struct UnixMasterWriter {
|
||||
fd: PtyFd,
|
||||
}
|
||||
|
||||
impl Drop for UnixMasterWriter {
|
||||
fn drop(&mut self) {
|
||||
let mut t: libc::termios = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||
if unsafe { libc::tcgetattr(self.fd.0.as_raw_fd(), &mut t) } == 0 {
|
||||
// EOF is only interpreted after a newline, so if it is set,
|
||||
// we send a newline followed by EOF.
|
||||
let eot = t.c_cc[libc::VEOF];
|
||||
if eot != 0 {
|
||||
let _ = self.fd.0.write_all(&[b'\n', eot]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for UnixMasterWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
||||
self.fd.write(buf)
|
||||
}
|
||||
fn flush(&mut self) -> Result<(), io::Error> {
|
||||
self.fd.flush()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user