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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! Free-standing identity & reuse-safe liveness queries for an **arbitrary** pid
//! — one the caller holds *outside* any [`ProcessGroup`](crate::ProcessGroup).
//!
//! [`process_info`] answers "does this pid name a process, and what is it?" with
//! the same best-effort fields a group member carries in a
//! [`MemberInfo`]; [`process_is_alive`] answers "is the *same*
//! process I saw earlier still running?" — reuse-safe, by pairing the pid with the
//! start-time token, so a recycled number is not mistaken for the original.
//!
//! Both reuse the crate's existing per-platform readers rather than a second
//! implementation, and both keep the crate's standing rules: **never** read a
//! process's argv/environment, and honestly tell **"no such process"** (a negative
//! answer) apart from **"not allowed to look"** (an error).
use crateMemberInfo;
use crate::;
/// Look up the identity and best-effort metadata of an **arbitrary** process by
/// pid — the standalone companion to
/// [`ProcessGroup::members_info`](crate::ProcessGroup::members_info), for a pid the
/// caller holds *outside* any group (a pid saved to disk across runs, a launch
/// registry, an e2e probe watching a process from outside its container).
///
/// Returns the very fields a group member's [`MemberInfo`] carries — parent pid,
/// image name, and the start-time identity token — read through the **same**
/// per-platform readers (`/proc/<pid>/stat` on Linux, `proc_pidinfo` on macOS,
/// `Toolhelp32` + the creation `FILETIME` on Windows), with the same honest
/// `Option` policy: a field the platform can't report is `None`, never fabricated.
///
/// # The three outcomes
///
/// - **`Ok(Some(info))`** — the process exists; inspect it via
/// [`MemberInfo::ppid`], [`exe_name`](MemberInfo::exe_name), and
/// [`start_time`](MemberInfo::start_time) (each `None` where unavailable).
/// - **`Ok(None)`** — the pid names **no** process. An honest negative, *not* an
/// error: this is the "it's gone" answer a liveness check wants.
/// - **`Err`** — the process may well exist, but its state couldn't be determined:
/// the caller lacks permission to inspect it, or the OS read failed. **Never**
/// read this as "dead" — that is the whole reason it is an error rather than
/// `Ok(None)`.
///
/// # No command line
///
/// The raw argv / environment is **deliberately never** read, on any platform — a
/// command line routinely carries secrets, and redaction is the consumer's policy
/// to own (the crate's standing "never argv/env" stance, the same one
/// [`MemberInfo`] documents).
///
/// # Point-in-time
///
/// A snapshot taken now: the process may exit immediately afterwards, and the pid
/// is only as stable as the OS's reuse policy. To tell a *recycled* number apart
/// from the original process later, pair the returned
/// [`start_time`](MemberInfo::start_time) with the pid and use
/// [`process_is_alive`] — do not trust the bare number.
///
/// # Platform notes
///
/// - **Linux / Android** — one `/proc/<pid>/stat` read; world-readable for other
/// users' processes on a default mount, so a foreign process is reported. A
/// `hidepid` mount that denies the read surfaces as `Err`, not a false "gone".
/// - **Windows** — `OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION)` is the
/// existence/permission oracle (the least-privilege query right, grantable across
/// sessions and integrity levels for ordinary processes); ppid and image name
/// come from one system-wide `Toolhelp32` snapshot. A **protected / higher-integrity
/// process** (an anti-malware PPL, the `System` process) the caller may not query
/// yields `Err` (access denied), distinct from a non-existent pid's `Ok(None)`.
/// - **macOS** — one `proc_pidinfo(PROC_PIDTBSDINFO)` fill; a process the caller may
/// not inspect yields `Err`, a gone pid `Ok(None)`.
/// - **the bare BSDs** — no per-process reader is wired up, so existence is probed
/// with a zero-signal `kill(pid, 0)` and the pid is reported with every enriching
/// field `None` (`Ok(Some(_))`). That is a correct best-effort result, not an
/// error, and never a false "gone".
///
/// # Errors
///
/// [`ErrorReason::Io`](crate::ErrorReason::Io) when the process may exist but couldn't be inspected — a
/// permission denial (a Windows protected process, a Linux `hidepid` mount, a macOS
/// restricted process) or another OS read failure.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> processkit::Result<()> {
/// let pid = 4321;
/// match processkit::process_info(pid)? {
/// Some(info) => println!(
/// "pid={} ppid={:?} exe={:?} start={:?}",
/// info.pid(),
/// info.ppid(),
/// info.exe_name(),
/// info.start_time(),
/// ),
/// None => println!("pid {pid} is not running"),
/// }
/// # Ok(())
/// # }
/// ```
/// Reuse-safe liveness: is the process at `pid` **still the same instance** you saw
/// earlier — the one whose [`start_time`](MemberInfo::start_time) you saved?
///
/// Pass the pid together with the `start_time` token from an earlier
/// [`process_info`] (or [`MemberInfo::start_time`]). Because the OS reuses pid
/// *numbers*, a bare pid check would answer "alive" for a stranger that recycled
/// the number after your process exited; pairing it with the start time — fixed at
/// creation and distinct for a later occupant — tells the original apart from a
/// recycled number. This is the same anti-reuse discipline the crate applies
/// internally to its own kills and stats reads, exposed for a pid you hold.
///
/// # Result
///
/// - **`Ok(true)`** — the process at `pid` exists **and** its current start time
/// matches the one you saved: your process is still running.
/// - **`Ok(false)`** — the process is gone: either the pid names nothing, or it
/// names a **different** process now (a recycled number — the start times
/// differ), which means *your* process is no longer alive.
/// - **`Err`** — the pid may name a live process but it couldn't be inspected
/// (permission denied, or an OS read failure). As with [`process_info`], never
/// read this as "dead".
///
/// # Reuse protection degrades honestly
///
/// The recycle check needs a start-time token on **both** sides. When one is
/// missing it can't *prove* a recycle, so it degrades to bare-pid liveness — a live
/// process at the number reads as `Ok(true)`:
/// - `start_time` is `None` (you saved no token — e.g. it originated on a
/// [bare BSD](process_info#platform-notes), which reports none), or
/// - the platform can't report a current token for the live process at `pid` (the
/// same structural `None`).
///
/// So on platforms that *do* report a start time (Windows, Linux, macOS), passing
/// the saved `Some(token)` gives full reuse protection; on a platform that reports
/// none, this is exactly the number-only liveness a caller would otherwise write by
/// hand — no weaker, and never a false "dead".
///
/// # Errors
///
/// [`ErrorReason::Io`](crate::ErrorReason::Io) when the pid may name a live process but couldn't be inspected —
/// the same permission/OS-error surface as [`process_info`].
///
/// # Examples
///
/// ```no_run
/// # fn main() -> processkit::Result<()> {
/// // Earlier: record a process's identity.
/// let pid = 4321;
/// let saved_start = processkit::process_info(pid)?.and_then(|i| i.start_time());
///
/// // Later (perhaps after a restart): is that same process still running?
/// if processkit::process_is_alive(pid, saved_start)? {
/// println!("the original process {pid} is still alive");
/// } else {
/// println!("process {pid} is gone (exited, or its number was recycled)");
/// }
/// # Ok(())
/// # }
/// ```
/// Reuse-safe identity comparison for a process known to be **present** at the pid:
/// decide whether the live process is the *same instance* the caller saved.
///
/// - Both tokens known → the instances match iff they are **equal** (a difference
/// is positive proof the number was recycled by a different process).
/// - Either token `None` → a recycle cannot be *proven*, so degrade to bare-pid
/// liveness: the process at the number is live, so report `true`. This mirrors
/// the crate's internal `is_recycled` stance (`None` is never proof) and keeps
/// the bare-BSD / number-only path exactly as strong as a hand-written liveness
/// check — never a false "dead".
///
/// Pure and platform-agnostic, so the reuse discipline is unit-tested directly
/// (the modelled "same pid, different start time" case) without waiting on a real
/// pid recycle.