--- /dev/null
+// Helper type to run and log commands
+
+// Copyright (C) 2021 Simon Ruderich
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package run
+
+import (
+ "bytes"
+ "fmt"
+ "os/exec"
+ "strings"
+
+ "ruderich.org/simon/safcm/remote/log"
+)
+
+type Cmd struct {
+ Runner Runner
+ logger *log.Logger
+}
+
+func NewCmd(runner Runner, logger *log.Logger) *Cmd {
+ return &Cmd{
+ Runner: runner,
+ logger: logger,
+ }
+}
+
+// Run runs a command and return stdout and stderr.
+//
+// Use this only for commands running on our behalf where we need to parse the
+// output. Use CombinedOutput() for user commands because these should show
+// the complete output and have stdout and stderr in the proper order and not
+// split.
+func (c *Cmd) Run(module string, args ...string) ([]byte, []byte, error) {
+ var stdout, stderr bytes.Buffer
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ quoted := QuoteForDebug(cmd)
+ c.logger.Debugf("%s: running %s", module, quoted)
+ err := c.Runner.Run(cmd)
+ if stdout.Len() > 0 {
+ c.logger.Debug2f("%s: command stdout:\n%s",
+ module, stdout.Bytes())
+ }
+ if stderr.Len() > 0 {
+ c.logger.Debug2f("%s: command stderr:\n%s",
+ module, stderr.Bytes())
+ }
+ if err != nil {
+ return nil, nil, fmt.Errorf(
+ "%s failed: %v; stdout: %q, stderr: %q",
+ quoted, err, stdout.String(), stderr.String())
+ }
+ return stdout.Bytes(), stderr.Bytes(), nil
+}
+
+// CombinedOutputCmd runs the command and returns its combined stdout and
+// stderr.
+func (c *Cmd) CombinedOutputCmd(module string, cmd *exec.Cmd) ([]byte, error) {
+ quoted := QuoteForDebug(cmd)
+ c.logger.Debugf("%s: running %s", module, quoted)
+ out, err := c.Runner.CombinedOutput(cmd)
+ if len(out) > 0 {
+ c.logger.Debug2f("%s: command output:\n%s", module, out)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("%s failed: %v; output: %q",
+ quoted, err, out)
+ }
+ return out, nil
+}
+
+func QuoteForDebug(cmd *exec.Cmd) string {
+ // TODO: proper shell escaping, remove quotes when not necessary
+ var quoted []string
+ for _, x := range append([]string{cmd.Path}, cmd.Args[1:]...) {
+ quoted = append(quoted, fmt.Sprintf("%q", x))
+ }
+ return strings.Join(quoted, " ")
+}