internal/toolpath/toolpath.go
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}