Initial commit

This commit is contained in:
Francisco Jiménez Cabrera
2023-03-17 15:54:31 +00:00
parent 70e94827b0
commit 356fb3f137
13 changed files with 565 additions and 1 deletions

30
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,30 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Run the command '...'
2. See the error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- OS: [e.g. Ubuntu 20.04, macOS 12.0]
- Rust version: [e.g. 1.57.0]
- killport version: [e.g. 0.1.0]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

33
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,33 @@
---
name: Pull Request
about: Submit a pull request to contribute to the project
title: ''
labels: ''
assignees: ''
---
**Description of the changes**
Please provide a clear and concise description of the changes you made.
**Related issue(s)**
If this PR is related to an existing issue, please link to it using the `Fixes #issue_number` or `Closes #issue_number` syntax.
**Type of change**
Please select one or multiple of the following options:
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Code cleanup (refactoring or improving code quality)
- [ ] Documentation update (adding or updating documentation, updating README)
**Checklist:**
- [ ] I have read the [CONTRIBUTING](CONTRIBUTING.md) document.
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
**Additional information**
Add any other information or screenshots about the pull request here.

26
.github/workflows/rustfmt.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Rustfmt
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
check_rustfmt:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
override: true
- name: Check code formatting
run: cargo fmt --all -- --check

25
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Run tests
run: cargo test --all

43
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,43 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [INSERT EMAIL ADDRESS]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

35
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,35 @@
# Contributing to killport
Thank you for considering a contribution to the killport project! We appreciate your help in making our project better. This document will guide you through the process of contributing to the killport project.
## Code of Conduct
Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md) to ensure a welcoming and inclusive environment for all contributors.
## Reporting Bugs or Requesting Features
- Before submitting a bug report or feature request, please check the existing [issues](https://github.com/jkfran/killport/issues) to avoid duplicates.
- If you find an existing issue that matches your problem or request, feel free to comment on it or add a reaction.
- If there is no existing issue, please create a new one with a clear description and steps to reproduce the bug or a detailed explanation of the requested feature.
## Contributing Code
1. Fork the repository on GitHub.
2. Clone your fork and create a new branch for your feature or bugfix.
(use `git clone` and `git checkout -b your-feature-branch` commands)
3. Make your changes, following our coding guidelines.
4. Add tests for your changes and ensure all tests pass.
(use `cargo test` command)
5. Commit your changes, following our commit message guidelines.
(use `git commit -m "Your commit message"` command)
6. Push your changes to your fork.
(use `git push origin your-feature-branch` command)
7. Create a pull request through the GitHub interface.
## Pull Request Process
1. Ensure that your pull request includes a clear and concise description of your changes.
2. Make sure your code adheres to our coding guidelines and passes all tests.
3. Be prepared for feedback from the maintainers and be open to making changes if needed.
We will review your pull request and provide feedback or merge your changes. Please be patient, as reviewing and testing changes can take time. We appreciate your contribution and will do our best to process it as quickly as possible.

36
Cargo.toml Normal file
View File

@@ -0,0 +1,36 @@
[package]
name = "killport"
version = "0.1.0"
authors = ["Francisco Jimenez Cabrera <jkfran@gmail.com>"]
edition = "2021"
license = "MIT"
description = "A command-line tool to easily kill processes running on a specified port."
readme = "README.md"
homepage = "https://github.com/jkfran/killport"
repository = "https://github.com/jkfran/killport"
keywords = ["cli", "port", "process", "kill", "linux"]
categories = ["command-line-utilities"]
[dependencies]
log = "0.4.17"
env_logger = "0.10.0"
clap-verbosity-flag = "2.0.0"
procfs = "0.15.1"
clap = { version = "4.1.8", features = ["derive"] }
nix = "0.26.2"
[dev-dependencies]
assert_cmd = "2.0.10"
tempfile = "3.4.0"
[package.metadata.deb]
maintainer = "Francisco Jimenez Cabrera <jkfran@gmail.com>"
copyright = "Copyright (c) 2023, Francisco Jimenez Cabrera <jkfran@gmail.com>"
depends = "$auto"
section = "admin"
priority = "optional"
asset = [
["target/release/killport", "usr/bin/", "755"],
["README.md", "usr/share/doc/killport/", "644"],
["LICENSE", "usr/share/doc/killport/", "644"],
]

View File

@@ -1,2 +1,86 @@
# killport
A command-line tool to easily kill processes running on a specified port.
`killport` is a command-line utility for killing processes listening on specific ports. It's designed to be simple, fast, and effective. The tool is built with Rust and works on Linux and macOS.
## Features
- Kill processes by port number
- Supports multiple port numbers
- Cross-platform: Linux and macOS
- Verbosity control
## Installation
### From Source
1. Install Rust: Follow the [official Rust installation guide](https://www.rust-lang.org/tools/install) to set up Rust on your system.
2. Clone the repository:
```sh
git clone https://github.com/jkfran/killport.git
```
3. Change to the killport directory:
```sh
cd killport
```
4. Build and install the binary:
```sh
cargo build --release
sudo cp target/release/killport /usr/local/bin/
```
### Binary Releases
// TODO
## Usage
```sh
killport [FLAGS] <ports>...
```
### Examples
Kill a single process listening on port 8080:
```sh
killport 8080
```
Kill multiple processes listening on ports 8045, 8046, and 8080:
```sh
killport 8045 8046 8080
```
### Flags
-v, --verbose
Increase the verbosity level. Use multiple times for more detailed output.
-h, --help
Display the help message and exit.
-V, --version
Display the version information and exit.
## Contributing
We welcome contributions to the killport project! Before you start, please read our [Code of Conduct](CODE_OF_CONDUCT.md) and the [Contributing Guidelines](CONTRIBUTING.md).
To contribute, follow these steps:
1. Fork the repository on GitHub.
2. Clone your fork and create a new branch for your feature or bugfix.
3. Make your changes, following our coding guidelines.
4. Add tests for your changes and ensure all tests pass.
5. Commit your changes, following our commit message guidelines.
6. Push your changes to your fork and create a pull request.
We'll review your pull request and provide feedback or merge your changes.
## License
This project is licensed under the [MIT License](LICENSE). See the LICENSE file for more information.

50
src/main.rs Normal file
View File

@@ -0,0 +1,50 @@
mod port;
mod process;
use clap::Parser;
use clap_verbosity_flag::{Verbosity, WarnLevel};
use log::error;
use port::kill_port;
use std::process::exit;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct KillPortArgs {
#[arg(name = "ports", help = "The list of port numbers to kill processes on")]
ports: Vec<u16>,
#[command(flatten)]
verbose: Verbosity<WarnLevel>,
}
fn main() {
let args = KillPortArgs::parse();
let log_level = args
.verbose
.log_level()
.map(|level| level.to_level_filter())
.unwrap();
env_logger::Builder::new()
.format_module_path(log_level == log::LevelFilter::Trace)
.format_target(log_level == log::LevelFilter::Trace)
.format_timestamp(Option::None)
.filter_level(log_level)
.init();
for port in args.ports {
match kill_port(port) {
Ok(killed) => {
if killed {
println!("Successfully killed processes using port {}.", port);
} else {
println!("No processes found using port {}.", port);
}
}
Err(err) => {
error!("{}", err);
exit(1);
}
}
}
}

42
src/port.rs Normal file
View File

@@ -0,0 +1,42 @@
use crate::process::kill_processes_by_inode;
use std::io::Error;
pub fn kill_port(port: u16) -> Result<bool, Error> {
if !cfg!(target_family = "unix") {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Unsupported platform",
));
}
let mut killed_any = false;
let target_inodes = find_target_inodes(port);
if !target_inodes.is_empty() {
for target_inode in target_inodes {
killed_any |= kill_processes_by_inode(target_inode)?;
}
}
Ok(killed_any)
}
fn find_target_inodes(port: u16) -> Vec<u64> {
let tcp = procfs::net::tcp().unwrap();
let tcp6 = procfs::net::tcp6().unwrap();
let mut target_inodes = Vec::new();
target_inodes.extend(
tcp.into_iter()
.filter(|tcp_entry| tcp_entry.local_address.port() == port)
.map(|tcp_entry| tcp_entry.inode),
);
target_inodes.extend(
tcp6.into_iter()
.filter(|tcp_entry| tcp_entry.local_address.port() == port)
.map(|tcp_entry| tcp_entry.inode),
);
target_inodes
}

89
src/process.rs Normal file
View File

@@ -0,0 +1,89 @@
use log::{debug, info, warn};
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
use procfs::process::FDTarget;
use std::io;
use std::io::Error;
use std::path::Path;
pub fn kill_processes_by_inode(target_inode: u64) -> Result<bool, Error> {
let processes = procfs::process::all_processes().unwrap();
let mut killed_any = false;
for p in processes {
let process = p.unwrap();
if let Ok(fds) = process.fd() {
for fd in fds {
if let FDTarget::Socket(inode) = fd.unwrap().target {
if target_inode == inode {
debug!("Found process with PID {}", process.pid);
if let Ok(cmdline) = process.cmdline() {
if let Some(cmd) = Path::new(&cmdline[0])
.file_name()
.and_then(|fname| fname.to_str())
{
if cmd.starts_with("docker") {
warn!("Found Docker. You might need to stop the container manually");
}
}
}
match kill_process_and_children(process.pid) {
Ok(_) => {
killed_any = true;
}
Err(err) => {
return Err(err);
}
}
break;
}
}
}
}
}
if !killed_any {
return Err(Error::new(
io::ErrorKind::Other,
"Unable to kill the process. The process might be running as another user or root. Try again with sudo",
));
}
Ok(killed_any)
}
fn kill_process_and_children(pid: i32) -> Result<(), std::io::Error> {
let mut children_pids = Vec::new();
collect_child_pids(pid, &mut children_pids)?;
for child_pid in children_pids {
kill_process(child_pid)?;
}
kill_process(pid)?;
Ok(())
}
fn collect_child_pids(pid: i32, child_pids: &mut Vec<i32>) -> Result<(), std::io::Error> {
let processes = procfs::process::all_processes().unwrap();
for p in processes {
let process = p.unwrap();
if process.stat().unwrap().ppid == pid {
child_pids.push(process.pid);
collect_child_pids(process.pid, child_pids)?;
}
}
Ok(())
}
fn kill_process(pid: i32) -> Result<(), std::io::Error> {
info!("Killing process with PID {}", pid);
let pid = Pid::from_raw(pid);
kill(pid, Signal::SIGKILL).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
}

51
tests/integration_test.rs Normal file
View File

@@ -0,0 +1,51 @@
use assert_cmd::Command;
use std::fs::File;
use std::io::Write;
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();
// 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 = 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");
let mut mock_process = Command::new(tempdir_path.join("mock_process"))
.spawn()
.expect("Failed to run the mock process");
// Test killport command
let mut cmd = Command::cargo_bin("killport").expect("Failed to find killport binary");
cmd.arg("8080")
.assert()
.success()
.stdout("Successfully killed process listening on port 8080\n");
// Cleanup: Terminate the mock process (if still running).
let _ = mock_process.kill();
}