From 067195f2abd9444c34e1e85bed9104d03f0a0d42 Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Sat, 18 Apr 2026 12:12:11 +0300 Subject: [PATCH] fix(linux): atomic binary copy + restart instead of start on re-install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-install failed with ETXTBSY (Text file busy) because std::fs::copy can't overwrite a binary that's currently being executed by the running service. Switch to copy-then-rename: write the new binary to /usr/local/bin/numa.new, then rename over /usr/local/bin/numa. Rename swaps the path while the running process keeps the old inode alive, so DNS keeps serving from the previous binary until restart. Bump systemctl start to restart so the new binary actually loads on re-install (start is a no-op when the unit is already active, which would silently leave the old binary running). Locally verified the full CI sequence: install → curl → reinstall → curl → uninstall → curl-fails. All three assertions pass. --- src/system_dns.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/system_dns.rs b/src/system_dns.rs index 60701e3..5a7b999 100644 --- a/src/system_dns.rs +++ b/src/system_dns.rs @@ -1708,10 +1708,18 @@ fn install_service_binary_linux() -> Result { std::fs::create_dir_all(parent) .map_err(|e| format!("failed to create {}: {}", parent.display(), e))?; } - std::fs::copy(&src, &dst).map_err(|e| { + // Atomic replace via temp + rename. Plain copy fails with ETXTBSY when + // re-installing while the service is running the previous binary — + // rename swaps the path while the running process keeps the old inode. + let tmp = dst.with_extension("new"); + std::fs::copy(&src, &tmp).map_err(|e| { + format!("failed to copy {} -> {}: {}", src.display(), tmp.display(), e) + })?; + std::fs::rename(&tmp, &dst).map_err(|e| { + let _ = std::fs::remove_file(&tmp); format!( - "failed to copy {} -> {}: {}", - src.display(), + "failed to rename {} -> {}: {}", + tmp.display(), dst.display(), e ) @@ -1734,7 +1742,9 @@ fn install_service_linux() -> Result<(), String> { eprintln!(" warning: failed to configure system DNS: {}", e); } - run_systemctl(&["start", "numa"])?; + // restart, not start: on re-install the service is already running + // the previous binary; restart picks up the new one. + run_systemctl(&["restart", "numa"])?; eprintln!(" Service installed and started."); eprintln!(" Numa will auto-start on boot and restart if killed.");