This commit is contained in:
Michael Zhang 2020-04-10 20:29:58 -05:00
commit b3766beb74
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
7 changed files with 982 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

625
Cargo.lock generated Normal file
View file

@ -0,0 +1,625 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "anyhow"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff"
[[package]]
name = "arc-swap"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
[[package]]
name = "asciinema"
version = "0.1.0"
dependencies = [
"anyhow",
"futures",
"libc",
"nix",
"parking_lot",
"serde",
"serde_derive",
"serde_json",
"signal-hook",
"termios",
"tokio",
"tokio-util",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bytes"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
[[package]]
name = "cc"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "fnv"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
dependencies = [
"bitflags",
"fuchsia-zircon-sys",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futures"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
[[package]]
name = "futures-executor"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
[[package]]
name = "futures-macro"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
[[package]]
name = "futures-task"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
[[package]]
name = "futures-util"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
]
[[package]]
name = "hermit-abi"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e"
dependencies = [
"libc",
]
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
"libc",
]
[[package]]
name = "itoa"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
dependencies = [
"winapi 0.2.8",
"winapi-build",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
[[package]]
name = "lock_api"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "mio"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
dependencies = [
"cfg-if",
"fuchsia-zircon",
"fuchsia-zircon-sys",
"iovec",
"kernel32-sys",
"libc",
"log",
"miow 0.2.1",
"net2",
"slab",
"winapi 0.2.8",
]
[[package]]
name = "mio-named-pipes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
dependencies = [
"log",
"mio",
"miow 0.3.3",
"winapi 0.3.8",
]
[[package]]
name = "mio-uds"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
dependencies = [
"iovec",
"libc",
"mio",
]
[[package]]
name = "miow"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
dependencies = [
"kernel32-sys",
"net2",
"winapi 0.2.8",
"ws2_32-sys",
]
[[package]]
name = "miow"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
dependencies = [
"socket2",
"winapi 0.3.8",
]
[[package]]
name = "net2"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
dependencies = [
"cfg-if",
"libc",
"winapi 0.3.8",
]
[[package]]
name = "nix"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"void",
]
[[package]]
name = "num_cpus"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "parking_lot"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb"
dependencies = [
"cfg-if",
"cloudabi",
"libc",
"redox_syscall",
"smallvec",
"winapi 0.3.8",
]
[[package]]
name = "pin-project-lite"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
[[package]]
name = "pin-utils"
version = "0.1.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
[[package]]
name = "proc-macro-hack"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63"
[[package]]
name = "proc-macro-nested"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
[[package]]
name = "proc-macro2"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "ryu"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
[[package]]
name = "serde_derive"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10b9f3a1686a29f53cfd91ee5e3db3c12313ec02d33765f02c1a9645a1811e2c"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
dependencies = [
"arc-swap",
"libc",
]
[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "smallvec"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a"
[[package]]
name = "socket2"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"winapi 0.3.8",
]
[[package]]
name = "syn"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termios"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2"
dependencies = [
"libc",
]
[[package]]
name = "tokio"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39fb9142eb6e9cc37f4f29144e62618440b149a138eee01a7bbe9b9226aaf17c"
dependencies = [
"bytes",
"fnv",
"futures-core",
"lazy_static",
"libc",
"memchr",
"mio",
"mio-named-pipes",
"mio-uds",
"num_cpus",
"pin-project-lite",
"signal-hook-registry",
"slab",
"tokio-macros",
"winapi 0.3.8",
]
[[package]]
name = "tokio-macros"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-util"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"log",
"pin-project-lite",
"tokio",
]
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "ws2_32-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
dependencies = [
"winapi 0.2.8",
"winapi-build",
]

19
Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "asciinema"
version = "0.1.0"
authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018"
[dependencies]
anyhow = "1.0.28"
futures = "0.3.4"
libc = "0.2.68"
nix = "0.17.0"
parking_lot = "0.10.1"
serde = "1.0.106"
serde_derive = "1.0.106"
serde_json = "1.0.51"
signal-hook = "0.1.13"
termios = "0.3.2"
tokio = { version = "0.2.16", features = ["fs", "io-std", "macros", "process", "rt-core", "rt-threaded", "rt-util", "sync", "time"] }
tokio-util = { version = "0.3.1", features = ["codec"] }

65
src/asciicast.rs Normal file
View file

@ -0,0 +1,65 @@
use std::collections::HashMap;
use serde::ser::{Serialize, SerializeSeq, Serializer};
#[derive(Serialize)]
pub struct Header {
version: u32,
width: u32,
height: u32,
timestamp: Option<u32>,
duration: Option<f64>,
idle_time_limit: Option<f64>,
command: Option<String>,
title: Option<String>,
env: Option<HashMap<String, String>>,
theme: Option<Theme>,
}
#[derive(Serialize)]
pub struct Theme {
fg: String,
bg: String,
palette: String,
}
pub struct Event<'a>(pub f64, pub EventKind<'a>);
pub enum EventKind<'a> {
Output(&'a [u8]),
Input(&'a [u8]),
}
impl<'a> Serialize for Event<'a> {
fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
let mut seq = s.serialize_seq(Some(3))?;
seq.serialize_element(&self.0)?;
match &self.1 {
EventKind::Output(s) => {
seq.serialize_element("o")?;
seq.serialize_element(std::str::from_utf8(s).unwrap())?;
}
EventKind::Input(s) => {
seq.serialize_element("i")?;
seq.serialize_element(std::str::from_utf8(s).unwrap())?;
}
}
seq.end()
}
}
fn build_header(width: u32, height: u32) -> Header {
Header {
version: 2,
width, height,
timestamp: None,
duration: None,
idle_time_limit: None,
command: None,
title: None,
env: None,
theme: None,
}
}

26
src/main.rs Normal file
View file

@ -0,0 +1,26 @@
#![recursion_limit = "256"]
#[macro_use]
extern crate serde_derive;
mod asciicast;
mod pty;
mod writer;
use std::fs::File;
use anyhow::Result;
use crate::writer::FileWriter;
async fn record() -> Result<()> {
let file = File::create("output.cast")?;
pty::record(&["sh", "-c", "/usr/bin/zsh"], FileWriter::new(file))?;
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
record().await?;
Ok(())
}

200
src/pty.rs Normal file
View file

@ -0,0 +1,200 @@
use std::env;
use std::ffi::CString;
use std::io;
use std::os::unix::io::RawFd;
use std::time::{Instant};
use anyhow::Error;
use nix::{
fcntl::{fcntl, FcntlArg, OFlag},
ioctl_write_buf,
pty::{forkpty, Winsize},
sys::{
select::{select, FdSet},
termios::{tcsetattr, SetArg, Termios},
wait::waitpid,
},
unistd::{execvpe, isatty, pipe, read, write, ForkResult},
};
use signal_hook::SigId;
use crate::writer::Writer;
const STDIN_FILENO: RawFd = 0;
const STDOUT_FILENO: RawFd = 1;
pub fn record(args: &[&str], writer: impl Writer + Send) -> Result<(), Error> {
let forkpty_result = forkpty(None, None)?;
let master_fd = forkpty_result.master;
let start_time = Instant::now();
let set_pty_size = || -> Result<(), Error> {
ioctl_write_buf!(helper_write, libc::TIOCGWINSZ, 104, Winsize);
let winsize = Winsize {
ws_row: 24,
ws_col: 80,
ws_xpixel: 0,
ws_ypixel: 0,
};
if isatty(STDOUT_FILENO)? {}
unsafe { helper_write(master_fd, &[winsize]) }?;
Ok(())
};
let write_stdout = |data: &[u8]| -> Result<(), Error> {
write(STDOUT_FILENO, &data)?;
Ok(())
};
let mut master_writer = writer.clone();
let mut handle_master_read = move |data: &[u8]| -> Result<(), Error> {
let elapsed = (Instant::now() - start_time).as_secs_f64();
master_writer.write_stdout(elapsed, data)?;
write_stdout(data)?;
Ok(())
};
let write_master = |data: &[u8]| -> Result<(), Error> {
let mut offset = 0;
while offset < data.len() {
let len = write(master_fd, data)?;
offset += len;
}
Ok(())
};
let mut stdin_writer = writer.clone();
let mut handle_stdin_read = |data: &[u8]| -> Result<(), Error> {
write_master(data)?;
let elapsed = (Instant::now() - start_time).as_secs_f64();
stdin_writer.write_stdin(elapsed, data)?;
Ok(())
};
let mut copy = |signal_fd: RawFd| -> Result<(), Error> {
let mut fdset = FdSet::new();
let mut buf = [0; 1024];
loop {
fdset.clear();
fdset.insert(master_fd);
fdset.insert(STDIN_FILENO);
fdset.insert(signal_fd);
select(None, &mut fdset, None, None, None)?;
if fdset.contains(master_fd) {
let len = read(master_fd, &mut buf)?;
if len == 0 {
fdset.remove(master_fd);
} else {
handle_master_read(&buf[..len])?;
}
} else if fdset.contains(STDIN_FILENO) {
let len = read(STDIN_FILENO, &mut buf)?;
if len == 0 {
fdset.remove(STDIN_FILENO);
} else {
handle_stdin_read(&buf[..len])?;
}
} else if fdset.contains(signal_fd) {
// TODO
}
}
};
let child_pid = match forkpty_result.fork_result {
ForkResult::Parent { child } => child,
ForkResult::Child => {
let cstr_args: Vec<_> = args
.into_iter()
.map(|s| CString::new(*s).unwrap())
.collect();
let args: Vec<_> = cstr_args.iter().map(|s| s.as_ref()).collect();
let cstr_env: Vec<_> = env::vars()
.map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap())
.collect();
let env: Vec<_> = cstr_env.iter().map(|s| s.as_ref()).collect();
execvpe(&args[0], &args, &env)?;
unreachable!();
}
};
let (pipe_r, pipe_w) = pipe()?;
let mut flags = fcntl(pipe_w, FcntlArg::F_GETFL)?;
flags |= libc::O_NONBLOCK;
fcntl(pipe_w, FcntlArg::F_SETFL(OFlag::from_bits(flags).unwrap()))?;
let old_handlers = set_signals(
[
signal_hook::SIGWINCH,
signal_hook::SIGCHLD,
signal_hook::SIGHUP,
signal_hook::SIGTERM,
signal_hook::SIGQUIT,
]
.iter()
.map(|sig| (*sig, move || eprintln!("HIT SIGNAL {:?}", *sig))),
)?;
// set_pty_size()?;
{
let _term = RawTerm::init(STDIN_FILENO)?;
copy(pipe_r)?;
}
unset_signals(old_handlers)?;
waitpid(child_pid, None)?;
Ok(())
}
fn set_signals<I, F>(signals_list: I) -> Result<Vec<SigId>, io::Error>
where
I: Iterator<Item = (i32, F)>,
F: Fn() + Sync + Send + 'static,
{
let mut old_handlers = Vec::new();
for (sig, handler) in signals_list {
old_handlers.push(unsafe { signal_hook::register(sig, handler) }?);
}
Ok(old_handlers)
}
fn unset_signals(handlers_list: Vec<SigId>) -> Result<(), io::Error> {
for handler in handlers_list {
signal_hook::unregister(handler);
}
Ok(())
}
struct RawTerm(RawFd, Termios);
impl RawTerm {
pub fn init(fd: RawFd) -> Result<Self, Error> {
use nix::sys::termios::*;
let saved_mode = tcgetattr(fd)?;
let mut mode = saved_mode.clone();
mode.input_flags &= !(InputFlags::BRKINT
| InputFlags::ICRNL
| InputFlags::INPCK
| InputFlags::ISTRIP
| InputFlags::IXON);
mode.output_flags &= !OutputFlags::OPOST;
mode.control_flags &= !(ControlFlags::CSIZE | ControlFlags::PARENB);
mode.control_flags |= ControlFlags::CS8;
mode.local_flags &=
!(LocalFlags::ECHO | LocalFlags::ICANON | LocalFlags::IEXTEN | LocalFlags::ISIG);
mode.control_chars[libc::VMIN] = 1;
mode.control_chars[libc::VTIME] = 0;
tcsetattr(fd, SetArg::TCSAFLUSH, &mode)?;
Ok(RawTerm(fd, saved_mode))
}
}
impl Drop for RawTerm {
fn drop(&mut self) {
tcsetattr(self.0, SetArg::TCSAFLUSH, &self.1).unwrap();
}
}

46
src/writer.rs Normal file
View file

@ -0,0 +1,46 @@
use std::fs::File;
use std::io::{self, Write};
use std::sync::Arc;
use parking_lot::Mutex;
// use tokio::{fs::File, io::AsyncWriteExt};
use crate::asciicast::{Event, EventKind};
pub trait Writer: Clone {
fn write_line(&mut self, line: String) -> Result<(), io::Error>;
fn write_stdin(&mut self, time: f64, data: &[u8]) -> Result<(), anyhow::Error> {
let output = EventKind::Input(data);
let event = Event(time, output);
self.write_line(serde_json::to_string(&event)?)?;
Ok(())
}
fn write_stdout(&mut self, time: f64, data: &[u8]) -> Result<(), anyhow::Error> {
let output = EventKind::Output(data);
let event = Event(time, output);
self.write_line(serde_json::to_string(&event)?)?;
Ok(())
}
}
#[derive(Clone)]
pub struct FileWriter(Arc<Mutex<File>>);
impl FileWriter {
pub fn new(file: File) -> Self {
FileWriter(Arc::new(Mutex::new(file)))
}
}
impl Writer for FileWriter {
fn write_line(&mut self, line: String) -> Result<(), io::Error> {
let mut writer = self.0.lock();
writer.write_all(line.as_bytes())?;
writer.write(b"\n")?;
writer.flush()?;
Ok(())
}
}