Skip to content

Commit

Permalink
Merge pull request #22 from NachiketNamjoshi/feature/support-other-si…
Browse files Browse the repository at this point in the history
…gnals

Support for remaining signals
  • Loading branch information
jkfran authored May 15, 2024
2 parents f0dbc2c + ceb53b9 commit 6f9d6d5
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 55 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,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
6 changes: 2 additions & 4 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use crate::KillPortSignalOptions;

use bollard::container::{KillContainerOptions, ListContainersOptions};
use bollard::Docker;
use log::{debug, info};
use log::{debug, info, warn};
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
use procfs::process::FDTarget;
Expand Down Expand Up @@ -197,7 +195,7 @@ impl Killable for DockerContainer {
/// returned if the operation failed or the platform is unsupported.
pub fn kill_processes_by_port(
port: u16,
signal: KillPortSignalOptions,
signal: Signal,
) -> Result<(bool, String), Error> {
let mut killed_any = false;
let mut killable_type = String::new();
Expand Down
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 @@ -45,7 +43,7 @@ fn collect_proc() -> Vec<TaskAllInfo> {
/// element is a string indicating the type of the killed entity.
pub fn kill_processes_by_port(
port: u16,
signal: KillPortSignalOptions,
signal: Signal,
) -> Result<(bool, String), io::Error> {
let process_infos = collect_proc();
let mut killed = false;
Expand Down Expand Up @@ -99,11 +97,7 @@ pub fn kill_processes_by_port(
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,6 +1,6 @@
use assert_cmd::Command;
use std::fs::File;
use std::io::Write;
use std::{fs::File, path};
use std::{thread, time};
use tempfile::tempdir;

Expand All @@ -9,62 +9,174 @@ 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");
}

0 comments on commit 6f9d6d5

Please sign in to comment.