mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-22 08:23:50 +01:00
108 lines
2.1 KiB
Go
108 lines
2.1 KiB
Go
package util
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
)
|
|
|
|
// An IOCommand exposes a forked process's std(in|out|err) through the io.ReadWriteCloser interface.
|
|
type IOCommand struct {
|
|
Cmd *exec.Cmd
|
|
CmdContext context.Context
|
|
CmdCancel context.CancelFunc
|
|
Stdin io.Writer
|
|
Stdout io.Reader
|
|
StderrBuf *bytes.Buffer
|
|
}
|
|
|
|
const IOCommandStderrBufSize = 1024
|
|
|
|
type IOCommandError struct {
|
|
WaitErr error
|
|
Stderr []byte
|
|
}
|
|
|
|
func (e IOCommandError) Error() string {
|
|
return fmt.Sprintf("underlying process exited with error: %s\nstderr: %s\n", e.WaitErr, e.Stderr)
|
|
}
|
|
|
|
func RunIOCommand(command string, args ...string) (c *IOCommand, err error) {
|
|
c, err = NewIOCommand(command, args, IOCommandStderrBufSize)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = c.Start()
|
|
return
|
|
}
|
|
|
|
func NewIOCommand(command string, args []string, stderrBufSize int) (c *IOCommand, err error) {
|
|
|
|
if stderrBufSize == 0 {
|
|
stderrBufSize = IOCommandStderrBufSize
|
|
}
|
|
|
|
c = &IOCommand{}
|
|
|
|
c.CmdContext, c.CmdCancel = context.WithCancel(context.Background())
|
|
c.Cmd = exec.CommandContext(c.CmdContext, command, args...)
|
|
|
|
if c.Stdout, err = c.Cmd.StdoutPipe(); err != nil {
|
|
return
|
|
}
|
|
|
|
if c.Stdin, err = c.Cmd.StdinPipe(); err != nil {
|
|
return
|
|
}
|
|
|
|
c.StderrBuf = bytes.NewBuffer(make([]byte, 0, stderrBufSize))
|
|
c.Cmd.Stderr = c.StderrBuf
|
|
|
|
return
|
|
|
|
}
|
|
|
|
func (c *IOCommand) Start() (err error) {
|
|
if err = c.Cmd.Start(); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Read from process's stdout.
|
|
// The behavior after Close()ing is undefined
|
|
func (c *IOCommand) Read(buf []byte) (n int, err error) {
|
|
n, err = c.Stdout.Read(buf)
|
|
if err == io.EOF {
|
|
if waitErr := c.doWait(); waitErr != nil {
|
|
err = waitErr
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *IOCommand) doWait() (err error) {
|
|
waitErr := c.Cmd.Wait()
|
|
if waitErr != nil {
|
|
err = IOCommandError{
|
|
WaitErr: waitErr,
|
|
Stderr: c.StderrBuf.Bytes(),
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Write to process's stdin.
|
|
// The behavior after Close()ing is undefined
|
|
func (c *IOCommand) Write(buf []byte) (n int, err error) {
|
|
return c.Stdin.Write(buf)
|
|
}
|
|
|
|
// Kill the child process and collect its exit status
|
|
func (c *IOCommand) Close() error {
|
|
c.CmdCancel()
|
|
return c.doWait()
|
|
}
|