flesh/snippets/shell-sigttou-minimal-reproduction.rs

82 lines
2.3 KiB
Rust
Raw Normal View History

extern crate signal_hook;
extern crate nix;
use std::process::{Command, Stdio};
use signal_hook::{consts, iterator::Signals};
use std::os::unix::process::CommandExt;
use std::thread::spawn;
use nix::{unistd, unistd::Pid, sys::termios::{tcgetattr, tcsetattr, SetArg}};
fn main() {
// setup signal handlers
let signals = Signals::new([
/* glibc says to block SIGCHLD but since I already have a backgroun thread
* it seems like a perfect way to know when background jobs have stopped...
*/
consts::SIGINT, consts::SIGQUIT, //consts::SIGCHLD,
consts::SIGTSTP, consts::SIGTTOU, consts::SIGTTIN,
]);
if let Err(e) = signals {
println!("couldn't spawn signal handler: {}", e);
return
}
spawn(move || {
for sig in signals.unwrap().forever() {
println!("Received signal {:?}", sig);
}
});
let parent_pid = unistd::getpid();
let attr = tcgetattr(0).unwrap();
let term_owner = unistd::tcgetpgrp(0).unwrap();
let parent_pgid = unistd::getpgid(Some(parent_pid)).unwrap();
if parent_pgid != term_owner {
nix::sys::signal::kill(
term_owner,
nix::sys::signal::Signal::SIGTTIN,
).expect("cant seize terminal");
}
if parent_pid != parent_pgid {
unistd::setpgid(parent_pid, parent_pid).unwrap()
}
unistd::tcsetpgrp(0, parent_pid).unwrap();
// kick off child process in its own PG
let mut chldproc = Command::new("/bin/bash")
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.process_group(0)
.spawn()
.unwrap();
let pid = chldproc.id();
unistd::setpgid(
Pid::from_raw(pid as i32),
Pid::from_raw(pid as i32),
).unwrap();
// give child terminal
unistd::tcsetpgrp(
0, Pid::from_raw((chldproc.id() as i32).into()),
).unwrap();
// cant use i.wait() because it closes stdin
loop {
if let Ok(maybe) = chldproc.try_wait() {
if let Some(_) = maybe {
unistd::tcsetpgrp(0, parent_pid).unwrap();
tcsetattr(0, SetArg::TCSADRAIN, &attr).ok();
break;
}
} else {
panic!()
}
}
println!("finished successfully!");
}