diff --git a/Cargo.lock b/Cargo.lock index 19e3768..9ce7000 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,7 +1123,7 @@ dependencies = [ [[package]] name = "ostiary" -version = "1.0.3" +version = "1.0.4" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 07950a4..e352469 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ostiary" -version = "1.0.3" +version = "1.0.4" edition = "2024" [dependencies] diff --git a/src/app.rs b/src/app.rs index 316c531..837897a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,7 +19,8 @@ pub enum Event { StructuredFinished(i32), UpdateAvailable(updater::UpdateInfo), UpdateProgress(u64), - UpdateDone, + /// Carries the exe path captured before the binary was replaced. + UpdateDone(std::path::PathBuf), UpdateError(String), } @@ -40,7 +41,8 @@ pub struct App { pub event_tx: UnboundedSender, /// When set, main loop suspends ratatui, runs the command, then restores. pub pending_exec: Option, - pub pending_restart: bool, + /// Exe path to exec after update; captured before the binary was replaced. + pub pending_restart: Option, } pub enum Popup { @@ -104,7 +106,7 @@ impl App { popup: None, event_tx, pending_exec: None, - pending_restart: false, + pending_restart: None, } } @@ -241,11 +243,11 @@ impl App { } Ok(false) } - Event::UpdateDone => { + Event::UpdateDone(exe_path) => { if let Some(Popup::Updating { status, .. }) = &mut self.popup { *status = UpdatingStatus::Done; } - self.pending_restart = true; + self.pending_restart = Some(exe_path); Ok(false) } Event::UpdateError(msg) => { @@ -507,8 +509,8 @@ impl App { ) .await { - Ok(()) => { - let _ = tx2.send(Event::UpdateDone); + Ok(exe_path) => { + let _ = tx2.send(Event::UpdateDone(exe_path)); } Err(e) => { let _ = diff --git a/src/main.rs b/src/main.rs index 07d52a9..584cfd9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,7 +74,7 @@ async fn run_app( } // Check for pending restart (after update applied) — before pending_exec. - if app.pending_restart { + if let Some(exe_path) = app.pending_restart.take() { disable_raw_mode()?; execute!( terminal.backend_mut(), @@ -83,7 +83,7 @@ async fn run_app( )?; terminal.show_cursor()?; #[cfg(unix)] - updater::exec_updated(); + updater::exec_updated(&exe_path); // fallback for non-unix or if exec failed break; } diff --git a/src/updater.rs b/src/updater.rs index ca274c4..f203786 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -94,11 +94,12 @@ pub async fn check(api_base: &str, timeout_sec: u64) -> Result, -) -> Result<()> { +) -> Result { let client = reqwest::Client::new(); let response = client @@ -144,25 +145,28 @@ pub async fn download_and_apply( std::fs::set_permissions(&tmp_path, perms).context("failed to set permissions")?; } - // Atomically replace current exe + // Atomically replace current exe. + // current_exe is captured BEFORE this rename — after rename, /proc/self/exe + // on Linux returns the path with " (deleted)" appended, but the PathBuf we + // hold still refers to the correct filesystem path of the new binary. std::fs::rename(&tmp_path, ¤t_exe).context("failed to replace current exe")?; - Ok(()) + Ok(current_exe) } /// Replaces current process via execv (Unix). Never returns on success. +/// `exe_path` must be the path captured *before* the binary was replaced — +/// do NOT call std::env::current_exe() here, it returns "(deleted)" on Linux. #[cfg(unix)] -pub fn exec_updated() -> ! { +pub fn exec_updated(exe_path: &std::path::Path) -> ! { use std::os::unix::process::CommandExt; - let exe = std::env::current_exe().expect("failed to get current exe path"); let args: Vec = std::env::args().collect(); - let err = std::process::Command::new(&exe) + let err = std::process::Command::new(exe_path) .args(&args[1..]) .exec(); - // exec only returns if it failed eprintln!("Failed to exec updated binary: {}", err); std::process::exit(1); }