| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package remote | |
| import ( | |
| "fmt" | |
| "io" | |
| "sync" | |
| ) | |
| // Cmd represents a remote command being prepared or run. | |
| type Cmd struct { | |
| // Command is the command to run remotely. This is executed as if | |
| // it were a shell command, so you are expected to do any shell escaping | |
| // necessary. | |
| Command string | |
| // Stdin specifies the process's standard input. If Stdin is | |
| // nil, the process reads from an empty bytes.Buffer. | |
| Stdin io.Reader | |
| // Stdout and Stderr represent the process's standard output and | |
| // error. | |
| // | |
| // If either is nil, it will be set to ioutil.Discard. | |
| Stdout io.Writer | |
| Stderr io.Writer | |
| // Once Wait returns, his will contain the exit code of the process. | |
| exitStatus int | |
| // Internal fields | |
| exitCh chan struct{} | |
| // err is used to store any error reported by the Communicator during | |
| // execution. | |
| err error | |
| // This thing is a mutex, lock when making modifications concurrently | |
| sync.Mutex | |
| } | |
| // Init must be called by the Communicator before executing the command. | |
| func (c *Cmd) Init() { | |
| c.Lock() | |
| defer c.Unlock() | |
| c.exitCh = make(chan struct{}) | |
| } | |
| // SetExitStatus stores the exit status of the remote command as well as any | |
| // communicator related error. SetExitStatus then unblocks any pending calls | |
| // to Wait. | |
| // This should only be called by communicators executing the remote.Cmd. | |
| func (c *Cmd) SetExitStatus(status int, err error) { | |
| c.Lock() | |
| defer c.Unlock() | |
| c.exitStatus = status | |
| c.err = err | |
| close(c.exitCh) | |
| } | |
| // Wait waits for the remote command to complete. | |
| // Wait may return an error from the communicator, or an ExitError if the | |
| // process exits with a non-zero exit status. | |
| func (c *Cmd) Wait() error { | |
| <-c.exitCh | |
| c.Lock() | |
| defer c.Unlock() | |
| if c.err != nil || c.exitStatus != 0 { | |
| return &ExitError{ | |
| Command: c.Command, | |
| ExitStatus: c.exitStatus, | |
| Err: c.err, | |
| } | |
| } | |
| return nil | |
| } | |
| // ExitError is returned by Wait to indicate and error executing the remote | |
| // command, or a non-zero exit status. | |
| type ExitError struct { | |
| Command string | |
| ExitStatus int | |
| Err error | |
| } | |
| func (e *ExitError) Error() string { | |
| if e.Err != nil { | |
| return fmt.Sprintf("error executing %q: %v", e.Command, e.Err) | |
| } | |
| return fmt.Sprintf("%q exit status: %d", e.Command, e.ExitStatus) | |
| } |