Skip to main content

self_update_extras/
restart.rs

1//! Restart wrapper: re-executes the process after a successful update.
2
3use self_update::Status;
4use self_update::errors::{Error, Result};
5use self_update::update::{Release, ReleaseUpdate, UpdateStatus};
6
7/// Default guard environment variable name.
8const DEFAULT_GUARD_ENV: &str = "RESTART_GUARD";
9
10/// Builder for a restart [`Update`].
11///
12/// Configure the inner [`ReleaseUpdate`] backend and an optional guard
13/// environment variable, then call [`build`](Self::build) to produce a
14/// `Box<dyn ReleaseUpdate>` that re-executes the process after a successful
15/// update.
16#[derive(Default)]
17pub struct UpdateBuilder {
18    release_update: Option<Box<dyn ReleaseUpdate>>,
19    guard_env: Option<String>,
20}
21
22impl UpdateBuilder {
23    /// Initialize a new builder.
24    pub fn new() -> Self {
25        Default::default()
26    }
27
28    /// Set the release update implementation to wrap. Required.
29    pub fn release_update(&mut self, release_update: Box<dyn ReleaseUpdate>) -> &mut Self {
30        self.release_update = Some(release_update);
31        self
32    }
33
34    /// Set the guard environment variable name used to prevent restart loops.
35    /// Defaults to `RESTART_GUARD`.
36    pub fn guard_env(&mut self, env: &str) -> &mut Self {
37        self.guard_env = Some(env.to_owned());
38        self
39    }
40
41    /// Confirm config and create a ready-to-use `Update`.
42    ///
43    /// * Errors:
44    ///     * Config - `release_update` was not provided
45    pub fn build(&mut self) -> Result<Box<dyn ReleaseUpdate>> {
46        let inner = self
47            .release_update
48            .take()
49            .ok_or_else(|| Error::Config("`release_update` required".to_owned()))?;
50
51        Ok(Box::new(Update {
52            inner,
53            guard_env: self
54                .guard_env
55                .clone()
56                .unwrap_or_else(|| DEFAULT_GUARD_ENV.to_owned()),
57        }))
58    }
59}
60
61/// Wraps a [`ReleaseUpdate`] and restarts the process with the freshly
62/// installed binary after a successful update.
63///
64/// After `update()` returns `Status::Updated`, the process is re-executed
65/// using the new binary. A guard environment variable prevents restart loops:
66/// once the restarted process detects the guard, it returns `UpToDate` instead
67/// of restarting again.
68///
69/// # Platform behavior
70///
71/// - **Unix**: Replaces the current process image via `exec` (never returns on success).
72/// - **Windows**: Spawns the new binary and exits with the child's status code.
73///
74/// # Guard environment variable
75///
76/// Defaults to `RESTART_GUARD` but can be customized via
77/// [`UpdateBuilder::guard_env`](UpdateBuilder::guard_env). The value is set to
78/// `"1"` during re-execution.
79pub struct Update {
80    inner: Box<dyn ReleaseUpdate>,
81    guard_env: String,
82}
83
84impl Update {
85    /// Initialize a new `Update` builder.
86    pub fn configure() -> UpdateBuilder {
87        UpdateBuilder::new()
88    }
89
90    /// The guard environment variable used to prevent restart loops.
91    pub fn guard_env(&self) -> &str {
92        &self.guard_env
93    }
94
95    /// Restart the process using the freshly installed executable.
96    ///
97    /// On Unix the current process image is replaced via `exec`, so this never
98    /// returns on success. Windows has no `exec`, so the new binary is spawned
99    /// and the current process exits with the child's status code.
100    fn restart(&self) -> Result<()> {
101        let args: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect();
102        let mut command = std::process::Command::new(self.inner.bin_install_path());
103        command.args(&args).env(self.guard_env.clone(), "1");
104
105        #[cfg(unix)]
106        {
107            use std::os::unix::process::CommandExt;
108            let err = command.exec();
109            Err(Error::Release(format!(
110                "re-executing updated binary: {}",
111                err
112            )))
113        }
114
115        #[cfg(windows)]
116        {
117            let status = command
118                .status()
119                .map_err(|e| Error::Release(format!("re-executing updated binary: {}", e)))?;
120            std::process::exit(status.code().unwrap_or(0));
121        }
122    }
123}
124
125impl ReleaseUpdate for Update {
126    fn get_latest_release(&self) -> Result<Release> {
127        self.inner.get_latest_release()
128    }
129
130    fn get_latest_releases(&self, current_version: &str) -> Result<Vec<Release>> {
131        self.inner.get_latest_releases(current_version)
132    }
133
134    fn get_release_version(&self, ver: &str) -> Result<Release> {
135        self.inner.get_release_version(ver)
136    }
137
138    fn current_version(&self) -> String {
139        self.inner.current_version()
140    }
141
142    fn target(&self) -> String {
143        self.inner.target()
144    }
145
146    fn target_version(&self) -> Option<String> {
147        self.inner.target_version()
148    }
149
150    fn bin_name(&self) -> String {
151        self.inner.bin_name()
152    }
153
154    fn bin_install_path(&self) -> std::path::PathBuf {
155        self.inner.bin_install_path()
156    }
157
158    fn bin_path_in_archive(&self) -> String {
159        self.inner.bin_path_in_archive()
160    }
161
162    fn show_download_progress(&self) -> bool {
163        self.inner.show_download_progress()
164    }
165
166    fn show_output(&self) -> bool {
167        self.inner.show_output()
168    }
169
170    fn no_confirm(&self) -> bool {
171        self.inner.no_confirm()
172    }
173
174    fn progress_template(&self) -> String {
175        self.inner.progress_template()
176    }
177
178    fn progress_chars(&self) -> String {
179        self.inner.progress_chars()
180    }
181
182    fn auth_token(&self) -> Option<String> {
183        self.inner.auth_token()
184    }
185
186    fn update(&self) -> Result<Status> {
187        let current_version = self.current_version();
188        self.update_extended()
189            .map(|s| s.into_status(current_version))
190    }
191
192    fn update_extended(&self) -> Result<UpdateStatus> {
193        // Already restarted after an update: nothing left to do.
194        if std::env::var_os(&self.guard_env).is_some() {
195            return Ok(UpdateStatus::UpToDate);
196        }
197
198        let status = self.inner.update_extended()?;
199        if let UpdateStatus::Updated(_) = status {
200            self.restart()?;
201        }
202        Ok(status)
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::test_support::MockRelease;
210
211    // These tests mutate a process-global environment variable, so they run
212    // serially. The mock only reports `Updated` when the guard is set, which
213    // means `inner.update` is short-circuited before any real re-exec occurs.
214
215    #[test]
216    #[serial_test::serial(env_guard)]
217    fn update_is_skipped_when_guard_env_is_set() {
218        let guard = "AUTO_UPDATE_TEST_GUARD_SET";
219        unsafe { std::env::set_var(guard, "1") };
220
221        let mock = MockRelease::new("mock-guard-set").report_updated(true);
222        let calls = mock.call_counter();
223        let updater = Update::configure()
224            .release_update(Box::new(mock))
225            .guard_env(guard)
226            .build()
227            .unwrap();
228
229        let status = updater.update().unwrap();
230
231        assert!(matches!(status, Status::UpToDate(_)));
232        assert_eq!(
233            calls.get(),
234            0,
235            "inner update must be skipped when the guard is set"
236        );
237
238        unsafe { std::env::remove_var(guard) };
239    }
240
241    #[test]
242    #[serial_test::serial(env_guard)]
243    fn update_extended_is_skipped_when_guard_env_is_set() {
244        let guard = "AUTO_UPDATE_TEST_GUARD_EXT";
245        unsafe { std::env::set_var(guard, "1") };
246
247        let mock = MockRelease::new("mock-guard-ext");
248        let calls = mock.call_counter();
249        let updater = Update::configure()
250            .release_update(Box::new(mock))
251            .guard_env(guard)
252            .build()
253            .unwrap();
254
255        let status = updater.update_extended().unwrap();
256
257        assert!(matches!(status, UpdateStatus::UpToDate));
258        assert_eq!(calls.get(), 0);
259
260        unsafe { std::env::remove_var(guard) };
261    }
262
263    #[test]
264    #[serial_test::serial(env_guard)]
265    fn update_runs_inner_when_guard_env_is_unset() {
266        let guard = "AUTO_UPDATE_TEST_GUARD_UNSET";
267        unsafe { std::env::remove_var(guard) };
268
269        // Mock reports `UpToDate`, so no restart (process re-exec) is triggered.
270        let mock = MockRelease::new("mock-guard-unset");
271        let calls = mock.call_counter();
272        let updater = Update::configure()
273            .release_update(Box::new(mock))
274            .guard_env(guard)
275            .build()
276            .unwrap();
277
278        let status = updater.update().unwrap();
279
280        assert!(matches!(status, Status::UpToDate(_)));
281        assert_eq!(calls.get(), 1);
282    }
283
284    #[test]
285    fn guard_env_getter_reports_the_configured_guard() {
286        let updater = Update {
287            inner: Box::new(MockRelease::new("mock-guard-getter")),
288            guard_env: "CUSTOM_GUARD".to_owned(),
289        };
290        assert_eq!(updater.guard_env(), "CUSTOM_GUARD");
291    }
292
293    #[test]
294    fn build_requires_a_release_update() {
295        assert!(Update::configure().build().is_err());
296    }
297
298    #[test]
299    fn metadata_methods_delegate_to_inner() {
300        let updater = Update::configure()
301            .release_update(Box::new(MockRelease::new("mock-forward")))
302            .build()
303            .unwrap();
304
305        assert_eq!(updater.current_version(), "1.0.0");
306        assert_eq!(updater.target(), "test-target");
307        assert_eq!(updater.target_version(), None);
308        assert_eq!(updater.bin_name(), "mock-forward");
309        assert_eq!(
310            updater.bin_install_path(),
311            std::env::temp_dir().join("mock-forward")
312        );
313        assert_eq!(updater.bin_path_in_archive(), "mock-forward");
314        assert!(!updater.show_download_progress());
315        assert!(!updater.show_output());
316        assert!(updater.no_confirm());
317        assert_eq!(updater.progress_template(), "");
318        assert_eq!(updater.progress_chars(), "");
319        assert_eq!(updater.auth_token(), None);
320        assert!(updater.get_latest_release().is_ok());
321        assert!(updater.get_latest_releases("1.0.0").is_ok());
322        assert!(updater.get_release_version("1.0.0").is_ok());
323    }
324}