self_replace/
lib.rs

1//! `self-replace` is a crate that allows binaries to replace themselves with newer
2//! versions or to uninstall themselves.  On Unix systems this is a simple feat, but
3//! on Windows a few hacks are needed which is why this crate exists.
4//!
5//! This is a useful operation when working with single-executable utilties that
6//! want to implement a form of self updating or self uninstallation.
7//!
8//! ## Self Deletion
9//!
10//! The [`self_delete`] function schedules a binary for self deletion.  On Unix the
11//! file system entry is immediately deleted, on Windows the file is deleted after the
12//! process shuts down.  Note that you should not use this function to be followed up
13//! by a replacement operation, for that use [`self_replace`] as on Windows the file
14//! will still be locked.
15//!
16//! ```
17//! # fn foo() -> Result<(), std::io::Error> {
18//! self_replace::self_delete()?;
19//! # Ok(()) }
20//! ```
21//!
22//! On Windows self deletion requires some place in the folder the deletion is taking
23//! place.  This will prevent the abiltiy of the program to also delete the folder the
24//! executable is placed in.  To avoid this you can use the [`self_delete_outside_path`]
25//! function which will ensure that the deletion does not take place in the path
26//! provided if it's possible to do so.  That way you can delete entire structures safely.
27//!
28//! ```
29//! # use std::fs;
30//! # fn foo() -> Result<(), std::io::Error> {
31//! let itself = std::env::current_exe().unwrap();
32//! let parent = itself.parent().unwrap();
33//! self_replace::self_delete_outside_path(&parent)?;
34//! fs::remove_dir_all(&parent);
35//! # Ok(()) }
36//! ```
37//!
38//! ## Self Replacing
39//!
40//! This replaces the binary with another binary.  The provided path is copied over and
41//! if the function successfully completes, you can delete the source binary.
42//!
43//! ```
44//! use std::fs;
45//!
46//! # fn foo() -> Result<(), std::io::Error> {
47//! let new_binary = "/path/to/new/binary";
48//! self_replace::self_replace(&new_binary)?;
49//! fs::remove_file(&new_binary)?;
50//! # Ok(()) }
51//! ```
52//!
53//! ## Implementation
54//!
55//! The way this is implemented depends on the operating system.  On UNIX systems you
56//! can usually not directly write into an executable, but you can swap it out which is
57//! exactly what this is doing.  For deleting, the file is just unlinked, for replacing
58//! a new file is placed right next to the current executable and an atomic move with
59//! `rename` is performed.
60//!
61//! On Windows the situation is trickier because when an executable launches it can be
62//! renamed, but it cannot be unlinked.  This means that we cannot clean up after
63//! ourselves easily.  In either case, we first move our executable aside so the name
64//! on the file system is made available for the new executable.  Then for both
65//! deleting and replacing, we create a copy of our own executable first.  After this we
66//! open that copied executable with `FILE_FLAG_DELETE_ON_CLOSE`.  Then we spawn it and
67//! wait for our own shut down.
68//!
69//! This library contains a special glue code that detects this copy of the executable
70//! and does nothing else but waiting for the parent to quit and to then delete the
71//! parent executable.  There is an extra hack in there in that it spawns another system
72//! executable that stays alive until after we shut down to make the self deletion of
73//! the copy work.  This is necessary because our running executable must not be the
74//! last user of that file handle as otherwise the deletion won't work as the
75//! executable still cannot be deleted.  Presumably this is because `CreateProcess`
76//! and friends do not open the executable with `FILE_FLAG_DELETE_ON_CLOSE`.
77//!
78//! **Special note on Windows:** the system will attempt to run the parent deletion logic
79//! if the executable has the suffix `.__selfdelete__.exe`.  This means if you
80//! name your executable `foo.exe.__selfdelete__.exe`, this logic would kick in.
81//!
82//! ## Alternatives for Windows
83//!
84//! Various proposals were made and tried for alternative solutions on Windows.  One
85//! quite popular option is to spawn a batch file for the deletion job as a batch file
86//! can self delete.  This could be used for both replace and self deletion, but it
87//! has the disadvantage that this is a very racy and quite dirty approach as the batch
88//! file cannot easily wait for the shutdown of the parent process.
89//!
90//! For just replaces, co-operation between parent and client could be implemented in
91//! simpler terms where first the new executable replaces the old, and then on
92//! startup replaces the new one.  There are two downsides of this approach: the first
93//! is that it requires that the new replacing executable has matching logic for the
94//! cleanup.  The second issue with this approach is that it requires either launching
95//! the new executable or waiting for the executable to launch for natural reasons.
96//! The former might not always be preferrable, the second leaves files lingering
97//! around for an extended period of time.
98//!
99//! The third, and somewhat official solution is to use `MOVEFILE_DELAY_UNTIL_REBOOT`
100//! with `MoveFileEx`.  This causes windows to store an entry in the registry and
101//! will perform the delete / move on restart.  This means that if a restart of the
102//! machine does not happen, no cleanup is performed.  It also is a privileged
103//! operation that is not available to all user accounts.
104//!
105//! ## Limitations
106//!
107//! Because files need to be placed temporarily on the file system, there is a chance
108//! that if power is cut in just the wrong moment, some files are left over.  These
109//! files resemble the original names of the executable prefixed with a dot (`.`) and
110//! a random suffix.  The likelihood of this happening should be small.  It's not
111//! recommended to run automatic cleanup on startup as the location of those temporary
112//! files placed is left undefined.  In many cases the temporary files will be placed
113//! in temporary locations and the operating system will take care of the deletion on
114//! restart.
115use std::io;
116use std::path::Path;
117
118#[cfg(unix)]
119mod unix;
120#[cfg(windows)]
121mod windows;
122
123/// Deletes the executable in a platform independent manner.
124///
125/// The deletion on windows is delayed until the process shuts down.  For updating
126/// instead of deleting, use [`self_replace`] instead.  Not that you must only
127/// only call this function once during the execution of the program and you should
128/// exist quickly afterwards to make the delete take effect on Windows.
129///
130/// ```
131/// # fn foo() -> Result<(), std::io::Error> {
132/// self_replace::self_delete()?;
133/// # Ok(()) }
134/// ```
135pub fn self_delete() -> Result<(), io::Error> {
136    self_delete_at(std::env::current_exe()?)
137}
138
139/// Like [`self_delete`] but accepts a path which is assumed to be the current executable path.
140///
141/// This can be useful if the executable was moved to a different location while
142/// it was running.  Note that on Windows this has no effect on the name given
143/// to the temporary files.  They are always based on the original, reported
144/// file name of the current executable.
145pub fn self_delete_at<P: AsRef<Path>>(exe: P) -> Result<(), io::Error> {
146    #[cfg(unix)]
147    {
148        crate::unix::self_delete(exe.as_ref())
149    }
150    #[cfg(windows)]
151    {
152        crate::windows::self_delete(exe.as_ref(), None)
153    }
154}
155
156/// Like [`self_delete`] but accepts a path which must not be used for temporary operations.
157///
158/// This is equivalent to [`self_delete`] on Unix, but it instructs the deletion logic to
159/// not place temporary files in the given path (or any subdirectory of) for the duration
160/// of the deletion operation.  This is necessary to demolish folder more complex folder
161/// structures on Windows.
162pub fn self_delete_outside_path<P: AsRef<Path>>(p: P) -> Result<(), io::Error> {
163    let exe = std::env::current_exe()?;
164    #[cfg(unix)]
165    {
166        let _ = p;
167        crate::unix::self_delete(&exe)
168    }
169    #[cfg(windows)]
170    {
171        crate::windows::self_delete(&exe, Some(p.as_ref()))
172    }
173}
174
175/// Replaces the running executable with a different one.
176///
177/// This replaces the binary with another binary.  The provided path is copied over and
178/// if the function successfully completes, you can delete the source binary.
179///
180/// ```
181/// use std::fs;
182///
183/// # fn foo() -> Result<(), std::io::Error> {
184/// let new_binary = "/path/to/new/binary";
185/// self_replace::self_replace(&new_binary)?;
186/// fs::remove_file(&new_binary)?;
187/// # Ok(()) }
188/// ```
189///
190/// Note that after this function concludes, the new executable is already placed at the
191/// old location, and the previous executable has been moved to a temporary alternative
192/// location.  This also means that if you want to manipulate that file further (for
193/// instance to change the permissions) you can do so.
194///
195/// By default the permissions of the original file are restored.
196pub fn self_replace<P: AsRef<Path>>(new_executable: P) -> Result<(), io::Error> {
197    #[cfg(unix)]
198    {
199        crate::unix::self_replace(new_executable.as_ref())
200    }
201    #[cfg(windows)]
202    {
203        crate::windows::self_replace(new_executable.as_ref())
204    }
205}