First working version

This commit is contained in:
aNNiMON 2024-07-23 19:37:06 +03:00
commit d7d723b549
5 changed files with 331 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

109
Cargo.lock generated Normal file
View File

@ -0,0 +1,109 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "pkill"
version = "0.1.0"
dependencies = [
"itertools",
"windows",
]
[[package]]
name = "windows"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-core",
"windows-targets",
]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "pkill"
version = "0.1.0"
edition = "2021"
[dependencies]
windows = { version = "0.52.0", features = [
"Win32_Foundation",
"Win32_System_Diagnostics_ToolHelp",
"Win32_System_Threading",
] }
itertools = "0.13.0"

144
src/main.rs Normal file
View File

@ -0,0 +1,144 @@
use itertools::Itertools;
use processes::terminate_process;
use std::process::ExitCode;
use std::{env, fmt::Display};
mod processes;
#[derive(Debug)]
enum Mode {
Minimal,
All,
Filtered(String),
Single(String),
TerminateByPid(u32),
TerminateByName(String),
}
// pkill => all unique processed, excl. svchost
// pkill --all => all processes, incl. svchost
// pkill <regex> => filtered processes
// pkill --pid name => PID of the first occurence
// pkill --kill PID/name => terminate process by its PID or name
fn main() -> ExitCode {
match parse_mode() {
Mode::All => {
show_processes_table(processes::list_processes());
ExitCode::SUCCESS
}
Mode::Minimal => {
let filterlist = [
"svchost",
"registry",
"[system process]",
"smss.exe",
"csrss.exe",
"lsass.exe",
];
show_processes_table(
processes::list_processes()
.iter()
.filter(|p| {
let pname = p.name.to_ascii_lowercase();
!filterlist.iter().any(|n| pname.starts_with(n))
})
.unique_by(|p| &p.name)
.sorted_by_key(|p| &p.name),
);
ExitCode::SUCCESS
}
Mode::Filtered(filter) => {
show_processes_table(
processes::list_processes()
.iter()
.filter(|p| p.name.to_ascii_lowercase().contains(&filter))
.sorted_by_key(|p| &p.name),
);
ExitCode::SUCCESS
}
Mode::Single(name) => {
if let Some(pid) = processes::list_processes()
.iter()
.find(|p| p.name.to_ascii_lowercase().contains(&name))
.map(|p| p.pid)
{
println!("{}", pid);
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
Mode::TerminateByPid(pid) => {
if terminate_process(pid) {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
Mode::TerminateByName(name) => {
if processes::list_processes()
.iter()
.find(|p| p.name.to_ascii_lowercase().contains(&name))
.map(|p| p.pid)
.map(terminate_process)
.is_some()
{
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
}
}
}
fn parse_mode() -> Mode {
let mut mode: Mode = Mode::Minimal;
let mut iter = env::args().skip(1).into_iter();
while let Some(el) = iter.next() {
match el.as_str() {
"--all" => {
mode = Mode::All;
}
"--pid" => {
if let Some(name) = iter.next() {
mode = Mode::Single(name.to_ascii_lowercase());
} else {
continue;
}
}
"--kill" => {
if let Some(name) = iter.next() {
mode = match name.parse() {
Ok(pid) => Mode::TerminateByPid(pid),
Err(_) => Mode::TerminateByName(name.to_ascii_lowercase()),
};
} else {
continue;
}
}
search => {
mode = Mode::Filtered(search.to_ascii_lowercase().to_string());
}
}
break;
}
mode
}
fn show_processes_table<T>(items: T)
where
T: IntoIterator,
T::Item: Display,
{
let lines: Vec<String> = items.into_iter().map(|p| format!("{}\n", p)).collect();
if lines.is_empty() {
println!("No processes");
} else {
println!("{:10} {:10} {:8} {:32}", "PID", "Parent", "Threads", "Name");
println!("{}", lines.concat().trim_end());
let len = lines.len();
if len > 10 {
println!("{} processes found", lines.len());
}
}
}

65
src/processes.rs Normal file
View File

@ -0,0 +1,65 @@
use core::fmt;
use std::{ffi, mem};
use windows::Win32::{
Foundation::CloseHandle,
System::{
Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32,
TH32CS_SNAPPROCESS,
},
Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE},
},
};
pub struct ProcessInfo {
pub pid: u32,
pub parent_pid: u32,
pub name: String,
pub threads_count: u32,
}
impl fmt::Display for ProcessInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:<10} {:<10} {:<8} {:<32}", self.pid, self.parent_pid, self.threads_count, self.name)
}
}
pub fn list_processes() -> Vec<ProcessInfo> {
let mut processes = Vec::new();
unsafe {
let snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).unwrap();
if let Ok(process_entry_size) = u32::try_from(mem::size_of::<PROCESSENTRY32>()) {
let mut process_entry = mem::zeroed::<PROCESSENTRY32>();
process_entry.dwSize = process_entry_size;
if Process32First(snapshot_handle, &mut process_entry).is_ok() {
loop {
processes.push(ProcessInfo {
pid: process_entry.th32ProcessID,
parent_pid: process_entry.th32ParentProcessID,
name: ffi::CStr::from_ptr(process_entry.szExeFile.as_ptr().cast())
.to_string_lossy()
.into_owned(),
threads_count: process_entry.cntThreads,
});
if Process32Next(snapshot_handle, &mut process_entry).is_err() {
break;
}
}
}
}
CloseHandle(snapshot_handle).unwrap();
}
processes
}
pub fn terminate_process(process_id: u32) -> bool {
unsafe {
if let Ok(process_handle) = OpenProcess(PROCESS_TERMINATE, false, process_id) {
let status = TerminateProcess(process_handle, 1);
CloseHandle(process_handle).unwrap();
status.is_ok()
} else {
false
}
}
}