Skip to main content

restart_manager/
application.rs

1//! Domain types describing applications affected by registered resources.
2
3use std::ffi::{OsStr, OsString};
4
5use bitflags::bitflags;
6
7/// A process identity made from its ID and creation time.
8///
9/// Pairing both values prevents a recycled PID from identifying an unrelated
10/// newer process.
11#[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    /// Builds an identity from a process ID and a Windows `FILETIME` value.
19    ///
20    /// The creation time is counted in 100-nanosecond units since
21    /// 1601-01-01 UTC. PID 0 and the native invalid sentinel are rejected.
22    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    /// Returns the process identifier.
47    #[must_use]
48    pub const fn pid(self) -> u32 {
49        self.pid
50    }
51
52    /// Returns the creation time in 100-nanosecond ticks since 1601-01-01 UTC.
53    #[must_use]
54    pub const fn creation_time_100ns_since_1601(self) -> u64 {
55        self.creation_time_100ns_since_1601
56    }
57}
58
59/// The kind of an affected application, corresponding to `RM_APP_TYPE`.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61#[non_exhaustive]
62pub enum ApplicationType {
63    /// Windows explicitly reported `RmUnknownApp`.
64    Unknown,
65    /// An application with a top-level window.
66    MainWindow,
67    /// An application without a top-level window.
68    OtherWindow,
69    /// A Windows service.
70    Service,
71    /// Windows Explorer.
72    Explorer,
73    /// A console application.
74    Console,
75    /// Windows reported `RmCritical`.
76    ///
77    /// This can describe applications that cannot be shut down without a
78    /// reboot; it is not merely a process criticality flag.
79    Critical,
80    /// A value introduced by a newer Windows version.
81    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    /// Returns the underlying `RM_APP_TYPE` integer.
99    #[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    /// OR-able history bits from `RM_APP_STATUS`.
116    ///
117    /// Zero means that Windows did not report a known state. Unknown bits are
118    /// retained so newer Windows releases remain lossless.
119    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120    pub struct ApplicationStatus: u32 {
121        /// The application is running.
122        const RUNNING = 0x01;
123        /// Restart Manager stopped the application.
124        const STOPPED = 0x02;
125        /// Something other than Restart Manager stopped the application.
126        const STOPPED_OTHER = 0x04;
127        /// Restart Manager restarted the application.
128        const RESTARTED = 0x08;
129        /// Restart Manager could not stop the application.
130        const ERROR_ON_STOP = 0x10;
131        /// Restart Manager could not restart the application.
132        const ERROR_ON_RESTART = 0x20;
133        /// A filter masked shutdown.
134        const SHUTDOWN_MASKED = 0x40;
135        /// A filter masked restart.
136        const RESTART_MASKED = 0x80;
137    }
138}
139
140bitflags! {
141    /// Reasons Windows says a reboot may be required.
142    ///
143    /// Unknown bits are retained.
144    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
145    pub struct RebootReasons: u32 {
146        /// Access was denied while inspecting or acting on a resource.
147        const PERMISSION_DENIED = 0x01;
148        /// An affected process is in a different Terminal Services session.
149        const SESSION_MISMATCH = 0x02;
150        /// An affected process was reported as critical.
151        const CRITICAL_PROCESS = 0x04;
152        /// An affected service was reported as critical.
153        const CRITICAL_SERVICE = 0x08;
154        /// Restart Manager detected the caller among the affected processes.
155        const DETECTED_SELF = 0x10;
156    }
157}
158
159/// One application or service using a registered resource.
160#[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    /// Returns the display name without lossy Unicode conversion.
173    #[must_use]
174    pub fn display_name(&self) -> &OsStr {
175        &self.display_name
176    }
177
178    /// Returns the service short name for service entries.
179    #[must_use]
180    pub fn service_name(&self) -> Option<&OsStr> {
181        self.service_name.as_deref()
182    }
183
184    /// Returns the kind reported by Windows.
185    #[must_use]
186    pub const fn application_type(&self) -> ApplicationType {
187        self.application_type
188    }
189
190    /// Returns the accumulated status/history bits.
191    #[must_use]
192    pub const fn status(&self) -> ApplicationStatus {
193        self.status
194    }
195
196    /// Reports whether Windows can restart this application.
197    #[must_use]
198    pub const fn is_restartable(&self) -> bool {
199        self.restartable
200    }
201
202    /// Returns the process identity, if this entry has a valid process.
203    #[must_use]
204    pub const fn process(&self) -> Option<ProcessIdentity> {
205        self.process
206    }
207
208    /// Returns the Terminal Services session ID when Windows supplied one.
209    #[must_use]
210    pub const fn terminal_session_id(&self) -> Option<u32> {
211        self.terminal_session_id
212    }
213}
214
215/// A reusable affected-application report plus its reboot reasons.
216#[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    /// Returns the affected entries.
224    #[must_use]
225    pub fn applications(&self) -> &[AffectedApplication] {
226        &self.applications
227    }
228
229    /// Returns the reboot reasons supplied with this snapshot.
230    #[must_use]
231    pub const fn reboot_reasons(&self) -> RebootReasons {
232        self.reboot_reasons
233    }
234
235    /// Returns an iterator over the affected entries.
236    pub fn iter(&self) -> std::slice::Iter<'_, AffectedApplication> {
237        self.applications.iter()
238    }
239
240    /// Returns whether the report contains no affected entries.
241    #[must_use]
242    pub fn is_empty(&self) -> bool {
243        self.applications.is_empty()
244    }
245
246    /// Returns the number of affected entries.
247    #[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}