11// Package rootful provides utilities for running operations that require
2- // root privileges via sudo.
2+ // root privileges via a configurable sudo command .
33package rootful
44
55import (
@@ -12,22 +12,28 @@ import (
1212
1313//nolint:gochecknoglobals // singleton: process-wide memoization is the intent
1414var (
15- validateOnce sync.Once
16- errValidate error
15+ validateOnce sync.Once
16+ errValidate error
17+ cachedSudoCommand string
1718)
1819
19- // Validate ensures that sudo is available and the user can elevate.
20- // It runs `sudo -v` at most once per process: the first call performs the
21- // check and caches the result; subsequent calls return the cached result
22- // without re-running the command.
23- func Validate (ctx context.Context ) error {
20+ // Validate ensures that sudoCommand is available and the user can elevate.
21+ // It runs `<sudoCommand> -v` at most once per process: the first call performs
22+ // the check and caches the result; subsequent calls return the cached result
23+ // without re-running the command. Passing a different sudoCommand after the
24+ // first call is a programming error and returns an explicit error.
25+ func Validate (ctx context.Context , sudoCommand string ) error {
26+ if cachedSudoCommand != "" && cachedSudoCommand != sudoCommand {
27+ return fmt .Errorf ("sudoCommand mismatch: already validated with %q, got %q" , cachedSudoCommand , sudoCommand )
28+ }
2429 validateOnce .Do (func () {
25- cmd := exec .CommandContext (ctx , "sudo" , "-v" )
30+ cachedSudoCommand = sudoCommand
31+ cmd := exec .CommandContext (ctx , sudoCommand , "-v" )
2632 cmd .Stdin = os .Stdin
2733 cmd .Stdout = os .Stdout
2834 cmd .Stderr = os .Stderr
2935 if err := cmd .Run (); err != nil {
30- errValidate = fmt .Errorf ("failed to validate sudo : %w" , err )
36+ errValidate = fmt .Errorf ("failed to validate %q : %w" , sudoCommand , err )
3137 }
3238 })
3339 return errValidate
0 commit comments