Skip to content

Commit

Permalink
fix: update windows for new changes
Browse files Browse the repository at this point in the history
Looks like a fair bit of unix-specific code has gotten tangled up in the main files

Have separated the unix specific process logic into the unix.rs module

Created a wrapper for the Signal type since its not available on non unix platforms, non unix platforms just handle it as a uppercase string (No special handling at this stage, but could be implemented at a later stage if we make our own signal enum that maps to the unix enum)

Added a new WindowsProcess type for windows processes,

Wired up the code to collect and kill in separate places

Added process name lookup for windows processes (To match the other impls) if a process has a bad name or fails to obtain its name None is used instead

Docker container killing is working, however it doesn't work properly if you don't explicitly specify the container mode (I think its trying to kill the wrong one causing it to fail?)

Made the type for a killable into an enum as its possible values are constant?

Renamed the killport_tests.rs to killport_unix_tests.rs and gated it to unix only since its using unix specific features, Windows tests will need to be added

Added a build and release target for windows to the github workflows
  • Loading branch information
jacobtread committed May 21, 2024
1 parent 4997258 commit 0624cb5
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 152 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
- s390x-unknown-linux-gnu
- aarch64-apple-darwin
- x86_64-apple-darwin
- x86_64-pc-windows-gnu
runs-on: ${{ (matrix.target == 'aarch64-apple-darwin' || matrix.target == 'x86_64-apple-darwin') && 'macos-latest' || 'ubuntu-latest' }}
steps:
- name: Checkout repository
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
- s390x-unknown-linux-gnu
- aarch64-apple-darwin
- x86_64-apple-darwin
# - x86_64-pc-windows-gnu
- x86_64-pc-windows-gnu
runs-on: ${{ (matrix.target == 'aarch64-apple-darwin' || matrix.target == 'x86_64-apple-darwin') && 'macos-latest' || 'ubuntu-latest' }}
steps:
- name: Checkout repository
Expand Down
18 changes: 5 additions & 13 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use clap::{Parser, ValueEnum};
use clap_verbosity_flag::{Verbosity, WarnLevel};
use core::fmt;
use nix::sys::signal::Signal;
use std::str::FromStr;

use crate::signal::KillportSignal;

/// Modes of operation for killport.
#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)]
Expand Down Expand Up @@ -67,7 +67,7 @@ pub struct KillPortArgs {
default_value = "sigkill",
value_parser = parse_signal
)]
pub signal: Signal,
pub signal: KillportSignal,

/// A verbosity flag to control the level of logging output.
#[command(flatten)]
Expand All @@ -81,14 +81,6 @@ pub struct KillPortArgs {
pub dry_run: bool,
}

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)),
}
fn parse_signal(arg: &str) -> Result<KillportSignal, std::io::Error> {
arg.to_uppercase().parse()
}
10 changes: 3 additions & 7 deletions src/docker.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::signal::KillportSignal;
use bollard::container::{KillContainerOptions, ListContainersOptions};
use bollard::Docker;
use log::debug;
use nix::sys::signal::Signal;
use std::collections::HashMap;
use std::io::Error;
use tokio::runtime::Runtime;
Expand All @@ -17,7 +17,7 @@ impl DockerContainer {
///
/// * `name` - A container name.
/// * `signal` - A enum value representing the signal type.
pub fn kill_container(name: &String, signal: Signal) -> Result<(), Error> {
pub fn kill_container(name: &str, signal: KillportSignal) -> Result<(), Error> {
let rt = Runtime::new()?;
rt.block_on(async {
let docker = Docker::connect_with_socket_defaults()
Expand Down Expand Up @@ -63,11 +63,7 @@ impl DockerContainer {
.as_ref()?
.first()
.map(|name| DockerContainer {
name: if name.starts_with('/') {
name[1..].to_string()
} else {
name.clone()
},
name: name.strip_prefix('/').unwrap_or(name).to_string(),
})
})
.collect())
Expand Down
90 changes: 28 additions & 62 deletions src/killport.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,32 @@
use crate::cli::Mode;
use crate::docker::DockerContainer;
#[cfg(target_os = "linux")]
use crate::linux::find_target_processes;
#[cfg(target_os = "macos")]
use crate::macos::find_target_processes;
use log::info;
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
use std::io::Error;

#[derive(Debug)]
pub struct NativeProcess {
/// System native process ID.
pub pid: Pid,
pub name: String,
}
#[cfg(target_os = "windows")]
use crate::windows::find_target_processes;
use crate::{cli::Mode, signal::KillportSignal};
use std::{fmt::Display, io::Error};

/// Interface for killable targets such as native process and docker container.
pub trait Killable {
fn kill(&self, signal: Signal) -> Result<bool, Error>;
fn get_type(&self) -> String;
fn kill(&self, signal: KillportSignal) -> Result<bool, Error>;
fn get_type(&self) -> KillableType;
fn get_name(&self) -> String;
}

impl Killable for NativeProcess {
/// Entry point to kill the linux native process.
///
/// # Arguments
///
/// * `signal` - A enum value representing the signal type.
fn kill(&self, signal: Signal) -> Result<bool, Error> {
info!("Killing process '{}' with PID {}", self.name, self.pid);

kill(self.pid, signal).map(|_| true).map_err(|e| {
Error::new(
std::io::ErrorKind::Other,
format!(
"Failed to kill process '{}' with PID {}: {}",
self.name, self.pid, e
),
)
})
}

/// Returns the type of the killable target.
///
/// This method is used to identify the type of the target (either a native process or a Docker container)
/// that is being handled. This information can be useful for logging, error handling, or other needs
/// where type of the target is relevant.
///
/// # Returns
///
/// * `String` - A string that describes the type of the killable target. For a `NativeProcess` it will return "process",
/// and for a `DockerContainer` it will return "container".
fn get_type(&self) -> String {
"process".to_string()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KillableType {
Process,
Container,
}

fn get_name(&self) -> String {
self.name.to_string()
impl Display for KillableType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
KillableType::Process => "process",
KillableType::Container => "container",
})
}
}

Expand All @@ -68,10 +36,8 @@ impl Killable for DockerContainer {
/// # Arguments
///
/// * `signal` - A enum value representing the signal type.
fn kill(&self, signal: Signal) -> Result<bool, Error> {
if let Err(err) = Self::kill_container(&self.name, signal) {
return Err(err);
}
fn kill(&self, signal: KillportSignal) -> Result<bool, Error> {
Self::kill_container(&self.name, signal)?;

Ok(true)
}
Expand All @@ -84,10 +50,10 @@ impl Killable for DockerContainer {
///
/// # Returns
///
/// * `String` - A string that describes the type of the killable target. For a `NativeProcess` it will return "process",
/// * `String` - A string that describes the type of the killable target. For a `UnixProcess` it will return "process",
/// and for a `DockerContainer` it will return "container".
fn get_type(&self) -> String {
"container".to_string()
fn get_type(&self) -> KillableType {
KillableType::Container
}

fn get_name(&self) -> String {
Expand All @@ -104,10 +70,10 @@ pub trait KillportOperations {
fn kill_service_by_port(
&self,
port: u16,
signal: Signal,
signal: KillportSignal,
mode: Mode,
dry_run: bool,
) -> Result<Vec<(String, String)>, Error>;
) -> Result<Vec<(KillableType, String)>, Error>;
}

pub struct Killport;
Expand All @@ -133,7 +99,7 @@ impl KillportOperations for Killport {

for process in target_processes {
// Check if the process name contains 'docker' and skip if in docker mode
if docker_present && process.name.to_lowercase().contains("docker") {
if docker_present && process.get_name().to_lowercase().contains("docker") {
continue;
}
target_killables.push(Box::new(process));
Expand Down Expand Up @@ -166,10 +132,10 @@ impl KillportOperations for Killport {
fn kill_service_by_port(
&self,
port: u16,
signal: Signal,
signal: KillportSignal,
mode: Mode,
dry_run: bool,
) -> Result<Vec<(String, String)>, Error> {
) -> Result<Vec<(KillableType, String)>, Error> {
let mut results = Vec::new();
let target_killables = self.find_target_killables(port, mode)?; // Use the existing function to find targets

Expand All @@ -179,7 +145,7 @@ impl KillportOperations for Killport {
results.push((killable.get_type(), killable.get_name()));
} else {
// In actual mode, attempt to kill the entity and collect its information if successful
if killable.kill(signal)? {
if killable.kill(signal.clone())? {
results.push((killable.get_type(), killable.get_name()));
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
pub mod cli;
pub mod docker;
pub mod killport;
pub mod signal;

#[cfg(unix)]
pub mod unix;

#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "macos")]
Expand Down
11 changes: 4 additions & 7 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::killport::NativeProcess;
use crate::unix::UnixProcess;

use log::debug;
use nix::unistd::Pid;
Expand Down Expand Up @@ -75,8 +75,8 @@ fn find_target_inodes(port: u16) -> Vec<u64> {
/// # Arguments
///
/// * `inodes` - Target inodes
pub fn find_target_processes(port: u16) -> Result<Vec<NativeProcess>, Error> {
let mut target_pids: Vec<NativeProcess> = vec![];
pub fn find_target_processes(port: u16) -> Result<Vec<UnixProcess>, Error> {
let mut target_pids: Vec<UnixProcess> = vec![];
let inodes = find_target_inodes(port);

for inode in inodes {
Expand All @@ -96,10 +96,7 @@ pub fn find_target_processes(port: u16) -> Result<Vec<NativeProcess>, Error> {
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
.join(" ");
debug!("Found process '{}' with PID {}", name, process.pid());
target_pids.push(NativeProcess {
pid: Pid::from_raw(process.pid),
name: name,
});
target_pids.push(UnixProcess::new(Pid::from_raw(process.pid), name));
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::killport::NativeProcess;
use crate::unix::UnixProcess;

use libproc::libproc::file_info::pidfdinfo;
use libproc::libproc::file_info::{ListFDs, ProcFDType};
Expand All @@ -16,8 +16,8 @@ use std::io;
/// # Arguments
///
/// * `port` - Target port number
pub fn find_target_processes(port: u16) -> Result<Vec<NativeProcess>, io::Error> {
let mut target_pids: Vec<NativeProcess> = vec![];
pub fn find_target_processes(port: u16) -> Result<Vec<UnixProcess>, io::Error> {
let mut target_pids: Vec<UnixProcess> = vec![];

if let Ok(procs) = pids_by_type(ProcFilter::All) {
for p in procs {
Expand Down Expand Up @@ -56,10 +56,10 @@ pub fn find_target_processes(port: u16) -> Result<Vec<NativeProcess>, io::Error>
"Found process '{}' with PID {} listening on port {}",
process_name, pid, port
);
target_pids.push(NativeProcess {
pid: Pid::from_raw(pid),
name: process_name,
});
target_pids.push(UnixProcess::new(
Pid::from_raw(pid),
process_name,
));
}
}
_ => (),
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fn main() {

// Attempt to kill processes listening on specified ports
for port in args.ports {
match killport.kill_service_by_port(port, args.signal, args.mode, args.dry_run) {
match killport.kill_service_by_port(port, args.signal.clone(), args.mode, args.dry_run) {
Ok(killed_services) => {
if killed_services.is_empty() {
println!("No {} found using port {}", service_type_singular, port);
Expand Down
35 changes: 35 additions & 0 deletions src/signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Wrapper around signals for platforms that they are not supported on

use std::{fmt::Display, str::FromStr};

#[cfg(unix)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KillportSignal(pub nix::sys::signal::Signal);

/// On a platform where we don't have the proper signals enum
#[cfg(not(unix))]
#[derive(Debug, Clone)]
pub struct KillportSignal(pub String);

impl Display for KillportSignal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}

impl FromStr for KillportSignal {
type Err = std::io::Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
#[cfg(unix)]
{
let signal = nix::sys::signal::Signal::from_str(value)?;
Ok(KillportSignal(signal))
}

#[cfg(not(unix))]
{
Ok(KillportSignal(value.to_string()))
}
}
}
Loading

0 comments on commit 0624cb5

Please sign in to comment.