X-Git-Url: https://ruderich.org/simon/gitweb/?a=blobdiff_plain;ds=sidebyside;f=remote%2Frun%2Fcmd.go;fp=remote%2Frun%2Fcmd.go;h=a1c5e7f7de760deadfef7fbfc2b063b2c5ae83ce;hb=9269fa3c94e700afc0be823f58ea473a2db8f3dc;hp=0000000000000000000000000000000000000000;hpb=fd97e8019e2ab166d9475ed59782c86247d8430b;p=safcm%2Fsafcm.git diff --git a/remote/run/cmd.go b/remote/run/cmd.go new file mode 100644 index 0000000..a1c5e7f --- /dev/null +++ b/remote/run/cmd.go @@ -0,0 +1,94 @@ +// 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 . + +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, " ") +}