1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Is this pid a trusty-mpm daemon? Three answers, read from argv (#9034).
//!
//! Why: `find_daemon_pids` refreshed the process table with
//! `ProcessRefreshKind::nothing()`, which never loads argv, so `cmd()` was
//! always empty and no process ever matched. The autostart lock check then
//! called a LIVE daemon "not a daemon" and deleted its lock. A pid whose argv
//! cannot be read (another user's process, KERN_PROCARGS2 denied) must not be
//! mistaken for "not a daemon" either.
//! What: [`classify_process`] is the pure rule over a process name and argv;
//! [`refresh_with_cmd`] is the one refresh that loads argv; [`pid_identity`]
//! answers for one pid.
//! Test: `classify_process_*`, `real_pid_identity_reads_argv_for_this_process`,
//! `real_pid_identity_never_calls_sleep_a_daemon`,
//! `find_daemon_pids_finds_a_tm_daemon_process` in
//! `daemon_pid_identity_tests.rs`.
use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System, UpdateKind};
/// What the process table says about one pid.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PidIdentity {
/// A tm/trusty-mpm binary running the `daemon` subcommand.
Daemon,
/// Positively another program, or a tm CLI invocation.
NotDaemon,
/// The argv could not be read, or the pid is not in the table.
Unknown,
}
/// Classify a process from its name and argv.
///
/// Why: one rule for `tm stop` and for the autostart lock check.
/// What: empty `cmd` → `Unknown` (argv unreadable; never a verdict); a name in
/// `OWN_BINARY_NAMES` whose SUBCOMMAND ([`subcommand_of`]) is `daemon` →
/// `Daemon`; else `NotDaemon`.
/// Test: `classify_process_daemon`, `classify_process_cli_is_not_daemon`,
/// `classify_process_daemon_word_after_the_subcommand_is_not_daemon`,
/// `classify_process_empty_argv_is_unknown`.
pub(crate) fn classify_process(name: &str, cmd: &[String]) -> PidIdentity {
if cmd.is_empty() {
return PidIdentity::Unknown;
}
let is_tm_binary = trusty_mpm::core::own_binary_names::OWN_BINARY_NAMES.contains(&name);
if is_tm_binary && subcommand_of(cmd) == Some("daemon") {
PidIdentity::Daemon
} else {
PidIdentity::NotDaemon
}
}
/// The `tm` global flags that take a separate value (`--url X`), so the value
/// is not mistaken for the subcommand.
const GLOBAL_VALUE_FLAGS: &[&str] = &["--url", "--account", "--user"];
/// The subcommand in a `tm` argv: the first non-flag argument after `argv[0]`.
///
/// Why: #9034 — `daemon` anywhere in argv made `tm build-lease -- cargo test
/// daemon` or `tm doctor daemon` a "daemon" that `tm stop` would kill.
/// What: skips global flags (and the value of a [`GLOBAL_VALUE_FLAGS`] flag
/// given without `=`); stops at `--`; returns the first positional argument.
/// Test: `classify_process_daemon_word_after_the_subcommand_is_not_daemon`.
fn subcommand_of(cmd: &[String]) -> Option<&str> {
let mut args = cmd.iter().skip(1).map(String::as_str);
while let Some(arg) = args.next() {
if arg == "--" {
return None;
}
if !arg.starts_with('-') {
return Some(arg);
}
if GLOBAL_VALUE_FLAGS.contains(&arg) {
args.next();
}
}
None
}
/// Refresh `which` processes WITH argv loaded.
///
/// Why: #9034 — `ProcessRefreshKind::nothing()` leaves `cmd()` empty.
/// Test: `real_pid_identity_reads_argv_for_this_process`.
pub(crate) fn refresh_with_cmd(which: ProcessesToUpdate<'_>) -> System {
let mut sys = System::new();
sys.refresh_processes_specifics(
which,
true,
ProcessRefreshKind::nothing().with_cmd(UpdateKind::OnlyIfNotSet),
);
sys
}
/// The name and argv of a process in `sys`, as strings.
pub(crate) fn name_and_cmd(proc_: &sysinfo::Process) -> (String, Vec<String>) {
let name = proc_.name().to_string_lossy().into_owned();
let cmd = proc_
.cmd()
.iter()
.map(|a| a.to_string_lossy().into_owned())
.collect();
(name, cmd)
}
/// The production identity check for one pid.
///
/// What: refreshes only `pid` with argv, then [`classify_process`]; a pid
/// missing from the table is `Unknown`.
/// Test: `real_pid_identity_reads_argv_for_this_process`,
/// `real_pid_identity_never_calls_sleep_a_daemon`.
pub(crate) fn pid_identity(pid: u32) -> PidIdentity {
let target = Pid::from_u32(pid);
let sys = refresh_with_cmd(ProcessesToUpdate::Some(&[target]));
match sys.process(target) {
Some(p) => {
let (name, cmd) = name_and_cmd(p);
classify_process(&name, &cmd)
}
None => PidIdentity::Unknown,
}
}
#[cfg(test)]
#[path = "daemon_pid_identity_tests.rs"]
mod tests;