mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-15 19:28:00 +01:00
Implement basic "PTY exec" in Rust
This commit is contained in:
173
Cargo.lock
generated
173
Cargo.lock
generated
@@ -2,6 +2,179 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "asciinema"
|
||||
version = "3.0.0-alpha.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"mio",
|
||||
"nix",
|
||||
"termion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||
dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"numtoa",
|
||||
"redox_syscall",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
@@ -6,3 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
nix = { version = "0.27", features = [ "fs", "term", "process" ] }
|
||||
mio = { version ="0.8", features = ["os-poll", "os-ext"] }
|
||||
termion = "2.0.1"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
mod pty;
|
||||
use anyhow::Result;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
pty::exec(&["/bin/bash"])
|
||||
}
|
||||
|
||||
192
src/pty.rs
Normal file
192
src/pty.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
use mio::unix::SourceFd;
|
||||
use nix::{fcntl, libc, pty, unistd, unistd::ForkResult};
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::ops::Deref;
|
||||
use std::os::fd::RawFd;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use termion::raw::IntoRawMode;
|
||||
|
||||
pub fn exec<S: AsRef<str>>(args: &[S]) -> anyhow::Result<()> {
|
||||
let tty = open_tty()?;
|
||||
let winsize = get_tty_size(tty.as_raw_fd());
|
||||
let result = unsafe { pty::forkpty(Some(&winsize), None) }?;
|
||||
|
||||
match result.fork_result {
|
||||
ForkResult::Parent { .. } => {
|
||||
handle_parent(result.master.as_raw_fd(), tty)?;
|
||||
}
|
||||
|
||||
ForkResult::Child => {
|
||||
handle_child(args)?;
|
||||
// TODO wait for child pid
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open_tty() -> io::Result<fs::File> {
|
||||
fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/tty")
|
||||
}
|
||||
|
||||
fn get_tty_size(tty_fd: i32) -> pty::Winsize {
|
||||
let mut winsize = pty::Winsize {
|
||||
ws_row: 24,
|
||||
ws_col: 80,
|
||||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
};
|
||||
|
||||
unsafe { libc::ioctl(tty_fd, libc::TIOCGWINSZ, &mut winsize) };
|
||||
|
||||
winsize
|
||||
}
|
||||
|
||||
const MASTER: mio::Token = mio::Token(0);
|
||||
const TTY: mio::Token = mio::Token(1);
|
||||
const BUF_SIZE: usize = 128 * 1024;
|
||||
|
||||
fn handle_parent(master_fd: RawFd, tty: fs::File) -> anyhow::Result<()> {
|
||||
let mut master_file = unsafe { fs::File::from_raw_fd(master_fd) };
|
||||
let mut poll = mio::Poll::new()?;
|
||||
let mut events = mio::Events::with_capacity(128);
|
||||
let mut master_source = SourceFd(&master_fd);
|
||||
let mut tty = tty.into_raw_mode()?;
|
||||
let tty_fd = tty.as_raw_fd();
|
||||
let mut tty_source = SourceFd(&tty_fd);
|
||||
let mut buf = [0u8; BUF_SIZE];
|
||||
let mut input: Vec<u8> = Vec::with_capacity(BUF_SIZE);
|
||||
let mut output: Vec<u8> = Vec::with_capacity(BUF_SIZE);
|
||||
|
||||
set_non_blocking(&master_fd)?;
|
||||
set_non_blocking(&tty_fd)?;
|
||||
|
||||
poll.registry()
|
||||
.register(&mut master_source, MASTER, mio::Interest::READABLE)?;
|
||||
|
||||
poll.registry()
|
||||
.register(&mut tty_source, TTY, mio::Interest::READABLE)?;
|
||||
|
||||
loop {
|
||||
poll.poll(&mut events, None).unwrap();
|
||||
|
||||
for event in events.iter() {
|
||||
match event.token() {
|
||||
MASTER => {
|
||||
if event.is_readable() {
|
||||
let read = read_all(&mut master_file, &mut buf, &mut output)?;
|
||||
|
||||
if read > 0 {
|
||||
poll.registry().reregister(
|
||||
&mut tty_source,
|
||||
TTY,
|
||||
mio::Interest::READABLE | mio::Interest::WRITABLE,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if event.is_writable() {
|
||||
master_file.write_all(&input).unwrap();
|
||||
input.clear();
|
||||
|
||||
poll.registry().reregister(
|
||||
&mut master_source,
|
||||
MASTER,
|
||||
mio::Interest::READABLE,
|
||||
)?;
|
||||
}
|
||||
|
||||
if event.is_read_closed() {
|
||||
return Ok(());
|
||||
// TODO don't return but deregister master_source and flush remaining output to tty
|
||||
}
|
||||
}
|
||||
|
||||
TTY => {
|
||||
if event.is_writable() {
|
||||
tty.write_all(&output)?;
|
||||
output.clear();
|
||||
|
||||
poll.registry().reregister(
|
||||
&mut tty_source,
|
||||
TTY,
|
||||
mio::Interest::READABLE,
|
||||
)?;
|
||||
}
|
||||
|
||||
if event.is_readable() {
|
||||
let read = read_all(&mut tty.deref(), &mut buf, &mut input)?;
|
||||
|
||||
if read > 0 {
|
||||
poll.registry().reregister(
|
||||
&mut master_source,
|
||||
MASTER,
|
||||
mio::Interest::READABLE | mio::Interest::WRITABLE,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if event.is_read_closed() {
|
||||
poll.registry().deregister(&mut tty_source).unwrap();
|
||||
return Ok(());
|
||||
// TODO don't return but deregister tty_source and flush remaining input to master
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_child<S: AsRef<str>>(args: &[S]) -> anyhow::Result<()> {
|
||||
use std::ffi::{CString, NulError};
|
||||
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|s| CString::new(s.as_ref()))
|
||||
.collect::<Result<Vec<CString>, NulError>>()?;
|
||||
|
||||
unistd::execvp(&args[0], &args)?;
|
||||
unsafe { libc::_exit(1) }
|
||||
}
|
||||
|
||||
fn set_non_blocking(fd: &i32) -> Result<(), io::Error> {
|
||||
use fcntl::{fcntl, FcntlArg::*, OFlag};
|
||||
|
||||
let flags = fcntl(*fd, F_GETFL)?;
|
||||
let mut oflags = OFlag::from_bits_truncate(flags);
|
||||
oflags |= OFlag::O_NONBLOCK;
|
||||
fcntl(*fd, F_SETFL(oflags))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_all<R: Read>(source: &mut R, buf: &mut [u8], out: &mut Vec<u8>) -> io::Result<usize> {
|
||||
let mut read = 0;
|
||||
|
||||
loop {
|
||||
match source.read(buf) {
|
||||
Ok(0) => (),
|
||||
|
||||
Ok(n) => {
|
||||
out.extend_from_slice(&buf[0..n]);
|
||||
read += n;
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
if e.kind() == std::io::ErrorKind::WouldBlock {
|
||||
break;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(read)
|
||||
}
|
||||
Reference in New Issue
Block a user