go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/_motor/providers/os/cmd/command.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package cmd
     5  
     6  import (
     7  	"bytes"
     8  	"os/exec"
     9  	"strings"
    10  	"syscall"
    11  	"time"
    12  
    13  	"go.mondoo.com/cnquery/motor/providers/os"
    14  )
    15  
    16  type CommandRunner struct {
    17  	os.Command
    18  	cmdExecutor *exec.Cmd
    19  	Shell       []string
    20  }
    21  
    22  func (c *CommandRunner) Exec(usercmd string, args []string) (*os.Command, error) {
    23  	c.Command.Stats.Start = time.Now()
    24  
    25  	var cmd string
    26  	cmdArgs := []string{}
    27  
    28  	if len(c.Shell) > 0 {
    29  		shellCommand, shellArgs := c.Shell[0], c.Shell[1:]
    30  		cmd = shellCommand
    31  		cmdArgs = append(cmdArgs, shellArgs...)
    32  		cmdArgs = append(cmdArgs, usercmd)
    33  	} else {
    34  		cmd = usercmd
    35  	}
    36  	cmdArgs = append(cmdArgs, args...)
    37  
    38  	// this only stores the user command, not the shell
    39  	c.Command.Command = usercmd + " " + strings.Join(args, " ")
    40  	c.cmdExecutor = exec.Command(cmd, cmdArgs...)
    41  
    42  	var stdoutBuffer bytes.Buffer
    43  	var stderrBuffer bytes.Buffer
    44  
    45  	// create buffered stream
    46  	c.Command.Stdout = &stdoutBuffer
    47  	c.Command.Stderr = &stderrBuffer
    48  
    49  	c.cmdExecutor.Stdout = c.Command.Stdout
    50  	c.cmdExecutor.Stderr = c.Command.Stderr
    51  
    52  	err := c.cmdExecutor.Run()
    53  	c.Command.Stats.Duration = time.Since(c.Command.Stats.Start)
    54  
    55  	// command completed successfully, great :-)
    56  	if err == nil {
    57  		return &c.Command, nil
    58  	}
    59  
    60  	// if the program failed, we do not return err but its exit code
    61  	if exiterr, ok := err.(*exec.ExitError); ok {
    62  		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
    63  			c.Command.ExitStatus = status.ExitStatus()
    64  		}
    65  		return &c.Command, nil
    66  	}
    67  
    68  	// all other errors are real errors and not expected
    69  	return &c.Command, err
    70  }