diff --git a/Cargo.lock b/Cargo.lock index f15f44a3..8e1f3666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2242,9 +2242,9 @@ dependencies = [ [[package]] name = "self-replace" -version = "1.3.7" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525db198616b2bcd0f245daf7bfd8130222f7ee6af9ff9984c19a61bf1160c55" +checksum = "f7828a58998685d8bf5a3c5e7a3379a5867289c20828c3ee436280b44b598515" dependencies = [ "fastrand 1.9.0", "tempfile", @@ -2793,6 +2793,7 @@ dependencies = [ "regex-split", "rust-i18n", "rust-ini", + "self-replace", "self_update", "semver", "serde", diff --git a/Cargo.toml b/Cargo.toml index f57e468d..2225c9a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,7 @@ self_update_crate = { version = "~0.40", default-features = false, optional = tr self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] } winapi = "~0.3" parselnk = "~0.1" +self-replace = "~1.4" [profile.release] lto = true diff --git a/src/self_renamer.rs b/src/self_renamer.rs index 96669870..a2a2c45b 100644 --- a/src/self_renamer.rs +++ b/src/self_renamer.rs @@ -1,6 +1,6 @@ use color_eyre::eyre::Result; use std::{env::current_exe, fs, path::PathBuf}; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; pub struct SelfRenamer { exe_path: PathBuf, @@ -10,12 +10,40 @@ pub struct SelfRenamer { impl SelfRenamer { pub fn create() -> Result { let tempdir = tempfile::tempdir()?; - let temp_path = tempdir.path().join("topgrade.exe"); + let mut temp_path = tempdir.path().join("topgrade.exe"); let exe_path = current_exe()?; - debug!("Current exe in {:?}. Moving it to {:?}", exe_path, temp_path); + debug!( + "Current exe in {:?}. Attempting to move it to {:?}", + exe_path, temp_path + ); - fs::rename(&exe_path, &temp_path)?; + match fs::rename(&exe_path, &temp_path) { + // cross-device error + Err(e) if e.raw_os_error() == Some(17) => { + debug!("Temporary directory is on a different device. Using the binary parent directory instead"); + + let Some(parent_dir) = exe_path.parent() else { + return Err(color_eyre::eyre::Report::msg( + "Could not get parent directory of the current binary", + )); + }; + + let mut builder = tempfile::Builder::new(); + builder.prefix("topgrade").suffix(".exe"); + let temp_file = builder.tempfile_in(parent_dir)?; + temp_path = temp_file.path().to_path_buf(); + + // Delete the temporary file immediately to free up the name + if let Err(e) = temp_file.close() { + warn!("Could not close temporary file: {}", e); + } + + debug!("Moving current exe in {:?} to {:?}", exe_path, temp_path); + fs::rename(&exe_path, &temp_path) + } + other => other, + }?; Ok(SelfRenamer { exe_path, temp_path }) } @@ -25,6 +53,9 @@ impl Drop for SelfRenamer { fn drop(&mut self) { if self.exe_path.exists() { debug!("{:?} exists. Topgrade was probably upgraded", self.exe_path); + if let Err(e) = self_replace::self_delete_at(&self.temp_path) { + error!("Could not clean up temporarily renamed topgrade executable: {}", e); + } return; }