Skip to content

Commit 6935bd7

Browse files
Add Windows process listing command
1 parent d01d8e7 commit 6935bd7

5 files changed

Lines changed: 165 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ uptime = { package = "uu_uptime", path = "deps/coreutils/src/uu/uptime" }
125125
findutils = { package = "findutils", path = "deps/findutils" }
126126
grep = { package = "uu_grep", path = "deps/grep" }
127127
ntfind = { package = "find", path = "deps/ntfind" }
128+
ps = { package = "uu_ps", path = "deps/ntps" }
128129

129130
# For registry access in main.rs
130131
[dependencies.windows-sys]

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Legend: ✅ ships and works · ⚠️ ships but conflicts with a built-in ·
6666
| `mkdir` | ⚠️ | ⚠️ | |
6767
| `more` | 🛑 | 🛑 | Conflicts with the built-in DOS command (consider `edit` as an alternative) |
6868
| `mv` || ⚠️ | |
69+
| `ps` || ⚠️ | Windows-native process listing |
6970
| `pwd` || ⚠️ | |
7071
| `rm` || ⚠️ | |
7172
| `rmdir` | ⚠️ | ⚠️ | |

deps/ntps/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "uu_ps"
3+
edition = "2024"
4+
license = "MIT"
5+
repository = "https://github.com/microsoft/coreutils"
6+
rust-version = "1.88.0"
7+
version = "0.1.0"
8+
publish = false
9+
10+
[dependencies]
11+
clap = { version = "4.5", features = ["wrap_help", "cargo", "color"] }
12+
uucore = { path = "../coreutils/src/uucore" }
13+
windows-sys = { version = "*", features = [
14+
"Win32_Foundation",
15+
"Win32_System_Diagnostics_ToolHelp",
16+
] }

deps/ntps/src/lib.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use std::ffi::OsString;
5+
use std::io::{self, Write};
6+
use std::mem::{size_of, zeroed};
7+
use std::os::windows::ffi::OsStringExt as _;
8+
9+
use clap::{Arg, ArgAction, Command};
10+
use uucore::Args;
11+
use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
12+
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
13+
CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW, TH32CS_SNAPPROCESS,
14+
};
15+
16+
pub fn uumain<T: Args>(args: T) -> i32 {
17+
let matches = match uu_app().try_get_matches_from(args) {
18+
Ok(matches) => matches,
19+
Err(err) => {
20+
let _ = err.print();
21+
return err.exit_code();
22+
}
23+
};
24+
25+
let _ = matches;
26+
27+
match list_processes() {
28+
Ok(processes) => print_processes(&processes),
29+
Err(err) => {
30+
let _ = writeln!(io::stderr(), "ps: {err}");
31+
1
32+
}
33+
}
34+
}
35+
36+
pub fn uu_app() -> Command {
37+
Command::new("ps")
38+
.version(env!("CARGO_PKG_VERSION"))
39+
.about("List running processes")
40+
.arg(
41+
Arg::new("all")
42+
.short('A')
43+
.action(ArgAction::SetTrue)
44+
.help("List all processes"),
45+
)
46+
.arg(
47+
Arg::new("every")
48+
.short('e')
49+
.action(ArgAction::SetTrue)
50+
.help("List all processes"),
51+
)
52+
}
53+
54+
#[derive(Debug, Eq, PartialEq)]
55+
struct ProcessInfo {
56+
pid: u32,
57+
ppid: u32,
58+
name: String,
59+
}
60+
61+
fn list_processes() -> io::Result<Vec<ProcessInfo>> {
62+
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
63+
if snapshot == INVALID_HANDLE_VALUE {
64+
return Err(io::Error::last_os_error());
65+
}
66+
67+
let mut entry: PROCESSENTRY32W = unsafe { zeroed() };
68+
entry.dwSize = size_of::<PROCESSENTRY32W>() as u32;
69+
70+
let mut processes = Vec::new();
71+
72+
let mut ok = unsafe { Process32FirstW(snapshot, &mut entry) != 0 };
73+
while ok {
74+
processes.push(ProcessInfo {
75+
pid: entry.th32ProcessID,
76+
ppid: entry.th32ParentProcessID,
77+
name: process_name(&entry.szExeFile),
78+
});
79+
ok = unsafe { Process32NextW(snapshot, &mut entry) != 0 };
80+
}
81+
82+
unsafe {
83+
CloseHandle(snapshot);
84+
}
85+
86+
processes.sort_by_key(|process| process.pid);
87+
Ok(processes)
88+
}
89+
90+
fn print_processes(processes: &[ProcessInfo]) -> i32 {
91+
let mut out = io::stdout().lock();
92+
93+
if writeln!(out, "{:>8} {:>8} COMMAND", "PID", "PPID").is_err() {
94+
return 1;
95+
}
96+
97+
for process in processes {
98+
if writeln!(
99+
out,
100+
"{:>8} {:>8} {}",
101+
process.pid, process.ppid, process.name
102+
)
103+
.is_err()
104+
{
105+
return 1;
106+
}
107+
}
108+
109+
0
110+
}
111+
112+
fn process_name(buffer: &[u16]) -> String {
113+
let len = buffer.iter().position(|&ch| ch == 0).unwrap_or(buffer.len());
114+
OsString::from_wide(&buffer[..len])
115+
.to_string_lossy()
116+
.into_owned()
117+
}
118+
119+
#[cfg(test)]
120+
mod tests {
121+
use super::*;
122+
123+
#[test]
124+
fn trims_process_name_at_nul() {
125+
let mut buffer = [0u16; 260];
126+
let name: Vec<u16> = "example.exe".encode_utf16().collect();
127+
buffer[..name.len()].copy_from_slice(&name);
128+
129+
assert_eq!(process_name(&buffer), "example.exe");
130+
}
131+
132+
#[test]
133+
fn accepts_common_all_process_flags() {
134+
assert!(uu_app().try_get_matches_from(["ps", "-A"]).is_ok());
135+
assert!(uu_app().try_get_matches_from(["ps", "-e"]).is_ok());
136+
}
137+
}

0 commit comments

Comments
 (0)