From 38aa622dd6063a18cf6b97354141e4d1e2877a83 Mon Sep 17 00:00:00 2001 From: JGZYES Date: Sat, 5 Sep 2026 08:40:41 +0800 Subject: [PATCH] fix: allow pmm to reinstall itself on Windows (running exe lock) pdm_install_file previously extracted the new pmm.exe over the currently running binary; on Windows the running image is locked so tar failed with "Can't unlink already-existing object: Permission denied" (the same cause as Linux's "Text file busy"). A running image can be renamed but not overwritten, so before extraction we move the running pmm.exe aside (release .old), let tar write the new one, then drop the .old on success (or restore it on failure). Paths are compared case-insensitively with / and \ normalized so the install target matches pmm_self_path() regardless of separator. Non-pmm packages are unaffected (target != self path). --- src/pdm.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/pdm.c b/src/pdm.c index 6417fb7..5385152 100644 --- a/src/pdm.c +++ b/src/pdm.c @@ -355,6 +355,44 @@ static void flatten_bin(const char *dir) { #endif } +/* Case-insensitive path equality (Windows paths/FAT are case-insensitive, and + * separators may be '/' or '\'); the running exe path uses backslashes while the + * install target uses forward slashes. */ +static int path_eq_ci(const char *a, const char *b) { + while (*a && *b) { + char ca = *a, cb = *b; + if (ca == '/' || ca == '\\') ca = '/'; + if (cb == '/' || cb == '\\') cb = '/'; + if (ca >= 'A' && ca <= 'Z') ca = (char)(ca - 'A' + 'a'); + if (cb >= 'A' && cb <= 'Z') cb = (char)(cb - 'A' + 'a'); + if (ca != cb) return 0; + a++; b++; + } + return *a == '\0' && *b == '\0'; +} + +/* If `target` is the currently running pmm executable, move it aside to + * `.old` so a .pdm reinstall can write the new binary (a running image + * can be renamed, but not overwritten). Returns 1 if moved, 0 otherwise. */ +static int move_self_aside(const char *target) { + const char *self = pmm_self_path(); + if (!self || !*self) return 0; + if (!path_eq_ci(target, self)) return 0; + char old[1500]; + snprintf(old, sizeof(old), "%s.old", target); + remove(old); /* Windows rename can't clobber */ + if (rename(target, old) != 0) return 0; + return 1; +} + +/* Restore a previously-aside `.old` back to `target` (best-effort). */ +static void restore_aside(const char *target) { + char old[1500]; + snprintf(old, sizeof(old), "%s.old", target); + remove(target); + rename(old, target); +} + int pdm_info(const char *pdmfile) { pmm_info("%s\n", base_name(pdmfile)); /* Copy into the pm dir + chdir, then use relative names (works with both @@ -455,11 +493,23 @@ int pdm_install_file(const char *pdmfile) { /* extract data into root (relative target: cwd is already the pm dir) */ const char *tgt = flat ? "." : "root"; + /* If the package is pmm itself, its exe is the currently running binary and + * Windows/locked images can't be overwritten by tar. Move it aside first so + * the new binary can be written; restore on failure. */ + char self_target[1500]; + snprintf(self_target, sizeof(self_target), "%s/bin/pmm.exe", root); + int self_moved = move_self_aside(self_target); snprintf(cmd, sizeof(cmd), "tar -xzf \"%s/data.tar.gz\" -C \"%s\"", stage, tgt); if (system(cmd) != 0) { pmm_error(pmm_tr("msg.err.extract")); + if (self_moved) restore_aside(self_target); /* put old binary back */ chdir_restore(); remove(tmpname); return -1; } + if (self_moved) { + char old[1600]; + snprintf(old, sizeof(old), "%s.old", self_target); + remove(old); /* drop the old image */ + } /* flat mode (-p ): move bin/* up into the address itself, drop bin/ */ if (flat) {