Skip to content

Commit 98a2356

Browse files
authored
Merge pull request #322 from dezgeg/pgrep_pkill_unification2
pgrep, pkill: Unify process matching code
2 parents 7f610a2 + ba00e9c commit 98a2356

File tree

3 files changed

+264
-463
lines changed

3 files changed

+264
-463
lines changed

src/uu/pgrep/src/pgrep.rs

Lines changed: 5 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,14 @@
55

66
// Pid utils
77
pub mod process;
8+
pub mod process_matcher;
89

9-
use clap::{arg, crate_version, Arg, ArgAction, ArgGroup, ArgMatches, Command};
10-
use process::{walk_process, ProcessInformation, Teletype};
11-
use regex::Regex;
12-
use std::{collections::HashSet, sync::OnceLock};
13-
#[cfg(unix)]
14-
use uucore::{display::Quotable, signals::signal_by_name_or_value};
15-
use uucore::{
16-
error::{UResult, USimpleError},
17-
format_usage, help_about, help_usage,
18-
};
10+
use clap::{arg, crate_version, Arg, ArgAction, ArgGroup, Command};
11+
use uucore::{error::UResult, format_usage, help_about, help_usage};
1912

2013
const ABOUT: &str = help_about!("pgrep.md");
2114
const USAGE: &str = help_usage!("pgrep.md");
2215

23-
static REGEX: OnceLock<Regex> = OnceLock::new();
24-
25-
struct Settings {
26-
exact: bool,
27-
full: bool,
28-
ignore_case: bool,
29-
inverse: bool,
30-
newest: bool,
31-
oldest: bool,
32-
older: Option<u64>,
33-
parent: Option<Vec<u64>>,
34-
runstates: Option<String>,
35-
terminal: Option<HashSet<Teletype>>,
36-
}
37-
3816
/// # Conceptual model of `pgrep`
3917
///
4018
/// At first, `pgrep` command will check the patterns is legal.
@@ -50,67 +28,10 @@ struct Settings {
5028
#[uucore::main]
5129
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
5230
let matches = uu_app().try_get_matches_from(args)?;
53-
54-
let pattern = try_get_pattern_from(&matches)?;
55-
REGEX
56-
.set(Regex::new(&pattern).map_err(|e| USimpleError::new(2, e.to_string()))?)
57-
.unwrap();
58-
59-
let settings = Settings {
60-
exact: matches.get_flag("exact"),
61-
full: matches.get_flag("full"),
62-
ignore_case: matches.get_flag("ignore-case"),
63-
inverse: matches.get_flag("inverse"),
64-
newest: matches.get_flag("newest"),
65-
oldest: matches.get_flag("oldest"),
66-
parent: matches
67-
.get_many::<u64>("parent")
68-
.map(|parents| parents.copied().collect()),
69-
runstates: matches.get_one::<String>("runstates").cloned(),
70-
older: matches.get_one::<u64>("older").copied(),
71-
terminal: matches.get_many::<String>("terminal").map(|ttys| {
72-
ttys.cloned()
73-
.flat_map(Teletype::try_from)
74-
.collect::<HashSet<_>>()
75-
}),
76-
};
77-
78-
if (!settings.newest
79-
&& !settings.oldest
80-
&& settings.runstates.is_none()
81-
&& settings.older.is_none()
82-
&& settings.parent.is_none()
83-
&& settings.terminal.is_none())
84-
&& pattern.is_empty()
85-
{
86-
return Err(USimpleError::new(
87-
2,
88-
"no matching criteria specified\nTry `pgrep --help' for more information.",
89-
));
90-
}
91-
92-
// Parse signal
93-
#[cfg(unix)]
94-
let sig_num = parse_signal_value(matches.get_one::<String>("signal").unwrap())?;
31+
let settings = process_matcher::get_match_settings(&matches)?;
9532

9633
// Collect pids
97-
let pids = {
98-
let mut pids = collect_matched_pids(&settings);
99-
#[cfg(unix)]
100-
if matches.get_flag("require-handler") {
101-
pids.retain(|pid| {
102-
let mask =
103-
u64::from_str_radix(pid.clone().status().get("SigCgt").unwrap(), 16).unwrap();
104-
mask & (1 << sig_num) != 0
105-
});
106-
}
107-
if pids.is_empty() {
108-
uucore::error::set_exit_code(1);
109-
pids
110-
} else {
111-
process_flag_o_n(&settings, &mut pids)
112-
}
113-
};
34+
let pids = process_matcher::find_matching_pids(&settings);
11435

11536
// Processing output
11637
let output = if matches.get_flag("count") {
@@ -148,153 +69,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
14869
Ok(())
14970
}
15071

151-
/// Try to get the pattern from the command line arguments. Returns an empty string if no pattern
152-
/// is specified.
153-
fn try_get_pattern_from(matches: &ArgMatches) -> UResult<String> {
154-
let pattern = match matches.get_many::<String>("pattern") {
155-
Some(patterns) if patterns.len() > 1 => {
156-
return Err(USimpleError::new(
157-
2,
158-
"only one pattern can be provided\nTry `pgrep --help' for more information.",
159-
))
160-
}
161-
Some(mut patterns) => patterns.next().unwrap(),
162-
None => return Ok(String::new()),
163-
};
164-
165-
let pattern = if matches.get_flag("ignore-case") {
166-
&pattern.to_lowercase()
167-
} else {
168-
pattern
169-
};
170-
171-
let pattern = if matches.get_flag("exact") {
172-
&format!("^{}$", pattern)
173-
} else {
174-
pattern
175-
};
176-
177-
Ok(pattern.to_string())
178-
}
179-
180-
/// Collect pids with filter construct from command line arguments
181-
fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
182-
// Filtration general parameters
183-
let filtered: Vec<ProcessInformation> = {
184-
let mut tmp_vec = Vec::new();
185-
186-
for mut pid in walk_process().collect::<Vec<_>>() {
187-
let run_state_matched = match (&settings.runstates, pid.run_state()) {
188-
(Some(arg_run_states), Ok(pid_state)) => {
189-
arg_run_states.contains(&pid_state.to_string())
190-
}
191-
(_, Err(_)) => false,
192-
_ => true,
193-
};
194-
195-
let binding = pid.status();
196-
let name = binding.get("Name").unwrap();
197-
let name = if settings.ignore_case {
198-
name.to_lowercase()
199-
} else {
200-
name.into()
201-
};
202-
let pattern_matched = {
203-
let want = if settings.exact {
204-
// Equals `Name` in /proc/<pid>/status
205-
// The `unwrap` operation must succeed
206-
// because the REGEX has been verified as correct in `uumain`.
207-
&name
208-
} else if settings.full {
209-
// Equals `cmdline` in /proc/<pid>/cmdline
210-
&pid.cmdline
211-
} else {
212-
// From manpage:
213-
// The process name used for matching is limited to the 15 characters present in the output of /proc/pid/stat.
214-
&pid.proc_stat()[..15]
215-
};
216-
217-
REGEX.get().unwrap().is_match(want)
218-
};
219-
220-
let tty_matched = match &settings.terminal {
221-
Some(ttys) => ttys.contains(&pid.tty()),
222-
None => true,
223-
};
224-
225-
let arg_older = settings.older.unwrap_or(0);
226-
let older_matched = pid.start_time().unwrap() >= arg_older;
227-
228-
// the PPID is the fourth field in /proc/<PID>/stat
229-
// (https://www.kernel.org/doc/html/latest/filesystems/proc.html#id10)
230-
let stat = pid.stat();
231-
let ppid = stat.get(3);
232-
let parent_matched = match (&settings.parent, ppid) {
233-
(Some(parents), Some(ppid)) => parents.contains(&ppid.parse::<u64>().unwrap()),
234-
_ => true,
235-
};
236-
237-
if (run_state_matched
238-
&& pattern_matched
239-
&& tty_matched
240-
&& older_matched
241-
&& parent_matched)
242-
^ settings.inverse
243-
{
244-
tmp_vec.push(pid);
245-
}
246-
}
247-
tmp_vec
248-
};
249-
250-
filtered
251-
}
252-
253-
/// Sorting pids for flag `-o` and `-n`.
254-
///
255-
/// This function can also be used as a filter to filter out process information.
256-
fn process_flag_o_n(
257-
settings: &Settings,
258-
pids: &mut [ProcessInformation],
259-
) -> Vec<ProcessInformation> {
260-
if settings.oldest || settings.newest {
261-
pids.sort_by(|a, b| {
262-
b.clone()
263-
.start_time()
264-
.unwrap()
265-
.cmp(&a.clone().start_time().unwrap())
266-
});
267-
268-
let start_time = if settings.newest {
269-
pids.first().cloned().unwrap().start_time().unwrap()
270-
} else {
271-
pids.last().cloned().unwrap().start_time().unwrap()
272-
};
273-
274-
// There might be some process start at same time, so need to be filtered.
275-
let mut filtered = pids
276-
.iter()
277-
.filter(|it| (*it).clone().start_time().unwrap() == start_time)
278-
.collect::<Vec<_>>();
279-
280-
if settings.newest {
281-
filtered.sort_by(|a, b| b.pid.cmp(&a.pid));
282-
} else {
283-
filtered.sort_by(|a, b| a.pid.cmp(&b.pid));
284-
}
285-
286-
vec![filtered.first().cloned().unwrap().clone()]
287-
} else {
288-
pids.to_vec()
289-
}
290-
}
291-
292-
#[cfg(unix)]
293-
fn parse_signal_value(signal_name: &str) -> UResult<usize> {
294-
signal_by_name_or_value(signal_name)
295-
.ok_or_else(|| USimpleError::new(1, format!("Unknown signal {}", signal_name.quote())))
296-
}
297-
29872
#[allow(clippy::cognitive_complexity)]
29973
pub fn uu_app() -> Command {
30074
Command::new(uucore::util_name())

0 commit comments

Comments
 (0)