internal/toolpath/toolpath.go

f8b976a97290a20d552056a999511f5d27d8e8ec
gitbay/internal/toolpath/toolpath.go history · blame · raw

64 lines · 2029 bytes

 1// Package toolpath resolves the external programs gitbay runs — git, ssh,
 2// sh — to absolute paths once, when the process starts, instead of
 3// letting the kernel search PATH on every spawn.
 4//
 5// Three things it buys, in the order they matter.
 6//
 7// A missing tool becomes one legible failure at start-up rather than an
 8// opaque one at the first push, on whichever request happened to need it.
 9//
10// The command a long-lived daemon runs is then fixed for its lifetime,
11// decided from the environment it was started with rather than resolved
12// afresh each time. gitbayd and gitbay-runner both run under systemd with
13// a root-owned PATH and a read-only /usr, so a search was never
14// attacker-influenced in a shipped configuration — but a spawn that
15// cannot be redirected is one fewer thing to reason about, and it is what
16// the scanner asks for (go:S4036).
17//
18// And the lookup leaves the hot path: a repository page can spawn several
19// git processes, and each was searching PATH from scratch.
20package toolpath
21
22import (
23	"fmt"
24	"os/exec"
25	"strings"
26	"sync"
27)
28
29var (
30	mu      sync.Mutex
31	cache   = map[string]string{}
32	missing []string
33)
34
35// Look returns the absolute path to name, or name itself when it is not
36// on PATH. Returning the bare name keeps the failure where it already
37// was — exec reporting it — for anything that runs before Verify, and for
38// a tool a particular binary never actually uses.
39func Look(name string) string {
40	mu.Lock()
41	defer mu.Unlock()
42	if p, ok := cache[name]; ok {
43		return p
44	}
45	p, err := exec.LookPath(name)
46	if err != nil {
47		p = name
48		missing = append(missing, name)
49	}
50	cache[name] = p
51	return p
52}
53
54// Verify reports the tools that were asked for and not found, so a daemon
55// can refuse to start rather than fail on its first request. Call it after
56// the packages that need tools are initialised.
57func Verify() error {
58	mu.Lock()
59	defer mu.Unlock()
60	if len(missing) == 0 {
61		return nil
62	}
63	return fmt.Errorf("not found on PATH: %s", strings.Join(missing, ", "))
64}