Skip to content

Commit ba00e9c

Browse files
committed
pgrep, pkill: Extract common process_matcher module
The process matching logic for pgrep and pkill is identical, extract it to a common module.
1 parent 021a2b7 commit ba00e9c

File tree

3 files changed

+262
-470
lines changed

3 files changed

+262
-470
lines changed

src/uu/pgrep/src/pgrep.rs

Lines changed: 5 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -5,97 +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-
#[cfg(unix)]
13-
use uucore::{display::Quotable, signals::signal_by_name_or_value};
14-
use uucore::{
15-
error::{UResult, USimpleError},
16-
format_usage, help_about, help_usage,
17-
};
10+
use clap::{arg, crate_version, Arg, ArgAction, ArgGroup, Command};
11+
use uucore::{error::UResult, format_usage, help_about, help_usage};
1812

1913
const ABOUT: &str = help_about!("pgrep.md");
2014
const USAGE: &str = help_usage!("pgrep.md");
2115

22-
struct Settings {
23-
regex: Regex,
24-
25-
exact: bool,
26-
full: bool,
27-
ignore_case: bool,
28-
inverse: bool,
29-
newest: bool,
30-
oldest: bool,
31-
older: Option<u64>,
32-
parent: Option<Vec<u64>>,
33-
runstates: Option<String>,
34-
terminal: Option<HashSet<Teletype>>,
35-
signal: usize,
36-
require_handler: bool,
37-
}
38-
39-
fn get_match_settings(matches: &ArgMatches) -> UResult<Settings> {
40-
let pattern = try_get_pattern_from(matches)?;
41-
let regex = Regex::new(&pattern).map_err(|e| USimpleError::new(2, e.to_string()))?;
42-
43-
let settings = Settings {
44-
regex,
45-
exact: matches.get_flag("exact"),
46-
full: matches.get_flag("full"),
47-
ignore_case: matches.get_flag("ignore-case"),
48-
inverse: matches.get_flag("inverse"),
49-
newest: matches.get_flag("newest"),
50-
oldest: matches.get_flag("oldest"),
51-
parent: matches
52-
.get_many::<u64>("parent")
53-
.map(|parents| parents.copied().collect()),
54-
runstates: matches.get_one::<String>("runstates").cloned(),
55-
older: matches.get_one::<u64>("older").copied(),
56-
terminal: matches.get_many::<String>("terminal").map(|ttys| {
57-
ttys.cloned()
58-
.flat_map(Teletype::try_from)
59-
.collect::<HashSet<_>>()
60-
}),
61-
signal: parse_signal_value(matches.get_one::<String>("signal").unwrap())?,
62-
require_handler: matches.get_flag("require-handler"),
63-
};
64-
65-
if (!settings.newest
66-
&& !settings.oldest
67-
&& settings.runstates.is_none()
68-
&& settings.older.is_none()
69-
&& settings.parent.is_none()
70-
&& settings.terminal.is_none())
71-
&& pattern.is_empty()
72-
{
73-
return Err(USimpleError::new(
74-
2,
75-
"no matching criteria specified\nTry `pgrep --help' for more information.",
76-
));
77-
}
78-
Ok(settings)
79-
}
80-
81-
fn find_matching_pids(settings: &Settings) -> Vec<ProcessInformation> {
82-
let mut pids = collect_matched_pids(settings);
83-
#[cfg(unix)]
84-
if settings.require_handler {
85-
pids.retain(|pid| {
86-
let mask =
87-
u64::from_str_radix(pid.clone().status().get("SigCgt").unwrap(), 16).unwrap();
88-
mask & (1 << settings.signal) != 0
89-
});
90-
}
91-
if pids.is_empty() {
92-
uucore::error::set_exit_code(1);
93-
pids
94-
} else {
95-
process_flag_o_n(settings, &mut pids)
96-
}
97-
}
98-
9916
/// # Conceptual model of `pgrep`
10017
///
10118
/// At first, `pgrep` command will check the patterns is legal.
@@ -111,10 +28,10 @@ fn find_matching_pids(settings: &Settings) -> Vec<ProcessInformation> {
11128
#[uucore::main]
11229
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
11330
let matches = uu_app().try_get_matches_from(args)?;
114-
let settings = get_match_settings(&matches)?;
31+
let settings = process_matcher::get_match_settings(&matches)?;
11532

11633
// Collect pids
117-
let pids = find_matching_pids(&settings);
34+
let pids = process_matcher::find_matching_pids(&settings);
11835

11936
// Processing output
12037
let output = if matches.get_flag("count") {
@@ -152,153 +69,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
15269
Ok(())
15370
}
15471

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

0 commit comments

Comments
 (0)