restart_manager/
application_restart.rs1use std::ffi::{OsStr, OsString};
4use std::fmt;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7use crate::input::contains_nul;
8use crate::session::map_sys_error;
9use crate::{Error, ErrorKind, Result};
10
11const MAX_RESTART_ARGUMENT_UNITS: usize = 1024;
12static REGISTRATION_OWNED: AtomicBool = AtomicBool::new(false);
13
14#[derive(Clone, PartialEq, Eq)]
19pub struct ApplicationRestartOptions {
20 arguments: OsString,
21 restart_on_crash: bool,
22 restart_on_hang: bool,
23 restart_on_update: bool,
24 restart_on_reboot: bool,
25}
26
27impl Default for ApplicationRestartOptions {
28 fn default() -> Self {
29 Self {
30 arguments: OsString::new(),
31 restart_on_crash: true,
32 restart_on_hang: true,
33 restart_on_update: true,
34 restart_on_reboot: true,
35 }
36 }
37}
38
39impl fmt::Debug for ApplicationRestartOptions {
40 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
41 formatter
42 .debug_struct("ApplicationRestartOptions")
43 .field("arguments", &"<redacted>")
44 .field("restart_on_crash", &self.restart_on_crash)
45 .field("restart_on_hang", &self.restart_on_hang)
46 .field("restart_on_update", &self.restart_on_update)
47 .field("restart_on_reboot", &self.restart_on_reboot)
48 .finish()
49 }
50}
51
52impl ApplicationRestartOptions {
53 #[must_use]
55 pub fn new(arguments: impl Into<OsString>) -> Self {
56 Self {
57 arguments: arguments.into(),
58 ..Self::default()
59 }
60 }
61
62 #[must_use]
64 pub fn with_arguments(mut self, arguments: impl Into<OsString>) -> Self {
65 self.arguments = arguments.into();
66 self
67 }
68
69 #[must_use]
71 pub const fn with_restart_on_crash(mut self, enabled: bool) -> Self {
72 self.restart_on_crash = enabled;
73 self
74 }
75
76 #[must_use]
78 pub const fn with_restart_on_hang(mut self, enabled: bool) -> Self {
79 self.restart_on_hang = enabled;
80 self
81 }
82
83 #[must_use]
85 pub const fn with_restart_on_update(mut self, enabled: bool) -> Self {
86 self.restart_on_update = enabled;
87 self
88 }
89
90 #[must_use]
92 pub const fn with_restart_on_reboot(mut self, enabled: bool) -> Self {
93 self.restart_on_reboot = enabled;
94 self
95 }
96
97 #[must_use]
99 pub const fn restart_on_crash(&self) -> bool {
100 self.restart_on_crash
101 }
102
103 #[must_use]
105 pub const fn restart_on_hang(&self) -> bool {
106 self.restart_on_hang
107 }
108
109 #[must_use]
111 pub const fn restart_on_update(&self) -> bool {
112 self.restart_on_update
113 }
114
115 #[must_use]
117 pub const fn restart_on_reboot(&self) -> bool {
118 self.restart_on_reboot
119 }
120
121 fn native_flags(&self) -> u32 {
122 ((!self.restart_on_crash) as u32)
123 | (((!self.restart_on_hang) as u32) << 1)
124 | (((!self.restart_on_update) as u32) << 2)
125 | (((!self.restart_on_reboot) as u32) << 3)
126 }
127}
128
129pub struct ApplicationRestartRegistration {
131 options: ApplicationRestartOptions,
132 armed: bool,
133}
134
135impl fmt::Debug for ApplicationRestartRegistration {
136 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
137 formatter
138 .debug_struct("ApplicationRestartRegistration")
139 .field("options", &self.options)
140 .field("armed", &self.armed)
141 .finish()
142 }
143}
144
145impl ApplicationRestartRegistration {
146 pub fn register(options: ApplicationRestartOptions) -> Result<Self> {
148 validate_arguments(&options.arguments)?;
149 if REGISTRATION_OWNED
150 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
151 .is_err()
152 {
153 return Err(Error::new(
154 ErrorKind::ApplicationRestartInUse,
155 None,
156 "this process already owns an application restart registration",
157 ));
158 }
159 if let Err(error) =
160 crate::sys::register_application_restart(&options.arguments, options.native_flags())
161 {
162 REGISTRATION_OWNED.store(false, Ordering::Release);
163 return Err(map_sys_error(error));
164 }
165 Ok(Self {
166 options,
167 armed: true,
168 })
169 }
170
171 pub fn update(&mut self, options: ApplicationRestartOptions) -> Result<()> {
173 validate_arguments(&options.arguments)?;
174 crate::sys::register_application_restart(&options.arguments, options.native_flags())
175 .map_err(map_sys_error)?;
176 self.options = options;
177 Ok(())
178 }
179
180 pub fn unregister(mut self) -> Result<()> {
185 match crate::sys::unregister_application_restart() {
186 Ok(()) => {
187 self.disarm();
188 Ok(())
189 }
190 Err(error) => Err(map_sys_error(error)),
191 }
192 }
193
194 fn disarm(&mut self) {
195 self.armed = false;
196 REGISTRATION_OWNED.store(false, Ordering::Release);
197 }
198}
199
200impl Drop for ApplicationRestartRegistration {
201 fn drop(&mut self) {
202 if self.armed {
203 let _ = crate::sys::unregister_application_restart();
204 self.disarm();
205 }
206 }
207}
208
209fn validate_arguments(arguments: &OsStr) -> Result<()> {
210 if contains_nul(arguments) {
211 return Err(Error::new(
212 ErrorKind::InvalidInput,
213 None,
214 "application restart arguments may not contain an embedded NUL",
215 ));
216 }
217 if utf16_unit_count(arguments) > MAX_RESTART_ARGUMENT_UNITS {
218 return Err(Error::new(
219 ErrorKind::InvalidInput,
220 None,
221 "application restart arguments exceed 1024 UTF-16 code units",
222 ));
223 }
224 Ok(())
225}
226
227#[cfg(windows)]
228fn utf16_unit_count(value: &OsStr) -> usize {
229 use std::os::windows::ffi::OsStrExt;
230 value.encode_wide().count()
231}
232
233#[cfg(not(windows))]
234fn utf16_unit_count(value: &OsStr) -> usize {
235 value.to_string_lossy().encode_utf16().count()
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn options_use_positive_policies_and_redact_arguments() {
244 let options = ApplicationRestartOptions::new("secret")
245 .with_restart_on_crash(false)
246 .with_restart_on_reboot(false);
247 assert!(!options.restart_on_crash());
248 assert!(options.restart_on_hang());
249 assert!(options.restart_on_update());
250 assert!(!options.restart_on_reboot());
251 assert_eq!(options.native_flags(), 0x09);
252 let debug = format!("{options:?}");
253 assert!(debug.contains("<redacted>"));
254 assert!(!debug.contains("secret"));
255 }
256
257 #[test]
258 fn arguments_validate_nul_and_utf16_limit() {
259 assert!(validate_arguments(OsStr::new("bad\0argument")).is_err());
260 assert!(validate_arguments(OsStr::new(&"x".repeat(1024))).is_ok());
261 assert!(validate_arguments(OsStr::new(&"x".repeat(1025))).is_err());
262 }
263
264 #[cfg(windows)]
265 #[test]
266 fn all_builders_registration_debug_and_drop_are_exercised() {
267 let options = ApplicationRestartOptions::default()
268 .with_arguments("--updated")
269 .with_restart_on_hang(false)
270 .with_restart_on_update(false)
271 .with_restart_on_reboot(false);
272 assert!(options.restart_on_crash());
273 assert!(!options.restart_on_hang());
274 assert!(!options.restart_on_update());
275 assert!(!options.restart_on_reboot());
276
277 let registration = ApplicationRestartRegistration::register(options).unwrap();
278 let debug = format!("{registration:?}");
279 assert!(debug.contains("ApplicationRestartRegistration"));
280 assert!(debug.contains("<redacted>"));
281 drop(registration);
282
283 ApplicationRestartRegistration::register(ApplicationRestartOptions::default())
284 .unwrap()
285 .unregister()
286 .unwrap();
287 }
288}