github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/diff/stream_unix.go (about) 1 // +build !windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package diff 20 21 import ( 22 "bytes" 23 "context" 24 "fmt" 25 "io" 26 "os" 27 "os/exec" 28 "sync" 29 30 "github.com/gogo/protobuf/proto" 31 "github.com/gogo/protobuf/types" 32 "github.com/pkg/errors" 33 ) 34 35 // NewBinaryProcessor returns a binary processor for use with processing content streams 36 func NewBinaryProcessor(ctx context.Context, imt, rmt string, stream StreamProcessor, name string, args []string, payload *types.Any) (StreamProcessor, error) { 37 cmd := exec.CommandContext(ctx, name, args...) 38 cmd.Env = os.Environ() 39 40 var payloadC io.Closer 41 if payload != nil { 42 data, err := proto.Marshal(payload) 43 if err != nil { 44 return nil, err 45 } 46 r, w, err := os.Pipe() 47 if err != nil { 48 return nil, err 49 } 50 go func() { 51 io.Copy(w, bytes.NewReader(data)) 52 w.Close() 53 }() 54 55 cmd.ExtraFiles = append(cmd.ExtraFiles, r) 56 payloadC = r 57 } 58 cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", mediaTypeEnvVar, imt)) 59 var ( 60 stdin io.Reader 61 closer func() error 62 err error 63 ) 64 if f, ok := stream.(RawProcessor); ok { 65 stdin = f.File() 66 closer = f.File().Close 67 } else { 68 stdin = stream 69 } 70 cmd.Stdin = stdin 71 r, w, err := os.Pipe() 72 if err != nil { 73 return nil, err 74 } 75 cmd.Stdout = w 76 77 stderr := bytes.NewBuffer(nil) 78 cmd.Stderr = stderr 79 80 if err := cmd.Start(); err != nil { 81 return nil, err 82 } 83 p := &binaryProcessor{ 84 cmd: cmd, 85 r: r, 86 mt: rmt, 87 stderr: stderr, 88 } 89 go p.wait() 90 91 // close after start and dup 92 w.Close() 93 if closer != nil { 94 closer() 95 } 96 if payloadC != nil { 97 payloadC.Close() 98 } 99 return p, nil 100 } 101 102 type binaryProcessor struct { 103 cmd *exec.Cmd 104 r *os.File 105 mt string 106 stderr *bytes.Buffer 107 108 mu sync.Mutex 109 err error 110 } 111 112 func (c *binaryProcessor) Err() error { 113 c.mu.Lock() 114 defer c.mu.Unlock() 115 return c.err 116 } 117 118 func (c *binaryProcessor) wait() { 119 if err := c.cmd.Wait(); err != nil { 120 if _, ok := err.(*exec.ExitError); ok { 121 c.mu.Lock() 122 c.err = errors.New(c.stderr.String()) 123 c.mu.Unlock() 124 } 125 } 126 } 127 128 func (c *binaryProcessor) File() *os.File { 129 return c.r 130 } 131 132 func (c *binaryProcessor) MediaType() string { 133 return c.mt 134 } 135 136 func (c *binaryProcessor) Read(p []byte) (int, error) { 137 return c.r.Read(p) 138 } 139 140 func (c *binaryProcessor) Close() error { 141 err := c.r.Close() 142 if kerr := c.cmd.Process.Kill(); err == nil { 143 err = kerr 144 } 145 return err 146 }