From 2e115aef395f08bbc77e42f96ce61b9f31415839 Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 13 Jun 2026 10:51:10 -0500 Subject: [PATCH] fix(daemon): replace the embedded daemon even when the old binary is locked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a newer wallet build embeds a newer daemon, extractEmbeddedResources() detects the size change and tries to overwrite dragonxd.exe in the daemon dir — but the write is a plain truncating ofstream, which fails silently if the file is locked. A running (or just-killed, handle-not-yet-released) daemon locks the .exe on Windows (and Linux returns ETXTBSY), so the stale binary was kept and the wallet kept launching the old daemon version. If the direct write fails, move the stale binary aside to ".old" (renaming a running/locked executable is permitted on both Windows and Linux — the running process keeps the moved copy) and write the fresh one at the original path. A best-effort pass removes leftover .old files once the old process has exited. Co-Authored-By: Claude Opus 4.8 --- src/resources/embedded_resources.cpp | 39 +++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/resources/embedded_resources.cpp b/src/resources/embedded_resources.cpp index 8ba2d56..266f0b5 100644 --- a/src/resources/embedded_resources.cpp +++ b/src/resources/embedded_resources.cpp @@ -274,8 +274,30 @@ static bool extractResource(const EmbeddedResource* res, const std::string& dest // Write file std::ofstream file(destPath, std::ios::binary); if (!file) { - DEBUG_LOGF("[ERROR] Failed to open %s for writing\n", destPath.c_str()); - return false; + // The destination may be locked because the previous daemon is still using the binary: + // Windows locks a running .exe against truncation, Linux returns ETXTBSY. Both platforms + // DO allow renaming/moving such a file — the running process keeps the moved copy — so move + // the stale binary aside and write a fresh one at the original path. + std::error_code ec; + if (std::filesystem::exists(destPath)) { + std::string sidelined = destPath + ".old"; + std::filesystem::remove(sidelined, ec); // clear any leftover from a prior swap + ec.clear(); + std::filesystem::rename(destPath, sidelined, ec); + if (!ec) { + file.clear(); + file.open(destPath, std::ios::binary); + if (file) + DEBUG_LOGF("[INFO] Replaced in-use %s (old copy moved to .old)\n", destPath.c_str()); + } else { + DEBUG_LOGF("[WARN] Could not move stale %s aside: %s\n", + destPath.c_str(), ec.message().c_str()); + } + } + if (!file) { + DEBUG_LOGF("[ERROR] Failed to open %s for writing\n", destPath.c_str()); + return false; + } } file.write(reinterpret_cast(res->data), res->size); @@ -413,7 +435,18 @@ bool extractEmbeddedResources() } } #endif - + + // Best-effort cleanup of any ".old" binaries left behind by a previous in-use replacement. + // Once the old daemon/xmrig process has exited, the file is no longer locked and removes cleanly; + // if it's still running, the remove fails harmlessly and we retry on the next startup. + { + std::error_code ec; + for (const char* name : { RESOURCE_DRAGONXD, RESOURCE_XMRIG }) { + std::filesystem::remove(daemonDir + pathSep + name + std::string(".old"), ec); + ec.clear(); + } + } + return success; }