diff --git a/bin/stop-emrg.cmd b/bin/stop-emrg.cmd index 6553e07..69c2107 100644 --- a/bin/stop-emrg.cmd +++ b/bin/stop-emrg.cmd @@ -13,6 +13,12 @@ REM fallback), excludes the daemon (pythonw.exe -m emrg.server) REM 3. daemon: `emrg server stop` protocol shutdown via the OLD install's CLI REM (present since #364 -- version-safe), emrgd.pid poll (<=10s), then REM taskkill /F /PID fallback +REM 4. bundled git (rant 2026-08-11T17:56:25): kill orphaned git/ssh/bash +REM processes whose executable lives under %INSTALL%\git\ (the portable Git +REM the daemon spawns for evolution-cycle git ops). When the daemon is killed +REM mid-operation these become orphans holding install\git\usr\bin\msys-2.0.dll +REM -> Inno "DeleteFile failed; code 5" on upgrade. Filter is by executable +REM PATH so system Git (C:\Program Files\Git) is never touched. REM REM Returns 0 when nothing EMRG-related survives; 1 if a process could not be REM stopped (installer aborts with a clear message instead of hanging). @@ -61,6 +67,13 @@ set "DPID=" for /f "usebackq delims=" %%p in ("%EMRG_DIR%\emrgd.pid") do set "DPID=%%p" if defined DPID taskkill /F /PID %DPID% >nul 2>&1 +REM --- 4. bundled git: orphaned git/ssh/bash under %INSTALL%\git (msys-2.0.dll lock) --- +REM (host 2026-08-11T17:56:25: daemon killed mid-git-op leaves orphans holding +REM install\git\usr\bin\msys-2.0.dll -> Inno DeleteFile code 5. Filter by +REM ExecutablePath prefix so system Git is never touched. `\"` = PowerShell +REM quote escape precedent: TUI branch line 44.) +powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }" >nul 2>&1 + :verify set "EXIT_CODE=0" tasklist /FI "IMAGENAME eq EMRG.exe" 2>nul | findstr /i "EMRG.exe" >nul && set "EXIT_CODE=1" @@ -73,4 +86,8 @@ if exist "%EMRG_DIR%\emrgd.pid" ( tasklist /FI "PID eq !DPID!" 2>nul | findstr /i "!DPID!" >nul && set "EXIT_CODE=1" ) ) +REM bundled-git survival check: any process still under install\git\ -> exit 1 +REM (installer aborts with a clear message instead of a repeat "Try again" loop) +powershell -NoProfile -Command "if (Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\" }) { exit 1 }" >nul 2>&1 +if errorlevel 1 set "EXIT_CODE=1" endlocal & exit /b %EXIT_CODE% diff --git a/packaging/make-installer.sh b/packaging/make-installer.sh index 1a0168b..577dafb 100755 --- a/packaging/make-installer.sh +++ b/packaging/make-installer.sh @@ -372,8 +372,10 @@ end; // -m emrg.server 常驻锁文件),覆盖 ~/.emrg\install 时卡在"停止已有进程"。 // PrepareToInstall 在安装开始前运行 bin\stop-emrg.cmd:taskkill EMRG.exe // 优雅→/F 兜底、wmic/PowerShell 命令行过滤 TUI、emrg server stop 协议关闭 -// daemon + emrgd.pid 轮询兜底(顺序 GUI→TUI→daemon)。干净安装(无旧 install) -// 脚本自行跳过。返回非空字符串 = 中止安装并显示该消息(宁可中止也不卡死)。 +// daemon + emrgd.pid 轮询兜底、按可执行文件路径杀 bundled git 孤儿 +// (rant 2026-08-11T17:56:25: daemon 演化周期 git 操作中途被杀 → git/ssh/bash +// 孤儿锁 install\git\usr\bin\msys-2.0.dll → Inno DeleteFile code 5)。干净安装 +// (无旧 install)脚本自行跳过。返回非空字符串 = 中止安装并显示该消息(宁可中止也不卡死)。 // {cmd} = cmd.exe(Inno 预定义常量,批处理文件须经 cmd 启动)。 function PrepareToInstall(var NeedsRestart: Boolean): String; var @@ -386,10 +388,10 @@ begin if Exec(ExpandConstant('{cmd}'), '/c "' + StopScript + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin if ResultCode <> 0 then - Result := 'EMRG could not stop all running processes (stop-emrg.cmd exit code ' + IntToStr(ResultCode) + '). Please close EMRG (GUI/TUI) and retry the install.'; + Result := 'EMRG could not stop all running processes (stop-emrg.cmd exit code ' + IntToStr(ResultCode) + '). Please close EMRG (GUI/TUI) and retry the install, or restart the computer and retry (a helper process such as the bundled Git may still hold a file lock).'; end else - Result := 'EMRG could not run the process-stop helper (stop-emrg.cmd). Please close EMRG (GUI/TUI) and retry the install.'; + Result := 'EMRG could not run the process-stop helper (stop-emrg.cmd). Please close EMRG (GUI/TUI) and retry the install, or restart the computer and retry.'; end; EOF # Windows 路径转义(iscc 需要 Windows 路径,但在 bash/msys 下用当前路径) diff --git a/tests/test_installer_stop.py b/tests/test_installer_stop.py index 4b79462..726d1ec 100644 --- a/tests/test_installer_stop.py +++ b/tests/test_installer_stop.py @@ -39,6 +39,18 @@ def test_stop_emrg_cmd_covers_gui_tui_daemon_in_order(): # 顺序:GUI 在 daemon 之前(GUI 不能复活 daemon) daemon_line = content.index('call "%INSTALL%\\bin\\emrg.cmd" server stop') assert content.index("taskkill /IM EMRG.exe") < daemon_line + # step 4(rant 2026-08-11T17:56:25):按可执行文件路径杀 bundled git 孤儿 + # (daemon 演化周期 git 操作中途被杀 → git/ssh/bash 孤儿锁 msys-2.0.dll → Inno + # DeleteFile code 5)。判别:ExecutablePath 前缀过滤只命中 %INSTALL%\git\, + # 不误杀系统 Git;daemon 停止之后、:verify 之前。 + assert 'Get-CimInstance Win32_Process' in content + assert r'$env:USERPROFILE\.emrg\install\git\*' in content + # 锚定实际 kill 命令($_.ExecutablePath 只在 step 4 出现——TUI 分支是 + # -Filter Name='python.exe',不共享此模式),勿用 index() 撞到 TUI/REM 注释 + git_kill = content.index("$_.ExecutablePath -like") + daemon_line2 = content.index('call "%INSTALL%\\bin\\emrg.cmd" server stop') + verify_idx = content.index(':verify\nset "EXIT_CODE=0"') # 标签定义处(goto :verify 在前,勿用裸 index(":verify")) + assert daemon_line2 < git_kill < verify_idx # 干净安装安全:无旧 install 目录时跳过 assert 'set "INSTALL=%EMRG_DIR%\\install"' in content # 括号块内 pid 判定必须用延迟展开(!DPID!)——%DPID% 在块解析时展开, @@ -50,6 +62,10 @@ def test_stop_emrg_cmd_covers_gui_tui_daemon_in_order(): assert "PID eq !DPID!" in verify_block # 非延迟展开 %DPID% 不得出现在括号块内(块解析时展开=恒旧值) assert "%DPID%" not in verify_block + # :verify 的 bundled-git 存活判定:仍有进程在 install\git\ 下 → exit 1 + assert "exit 1" in verify_block + assert r'ExecutablePath -like \"$env:USERPROFILE\.emrg\install\git\*\"' in verify_block + assert "if errorlevel 1 set \"EXIT_CODE=1\"" in verify_block def test_emrgd_cmd_has_stop_branch(): @@ -66,6 +82,8 @@ def test_make_installer_iss_has_prepare_to_install(): assert "PrepareToInstall" in content assert "ExtractTemporaryFile('stop-emrg.cmd')" in content assert "SW_HIDE" in content # 批处理执行不弹控制台窗口(#592 纪律) + # rant 2026-08-11T17:56:25:中止消息含重启兜底引导(杀不掉时宿主可重启后重试) + assert "restart the computer" in content def test_build_runtime_copies_stop_emrg_cmd():