Files
magpie/internal/update/proc_windows.go
T
yetone 9ca64d104f update, gui: a new version Windows won't start is never left in place — tried before it goes in, and a restart into one that doesn't say it is up puts the running one back; that version isn't downloaded again by the clock, and Settings › About › Version says why (#894, hisiling) — on Windows 11 with Smart App Control on, the update put the new unsigned exe in place of the running one and restarted into it; CodeIntegrity (3033/3077) refused it, magpie never started again and the working version sat in .old. Now, on Windows, the downloaded exe is first started suspended with no window and ended at once (Windows checks a program when its process is created), and one refused stays out: the running exe isn't moved. At the restart the new process is given MAGPIE_STARTED, a file it writes first thing (AwaitPredecessor); the old one waits up to 20s for it and, if the start fails (ERROR_SYSTEM_INTEGRITY_POLICY_VIOLATION 4551, 1260 or any other), the new process quits before saying so, or it says nothing in time (then it is ended), puts the running exe back over the new one (the new one renamed .blocked if it can't go) and keeps running instead of quitting; nothing notes it as updated in the app. The version is noted in the config folder (update-blocked.json, this machine's only, never synced; gone once a version at least as new runs); the clock's checks leave it alone and show why, a Check clicked downloads and tries it once more. Settings › About › Version: for an application control policy, "0.1.x didn't start on this computer: Windows' Smart App Control (or another app control policy) blocked the new version, which isn't signed. magpie stays on 0.1.y. Download it and run it yourself, or turn Smart App Control off and check again", else the reason, with Download and Check; the error in full on hover. en/zh/ja/de. A user who double-clicks the refused exe is refused by Windows before magpie runs; only signing helps there. TestRelaunchRefusedPutsBack (4551 at the start), TestRelaunchSilentPutsBack (no word in time, killed; quit at once), TestRelaunchReportedStays, TestReportStarted, TestBlockedNote and TestUpdateThatWontStart (the restart doesn't quit, the note, the clock not downloading it again, a Check that does) are new and fail without the change; on Windows TestInstallBinaryRefusedStaysOut, TestTrialStart and TestStartCheckedPutsBackForReal (real processes: not a program, quits at once, runs on silent) pass on the Windows 11 test box with the whole internal/update package (Smart App Control itself isn't on there, so the refusal is simulated). update-blocked.test.cjs (Chromium and WebKit, en and zh: the reason and both versions, full error on hover, Download opens the release, Check downloads, the other reason, no select, no scroll, no left border) fails without the change; update-check, update-mirror, update-auto, update-wait and update-download-percent pass in WebKit; click-scroll's request-list case fails now and then the same on main. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
2026-10-05 19:43:31 +08:00

71 lines
1.8 KiB
Go

package update
import (
"os"
"os/exec"
"syscall"
"github.com/yetone/magpie/internal/proc"
)
const (
synchronize = 0x00100000
waitTimeout = 0x00000102
detachedProc = 0x00000008
newProcGroup = 0x00000200
suspended = 0x00000004
noWindow = 0x08000000
)
// alive reports whether pid is still running.
func alive(pid int) bool {
h, err := syscall.OpenProcess(synchronize, false, uint32(pid))
if err != nil {
return false
}
defer syscall.CloseHandle(h)
ev, _ := syscall.WaitForSingleObject(h, 0)
return ev == waitTimeout
}
// detach starts a relaunched magpie outside this one's console, so it
// outlives it. It gets no console at all, so Windows ignores the
// CREATE_NO_WINDOW proc may have set.
func detach(cmd *exec.Cmd) {
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
cmd.SysProcAttr.CreationFlags |= detachedProc | newProcGroup
}
// Reexec starts exe with args and env in this console and returns, for
// the caller to exit: Windows can't run another program in a process's
// place, so the new version goes on printing where this one did.
func Reexec(exe string, args, env []string) error {
cmd := proc.Command(exe, args...)
cmd.Env = env
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Start(); err != nil {
return err
}
return cmd.Process.Release()
}
// trialStart starts path suspended and ends it at once: Windows checks a
// program when it creates its process, so one Smart App Control or
// another application control policy won't run fails here, and none of
// one it will run runs.
func trialStart(path string) error {
cmd := proc.Command(path)
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
cmd.SysProcAttr.CreationFlags |= suspended | noWindow
if err := cmd.Start(); err != nil {
return err
}
cmd.Process.Kill()
cmd.Wait()
return nil
}