priv: a failed root probe is retried, not cached for the life of the daemon

The Mac mini's daemon started while the machine was still booting, sudo -n true
took longer than the 3 s timeout, and sync.Once kept the answer: no root, so the
next self-update could not replace the binary.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-09-19 07:05:32 -07:00
co-authored by Claude Fable 5.1
parent 43bf787696
commit 468f45251d
+19 -5
View File
@@ -13,12 +13,24 @@ import (
) )
var ( var (
once sync.Once mu sync.Mutex
mode string // "root" | "sudo" | "doas" | "" mode string // "root" | "sudo" | "doas" | ""
prefix []string prefix []string
probed time.Time
reprobe = 30 * time.Second
) )
// detect settles how root is reached. Finding a way is remembered for good; finding none
// is only believed for a while, because the probe can fail for reasons that pass — sudo
// taking longer than the timeout on a machine that is still booting left a daemon
// convinced it had no root until its next restart.
func detect() { func detect() {
mu.Lock()
defer mu.Unlock()
if mode != "" || (!probed.IsZero() && time.Since(probed) < reprobe) {
return
}
probed = time.Now()
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
mode = "root" mode = "root"
return return
@@ -38,7 +50,7 @@ func detect() {
} }
// Mode reports how root is reached: "root", "sudo", "doas", or "" when it can't be. // Mode reports how root is reached: "root", "sudo", "doas", or "" when it can't be.
func Mode() string { once.Do(detect); return mode } func Mode() string { detect(); mu.Lock(); defer mu.Unlock(); return mode }
// Available reports whether commands can be run as root without a prompt. // Available reports whether commands can be run as root without a prompt.
func Available() bool { return Mode() != "" } func Available() bool { return Mode() != "" }
@@ -46,7 +58,9 @@ func Available() bool { return Mode() != "" }
// Wrap returns args prefixed so they run as root. Without privilege it returns args // Wrap returns args prefixed so they run as root. Without privilege it returns args
// unchanged; callers gate on Available() where that matters. // unchanged; callers gate on Available() where that matters.
func Wrap(args ...string) []string { func Wrap(args ...string) []string {
once.Do(detect) detect()
mu.Lock()
defer mu.Unlock()
return append(append([]string{}, prefix...), args...) return append(append([]string{}, prefix...), args...)
} }