// Helper type to run and log commands // Copyright (C) 2021-2024 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, " ") }