Skip to content

Commit beb91fc

Browse files
authored
Remove htmlpty and fix up inapty (#128)
1 parent 9e09bf9 commit beb91fc

4 files changed

Lines changed: 54 additions & 168 deletions

File tree

‎htmlpty/Cargo.lock‎

Lines changed: 0 additions & 98 deletions
This file was deleted.

‎htmlpty/Cargo.toml‎

Lines changed: 0 additions & 10 deletions
This file was deleted.

‎htmlpty/src/main.rs‎

Lines changed: 0 additions & 53 deletions
This file was deleted.

‎inapty/src/main.rs‎

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fs::File;
2+
use std::io::{ErrorKind, Read, Write};
23
use std::os::fd::FromRawFd;
34
use std::os::unix::process::CommandExt;
45
use std::process::Command;
@@ -20,19 +21,65 @@ fn main() {
2021
if pid == 0 {
2122
// We are the child. Spawn the subprocess based off our arguments.
2223
let mut args = std::env::args_os().skip(1);
23-
Command::new(args.next().unwrap()).args(args).exec();
24-
unreachable!("exec should not return");
24+
let program = args.next().unwrap();
25+
// exec only returns if it failed, in which case it hands back the reason why.
26+
let err = Command::new(&program).args(args).exec();
27+
eprintln!("inapty: failed to exec {}: {}", program.display(), err);
28+
// Same codes a shell reports for a command it could not run.
29+
std::process::exit(if err.kind() == ErrorKind::NotFound {
30+
127
31+
} else {
32+
126
33+
});
2534
} else {
2635
// We are the originating process. Copy from the pty to output.
2736
// SAFETY: master is open and valid, it was just opened by forkpty
2837
let mut pty = unsafe { File::from_raw_fd(pty) };
29-
// Copy all the output from the child pty to our stdout
30-
while std::io::copy(&mut pty, &mut std::io::stdout()).is_ok() {}
38+
39+
let mut buf = [0u8; 8192];
40+
let mut stdout = std::io::stdout().lock();
41+
// Once our own output is gone there is nothing useful left to do with the child's bytes,
42+
// but we must keep draining the pty anyway. If we stop reading, the child blocks forever
43+
// writing into a full pty buffer and the waitpid below never returns.
44+
let mut stdout_broken = false;
45+
loop {
46+
let n = match pty.read(&mut buf) {
47+
// The child closed the pty. On Linux this is reported as EIO below instead, but
48+
// handle a real EOF too so that we cannot spin here.
49+
Ok(0) => break,
50+
Ok(n) => n,
51+
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
52+
// Any other error means the pty is done; the child has exited or is about to.
53+
Err(_) => break,
54+
};
55+
if !stdout_broken && stdout.write_all(&buf[..n]).is_err() {
56+
stdout_broken = true;
57+
}
58+
}
59+
let _ = stdout.flush();
60+
3161
// Exit according to our child's status
62+
let mut status = 0;
3263
// SAFETY: No preconditions
33-
let status = unsafe {
34-
let mut status = 0;
35-
libc::waitpid(pid, &mut status, 0);
64+
while unsafe { libc::waitpid(pid, &mut status, 0) } == -1 {
65+
let err = std::io::Error::last_os_error();
66+
// A signal can interrupt the wait before the child is reaped; retrying is the only
67+
// way to still learn its status.
68+
if err.kind() == ErrorKind::Interrupted {
69+
continue;
70+
}
71+
// Anything else means we will never learn it. `status` is still untouched here, so
72+
// using it would report a clean exit for a child we know nothing about.
73+
eprintln!("inapty: waitpid failed: {err}");
74+
std::process::exit(125);
75+
}
76+
77+
let status = if libc::WIFSIGNALED(status) {
78+
// WEXITSTATUS is only meaningful for a child that exited normally; for one killed
79+
// by a signal it reports 0, which would make a segfault or an OOM kill look like
80+
// a clean run. Report it the way a shell does instead.
81+
128 + libc::WTERMSIG(status)
82+
} else {
3683
libc::WEXITSTATUS(status)
3784
};
3885
std::process::exit(status);

0 commit comments

Comments
 (0)