diff --git a/Cargo.lock b/Cargo.lock index 4500f613..8271f217 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,10 +19,10 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "backtrace-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -33,7 +33,7 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -46,7 +46,7 @@ name = "c2-chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -57,7 +57,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.2" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -83,8 +83,8 @@ name = "getrandom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -106,7 +106,7 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.60" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -116,13 +116,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nix" -version = "0.14.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -131,13 +131,25 @@ name = "ppv-lite86" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ptyproc" +version = "0.1.0" +source = "git+https://github.com/TyPR124/ptyproc#299505e9d11f0267edce9657569ce5afc64a3212" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -199,7 +211,7 @@ name = "remove_dir_all" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -207,7 +219,7 @@ name = "rexpect" version = "0.4.0" dependencies = [ "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ptyproc 0.1.0 (git+https://github.com/TyPR124/ptyproc)", "regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -222,17 +234,22 @@ name = "spin" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -240,7 +257,7 @@ name = "thread_local" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -270,7 +287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -300,16 +317,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" "checksum cc 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7db2f146208d7e0fbee761b09cd65a7f51ccc38705d4e7262dad4d73b12a76b1" -"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" "checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" "checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" -"checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" -"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" +"checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" +"checksum ptyproc 0.1.0 (git+https://github.com/TyPR124/ptyproc)" = "" "checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" "checksum rand_chacha 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e193067942ef6f485a349a113329140d0ab9e2168ce92274499bb0e9a4190d9d" "checksum rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615e683324e75af5d43d8f7a39ffe3ee4a9dc42c5c701167a71dc59c3a493aca" @@ -320,6 +338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" "checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" +"checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" @@ -327,7 +346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 1bececa5..469e0c0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,11 @@ readme = "README.md" [dependencies] -nix = "0.14" regex = "1" error-chain = "0.12" tempfile = "3" +ptyproc = { version = "0.1", git = "https://github.com/TyPR124/ptyproc" } + [badges] travis-ci = { repository = "philippkeller/rexpect" } diff --git a/examples/exit_code.rs b/examples/exit_code.rs index 1b85d1e1..31f1a504 100644 --- a/examples/exit_code.rs +++ b/examples/exit_code.rs @@ -2,33 +2,40 @@ extern crate rexpect; use rexpect::spawn; use rexpect::errors::*; -use rexpect::process::wait; - /// The following code emits: /// cat exited with code 0, all good! /// cat exited with code 1 /// Output (stdout and stderr): cat: /this/does/not/exist: No such file or directory fn exit_code_fun() -> Result<()> { - - let p = spawn("cat /etc/passwd", Some(2000))?; + let mut p = spawn("cat /etc/passwd", Some(2000))?; match p.process.wait() { - Ok(wait::WaitStatus::Exited(_, 0)) => println!("cat exited with code 0, all good!"), - _ => println!("cat exited with code >0, or it was killed"), + Ok(status) if status.success() => println!("cat exited with code 0, all good!"), + Ok(status) => { + if let Some(code) = status.code() { + println!("Cat failed with exit code {}", code); + println!("Output (stdout and stderr): {}", p.exp_eof()?); + } else { + println!("cat was probably killed") + } + }, + Err(err) => println!("failed to wait on process {:?}", err), } - + let mut p = spawn("cat /this/does/not/exist", Some(2000))?; match p.process.wait() { - Ok(wait::WaitStatus::Exited(_, 0)) => println!("cat succeeded"), - Ok(wait::WaitStatus::Exited(_, c)) => { - println!("Cat failed with exit code {}", c); - println!("Output (stdout and stderr): {}", p.exp_eof()?); + Ok(status) if status.success() => println!("cat exited with code 0, all good!"), + Ok(status) => { + if let Some(code) = status.code() { + println!("Cat failed with exit code {}", code); + println!("Output (stdout and stderr): {}", p.exp_eof()?); + } else { + println!("cat was probably killed") + } }, - // for other possible return types of wait() - // see here: https://tailhook.github.io/rotor/nix/sys/wait/enum.WaitStatus.html - _ => println!("cat was probably killed"), + Err(err) => println!("failed to wait on process {:?}", err), } - + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index a7710fd3..7fb2ae5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,13 +78,14 @@ //! //! ``` -pub mod process; pub mod session; pub mod reader; pub use session::{spawn, spawn_bash, spawn_python, spawn_stream}; pub use reader::{Until}; +pub use ptyproc::{Command, PtyProcess, PtyReader, PtyWriter}; + pub mod errors { use std::time; // Create the Error, ErrorKind, ResultExt, and Result types diff --git a/src/process.rs b/src/process.rs deleted file mode 100644 index e251f0aa..00000000 --- a/src/process.rs +++ /dev/null @@ -1,276 +0,0 @@ -//! Start a process via pty - -use std; -use std::fs::File; -use std::process::Command; -use std::os::unix::process::CommandExt; -use std::os::unix::io::{FromRawFd, AsRawFd}; -use std::{thread, time}; -use nix::pty::{posix_openpt, grantpt, unlockpt, PtyMaster}; -use nix::fcntl::{OFlag, open}; -use nix; -use nix::sys::{stat, termios}; -use nix::unistd::{fork, ForkResult, setsid, dup, dup2, Pid}; -use nix::libc::{STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; -pub use nix::sys::{wait, signal}; -use crate::errors::*; // load error-chain - -/// Start a process in a forked tty so you can interact with it the same as you would -/// within a terminal -/// -/// The process and pty session are killed upon dropping PtyProcess -/// -/// # Example -/// -/// Typically you want to do something like this (for a more complete example see -/// unit test `test_cat` within this module): -/// -/// ``` -/// # #![allow(unused_mut)] -/// # #![allow(unused_variables)] -/// -/// extern crate nix; -/// extern crate rexpect; -/// -/// use rexpect::process::PtyProcess; -/// use std::process::Command; -/// use std::fs::File; -/// use std::io::{BufReader, LineWriter}; -/// use std::os::unix::io::{FromRawFd, AsRawFd}; -/// use nix::unistd::dup; -/// -/// # fn main() { -/// -/// let mut process = PtyProcess::new(Command::new("cat")).expect("could not execute cat"); -/// let fd = dup(process.pty.as_raw_fd()).unwrap(); -/// let f = unsafe { File::from_raw_fd(fd) }; -/// let mut writer = LineWriter::new(&f); -/// let mut reader = BufReader::new(&f); -/// process.exit().expect("could not terminate process"); -/// -/// // writer.write() sends strings to `cat` -/// // writer.reader() reads back what `cat` wrote -/// // send Ctrl-C with writer.write(&[3]) and writer.flush() -/// -/// # } -/// ``` -pub struct PtyProcess { - pub pty: PtyMaster, - pub child_pid: Pid, - kill_timeout: Option, -} - - -#[cfg(target_os = "linux")] -use nix::pty::ptsname_r; - -#[cfg(target_os = "macos")] -/// ptsname_r is a linux extension but ptsname isn't thread-safe -/// instead of using a static mutex this calls ioctl with TIOCPTYGNAME directly -/// based on https://blog.tarq.io/ptsname-on-osx-with-rust/ -fn ptsname_r(fd: &PtyMaster) -> nix::Result { - use std::ffi::CStr; - use nix::libc::{ioctl, TIOCPTYGNAME}; - - // the buffer size on OSX is 128, defined by sys/ttycom.h - let mut buf: [i8; 128] = [0; 128]; - - unsafe { - match ioctl(fd.as_raw_fd(), TIOCPTYGNAME as u64, &mut buf) { - 0 => { - let res = CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned(); - Ok(res) - } - _ => Err(nix::Error::last()), - } - } -} - -impl PtyProcess { - /// Start a process in a forked pty - pub fn new(mut command: Command) -> Result { - || -> nix::Result { - // Open a new PTY master - let master_fd = posix_openpt(OFlag::O_RDWR)?; - - // Allow a slave to be generated for it - grantpt(&master_fd)?; - unlockpt(&master_fd)?; - - // on Linux this is the libc function, on OSX this is our implementation of ptsname_r - let slave_name = ptsname_r(&master_fd)?; - - match fork()? { - ForkResult::Child => { - setsid()?; // create new session with child as session leader - let slave_fd = open(std::path::Path::new(&slave_name), - OFlag::O_RDWR, - stat::Mode::empty())?; - - // assign stdin, stdout, stderr to the tty, just like a terminal does - dup2(slave_fd, STDIN_FILENO)?; - dup2(slave_fd, STDOUT_FILENO)?; - dup2(slave_fd, STDERR_FILENO)?; - - // set echo off - let mut flags = termios::tcgetattr(STDIN_FILENO)?; - flags.local_flags &= !termios::LocalFlags::ECHO; - termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSANOW, &flags)?; - - command.exec(); - Err(nix::Error::last()) - } - ForkResult::Parent { child: child_pid } => { - Ok(PtyProcess { - pty: master_fd, - child_pid: child_pid, - kill_timeout: None, - }) - } - } - }() - .chain_err(|| format!("could not execute {:?}", command)) - } - - /// Get handle to pty fork for reading/writing - pub fn get_file_handle(&self) -> File { - // needed because otherwise fd is closed both by dropping process and reader/writer - let fd = dup(self.pty.as_raw_fd()).unwrap(); - unsafe { File::from_raw_fd(fd) } - } - - /// At the drop of PtyProcess the running process is killed. This is blocking forever if - /// the process does not react to a normal kill. If kill_timeout is set the process is - /// `kill -9`ed after duration - pub fn set_kill_timeout(&mut self, timeout_ms: Option) { - self.kill_timeout = timeout_ms.and_then(|millis| Some(time::Duration::from_millis(millis))); - } - - /// Get status of child process, nonblocking. - /// - /// This method runs waitpid on the process. - /// This means: If you ran `exit()` before or `status()` this method will - /// return `None` - /// - /// # Example - /// ```rust,no_run - /// - /// # extern crate rexpect; - /// use rexpect::process::{self, wait::WaitStatus}; - /// use std::process::Command; - /// - /// # fn main() { - /// let cmd = Command::new("/path/to/myprog"); - /// let process = process::PtyProcess::new(cmd).expect("could not execute myprog"); - /// while let Some(WaitStatus::StillAlive) = process.status() { - /// // do something - /// } - /// # } - /// ``` - /// - pub fn status(&self) -> Option { - if let Ok(status) = wait::waitpid(self.child_pid, Some(wait::WaitPidFlag::WNOHANG)) { - Some(status) - } else { - None - } - } - - /// Wait until process has exited. This is a blocking call. - /// If the process doesn't terminate this will block forever. - pub fn wait(&self) -> Result { - wait::waitpid(self.child_pid, None).chain_err(|| "wait: cannot read status") - } - - /// Regularly exit the process, this method is blocking until the process is dead - pub fn exit(&mut self) -> Result { - self.kill(signal::SIGTERM) - } - - /// Nonblocking variant of `kill()` (doesn't wait for process to be killed) - pub fn signal(&mut self, sig: signal::Signal) -> Result<()> { - signal::kill(self.child_pid, sig) - .chain_err(|| "failed to send signal to process")?; - Ok(()) - } - - /// Kill the process with a specific signal. This method blocks, until the process is dead - /// - /// repeatedly sends SIGTERM to the process until it died, - /// the pty session is closed upon dropping PtyMaster, - /// so we don't need to explicitely do that here. - /// - /// if `kill_timeout` is set and a repeated sending of signal does not result in the process - /// being killed, then `kill -9` is sent after the `kill_timeout` duration has elapsed. - pub fn kill(&mut self, sig: signal::Signal) -> Result { - let start = time::Instant::now(); - loop { - match signal::kill(self.child_pid, sig) { - Ok(_) => {} - // process was already killed before -> ignore - Err(nix::Error::Sys(nix::errno::Errno::ESRCH)) => { - return Ok(wait::WaitStatus::Exited(Pid::from_raw(0), 0)) - } - Err(e) => return Err(format!("kill resulted in error: {:?}", e).into()), - } - - - match self.status() { - Some(status) if status != wait::WaitStatus::StillAlive => return Ok(status), - Some(_) | None => thread::sleep(time::Duration::from_millis(100)), - } - // kill -9 if timout is reached - if let Some(timeout) = self.kill_timeout { - if start.elapsed() > timeout { - signal::kill(self.child_pid, signal::Signal::SIGKILL).chain_err(|| "")? - } - } - } - } -} - -impl Drop for PtyProcess { - fn drop(&mut self) { - match self.status() { - Some(wait::WaitStatus::StillAlive) => { - self.exit().expect("cannot exit"); - } - _ => {} - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::io::{BufReader, LineWriter}; - use nix::sys::{wait, signal}; - use std::io::prelude::*; - - #[test] - /// Open cat, write string, read back string twice, send Ctrl^C and check that cat exited - fn test_cat() { - // wrapping into closure so I can use ? - || -> std::io::Result<()> { - let process = PtyProcess::new(Command::new("cat")).expect("could not execute cat"); - let f = process.get_file_handle(); - let mut writer = LineWriter::new(&f); - let mut reader = BufReader::new(&f); - writer.write(b"hello cat\n")?; - let mut buf = String::new(); - reader.read_line(&mut buf)?; - assert_eq!(buf, "hello cat\r\n"); - - // this sleep solves an edge case of some cases when cat is somehow not "ready" - // to take the ^C (occasional test hangs) - thread::sleep(time::Duration::from_millis(100)); - writer.write_all(&[3])?; // send ^C - writer.flush()?; - let should = - wait::WaitStatus::Signaled(process.child_pid, signal::Signal::SIGINT, false); - assert_eq!(should, wait::waitpid(process.child_pid, None).unwrap()); - Ok(()) - }() - .unwrap_or_else(|e| panic!("test_cat failed: {}", e)); - } -} diff --git a/src/session.rs b/src/session.rs index 25674d5b..6a784a02 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,13 +1,15 @@ //! Main module of rexpect: start new process and interact with it -use crate::errors::*; // load error-chain -use crate::process::PtyProcess; +use crate::{Command, PtyProcess, PtyReader, PtyWriter}; use crate::reader::{NBReader, Regex, EOF, Needle, Str, Regx}; +use crate::errors::*; +// pub use crate::reader::ReadUntil; use std::fs::File; -use std::io::prelude::*; use std::io::LineWriter; +use std::io::prelude::*; +// use std::io::LineWriter; use std::ops::{Deref, DerefMut}; -use std::process::Command; +// use std::process::Command; use tempfile; pub struct StreamSession { @@ -136,21 +138,21 @@ impl StreamSession { #[allow(dead_code)] pub struct PtySession { pub process: PtyProcess, - pub stream: StreamSession, + pub stream: StreamSession, pub commandname: String, // only for debugging purposes now } // make StreamSession's methods available directly impl Deref for PtySession { - type Target = StreamSession; - fn deref(&self) -> &StreamSession { + type Target = StreamSession; + fn deref(&self) -> &StreamSession { &self.stream } } impl DerefMut for PtySession { - fn deref_mut(&mut self) -> &mut StreamSession { + fn deref_mut(&mut self) -> &mut StreamSession { &mut self.stream } } @@ -159,7 +161,7 @@ impl DerefMut for PtySession { /// /// # Example /// -/// ``` +/// ```no_run /// /// use rexpect::spawn; /// # use rexpect::errors::*; @@ -175,11 +177,14 @@ impl DerefMut for PtySession { /// # } /// ``` impl PtySession { - fn new(process: PtyProcess, timeout_ms: Option, commandname: String) -> Result { + fn new(mut process: PtyProcess, timeout_ms: Option, commandname: String) -> Result { - let f = process.get_file_handle(); - let reader = f.try_clone().chain_err(|| "couldn't open write stream")?; - let stream = StreamSession::new(reader, f, timeout_ms); + // let f = process.get_file_handle(); + // let (reader, writer) = process.take_io_handles().chain_err(|| "could take process IO handles")?; + // let reader = f.try_clone().chain_err(|| "couldn't open write stream")?; + let reader = process.take_reader().chain_err(|| "could not get pty reader")?; + let writer = process.take_writer().chain_err(|| "could not get pty writer")?; + let stream = StreamSession::new(reader, writer, timeout_ms); Ok(Self { process, stream, @@ -221,15 +226,16 @@ pub fn spawn(program: &str, timeout_ms: Option) -> Result { let prog = parts.remove(0); let mut command = Command::new(prog); command.args(parts); - spawn_command(command, timeout_ms) + spawn_command(&mut command, timeout_ms) } /// See `spawn` -pub fn spawn_command(command: Command, timeout_ms: Option) -> Result { +pub fn spawn_command(command: &mut Command, timeout_ms: Option) -> Result { let commandname = format!("{:?}", &command); let mut process = PtyProcess::new(command) .chain_err(|| "couldn't start process")?; - process.set_kill_timeout(timeout_ms); + // Not sure this is even needed. Seems timeout is mostly useful for exp_* methods + process.set_drop_timeout(std::time::Duration::from_millis(timeout_ms.unwrap_or(0))); PtySession::new(process, timeout_ms, commandname) } @@ -277,7 +283,7 @@ impl PtyReplSession { /// /// # Example: /// - /// ``` + /// ```no_run /// use rexpect::spawn_bash; /// # use rexpect::errors::*; /// @@ -387,7 +393,7 @@ pub fn spawn_bash(timeout: Option) -> Result { .to_str() .unwrap_or_else(|| return "temp file does not exist".into()), ]); - spawn_command(c, timeout).and_then(|p| { + spawn_command(&mut c, timeout).and_then(|p| { let new_prompt = "[REXPECT_PROMPT>"; let mut pb = PtyReplSession { prompt: new_prompt.to_string(), @@ -410,7 +416,7 @@ pub fn spawn_bash(timeout: Option) -> Result { /// /// This is just a proof of concept implementation (and serves for documentation purposes) pub fn spawn_python(timeout: Option) -> Result { - spawn_command(Command::new("python"), timeout).and_then(|p| { + spawn_command(&mut Command::new("python"), timeout).and_then(|p| { Ok(PtyReplSession { prompt: ">>> ".to_string(), pty_session: p, @@ -429,24 +435,26 @@ pub fn spawn_stream(reader: R, writer: W, ti mod tests { use super::*; use super::super::reader::{NBytes, Until, OrInterest}; - + + #[cfg(unix)] #[test] fn test_read_line() { || -> Result<()> { let mut s = spawn("cat", Some(1000))?; s.send_line("hans")?; assert_eq!("hans", s.read_line()?); - let should = crate::process::wait::WaitStatus::Signaled( - s.process.child_pid, - crate::process::signal::Signal::SIGTERM, - false, - ); - assert_eq!(should, s.process.exit()?); + // let should = crate::process::wait::WaitStatus::Signaled( + // s.process.child_pid, + // crate::process::signal::Signal::SIGTERM, + // false, + // ); + // assert_eq!(should, s.process.exit()?); Ok(()) }() .unwrap_or_else(|e| panic!("test_read_line failed: {}", e)); } + #[cfg(unix)] #[test] fn test_expect_eof_timeout() { || -> Result<()> { @@ -462,12 +470,14 @@ mod tests { .unwrap_or_else(|e| panic!("test_timeout failed: {}", e)); } + #[cfg(unix)] #[test] fn test_expect_eof_timeout2() { let mut p = spawn("sleep 1", Some(1100)).expect("cannot run sleep 1"); assert!(p.exp_eof().is_ok(), "expected eof"); } + #[cfg(unix)] #[test] fn test_expect_string() { || -> Result<()> { @@ -481,6 +491,7 @@ mod tests { .unwrap_or_else(|e| panic!("test_expect_string failed: {}", e)); } + #[cfg(unix)] #[test] fn test_read_string_before() { || -> Result<()> { @@ -492,6 +503,7 @@ mod tests { .unwrap_or_else(|e| panic!("test_read_string_before failed: {}", e)); } + #[cfg(unix)] #[test] fn test_expect_any() { || -> Result<()> { @@ -510,6 +522,7 @@ mod tests { .unwrap_or_else(|e| panic!("test_expect_any failed: {}", e)); } + #[cfg(unix)] #[test] fn test_expect_any_huge() { || -> Result<()> { @@ -539,6 +552,7 @@ mod tests { } } + #[cfg(unix)] #[test] fn test_kill_timeout() { || -> Result<()> { @@ -551,6 +565,7 @@ mod tests { // Since that is not enough to make bash exit, a kill -9 is sent within 1s (timeout) } + #[cfg(unix)] #[test] fn test_bash() { || -> Result<()> { @@ -564,6 +579,7 @@ mod tests { .unwrap_or_else(|e| panic!("test_bash failed: {}", e)); } + #[cfg(unix)] #[test] fn test_bash_control_chars() { || -> Result<()> { @@ -582,6 +598,28 @@ mod tests { .unwrap_or_else(|e| panic!("test_bash_control_chars failed: {}", e)); } + #[cfg(windows)] + #[test] + fn test_windows_ping_once() { + let mut p = spawn("ping -n 1 127.0.0.1", Some(2000)).unwrap(); + p.exp_string("Ping statistics for 127.0.0.1:").unwrap(); + assert!(p.process.wait().unwrap().success()); + } + + #[cfg(windows)] + #[test] + fn test_windows_ping_for_a_while() { + let mut p = spawn("ping -n 100 127.0.0.1", Some(3000)).unwrap(); + for _ in 0..4 { + // This fails because ping.exe blinks the cursor over the "R" in "Reply" + // p.exp_string("Reply from 127.0.0.1: bytes=32").unwrap(); + p.exp_string("eply from 127.0.0.1: bytes=32").unwrap(); + + } + p.send_control('c').unwrap(); + assert!(!p.process.wait().unwrap().success()); + } + #[test] fn test_tokenize_command() { let res = tokenize_command("prog arg1 arg2");