package handlers import ( "context" "fmt" "os" "os/exec" "runtime" "strings" "syscall" "time" "lure.sh/fakeroot" "mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/interp" ) // FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go // and modified to run commands in a fakeroot environent. func FakerootExecHandler(killTimeout time.Duration) interp.ExecHandlerFunc { return func(ctx context.Context, args []string) error { hc := interp.HandlerCtx(ctx) path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0]) if err != nil { fmt.Fprintln(hc.Stderr, err) return interp.NewExitStatus(127) } cmd := &exec.Cmd{ Path: path, Args: args, Env: execEnv(hc.Env), Dir: hc.Dir, Stdin: hc.Stdin, Stdout: hc.Stdout, Stderr: hc.Stderr, } err = fakeroot.Apply(cmd) if err != nil { return err } err = cmd.Start() if err == nil { if done := ctx.Done(); done != nil { go func() { <-done if killTimeout <= 0 || runtime.GOOS == "windows" { _ = cmd.Process.Signal(os.Kill) return } // TODO: don't temporarily leak this goroutine // if the program stops itself with the // interrupt. go func() { time.Sleep(killTimeout) _ = cmd.Process.Signal(os.Kill) }() _ = cmd.Process.Signal(os.Interrupt) }() } err = cmd.Wait() } switch x := err.(type) { case *exec.ExitError: // started, but errored - default to 1 if OS // doesn't have exit statuses if status, ok := x.Sys().(syscall.WaitStatus); ok { if status.Signaled() { if ctx.Err() != nil { return ctx.Err() } return interp.NewExitStatus(uint8(128 + status.Signal())) } return interp.NewExitStatus(uint8(status.ExitStatus())) } return interp.NewExitStatus(1) case *exec.Error: // did not start fmt.Fprintf(hc.Stderr, "%v\n", err) return interp.NewExitStatus(127) default: return err } } } // execEnv was extracted from github.com/mvdan/sh/interp/vars.go func execEnv(env expand.Environ) []string { list := make([]string, 0, 64) env.Each(func(name string, vr expand.Variable) bool { if !vr.IsSet() { // If a variable is set globally but unset in the // runner, we need to ensure it's not part of the final // list. Seems like zeroing the element is enough. // This is a linear search, but this scenario should be // rare, and the number of variables shouldn't be large. for i, kv := range list { if strings.HasPrefix(kv, name+"=") { list[i] = "" } } } if vr.Exported && vr.Kind == expand.String { list = append(list, name+"="+vr.String()) } return true }) return list }