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  }