1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use once_cell::sync::Lazy;

use crate::REGISTRY;

/// Initialize signal handlers and other state to keep track of tempfiles, and **must be called before the first tempfile is created**,
/// allowing to set the `mode` in which signal handlers are installed.
///
/// Only has an effect the first time it is called.
///
/// Note that it is possible to not call this function and instead call
/// [`registry::cleanup_tempfiles_signal_safe()`][crate::registry::cleanup_tempfiles_signal_safe()]
/// from a signal handler under the application's control.
pub fn setup(mode: handler::Mode) {
    handler::MODE.store(mode as usize, std::sync::atomic::Ordering::SeqCst);
    Lazy::force(&REGISTRY);
}

///
#[allow(clippy::empty_docs)]
pub mod handler {
    use std::sync::atomic::AtomicUsize;

    pub(crate) static MODE: AtomicUsize = AtomicUsize::new(Mode::None as usize);

    /// Define how our signal handlers act
    #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
    pub enum Mode {
        /// Do not install a signal handler at all, but have somebody else call our handler directly.
        None = 0,
        /// Delete all remaining registered tempfiles on termination.
        DeleteTempfilesOnTermination = 1,
        /// Delete all remaining registered tempfiles on termination and emulate the default handler behaviour.
        ///
        /// This typically leads to the process being aborted.
        DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour = 2,
    }

    impl Default for Mode {
        /// By default we will emulate the default behaviour and abort the process.
        ///
        /// While testing, we will not abort the process.
        fn default() -> Self {
            if cfg!(test) {
                Mode::DeleteTempfilesOnTermination
            } else {
                Mode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour
            }
        }
    }

    /// On linux we can handle the actual signal as we know it.
    #[cfg(not(windows))]
    pub(crate) fn cleanup_tempfiles_nix(sig: &libc::siginfo_t) {
        crate::registry::cleanup_tempfiles_signal_safe();
        let restore_original_behaviour = Mode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour as usize;
        if MODE.load(std::sync::atomic::Ordering::SeqCst) == restore_original_behaviour {
            signal_hook::low_level::emulate_default_handler(sig.si_signo).ok();
        }
    }

    /// On windows, assume sig-term and emulate sig-term unconditionally.
    #[cfg(windows)]
    pub(crate) fn cleanup_tempfiles_windows() {
        crate::registry::cleanup_tempfiles_signal_safe();
        let restore_original_behaviour = Mode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour as usize;
        if MODE.load(std::sync::atomic::Ordering::SeqCst) == restore_original_behaviour {
            signal_hook::low_level::emulate_default_handler(signal_hook::consts::SIGTERM).ok();
        }
    }

    #[cfg(test)]
    mod tests {
        use std::path::Path;

        use crate::{AutoRemove, ContainingDirectory};

        fn filecount_in(path: impl AsRef<Path>) -> usize {
            std::fs::read_dir(path).expect("valid dir").count()
        }

        #[test]
        fn various_termination_signals_remove_tempfiles_unconditionally() -> Result<(), Box<dyn std::error::Error>> {
            crate::signal::setup(Default::default());
            let dir = tempfile::tempdir()?;
            for sig in signal_hook::consts::TERM_SIGNALS {
                let _tempfile = crate::new(dir.path(), ContainingDirectory::Exists, AutoRemove::Tempfile)?;
                assert_eq!(
                    filecount_in(dir.path()),
                    1,
                    "only one tempfile exists no matter the iteration"
                );
                signal_hook::low_level::raise(*sig)?;
                assert_eq!(
                    filecount_in(dir.path()),
                    0,
                    "the signal triggers removal but won't terminate the process (anymore)"
                );
            }
            Ok(())
        }
    }
}