mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Fall back to argv0 for agent self-update path on FreeBSD (#1457)
Back-port the agentupdate half of v5 fix 8600706da to v6 (the install.sh
FreeBSD rc.d supervisor-pidfile change is already present in substance).
performUpdate now resolves the executable via resolveExecutablePath, which
falls back to an absolute, existing os.Args[0] when os.Executable() fails
or returns empty — the condition that breaks agent self-update on
FreeBSD/OPNsense. Adds an injectable osArgsFn and a unit test.
This commit is contained in:
parent
67a8c4188b
commit
b8a60db6fa
2 changed files with 76 additions and 2 deletions
|
|
@ -80,6 +80,7 @@ var (
|
|||
restartProcessFn = restartProcess
|
||||
execCommandContextFn = exec.CommandContext
|
||||
osExecutableFn = os.Executable
|
||||
osArgsFn = func() []string { return os.Args }
|
||||
evalSymlinksFn = filepath.EvalSymlinks
|
||||
createTempFn = os.CreateTemp
|
||||
chmodFn = os.Chmod
|
||||
|
|
@ -628,13 +629,39 @@ func (u *Updater) validatePulseURL() error {
|
|||
|
||||
// performUpdate downloads and installs the new agent binary.
|
||||
func (u *Updater) performUpdate(ctx context.Context) error {
|
||||
execPath, err := osExecutableFn()
|
||||
execPath, err := resolveExecutablePath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get executable path: %w", err)
|
||||
return err
|
||||
}
|
||||
return u.performUpdateWithExecPath(ctx, execPath)
|
||||
}
|
||||
|
||||
// resolveExecutablePath returns the running binary's path, falling back to an
|
||||
// absolute os.Args[0] when os.Executable() fails or returns empty. This happens
|
||||
// on FreeBSD/OPNsense, where without the fallback the agent cannot self-update
|
||||
// (#1457).
|
||||
func resolveExecutablePath() (string, error) {
|
||||
execPath, err := osExecutableFn()
|
||||
if err == nil && strings.TrimSpace(execPath) != "" {
|
||||
return execPath, nil
|
||||
}
|
||||
|
||||
args := osArgsFn()
|
||||
if len(args) > 0 {
|
||||
candidate := strings.TrimSpace(args[0])
|
||||
if filepath.IsAbs(candidate) {
|
||||
if info, statErr := os.Stat(candidate); statErr == nil && !info.IsDir() {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = errors.New("empty executable path")
|
||||
}
|
||||
return "", fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
func (u *Updater) performUpdateWithExecPath(ctx context.Context, execPath string) error {
|
||||
agentName, err := normalizeAgentName(u.cfg.AgentName)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package agentupdate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
|
@ -519,3 +520,49 @@ func TestConstants(t *testing.T) {
|
|||
t.Errorf("downloadTimeout = %v, want 5m", downloadTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveExecutablePath(t *testing.T) {
|
||||
origExec := osExecutableFn
|
||||
origArgs := osArgsFn
|
||||
t.Cleanup(func() {
|
||||
osExecutableFn = origExec
|
||||
osArgsFn = origArgs
|
||||
})
|
||||
|
||||
t.Run("returns os.Executable when available", func(t *testing.T) {
|
||||
osExecutableFn = func() (string, error) { return "/usr/local/bin/pulse-agent", nil }
|
||||
osArgsFn = func() []string { return []string{"argv0"} }
|
||||
got, err := resolveExecutablePath()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != "/usr/local/bin/pulse-agent" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("falls back to absolute argv0 when os.Executable is empty", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
bin := filepath.Join(dir, "pulse-agent")
|
||||
if err := os.WriteFile(bin, []byte("x"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
osExecutableFn = func() (string, error) { return "", nil }
|
||||
osArgsFn = func() []string { return []string{bin} }
|
||||
got, err := resolveExecutablePath()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != bin {
|
||||
t.Fatalf("got %q, want %q", got, bin)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("errors when neither source is usable", func(t *testing.T) {
|
||||
osExecutableFn = func() (string, error) { return "", errors.New("boom") }
|
||||
osArgsFn = func() []string { return []string{"relative-arg"} }
|
||||
if _, err := resolveExecutablePath(); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue