chj_unix_util/daemon/
warrants_restart.rs

1//! Trait and helpers to create restart need checkers for
2//! `Daemon.other_restart_checks`.
3
4use std::{
5    io::{stderr, ErrorKind},
6    ops::Deref,
7    time::SystemTime,
8};
9
10use anyhow::Result;
11
12use crate::{
13    eval_with_default::EvalWithDefault, re_exec::current_exe,
14    timestamp_formatter::TimestampFormatter,
15};
16
17/// The base trait for all "other" restart checkers.
18pub trait WarrantsRestart {
19    /// Whether a restart is warranted given the current situation.
20    fn warrants_restart(&self) -> bool;
21}
22
23#[derive(Debug, Clone, Copy, Default, clap::Args)]
24pub struct RestartForExecutableChangeOpts {
25    #[clap(long)]
26    pub restart_on_upgrades: bool,
27
28    /// Whether to restart the daemon when its executable is changed
29    /// (upgraded). Restarts only happen at safe checkpoints. The
30    /// options cancel each other out; the default is determined by
31    /// the application (see help text higher up).
32    #[clap(long)]
33    pub no_restart_on_upgrades: bool,
34}
35
36impl EvalWithDefault for RestartForExecutableChangeOpts {
37    fn explicit_yes_and_no(&self) -> (bool, bool) {
38        let RestartForExecutableChangeOpts {
39            restart_on_upgrades,
40            no_restart_on_upgrades,
41        } = self;
42        (*restart_on_upgrades, *no_restart_on_upgrades)
43    }
44}
45
46impl RestartForExecutableChangeOpts {
47    pub fn to_restarter(
48        &self,
49        default_want_restarting: bool,
50        timestamp_formatter: TimestampFormatter,
51    ) -> Result<RestartForExecutableChange> {
52        let restart_on_upgrades = self.eval_with_default(default_want_restarting);
53        let (do_check, opt_binary_mtime) = if restart_on_upgrades {
54            // Consciously *not* using the 'fixed' `current_exe`: it's
55            // *good* if we get the mangled path because our real path
56            // is gone, so that we don't use the wrong mtime.
57            let path = std::env::current_exe()?;
58            match path.metadata() {
59                Ok(m) => {
60                    if let Ok(mtime) = m.modified() {
61                        (true, Some(mtime))
62                    } else {
63                        (false, None)
64                    }
65                }
66                Err(e) => {
67                    let restart_if_path_shows_up_later = e.kind() == ErrorKind::NotFound;
68                    (restart_if_path_shows_up_later, None)
69                }
70            }
71        } else {
72            (false, None)
73        };
74        Ok(RestartForExecutableChange {
75            timestamp_formatter,
76            do_check,
77            opt_binary_mtime,
78        })
79    }
80}
81
82#[derive(Debug, Clone)]
83pub struct RestartForExecutableChange {
84    timestamp_formatter: TimestampFormatter,
85    do_check: bool,
86    opt_binary_mtime: Option<SystemTime>,
87}
88
89impl WarrantsRestart for &RestartForExecutableChange {
90    fn warrants_restart(&self) -> bool {
91        self.do_check && {
92            match current_exe() {
93                Ok(binary) => {
94                    if let Ok(metadata) = binary.metadata() {
95                        if let Ok(new_mtime) = metadata.modified() {
96                            let old_mtime = self.opt_binary_mtime;
97                            // if new_mtime > *mtime {  ? or allow downgrades, too:
98                            if Some(new_mtime) != old_mtime {
99                                use std::io::Write;
100                                let tmp;
101                                _ = writeln!(
102                                    &mut stderr(),
103                                    "the binary at {binary:?} has updated, from \
104                                     {} to {}, going to restart",
105                                    if let Some(old_mtime) = old_mtime {
106                                        tmp = self.timestamp_formatter.format_systemtime(old_mtime);
107                                        &tmp
108                                    } else {
109                                        "(none)"
110                                    },
111                                    self.timestamp_formatter.format_systemtime(new_mtime)
112                                );
113                                true
114                            } else {
115                                false
116                            }
117                        } else {
118                            false
119                        }
120                    } else {
121                        false
122                    }
123                }
124                Err(_) => false,
125            }
126        }
127    }
128}
129
130impl RestartForExecutableChange {
131    pub fn and_config_change_opts<RestartForConfigChange: Deref<Target: WarrantsRestart>>(
132        self,
133        restart_for_config_change_opts: RestartForConfigChangeOpts,
134        default_restart_for_config_change: bool,
135        restart_for_config_change: RestartForConfigChange,
136    ) -> RestartForExecutableOrConfigChange<RestartForConfigChange> {
137        let restart_for_executable_change = self;
138        let restart_for_config_change = if restart_for_config_change_opts
139            .eval_with_default(default_restart_for_config_change)
140        {
141            Some(restart_for_config_change)
142        } else {
143            None
144        };
145
146        RestartForExecutableOrConfigChange {
147            restart_for_executable_change,
148            restart_for_config_change,
149        }
150    }
151}
152
153// Derefence to itself. OOKAY? -- Oh, *this* one leads to infinite recursion.
154// impl Deref for RestartForExecutableChange {
155//     type Target = RestartForExecutableChange;
156
157//     fn deref(&self) -> &Self::Target {
158//         self
159//     }
160// }
161
162#[derive(Debug, Clone, Copy, Default, clap::Args)]
163pub struct RestartForConfigChangeOpts {
164    #[clap(long)]
165    pub restart_on_config_change: bool,
166
167    /// Whether to restart the daemon when its configuration is
168    /// changed. Restarts only happen at safe checkpoints. The options
169    /// cancel each other out; the default is determined by the
170    /// application (see help text higher up).
171    #[clap(long)]
172    pub no_restart_on_config_change: bool,
173}
174
175impl EvalWithDefault for RestartForConfigChangeOpts {
176    fn explicit_yes_and_no(&self) -> (bool, bool) {
177        let RestartForConfigChangeOpts {
178            restart_on_config_change,
179            no_restart_on_config_change,
180        } = self;
181        (*restart_on_config_change, *no_restart_on_config_change)
182    }
183}
184
185#[derive(Debug, Clone)]
186pub struct RestartForExecutableOrConfigChange<
187    RestartForConfigChange: Deref<Target: WarrantsRestart>,
188> {
189    restart_for_executable_change: RestartForExecutableChange,
190    restart_for_config_change: Option<RestartForConfigChange>,
191}
192
193impl<RestartForConfigChange: Deref<Target: WarrantsRestart>> WarrantsRestart
194    for RestartForExecutableOrConfigChange<RestartForConfigChange>
195{
196    fn warrants_restart(&self) -> bool {
197        (&self.restart_for_executable_change).warrants_restart()
198            || if let Some(restart_for_config_change) = &self.restart_for_config_change {
199                if restart_for_config_change.warrants_restart() {
200                    use std::io::Write;
201                    _ = writeln!(
202                        &mut stderr(),
203                        "the configuration has updated, going to restart",
204                    );
205                    true
206                } else {
207                    false
208                }
209            } else {
210                false
211            }
212    }
213}
214
215// Derefence to itself. OOKAY?
216impl<RestartForConfigChange: Deref<Target: WarrantsRestart>> Deref
217    for RestartForExecutableOrConfigChange<RestartForConfigChange>
218{
219    type Target = RestartForExecutableOrConfigChange<RestartForConfigChange>;
220
221    fn deref(&self) -> &Self::Target {
222        self
223    }
224}
225
226/// For when no situations (other than the daemon state, i.e. the user
227/// explicitly asking for it) are ever warranting restart.
228#[derive(Debug, Clone)]
229pub struct NoOtherRestarts;
230
231impl WarrantsRestart for NoOtherRestarts {
232    fn warrants_restart(&self) -> bool {
233        false
234    }
235}
236
237// Derefence to itself. OOKAY?
238impl Deref for NoOtherRestarts {
239    type Target = NoOtherRestarts;
240
241    fn deref(&self) -> &Self::Target {
242        self
243    }
244}
245
246// Do we even need this, for after Deref?
247// impl WarrantsRestart for &NoOtherRestarts {
248//     fn warrants_restart(&self) -> bool {
249//         false
250//     }
251// }