1use std::ffi::{OsStr, OsString};
4
5use bitflags::bitflags;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct ProcessIdentity {
13 pid: u32,
14 creation_time_100ns_since_1601: u64,
15}
16
17impl ProcessIdentity {
18 pub fn from_raw_parts(pid: u32, creation_time_100ns_since_1601: u64) -> crate::Result<Self> {
23 if pid == 0 || pid == u32::MAX {
24 return Err(crate::Error::new(
25 crate::ErrorKind::InvalidInput,
26 None,
27 "a process identity PID must be neither zero nor the native invalid sentinel",
28 ));
29 }
30 Ok(Self {
31 pid,
32 creation_time_100ns_since_1601,
33 })
34 }
35
36 pub(crate) const fn from_raw_parts_unchecked(
37 pid: u32,
38 creation_time_100ns_since_1601: u64,
39 ) -> Self {
40 Self {
41 pid,
42 creation_time_100ns_since_1601,
43 }
44 }
45
46 #[must_use]
48 pub const fn pid(self) -> u32 {
49 self.pid
50 }
51
52 #[must_use]
54 pub const fn creation_time_100ns_since_1601(self) -> u64 {
55 self.creation_time_100ns_since_1601
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61#[non_exhaustive]
62pub enum ApplicationType {
63 Unknown,
65 MainWindow,
67 OtherWindow,
69 Service,
71 Explorer,
73 Console,
75 Critical,
80 Unrecognized(i32),
82}
83
84impl ApplicationType {
85 pub(crate) const fn from_raw(value: i32) -> Self {
86 match value {
87 0 => Self::Unknown,
88 1 => Self::MainWindow,
89 2 => Self::OtherWindow,
90 3 => Self::Service,
91 4 => Self::Explorer,
92 5 => Self::Console,
93 1000 => Self::Critical,
94 other => Self::Unrecognized(other),
95 }
96 }
97
98 #[must_use]
100 pub const fn raw_value(self) -> i32 {
101 match self {
102 Self::Unknown => 0,
103 Self::MainWindow => 1,
104 Self::OtherWindow => 2,
105 Self::Service => 3,
106 Self::Explorer => 4,
107 Self::Console => 5,
108 Self::Critical => 1000,
109 Self::Unrecognized(value) => value,
110 }
111 }
112}
113
114bitflags! {
115 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120 pub struct ApplicationStatus: u32 {
121 const RUNNING = 0x01;
123 const STOPPED = 0x02;
125 const STOPPED_OTHER = 0x04;
127 const RESTARTED = 0x08;
129 const ERROR_ON_STOP = 0x10;
131 const ERROR_ON_RESTART = 0x20;
133 const SHUTDOWN_MASKED = 0x40;
135 const RESTART_MASKED = 0x80;
137 }
138}
139
140bitflags! {
141 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
145 pub struct RebootReasons: u32 {
146 const PERMISSION_DENIED = 0x01;
148 const SESSION_MISMATCH = 0x02;
150 const CRITICAL_PROCESS = 0x04;
152 const CRITICAL_SERVICE = 0x08;
154 const DETECTED_SELF = 0x10;
156 }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct AffectedApplication {
162 pub(crate) display_name: OsString,
163 pub(crate) service_name: Option<OsString>,
164 pub(crate) application_type: ApplicationType,
165 pub(crate) status: ApplicationStatus,
166 pub(crate) restartable: bool,
167 pub(crate) process: Option<ProcessIdentity>,
168 pub(crate) terminal_session_id: Option<u32>,
169}
170
171impl AffectedApplication {
172 #[must_use]
174 pub fn display_name(&self) -> &OsStr {
175 &self.display_name
176 }
177
178 #[must_use]
180 pub fn service_name(&self) -> Option<&OsStr> {
181 self.service_name.as_deref()
182 }
183
184 #[must_use]
186 pub const fn application_type(&self) -> ApplicationType {
187 self.application_type
188 }
189
190 #[must_use]
192 pub const fn status(&self) -> ApplicationStatus {
193 self.status
194 }
195
196 #[must_use]
198 pub const fn is_restartable(&self) -> bool {
199 self.restartable
200 }
201
202 #[must_use]
204 pub const fn process(&self) -> Option<ProcessIdentity> {
205 self.process
206 }
207
208 #[must_use]
210 pub const fn terminal_session_id(&self) -> Option<u32> {
211 self.terminal_session_id
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct AffectedApplications {
218 pub(crate) applications: Vec<AffectedApplication>,
219 pub(crate) reboot_reasons: RebootReasons,
220}
221
222impl AffectedApplications {
223 #[must_use]
225 pub fn applications(&self) -> &[AffectedApplication] {
226 &self.applications
227 }
228
229 #[must_use]
231 pub const fn reboot_reasons(&self) -> RebootReasons {
232 self.reboot_reasons
233 }
234
235 pub fn iter(&self) -> std::slice::Iter<'_, AffectedApplication> {
237 self.applications.iter()
238 }
239
240 #[must_use]
242 pub fn is_empty(&self) -> bool {
243 self.applications.is_empty()
244 }
245
246 #[must_use]
248 pub fn len(&self) -> usize {
249 self.applications.len()
250 }
251}
252
253impl IntoIterator for AffectedApplications {
254 type Item = AffectedApplication;
255 type IntoIter = std::vec::IntoIter<AffectedApplication>;
256
257 fn into_iter(self) -> Self::IntoIter {
258 self.applications.into_iter()
259 }
260}
261
262impl<'a> IntoIterator for &'a AffectedApplications {
263 type Item = &'a AffectedApplication;
264 type IntoIter = std::slice::Iter<'a, AffectedApplication>;
265
266 fn into_iter(self) -> Self::IntoIter {
267 self.iter()
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn application_type_distinguishes_unknown_and_future_values() {
277 let values = [0, 1, 2, 3, 4, 5, 1000, 77];
278 for value in values {
279 assert_eq!(ApplicationType::from_raw(value).raw_value(), value);
280 }
281 assert_eq!(ApplicationType::from_raw(0), ApplicationType::Unknown);
282 assert_eq!(
283 ApplicationType::from_raw(77),
284 ApplicationType::Unrecognized(77)
285 );
286 }
287
288 #[test]
289 fn status_and_reboot_reasons_retain_unknown_bits() {
290 let status = ApplicationStatus::from_bits_retain(0x4000_0001);
291 assert!(status.contains(ApplicationStatus::RUNNING));
292 assert_eq!(status.bits(), 0x4000_0001);
293
294 let reasons = RebootReasons::from_bits_retain(0x8000_0002);
295 assert!(reasons.contains(RebootReasons::SESSION_MISMATCH));
296 assert_eq!(reasons.bits(), 0x8000_0002);
297 }
298
299 #[test]
300 fn affected_report_accessors_and_iterators_are_reusable() {
301 let process = ProcessIdentity::from_raw_parts(42, 99).unwrap();
302 assert_eq!(process.pid(), 42);
303 assert_eq!(process.creation_time_100ns_since_1601(), 99);
304 let application = AffectedApplication {
305 display_name: OsString::from("display"),
306 service_name: Some(OsString::from("service")),
307 application_type: ApplicationType::Service,
308 status: ApplicationStatus::RUNNING | ApplicationStatus::RESTARTED,
309 restartable: true,
310 process: Some(process),
311 terminal_session_id: Some(7),
312 };
313 assert_eq!(application.display_name(), OsStr::new("display"));
314 assert_eq!(application.service_name(), Some(OsStr::new("service")));
315 assert_eq!(application.application_type(), ApplicationType::Service);
316 assert!(application.status().contains(ApplicationStatus::RUNNING));
317 assert!(application.is_restartable());
318 assert_eq!(application.process(), Some(process));
319 assert_eq!(application.terminal_session_id(), Some(7));
320
321 let report = AffectedApplications {
322 applications: vec![application],
323 reboot_reasons: RebootReasons::DETECTED_SELF,
324 };
325 assert_eq!(report.len(), 1);
326 assert!(!report.is_empty());
327 assert_eq!(report.reboot_reasons(), RebootReasons::DETECTED_SELF);
328 assert_eq!(report.iter().count(), 1);
329 assert_eq!((&report).into_iter().count(), 1);
330 assert_eq!(report.into_iter().count(), 1);
331 }
332
333 #[test]
334 fn process_identity_rejects_native_invalid_pids() {
335 assert!(ProcessIdentity::from_raw_parts(0, 1).is_err());
336 assert!(ProcessIdentity::from_raw_parts(u32::MAX, 1).is_err());
337 assert!(ProcessIdentity::from_raw_parts(1, 1).is_ok());
338 }
339}