Send signals correctly on linux

This commit is contained in:
Arne Beer 2020-10-03 18:24:17 +02:00
parent 5d37cf3e1d
commit 78ad33a9f5
5 changed files with 141 additions and 51 deletions

85
Cargo.lock generated
View file

@ -66,7 +66,7 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801"
dependencies = [
"async-task 4.0.2",
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
@ -76,9 +76,9 @@ dependencies = [
[[package]]
name = "async-global-executor"
version = "1.2.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5586e693d02f9b439742e9d5d68bd64d923c6861954f7d78f91001a0e152d589"
checksum = "fefeb39da249f4c33af940b779a56723ce45809ef5c54dad84bb538d4ffb6d9e"
dependencies = [
"async-executor",
"async-io",
@ -89,13 +89,14 @@ dependencies = [
[[package]]
name = "async-io"
version = "1.1.3"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9951f92a2b4f7793f8fc06a80bdb89b62c618c993497d4606474fb8c34941b5"
checksum = "e8cf20dd2c6d20ef09876e189b44f213e66e846972cb303eef28d9dfbe790928"
dependencies = [
"concurrent-queue",
"fastrand",
"futures-lite",
"libc",
"log",
"nb-connect",
"once_cell",
@ -103,6 +104,7 @@ dependencies = [
"polling",
"vec-arena",
"waker-fn",
"winapi",
]
[[package]]
@ -116,15 +118,14 @@ dependencies = [
[[package]]
name = "async-std"
version = "1.6.4"
version = "1.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c92085acfce8b32e5b261d0b59b8f3309aee69fea421ea3f271f8b93225754f"
checksum = "a9fa76751505e8df1c7a77762f60486f60c71bbd9b8557f4da6ad47d083732ed"
dependencies = [
"async-attributes",
"async-global-executor",
"async-io",
"async-mutex",
"async-task 3.0.0",
"blocking",
"crossbeam-utils",
"futures-channel",
@ -143,12 +144,6 @@ dependencies = [
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3"
[[package]]
name = "async-task"
version = "4.0.2"
@ -305,9 +300,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "chrono"
version = "0.4.18"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d021fddb7bd3e734370acfa4a83f34095571d8570c039f1420d77540f68d5772"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
@ -457,9 +452,9 @@ dependencies = [
[[package]]
name = "derive_more"
version = "0.99.10"
version = "0.99.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dcfabdab475c16a93d669dddfc393027803e347d09663f524447f642fbb84ba"
checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
dependencies = [
"proc-macro2",
"quote",
@ -515,9 +510,12 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fastrand"
version = "1.3.5"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c85295147490b8fcf2ea3d104080a105a8b2c63f9c319e82c02d8e952388919"
checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3"
dependencies = [
"instant",
]
[[package]]
name = "fnv"
@ -638,6 +636,15 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]]
name = "instant"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "0.4.6"
@ -683,9 +690,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.77"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98"
[[package]]
name = "libflate"
@ -752,11 +759,10 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "mio"
version = "0.7.0"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9971bc8349a361217a8f2a41f5d011274686bd4436465ba51730921039d7fb"
checksum = "6d5fc35678fa91ff960494e4bd72a0e1f8e72e035460887a2005de51915993cd"
dependencies = [
"lazy_static",
"libc",
"log",
"miow",
@ -776,9 +782,9 @@ dependencies = [
[[package]]
name = "nb-connect"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e847c76b390f44529c2071ef06d0b52fbb4bdb04cc8987a5cfa63954c000abca"
checksum = "701f47aeb98466d0a7fea67e2c2f667c33efa1f2e4fd7f76743aac1153196f72"
dependencies = [
"libc",
"winapi",
@ -945,9 +951,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.1.8"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71f349a4f0e70676ffb2dbafe16d0c992382d02f0a952e3ddf584fc289dac6b3"
checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95"
[[package]]
name = "pin-utils"
@ -963,14 +969,14 @@ checksum = "feb3b2b1033b8a60b4da6ee470325f887758c95d5320f52f9ce0df055a55940e"
[[package]]
name = "polling"
version = "1.1.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0720e0b9ea9d52451cf29d3413ba8a9303f8815d9d9653ef70e03ff73e65566"
checksum = "7215a098a80ab8ebd6349db593dc5faf741781bad0c4b7c5701fea6af548d52c"
dependencies = [
"cfg-if",
"libc",
"log",
"wepoll-sys-stjepang",
"wepoll-sys",
"winapi",
]
@ -1006,9 +1012,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.23"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ef7cd2518ead700af67bf9d1a658d90b6037d77110fd9c0445429d0ba1c6c9"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
@ -1021,7 +1027,6 @@ checksum = "c4a336c8310f4955f343935b9c11a30254d1ad8fad98ec257a4407a061a6fd49"
dependencies = [
"bitflags",
"byteorder",
"chrono",
"hex",
"lazy_static",
"libc",
@ -1273,9 +1278,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.57"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4"
dependencies = [
"itoa",
"ryu",
@ -1660,10 +1665,10 @@ dependencies = [
]
[[package]]
name = "wepoll-sys-stjepang"
version = "1.0.8"
name = "wepoll-sys"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fdfbb03f290ca0b27922e8d48a0997b4ceea12df33269b9f75e713311eb178d"
checksum = "142bc2cba3fe88be1a8fcb55c727fa4cd5b0cf2d7438722792e22f26f04bc1e0"
dependencies = [
"cc",
]

View file

@ -59,7 +59,7 @@ users = "^0.10"
nix = "^0.18"
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
procfs = "^0.8"
procfs = { version = "0.8", default-features = false }
[target.'cfg(target_os = "macos")'.dependencies]
psutil = "^3"

View file

@ -1,3 +1,6 @@
use ::std::process::Child;
use ::anyhow::Result;
use ::log::{debug, warn};
use ::nix::{
sys::signal::{self, Signal},
@ -7,23 +10,105 @@ use procfs::process::{all_processes, Process};
use crate::task_handler::ProcessAction;
/// Send a signal to one of Pueue's child process handles.
/// We need a special since there exists some inconsistent behavior.
///
/// In some circumstances and environments `sh -c $command` doesn't spawn a shell,
/// but rather spawns the `$command` directly.
///
/// This makes things a lot more complicated, since we need to either send signals
/// to the root process directly OR to all it's child processes.
/// This also affects the `--children` flag on all commands. We then have to either send the signal
/// to all direct children or to all of the childrens' children.
pub fn send_signal_to_child(
child: &Child,
action: &ProcessAction,
send_to_children: bool,
) -> Result<bool> {
let signal = get_signal_from_action(action);
let pid = child.id() as i32;
// Get the /proc representation of the child, so we can do some checks
let process = if let Ok(process) = Process::new(pid) {
process
} else {
// Process might have just gone away
return Ok(false);
};
// Get the root command and, so we check whether it's actually a shell with `sh -c`.
let mut cmdline = if let Ok(cmdline) = process.cmdline() {
cmdline
} else {
// Process might have just gone away
return Ok(false);
};
// Now we know whether this is a directly spawned process or a process wrapped by a shell.
let is_shell = is_cmdline_shell(&mut cmdline);
if is_shell {
// If it's a shell, we have to send the signal to the actual shell and to all it's children.
// There might be multiple children, for instance, when users use the `&` operator.
// If the `send_to_children` flag is given, the
// Send the signal to the shell, don't propagate to it's children yet.
send_signal_to_process(pid, action, false)?;
// Now send the signal to the shells child processes and their respective
// children if the user wants to do so.
let shell_children = get_child_processes(pid).unwrap();
for shell_child in shell_children {
send_signal_to_process(shell_child.pid(), action, send_to_children)?;
}
} else {
// If it isn't a shell, send the signal directly to the process.
// Handle children normally.
send_signal_to_process(pid, action, send_to_children)?;
}
signal::kill(Pid::from_raw(pid), signal)?;
Ok(true)
}
/// Check whether a process's commandline string is actually a shell or not
pub fn is_cmdline_shell(cmdline: &mut Vec<String>) -> bool {
if cmdline.len() < 3 {
return false;
}
if cmdline.remove(0) != "sh" {
return false;
}
if cmdline.remove(0) != "-c" {
return false;
}
true
}
/// Send a signal to a unix process.
pub fn send_signal(pid: u32, action: &ProcessAction, children: bool) -> Result<bool, nix::Error> {
pub fn send_signal_to_process(
pid: i32,
action: &ProcessAction,
send_to_children: bool,
) -> Result<bool, nix::Error> {
let signal = get_signal_from_action(action);
debug!("Sending signal {} to {}", signal, pid);
// Send the signal to all children, if that's what the user wants.
if children {
send_signal_to_children(pid as i32, action);
if send_to_children {
send_signal_to_children(pid, action);
}
signal::kill(Pid::from_raw(pid as i32), signal)?;
signal::kill(Pid::from_raw(pid), signal)?;
Ok(true)
}
/// A small helper that sends a signal to all children of a specific process by id.
pub fn send_signal_to_children(pid: i32, action: &ProcessAction) {
send_signal_to_processes(get_children(pid).unwrap(), action);
send_signal_to_processes(get_child_processes(pid).unwrap(), action);
}
fn get_signal_from_action(action: &ProcessAction) -> Signal {
@ -35,7 +120,7 @@ fn get_signal_from_action(action: &ProcessAction) -> Signal {
}
/// Get all children of a specific process
pub fn get_children(pid: i32) -> Option<Vec<Process>> {
pub fn get_child_processes(pid: i32) -> Option<Vec<Process>> {
Some(
all_processes()
.unwrap()

View file

@ -3,7 +3,7 @@ use ::std::sync::mpsc::Sender;
use ::anyhow::{bail, Result};
use ::async_std::net::{TcpListener, TcpStream};
use ::async_std::task;
use ::log::{info, warn};
use ::log::{debug, info, warn};
use ::pueue::message::*;
use ::pueue::protocol::*;
@ -75,7 +75,7 @@ async fn handle_incoming(
loop {
// Receive the actual instruction from the client
let message = receive_message(&mut socket).await?;
info!("Received instruction: {:?}", message);
debug!("Received instruction: {:?}", message);
let response = if let Message::StreamRequest(message) = message {
// The client requested the output of a task.

View file

@ -469,7 +469,7 @@ impl TaskHandler {
match self.children.get(&id) {
Some(child) => {
debug!("Executing action {:?} to {}", action, id);
send_signal(child.id(), &action, children)?;
send_signal_to_child(child, &action, children)?;
Ok(true)
}
@ -684,7 +684,7 @@ impl TaskHandler {
// we get the chance to kill the parent.
let mut children = None;
if kill_children {
children = get_children(child.id() as i32);
children = get_child_processes(child.id() as i32);
}
match child.kill() {