fix(linux): the update relaunch is per-platform again
Restoring the PowerShell helper put it in update.go, which is shared — so a Linux build would have tried to run `powershell` to relaunch itself. It compiled and vetted cleanly for linux/amd64, which is exactly why it needed catching before somebody built it: the fault only shows on a real update, on a machine that has no PowerShell. scheduleRelaunch now lives in the platform files. Windows keeps the helper. Linux starts the new binary directly, which is right there and not a compromise: nothing holds an executable open while it runs, so the swap has already succeeded, and there is no mutex to race — the single-instance guard is an flock the dying process releases as it exits, and the new one waits for our pid first. The two guards were looking at the old location and had to follow: the Wait-Process/Start-Process check moves into relaunch_windows_test.go where it belongs, and TestEveryRelaunchPassesItsPid now scans updateswap_linux.go too — the direct spawn moved there, and without it the test would have gone quiet again. Checked from Windows, as BUILDING-LINUX.md says is done at every release: GOOS=linux go build ./... and go vet ./... both clean.
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -214,51 +213,14 @@ func (a *App) DownloadAndApplyUpdate(url string) error {
|
||||
}
|
||||
applog.Printf("update: installed new build, scheduling relaunch")
|
||||
|
||||
// A DETACHED, HIDDEN POWERSHELL waits for this process to exit and THEN
|
||||
// starts the new exe. Restored, verbatim, from before 0430aab.
|
||||
//
|
||||
// Two rewrites tried to do without it and both failed on real stations.
|
||||
// Starting the new exe from here means launching it while this process is
|
||||
// still alive — and the comment on the original said exactly what that
|
||||
// costs: "Launching the new exe directly while we're still alive raced the
|
||||
// mutex and often left nothing running". Telling the new instance our pid so
|
||||
// it could wait on the other side looked equivalent and was not; operators
|
||||
// kept reporting no window after an update, on 0.27.23 and again after.
|
||||
// What the helper has that neither rewrite did is that it OUTLIVES us: the
|
||||
// launch happens after this process is completely gone, from a process that
|
||||
// was never our child.
|
||||
//
|
||||
// The cost is known and accepted. Windows Defender removed 0.27.14 from a
|
||||
// station as Trojan:Script/Wacatac.H!ml: an unsigned binary that replaces
|
||||
// itself, clears the mark-of-the-web and spawns a windowless script to start
|
||||
// another executable has the shape of a dropper, and the model reads shapes,
|
||||
// not intentions. The operator's answer is to allow OpsLog in Defender. An
|
||||
// updater that works and occasionally needs whitelisting beats one that
|
||||
// leaves people with no running program.
|
||||
//
|
||||
// HideWindow here is right and is NOT the bug that made the updated OpsLog
|
||||
// invisible: it hides POWERSHELL's console, which is the whole point. The
|
||||
// new OpsLog is started by Start-Process, with a normal show.
|
||||
quoted := strings.ReplaceAll(exe, "'", "''")
|
||||
ps := fmt.Sprintf(
|
||||
"Wait-Process -Id %d -ErrorAction SilentlyContinue; Start-Sleep -Milliseconds 400; Start-Process -FilePath '%s' -ArgumentList '--post-update'",
|
||||
os.Getpid(), quoted)
|
||||
cmd := exec.Command("powershell", "-NoProfile", "-WindowStyle", "Hidden", "-Command", ps)
|
||||
cmd.Dir = dir
|
||||
hideConsole(cmd)
|
||||
if err := cmd.Start(); err != nil {
|
||||
// How the new build gets started differs by platform — see
|
||||
// scheduleRelaunch in updateswap_windows.go and updateswap_linux.go. On
|
||||
// Windows it is a helper that outlives us; on Linux it is simply the new
|
||||
// binary, because nothing there holds an image open.
|
||||
if err := a.scheduleRelaunch(exe, dir); err != nil {
|
||||
applog.Printf("update: the relaunch could not be started: %v", err)
|
||||
return fmt.Errorf("schedule relaunch: %w", err)
|
||||
return err
|
||||
}
|
||||
// The HELPER's pid, so the log says the launcher was started and not just
|
||||
// that we meant to. The new OpsLog logs its own arrival in startup.log; the
|
||||
// two together tell "the helper never ran" from "it ran and the exe did not
|
||||
// start", which have different causes.
|
||||
applog.Printf("update: relaunch helper started as pid %d — it waits for this process (pid %d) to exit, then starts %s",
|
||||
cmd.Process.Pid, os.Getpid(), filepath.Base(exe))
|
||||
// Released rather than waited on: this process is about to exit, and a child
|
||||
// that outlives its parent must not be left as a zombie handle.
|
||||
_ = cmd.Process.Release()
|
||||
if a.ctx != nil {
|
||||
wruntime.Quit(a.ctx)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user