use std::process;
use std::thread;
use std::time::Duration;
use nix::sys::signal::Signal;
use nix::sys::signal::{self};
use nix::unistd::Pid;
use ptools::model::stat::ProcState;
fn verify_stopped(pid: u64) -> bool {
let mut backoff = Duration::from_millis(10);
let cap = Duration::from_millis(100);
let mut warned_d = false;
loop {
let handle = ptools::proc::ProcHandle::from_pid(pid);
match handle.state() {
Ok(ProcState::Stopped) => return true,
Ok(ProcState::Tracing) => {
eprintln!("pstop: process {pid} is stopped under a debugger, not by us");
return false;
}
Err(_) => {
eprintln!("pstop: process {pid} has exited");
return false;
}
Ok(ProcState::Waiting) => {
if !warned_d {
eprintln!(
"pstop: process {pid} is in uninterruptible sleep; \
SIGSTOP is pending but may take time to take effect"
);
warned_d = true;
}
thread::sleep(backoff);
backoff = (backoff * 2).min(cap);
}
Ok(_) => {
thread::sleep(backoff);
backoff = (backoff * 2).min(cap);
}
}
}
}
fn stop_process(pid: u64) -> bool {
let nix_pid = Pid::from_raw(pid as i32);
if let Err(e) = signal::kill(nix_pid, Signal::SIGSTOP) {
eprintln!("pstop: cannot stop {pid}: {e}");
return false;
}
verify_stopped(pid)
}
struct Args {
pid: Vec<u64>,
}
fn print_usage() {
eprintln!("Usage: pstop PID...");
eprintln!("Stop processes with SIGSTOP. A /proc/pid path may also be used.");
eprintln!();
eprintln!("Options:");
eprintln!(" -h, --help Print help");
eprintln!(" -V, --version Print version");
}
fn parse_args() -> Args {
use lexopt::prelude::*;
let mut args = Args { pid: Vec::new() };
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next().unwrap_or_else(|e| {
eprintln!("pstop: {e}");
process::exit(2);
}) {
match arg {
Short('h') | Long("help") => {
print_usage();
process::exit(0);
}
Short('V') | Long("version") => {
println!("pstop {}", env!("CARGO_PKG_VERSION"));
process::exit(0);
}
Value(val) => {
let s = val.to_string_lossy();
match ptools::proc::parse_pid_arg(&s) {
Ok(ptools::proc::PidArg::Pid(pid)) => args.pid.push(pid),
Ok(ptools::proc::PidArg::Skip) => {}
Err(msg) => {
eprintln!("pstop: {msg}");
process::exit(2);
}
}
}
_ => {
eprintln!("pstop: unexpected argument: {arg:?}");
process::exit(2);
}
}
}
if args.pid.is_empty() {
eprintln!("pstop: at least one PID required");
process::exit(2);
}
args
}
fn main() {
ptools::reset_sigpipe();
let args = parse_args();
let mut failed = false;
for &pid in &args.pid {
if !stop_process(pid) {
failed = true;
}
}
if failed {
process::exit(1);
}
}