Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for remaining signals #22

Merged
merged 5 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,41 @@ Kill processes with specified signal:
killport -s sigkill 8080
```

Supported Signals:
- sighup
- sigint
- sigquit
- sigill
- sigtrap
- sigabrt
- sigbus
- sigfpe
- sigkill
- sigusr1
- sigsegv
- sigusr2
- sigpipe
- sigalrm
- sigterm
- sigstkflt
- sigchld
- sigcont
- sigstop
- sigtstp
- sigttin
- sigttou
- sigurg
- sigxcpu
- sigxfsz
- sigvtalrm
- sigprof
- sigwinch
- sigio
- sigpwr
- sigsys
- sigemt
- siginfo

### Flags

-s, --signal
Expand Down
22 changes: 5 additions & 17 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use crate::KillPortSignalOptions;

use log::{debug, info, warn};
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
Expand All @@ -18,7 +16,7 @@ use std::path::Path;
///
/// * `port` - A u16 value representing the port number.
/// * `signal` - A enum value representing the signal type.
pub fn kill_processes_by_port(port: u16, signal: KillPortSignalOptions) -> Result<bool, Error> {
pub fn kill_processes_by_port(port: u16, signal: Signal) -> Result<bool, Error> {
let mut killed_any = false;

let target_inodes = find_target_inodes(port);
Expand Down Expand Up @@ -105,10 +103,7 @@ fn find_target_inodes(port: u16) -> Vec<u64> {
///
/// * `target_inode` - A u64 value representing the target inode.
/// * `signal` - A enum value representing the signal type.
fn kill_processes_by_inode(
target_inode: u64,
signal: KillPortSignalOptions,
) -> Result<bool, Error> {
fn kill_processes_by_inode(target_inode: u64, signal: Signal) -> Result<bool, Error> {
let processes = procfs::process::all_processes().unwrap();
let mut killed_any = false;

Expand Down Expand Up @@ -162,10 +157,7 @@ fn kill_processes_by_inode(
///
/// * `pid` - An i32 value representing the process ID.
/// * `signal` - A enum value representing the signal type.
fn kill_process_and_children(
pid: i32,
signal: KillPortSignalOptions,
) -> Result<(), std::io::Error> {
fn kill_process_and_children(pid: i32, signal: Signal) -> Result<(), std::io::Error> {
let mut children_pids = Vec::new();
collect_child_pids(pid, &mut children_pids)?;

Expand Down Expand Up @@ -206,13 +198,9 @@ fn collect_child_pids(pid: i32, child_pids: &mut Vec<i32>) -> Result<(), std::io
///
/// * `pid` - An i32 value representing the process ID.
/// * `signal` - A enum value representing the signal type.
fn kill_process(pid: i32, signal: KillPortSignalOptions) -> Result<(), std::io::Error> {
fn kill_process(pid: i32, signal: Signal) -> Result<(), std::io::Error> {
info!("Killing process with PID {}", pid);
let pid = Pid::from_raw(pid);

let system_signal = match signal {
KillPortSignalOptions::SIGKILL => Signal::SIGKILL,
KillPortSignalOptions::SIGTERM => Signal::SIGTERM,
};
kill(pid, system_signal).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
kill(pid, signal).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
}
10 changes: 2 additions & 8 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use crate::KillPortSignalOptions;

use libproc::libproc::file_info::pidfdinfo;
use libproc::libproc::file_info::{ListFDs, ProcFDType};
use libproc::libproc::net_info::{SocketFDInfo, SocketInfoKind};
Expand Down Expand Up @@ -41,7 +39,7 @@ fn collect_proc() -> Vec<TaskAllInfo> {
/// # Returns
///
/// A `Result` containing a boolean value. If true, at least one process was killed; otherwise, false.
pub fn kill_processes_by_port(port: u16, signal: KillPortSignalOptions) -> Result<bool, io::Error> {
pub fn kill_processes_by_port(port: u16, signal: Signal) -> Result<bool, io::Error> {
let process_infos = collect_proc();
let mut killed = false;

Expand Down Expand Up @@ -93,11 +91,7 @@ pub fn kill_processes_by_port(port: u16, signal: KillPortSignalOptions) -> Resul
warn!("Warning: Found Docker. You might need to stop the container manually.");
} else {
info!("Killing process with PID {}", pid);
let system_signal = match signal {
KillPortSignalOptions::SIGKILL => Signal::SIGKILL,
KillPortSignalOptions::SIGTERM => Signal::SIGTERM,
};
match signal::kill(pid, system_signal) {
match signal::kill(pid, signal) {
Ok(_) => {
killed = true;
}
Expand Down
30 changes: 18 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,12 @@ use macos::kill_processes_by_port;
#[cfg(target_os = "windows")]
use windows::kill_processes_by_port;

use clap::{Parser, ValueEnum};
use clap::Parser;
use clap_verbosity_flag::{Verbosity, WarnLevel};
use log::error;
use std::process::exit;
use nix::sys::signal::Signal;
use std::{process::exit, str::FromStr};

/// The `KillPortSignalOptions` enum is used to specify signal types on the command-line arguments.
#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum KillPortSignalOptions {
SIGKILL,
SIGTERM,
}

/// The `KillPortArgs` struct is used to parse command-line arguments for the
/// `killport` utility.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
Expand All @@ -49,15 +42,28 @@ struct KillPortArgs {
short = 's',
name = "SIG",
help = "SIG is a signal name",
default_value = "sigterm"
default_value = "sigterm",
value_parser = parse_signal
)]
signal: KillPortSignalOptions,
signal: Signal,

/// A verbosity flag to control the level of logging output.
#[command(flatten)]
verbose: Verbosity<WarnLevel>,
}

fn parse_signal(arg: &str) -> Result<Signal, std::io::Error> {
let str_arg = arg.parse::<String>();
match str_arg {
Ok(str_arg) => {
let signal_str = str_arg.to_uppercase();
let signal = Signal::from_str(signal_str.as_str())?;
return Ok(signal);
}
Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
}
}

/// The `main` function is the entry point of the `killport` utility.
///
/// It parses command-line arguments, sets up the logging environment, and
Expand Down
174 changes: 143 additions & 31 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -1,69 +1,181 @@
use assert_cmd::Command;
use std::fs::File;
use std::io::Write;
use std::{fs::File, path};
use tempfile::tempdir;

#[test]
fn test_killport() {
// Create a temporary directory for testing.
let tempdir = tempdir().expect("Failed to create temporary directory");
let tempdir_path = tempdir.path();
generate_temp_process(tempdir_path);

// Create a mock process that listens on a port.
let mock_process = format!(
r#"
use std::net::TcpListener;
fn main() {{
let _listener = TcpListener::bind("127.0.0.1:8080").unwrap();
loop {{}}
}}
"#
);

let mock_process_path = tempdir_path.join("mock_process.rs");
let mut file = File::create(&mock_process_path).expect("Failed to create mock_process.rs file");
file.write_all(mock_process.as_bytes())
.expect("Failed to write mock_process.rs content");

// Compile and run the mock process in the background.
let status = std::process::Command::new("rustc")
.arg(&mock_process_path)
.arg("--out-dir")
.arg(&tempdir_path)
.status()
.expect("Failed to compile mock_process.rs");
// Test killport execution without options
test_killport_noargs(tempdir_path);

assert!(status.success(), "Mock process compilation failed");
// Test killport execution with -s option
// Hangup
test_killport_signal_arg(tempdir_path, "sighup");
// Interrupt
test_killport_signal_arg(tempdir_path, "sigint");
// Quit
test_killport_signal_arg(tempdir_path, "sigquit");
// Illegal instruction (not reset when caught)
test_killport_signal_arg(tempdir_path, "sigill");
// Trace trap (not reset when caught)
test_killport_signal_arg(tempdir_path, "sigtrap");
// Abort
test_killport_signal_arg(tempdir_path, "sigabrt");
// Bus error
test_killport_signal_arg(tempdir_path, "sigbus");
// Floating point exception
test_killport_signal_arg(tempdir_path, "sigfpe");
// Kill (cannot be caught or ignored)
test_killport_signal_arg(tempdir_path, "sigkill");
// User defined signal 1
test_killport_signal_arg(tempdir_path, "sigusr1");
// Segmentation violation
test_killport_signal_arg(tempdir_path, "sigsegv");
// User defined signal 2
test_killport_signal_arg(tempdir_path, "sigusr2");
// Write on a pipe with no one to read it
test_killport_signal_arg(tempdir_path, "sigpipe");
// Alarm clock
test_killport_signal_arg(tempdir_path, "sigalrm");
// Software termination signal from kill
test_killport_signal_arg(tempdir_path, "sigterm");
// Stack fault (obsolete)
#[cfg(all(
any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux"
),
not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))
))]
test_killport_signal_arg(tempdir_path, "sigstkflt");
// To parent on child stop or exit
test_killport_signal_arg(tempdir_path, "sigchld");
// Continue a stopped process
test_killport_signal_arg(tempdir_path, "sigcont");
// Sendable stop signal not from tty
test_killport_signal_arg(tempdir_path, "sigstop");
// Stop signal from tty
test_killport_signal_arg(tempdir_path, "sigtstp");
// To readers pgrp upon background tty read
test_killport_signal_arg(tempdir_path, "sigttin");
// Like TTIN if (tp->t_local&LTOSTOP)
test_killport_signal_arg(tempdir_path, "sigttou");
// Urgent condition on IO channel
test_killport_signal_arg(tempdir_path, "sigurg");
// Exceeded CPU time limit
test_killport_signal_arg(tempdir_path, "sigxcpu");
// Exceeded file size limit
test_killport_signal_arg(tempdir_path, "sigxfsz");
// Virtual time alarm
test_killport_signal_arg(tempdir_path, "sigvtalrm");
// Profiling time alarm
test_killport_signal_arg(tempdir_path, "sigprof");
// Window size changes
test_killport_signal_arg(tempdir_path, "sigwinch");
// Input/output possible signal
#[cfg(not(target_os = "haiku"))]
#[cfg_attr(docsrs, doc(cfg(all())))]
test_killport_signal_arg(tempdir_path, "sigio");
#[cfg(any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux"
))]
#[cfg_attr(docsrs, doc(cfg(all())))]
// Power failure imminent.
test_killport_signal_arg(tempdir_path, "sigpwr");
// Bad system call
test_killport_signal_arg(tempdir_path, "sigsys");
#[cfg(not(any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux",
target_os = "redox",
target_os = "haiku"
)))]
#[cfg_attr(docsrs, doc(cfg(all())))]
// Emulator trap
test_killport_signal_arg(tempdir_path, "sigemt");
#[cfg(not(any(
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "linux",
target_os = "redox",
target_os = "haiku"
)))]
#[cfg_attr(docsrs, doc(cfg(all())))]
// Information request
test_killport_signal_arg(tempdir_path, "siginfo");
}

// Test killport execution without options
fn test_killport_signal_arg(tempdir_path: &path::Path, signal: &str) {
let mut mock_process = std::process::Command::new(tempdir_path.join("mock_process"))
.spawn()
.expect("Failed to run the mock process");

// Test killport command
// Test killport command with specifying a signal name
let mut cmd = Command::cargo_bin("killport").expect("Failed to find killport binary");
cmd.arg("8080")
.arg("-s")
.arg(signal)
.assert()
.success()
.stdout("Successfully killed process listening on port 8080\n");

// Cleanup: Terminate the mock process (if still running).
let _ = mock_process.kill();
}

// Test killport execution with -s option
fn test_killport_noargs(tempdir_path: &path::Path) {
let mut mock_process = std::process::Command::new(tempdir_path.join("mock_process"))
.spawn()
.expect("Failed to run the mock process");

// Test killport command with specifying a signal name
// Test killport command
let mut cmd = Command::cargo_bin("killport").expect("Failed to find killport binary");
cmd.arg("8080")
.arg("-s")
.arg("sigterm")
.assert()
.success()
.stdout("Successfully killed process listening on port 8080\n");

// Cleanup: Terminate the mock process (if still running).
let _ = mock_process.kill();
}

fn generate_temp_process(tempdir_path: &path::Path) {
// Create a mock process that listens on a port.
let mock_process = format!(
r#"
use std::net::TcpListener;
fn main() {{
let _listener = TcpListener::bind("127.0.0.1:8080").unwrap();
loop {{}}
}}
"#
);

let mock_process_path = tempdir_path.join("mock_process.rs");
let mut file = File::create(&mock_process_path).expect("Failed to create mock_process.rs file");
file.write_all(mock_process.as_bytes())
.expect("Failed to write mock_process.rs content");

// Compile and run the mock process in the background.
let status = std::process::Command::new("rustc")
.arg(&mock_process_path)
.arg("--out-dir")
.arg(&tempdir_path)
.status()
.expect("Failed to compile mock_process.rs");

assert!(status.success(), "Mock process compilation failed");
}