]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - remote/run/cmd.go
Use SPDX license identifiers
[safcm/safcm.git] / remote / run / cmd.go
1 // Helper type to run and log commands
2
3 // SPDX-License-Identifier: GPL-3.0-or-later
4 // Copyright (C) 2021-2024  Simon Ruderich
5
6 package run
7
8 import (
9         "bytes"
10         "fmt"
11         "os/exec"
12         "strings"
13
14         "ruderich.org/simon/safcm/remote/log"
15 )
16
17 type Cmd struct {
18         Runner Runner
19         logger *log.Logger
20 }
21
22 func NewCmd(runner Runner, logger *log.Logger) *Cmd {
23         return &Cmd{
24                 Runner: runner,
25                 logger: logger,
26         }
27 }
28
29 // Run runs a command and return stdout and stderr.
30 //
31 // Use this only for commands running on our behalf where we need to parse the
32 // output. Use CombinedOutput() for user commands because these should show
33 // the complete output and have stdout and stderr in the proper order and not
34 // split.
35 func (c *Cmd) Run(module string, args ...string) ([]byte, []byte, error) {
36         var stdout, stderr bytes.Buffer
37         cmd := exec.Command(args[0], args[1:]...)
38         cmd.Stdout = &stdout
39         cmd.Stderr = &stderr
40         quoted := QuoteForDebug(cmd)
41         c.logger.Debugf("%s: running %s", module, quoted)
42         err := c.Runner.Run(cmd)
43         if stdout.Len() > 0 {
44                 c.logger.Debug2f("%s: command stdout:\n%s",
45                         module, stdout.Bytes())
46         }
47         if stderr.Len() > 0 {
48                 c.logger.Debug2f("%s: command stderr:\n%s",
49                         module, stderr.Bytes())
50         }
51         if err != nil {
52                 return nil, nil, fmt.Errorf(
53                         "%s failed: %v; stdout: %q, stderr: %q",
54                         quoted, err, stdout.String(), stderr.String())
55         }
56         return stdout.Bytes(), stderr.Bytes(), nil
57 }
58
59 // CombinedOutputCmd runs the command and returns its combined stdout and
60 // stderr.
61 func (c *Cmd) CombinedOutputCmd(module string, cmd *exec.Cmd) ([]byte, error) {
62         quoted := QuoteForDebug(cmd)
63         c.logger.Debugf("%s: running %s", module, quoted)
64         out, err := c.Runner.CombinedOutput(cmd)
65         if len(out) > 0 {
66                 c.logger.Debug2f("%s: command output:\n%s", module, out)
67         }
68         if err != nil {
69                 return nil, fmt.Errorf("%s failed: %v; output: %q",
70                         quoted, err, out)
71         }
72         return out, nil
73 }
74
75 func QuoteForDebug(cmd *exec.Cmd) string {
76         // TODO: proper shell escaping, remove quotes when not necessary
77         var quoted []string
78         for _, x := range append([]string{cmd.Path}, cmd.Args[1:]...) {
79                 quoted = append(quoted, fmt.Sprintf("%q", x))
80         }
81         return strings.Join(quoted, " ")
82 }