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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Mortem is a library for that strives to achieve one thing; ensuring the executable is deleted
//! after execution stops and be out of the way while doing so.
//!
//! Mortem requires only one line to function and works completely transparently in the background.
//! It does this by providing a [`Guard`] type.
//! When a [`Guard`] is created, it does nothing.
//! When it gets dropped, however, it begins the process of deleting the host executable.
//! It does this with the best of it's ability, either trying once and exiting successfully upon failure (provided by [`Guard::soft()`]) or trying continually and blocking till it succeeds (provided by [`Guard::hard()`]).
//!
//! This means, for Mortem to do it's work, all that it needs is to be dropped at the end of the
//! main function.
//!
//! # Usage
//! Simply register a guard (either `soft` or `hard`) in the main function, and have it be dropped to delete the binary.
//! ```rust
//! fn main() {
//!     let _mortem = mortem::hard(); // register mortem guard
//!
//!     // some code
//!     println!("Hello!")
//!
//!     // _mortem drops and executable is deleted
//! }
//! ```
//!
//! # Async
//! Using Mortem in an async runtime is functionally the same and no user action is required beyond
//! the usual.
//!
//! #### Tokio
//! ```rust
//! #[tokio::main]
//! async fn main() {
//!     let _mortem = mortem::hard(); // register mortem guard
//!
//!     // some code
//!     tokio::spawn(async {
//!         println!("Hello!")
//!     }).await;
//!
//!     // _mortem drops and executable is deleted
//! }
//! ```
//!
//! #### async-std
//! ```rust
//! #[async_std::main]
//! async fn main() {
//!     let _mortem = mortem::hard(); // register mortem guard
//!
//!     // some code
//!     async_std::task::spawn(async {
//!         println!("Hello!")
//!     }).await;
//!
//!     // _mortem drops and executable is deleted
//! }
//! ```

use std::env::current_exe;
use std::fs::remove_file;
use std::ops::Drop;

#[cfg(feature = "tracing")]
use tracing::{debug, error};

/// Create a guard that when dropped tries to delete the host executable.
///
/// Self-destructs when dropped. Doesn't ensure that executable is always deleted, so may not work 100% of the time.
///
/// ### Usage
/// ```rust
/// fn main() {
///     let _mortem = mortem::soft(); // register guard
///
///     // some code
///     println!("Hello!")
///
///     // functions ends, _mortem drops and executable is deleted
/// }
/// ```
#[inline(always)]
pub fn soft() -> Guard {
    Guard::soft()
}

/// Create a guard that when dropped blocks till the host executable is successfully deleted.
///
/// ### Usage
/// ```rust
/// fn main() {
///     let _mortem = mortem::hard(); // register guard
///
///     // some code
///     println!("Hello!")
///
///     // functions ends, _mortem drops and executable is deleted
/// }
/// ```
#[inline(always)]
pub fn hard() -> Guard {
    Guard::hard()
}

/// Executable guard.
pub struct Guard {
    /// Ensure deletion of the file, retrying till executable is deleted.
    ensure: bool,
}

impl Guard {
    fn new(ensure: bool) -> Self {
        #[cfg(feature = "tracing")]
        debug!(?ensure, "creating mortem guard");
        Guard { ensure }
    }

    pub fn soft() -> Self {
        Self::new(false)
    }

    /// Create a guard that blocks till the executable is successfully deleted
    ///
    /// See [`hard`].
    pub fn hard() -> Self {
        Self::new(true)
    }
}

impl Drop for Guard {
    fn drop(&mut self) {
        #[cfg(feature = "tracing")]
        debug!(ensure = self.ensure, "dropping mortem guard");

        loop {
            match current_exe() {
                Err(_) if self.ensure => continue,
                Err(_) => {
                    #[cfg(feature = "tracing")]
                    error!(ensure = self.ensure, "failed to delete executable");
                    panic!("failed to delete executable")
                }
                Ok(path) => {
                    if remove_file(path).is_err() && self.ensure {
                        #[cfg(feature = "tracing")]
                        error!(
                            ensure = self.ensure,
                            "failed to delete executable; retrying"
                        );
                        continue;
                    }
                }
            }
            break;
        }
    }
}