]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - remote/run/cmd.go
safcm: add commit date to version output
[safcm/safcm.git] / remote / run / cmd.go
1 // Helper type to run and log commands
2
3 // Copyright (C) 2021  Simon Ruderich
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 package run
19
20 import (
21         "bytes"
22         "fmt"
23         "os/exec"
24         "strings"
25
26         "ruderich.org/simon/safcm/remote/log"
27 )
28
29 type Cmd struct {
30         Runner Runner
31         logger *log.Logger
32 }
33
34 func NewCmd(runner Runner, logger *log.Logger) *Cmd {
35         return &Cmd{
36                 Runner: runner,
37                 logger: logger,
38         }
39 }
40
41 // Run runs a command and return stdout and stderr.
42 //
43 // Use this only for commands running on our behalf where we need to parse the
44 // output. Use CombinedOutput() for user commands because these should show
45 // the complete output and have stdout and stderr in the proper order and not
46 // split.
47 func (c *Cmd) Run(module string, args ...string) ([]byte, []byte, error) {
48         var stdout, stderr bytes.Buffer
49         cmd := exec.Command(args[0], args[1:]...)
50         cmd.Stdout = &stdout
51         cmd.Stderr = &stderr
52         quoted := QuoteForDebug(cmd)
53         c.logger.Debugf("%s: running %s", module, quoted)
54         err := c.Runner.Run(cmd)
55         if stdout.Len() > 0 {
56                 c.logger.Debug2f("%s: command stdout:\n%s",
57                         module, stdout.Bytes())
58         }
59         if stderr.Len() > 0 {
60                 c.logger.Debug2f("%s: command stderr:\n%s",
61                         module, stderr.Bytes())
62         }
63         if err != nil {
64                 return nil, nil, fmt.Errorf(
65                         "%s failed: %v; stdout: %q, stderr: %q",
66                         quoted, err, stdout.String(), stderr.String())
67         }
68         return stdout.Bytes(), stderr.Bytes(), nil
69 }
70
71 // CombinedOutputCmd runs the command and returns its combined stdout and
72 // stderr.
73 func (c *Cmd) CombinedOutputCmd(module string, cmd *exec.Cmd) ([]byte, error) {
74         quoted := QuoteForDebug(cmd)
75         c.logger.Debugf("%s: running %s", module, quoted)
76         out, err := c.Runner.CombinedOutput(cmd)
77         if len(out) > 0 {
78                 c.logger.Debug2f("%s: command output:\n%s", module, out)
79         }
80         if err != nil {
81                 return nil, fmt.Errorf("%s failed: %v; output: %q",
82                         quoted, err, out)
83         }
84         return out, nil
85 }
86
87 func QuoteForDebug(cmd *exec.Cmd) string {
88         // TODO: proper shell escaping, remove quotes when not necessary
89         var quoted []string
90         for _, x := range append([]string{cmd.Path}, cmd.Args[1:]...) {
91                 quoted = append(quoted, fmt.Sprintf("%q", x))
92         }
93         return strings.Join(quoted, " ")
94 }