1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
package interp
// I/O streams are interfaces which allow file redirects and command pipelines to be treated
// equivalently.
import (
"bufio"
"errors"
"io"
"os/exec"
"syscall"
)
const (
notClosedExitCode = -127
)
var (
errDoubleClose = errors.New("close: stream already closed")
)
// firstError returns the first non-nil error or nil if all errors are nil.
func firstError(errs ...error) error {
for _, err := range errs {
if err != nil {
return err
}
}
return nil
}
// Close the cmd and convert the error result into the result returned from goawk builtin functions.
// A nil error is returned if that error describes a non-zero exit status or an unhandled signal.
// Any other type of error returns -1 and err.
//
// The result mimicks gawk for expected child process errors:
// 1. Returns the exit status of the child process and nil error on normal process exit.
// 2. Returns 256 + signal on unhandled signal exit.
// 3. Returns 512 + signal on unhandled signal exit which caused a core dump.
func waitExitCode(cmd *exec.Cmd) (int, error) {
err := cmd.Wait()
if err == nil {
return 0, nil
}
ee, ok := err.(*exec.ExitError)
if !ok {
// Wait() returned an io error.
return -1, err
}
status, ok := ee.ProcessState.Sys().(syscall.WaitStatus)
if !ok {
// Maybe not all platforms support WaitStatus?
return -1, err
}
switch {
case status.CoreDump():
return 512 + int(status.Signal()), nil
case status.Signaled():
return 256 + int(status.Signal()), nil
case status.Exited():
return status.ExitStatus(), nil
default:
return -1, err
}
}
type inputStream interface {
io.ReadCloser
ExitCode() int
}
type outputStream interface {
io.WriteCloser
Flush() error
ExitCode() int
}
type outFileStream struct {
*bufio.Writer
closer io.Closer
exitCode int
closed bool
}
func newOutFileStream(wc io.WriteCloser, size int) outputStream {
b := bufio.NewWriterSize(wc, size)
return &outFileStream{b, wc, notClosedExitCode, false}
}
func (s *outFileStream) Close() error {
if s.closed {
return errDoubleClose
}
s.closed = true
flushErr := s.Writer.Flush()
closeErr := s.closer.Close()
if err := firstError(flushErr, closeErr); err != nil {
s.exitCode = -1
return err
}
s.exitCode = 0
return nil
}
func (s *outFileStream) ExitCode() int {
return s.exitCode
}
type outCmdStream struct {
*bufio.Writer
closer io.Closer
cmd *exec.Cmd
exitCode int
closed bool
}
func newOutCmdStream(cmd *exec.Cmd) (outputStream, error) {
w, err := cmd.StdinPipe()
if err != nil {
return nil, newError("error connecting to stdin pipe: %v", err)
}
err = cmd.Start()
if err != nil {
w.Close()
return nil, err
}
out := &outCmdStream{bufio.NewWriterSize(w, outputBufSize), w, cmd, notClosedExitCode, false}
return out, nil
}
func (s *outCmdStream) Close() error {
if s.closed {
return errDoubleClose
}
s.closed = true
flushErr := s.Writer.Flush()
closeErr := s.closer.Close()
var waitErr error
s.exitCode, waitErr = waitExitCode(s.cmd)
return firstError(waitErr, flushErr, closeErr)
}
func (s *outCmdStream) ExitCode() int {
return s.exitCode
}
// An outNullStream allows writes to not do anything while fulfilling the outputStream interface.
type outNullStream struct {
io.Writer
closed bool
}
func newOutNullStream() outputStream { return &outNullStream{io.Discard, false} }
func (s outNullStream) Flush() error { return nil }
func (s *outNullStream) Close() error {
if s.closed {
return errDoubleClose
}
s.closed = true
return nil
}
func (s outNullStream) ExitCode() int { return -1 }
type inFileStream struct {
io.ReadCloser
exitCode int
closed bool
}
func newInFileStream(rc io.ReadCloser) inputStream {
return &inFileStream{rc, notClosedExitCode, false}
}
func (s *inFileStream) Close() error {
if s.closed {
return errDoubleClose
}
s.closed = true
if err := s.ReadCloser.Close(); err != nil {
s.exitCode = -1
return err
}
s.exitCode = 0
return nil
}
func (s *inFileStream) ExitCode() int {
return s.exitCode
}
type inCmdStream struct {
io.ReadCloser
cmd *exec.Cmd
exitCode int
closed bool
}
func newInCmdStream(cmd *exec.Cmd) (inputStream, error) {
r, err := cmd.StdoutPipe()
if err != nil {
return nil, newError("error connecting to stdout pipe: %v", err)
}
err = cmd.Start()
if err != nil {
r.Close()
return nil, err
}
return &inCmdStream{r, cmd, notClosedExitCode, false}, nil
}
func (s *inCmdStream) Close() error {
if s.closed {
return errDoubleClose
}
s.closed = true
closeErr := s.ReadCloser.Close()
var waitErr error
s.exitCode, waitErr = waitExitCode(s.cmd)
return firstError(waitErr, closeErr)
}
func (s *inCmdStream) ExitCode() int {
return s.exitCode
}
|