Skip to main content

restart_manager/
filter.rs

1//! Typed shutdown and restart filters.
2
3use std::ffi::{OsStr, OsString};
4use std::path::{Path, PathBuf};
5
6use crate::input::{
7    absolute_user_path, validate_absolute_os_path, validate_os_output, validate_user_os_value,
8};
9use crate::{ProcessIdentity, Result};
10
11/// The executable, process, or service selected by a filter.
12///
13/// The representation is intentionally opaque. Executable paths are made
14/// absolute exactly once during construction, so a later working-directory
15/// change cannot alter the identity used to remove the filter.
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct FilterTarget {
18    kind: TargetKind,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22enum TargetKind {
23    Executable(PathBuf),
24    Process(ProcessIdentity),
25    Service(OsString),
26}
27
28impl FilterTarget {
29    /// Creates a validated executable-path target.
30    pub fn executable(path: impl Into<PathBuf>) -> Result<Self> {
31        let path = absolute_user_path(path.into(), "an executable path")?;
32        Ok(Self {
33            kind: TargetKind::Executable(path),
34        })
35    }
36
37    /// Creates a process target.
38    #[must_use]
39    pub const fn process(process: ProcessIdentity) -> Self {
40        Self {
41            kind: TargetKind::Process(process),
42        }
43    }
44
45    /// Creates a validated service-short-name target.
46    pub fn service(name: impl Into<OsString>) -> Result<Self> {
47        let name = name.into();
48        validate_user_os_value(&name, "a service short name")?;
49        Ok(Self {
50            kind: TargetKind::Service(name),
51        })
52    }
53
54    /// Returns the absolute executable path for an executable filter.
55    #[must_use]
56    pub fn as_executable(&self) -> Option<&Path> {
57        match &self.kind {
58            TargetKind::Executable(path) => Some(path),
59            TargetKind::Process(_) | TargetKind::Service(_) => None,
60        }
61    }
62
63    /// Returns the process identity for a process filter.
64    #[must_use]
65    pub const fn as_process(&self) -> Option<ProcessIdentity> {
66        match self.kind {
67            TargetKind::Process(process) => Some(process),
68            TargetKind::Executable(_) | TargetKind::Service(_) => None,
69        }
70    }
71
72    /// Returns the service short name for a service filter.
73    #[must_use]
74    pub fn as_service(&self) -> Option<&OsStr> {
75        match &self.kind {
76            TargetKind::Service(name) => Some(name),
77            TargetKind::Executable(_) | TargetKind::Process(_) => None,
78        }
79    }
80
81    pub(crate) fn from_raw_executable(path: PathBuf) -> Result<Self> {
82        validate_absolute_os_path(&path, "executable filter path")?;
83        Ok(Self {
84            kind: TargetKind::Executable(path),
85        })
86    }
87
88    pub(crate) const fn from_raw_process(process: ProcessIdentity) -> Self {
89        Self::process(process)
90    }
91
92    pub(crate) fn from_raw_service(name: OsString) -> Result<Self> {
93        validate_os_output(&name, "service filter name")?;
94        Ok(Self {
95            kind: TargetKind::Service(name),
96        })
97    }
98}
99
100/// The official `RM_FILTER_ACTION` behavior.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
102pub enum FilterAction {
103    /// Permit shutdown but prevent a later restart (`RmNoRestart`).
104    PreventRestart,
105    /// Prevent both shutdown and restart (`RmNoShutdown`).
106    PreventShutdown,
107}
108
109/// One filter returned by [`crate::RestartSession::filters`].
110#[derive(Debug, Clone, PartialEq, Eq, Hash)]
111pub struct Filter {
112    pub(crate) target: FilterTarget,
113    pub(crate) action: FilterAction,
114}
115
116impl Filter {
117    /// Returns the selected resource.
118    #[must_use]
119    pub const fn target(&self) -> &FilterTarget {
120        &self.target
121    }
122
123    /// Returns the modification applied to that resource.
124    #[must_use]
125    pub const fn action(&self) -> FilterAction {
126        self.action
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn targets_are_validated_and_disjoint() {
136        let executable = FilterTarget::executable("demo.exe").unwrap();
137        assert!(executable.as_executable().unwrap().is_absolute());
138        assert_eq!(executable.as_process(), None);
139        assert_eq!(executable.as_service(), None);
140
141        let process = ProcessIdentity::from_raw_parts(1, 2).unwrap();
142        let process_target = FilterTarget::process(process);
143        assert_eq!(process_target.as_process(), Some(process));
144
145        let service = FilterTarget::service("EventLog").unwrap();
146        assert_eq!(service.as_service(), Some(OsStr::new("EventLog")));
147        assert!(FilterTarget::service("").is_err());
148        assert!(FilterTarget::executable("").is_err());
149        assert!(FilterTarget::service("bad\0name").is_err());
150    }
151}