Skip to content

Commit

Permalink
Various fixes
Browse files Browse the repository at this point in the history
1)

If output is piped into `less`, and `less` aborts, then the ensuing
EPIPE failures should not cause a panic. See Rust issue:

    rust-lang/rust#46016

The workaround is to not use `print!()` or `println!()`, unfortunately.

2)

Before this change, if any of the child processes has terminated
pty-for-each has not really waited for the pid, but only for the closing
the terminal pipe. This change makes pty-for-each wait for both.

3)

Any non-zero exit status for one or more of the child processes should
cause a non-zero exit status from pty-for-each by default. This change
implements an exit status of 0xfe if all processes exited with a signal
0xff if all processes exited normally with a non-zero exit status, and
0xfd on mixed situations of the former two.
  • Loading branch information
da-x committed Jan 27, 2018
1 parent 297ed6f commit 6fad33f
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 30 deletions.
72 changes: 64 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ extern crate regex;
#[macro_use]
extern crate structopt_derive;

use std::io::BufReader;
use std::io::BufRead;
use structopt::StructOpt;
use structopt::clap::AppSettings;
use std::io::{BufReader, BufRead, Write};
use std::io;
use std::thread;
use std::collections::HashMap;
use std::sync::mpsc::{sync_channel, Receiver};
use std::process::ExitStatus;
use structopt::StructOpt;
use structopt::clap::AppSettings;
use regex::Regex;

/// Process ID of child process
Expand Down Expand Up @@ -81,6 +82,12 @@ struct Subprogram {
prefix: String,
}

impl Subprogram {
fn wait(&mut self) -> io::Result<ExitStatus> {
self.pty.child.wait()
}
}

struct Interpolator(String, Regex, Regex);

impl Interpolator {
Expand Down Expand Up @@ -155,7 +162,7 @@ fn make_programs(opt: &Opt) -> HashMap<u32, Subprogram> {
Err(_) => (25, columns),
};

let prefix_delim = if prefix.len() > 0 {
let prefix_delim = if prefix.len() > 0 {
format!("{}: ", prefix)
} else {
prefix.clone()
Expand Down Expand Up @@ -211,24 +218,73 @@ fn handle_programs(programs: &HashMap<u32, Subprogram>) -> (ThreadsMap, Receiver
(threads, receiver)
}

fn print(text: &String) -> bool
{
// print!() macro panics on EPIPE, this is the alternative
//
// TODO: more print invocations require replacements.
match io::stdout().write(text.as_bytes()) {
Err(_) => false,
_ => true,
}
}

fn handle_mainloop(mut programs: HashMap<u32, Subprogram>,
mut threads: ThreadsMap,
receiver: Receiver<Message>)
{
let mut stdout_gone = false;
let mut code : i32 = 0;

while programs.len() > 0 {
let msg = receiver.recv().unwrap();
match msg {
Message::Line(key, line) => {
let program = programs.get(&key).unwrap();
print!("{}{}", program.prefix, line);
if !stdout_gone {
let program = programs.get(&key).unwrap();
if !print(&format!("{}{}", program.prefix, line)) {
stdout_gone = true;
}
}
}
Message::Terminated(key) => {
programs.remove(&key);
let mut program = programs.remove(&key).unwrap();
let thread = threads.remove(&key).unwrap();
thread.join().expect("join error");

loop {
match program.wait() {
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock {
continue;
}
panic!("program wait returned: {}", e);
}
Ok(res) => {
/*
* One of the subprogram's error is enough for us
* to change our own exit status to an error. However,
* we cannot reliably forward errors as they are.
*/
if !res.success() {
if code == 0 {
match res.code() {
Some(_) => code = 0xff,
None => code = 0xfe,
}
} else {
code = 0xfc;
}
}
break;
}
}
}
}
}
}

std::process::exit(code);
}

fn main() {
Expand Down
29 changes: 7 additions & 22 deletions src/tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,8 @@ use std::fs::File;
use std::os::unix::io::FromRawFd;
use std::os::unix::process::CommandExt;
use std::ptr;
use std::process::{Command, Stdio};
use libc::{self, winsize, c_int, WNOHANG, SIGCHLD, TIOCSCTTY};

extern "C" fn sigchld(_a: c_int) {
let mut status: c_int = 0;
unsafe {
loop {
let p = libc::waitpid(-1, &mut status, WNOHANG);
if p <= 0 {
// die!("Waiting for pid {} failed: {}\n", PID, errno());
break;
}
}
}
}
use std::process::{Command, Stdio, Child};
use libc::{self, winsize, c_int, TIOCSCTTY};

/// Get the current value of errno
fn errno() -> c_int {
Expand Down Expand Up @@ -113,6 +100,8 @@ pub fn new(command: &Vec<String>, (rows, cols): (u16, u16)) -> Result<Pty, Parse
ws_ypixel: 0,
};

unsafe {libc::signal(libc::SIGPIPE, libc::SIG_IGN); };

let (master, slave) = openpty(win.ws_row as _, win.ws_col as _);

let mut builder = Command::new(&command[0]);
Expand Down Expand Up @@ -157,22 +146,18 @@ pub fn new(command: &Vec<String>, (rows, cols): (u16, u16)) -> Result<Pty, Parse
});

match builder.spawn() {
Ok(_) => {
unsafe {
// Handle SIGCHLD
libc::signal(SIGCHLD, sigchld as _);
}
Ok (Pty { fd: master })
Ok(child) => {
Ok (Pty { fd: master, child })
},
Err(err) => {
die!("Command::spawn() failed: {}", err);
}
}
}

#[derive(Copy, Clone)]
pub struct Pty {
fd: c_int,
pub child: Child,
}

impl Pty {
Expand Down

0 comments on commit 6fad33f

Please sign in to comment.