github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/diff/stream_windows.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  	"io/ioutil"
    27  	"os"
    28  	"os/exec"
    29  	"path/filepath"
    30  	"sync"
    31  
    32  	winio "github.com/Microsoft/go-winio"
    33  	"github.com/gogo/protobuf/proto"
    34  	"github.com/gogo/protobuf/types"
    35  	"github.com/pkg/errors"
    36  	"github.com/sirupsen/logrus"
    37  )
    38  
    39  const processorPipe = "STREAM_PROCESSOR_PIPE"
    40  
    41  // NewBinaryProcessor returns a binary processor for use with processing content streams
    42  func NewBinaryProcessor(ctx context.Context, imt, rmt string, stream StreamProcessor, name string, args []string, payload *types.Any) (StreamProcessor, error) {
    43  	cmd := exec.CommandContext(ctx, name, args...)
    44  	cmd.Env = os.Environ()
    45  
    46  	if payload != nil {
    47  		data, err := proto.Marshal(payload)
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  		up, err := getUiqPath()
    52  		if err != nil {
    53  			return nil, err
    54  		}
    55  		path := fmt.Sprintf("\\\\.\\pipe\\containerd-processor-%s-pipe", up)
    56  		l, err := winio.ListenPipe(path, nil)
    57  		if err != nil {
    58  			return nil, err
    59  		}
    60  		go func() {
    61  			defer l.Close()
    62  			conn, err := l.Accept()
    63  			if err != nil {
    64  				logrus.WithError(err).Error("accept npipe connection")
    65  				return
    66  			}
    67  			io.Copy(conn, bytes.NewReader(data))
    68  			conn.Close()
    69  		}()
    70  		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", processorPipe, path))
    71  	}
    72  	cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", mediaTypeEnvVar, imt))
    73  	var (
    74  		stdin  io.Reader
    75  		closer func() error
    76  		err    error
    77  	)
    78  	if f, ok := stream.(RawProcessor); ok {
    79  		stdin = f.File()
    80  		closer = f.File().Close
    81  	} else {
    82  		stdin = stream
    83  	}
    84  	cmd.Stdin = stdin
    85  	r, w, err := os.Pipe()
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	cmd.Stdout = w
    90  	stderr := bytes.NewBuffer(nil)
    91  	cmd.Stderr = stderr
    92  
    93  	if err := cmd.Start(); err != nil {
    94  		return nil, err
    95  	}
    96  	p := &binaryProcessor{
    97  		cmd:    cmd,
    98  		r:      r,
    99  		mt:     rmt,
   100  		stderr: stderr,
   101  	}
   102  	go p.wait()
   103  
   104  	// close after start and dup
   105  	w.Close()
   106  	if closer != nil {
   107  		closer()
   108  	}
   109  	return p, nil
   110  }
   111  
   112  type binaryProcessor struct {
   113  	cmd    *exec.Cmd
   114  	r      *os.File
   115  	mt     string
   116  	stderr *bytes.Buffer
   117  
   118  	mu  sync.Mutex
   119  	err error
   120  }
   121  
   122  func (c *binaryProcessor) Err() error {
   123  	c.mu.Lock()
   124  	defer c.mu.Unlock()
   125  	return c.err
   126  }
   127  
   128  func (c *binaryProcessor) wait() {
   129  	if err := c.cmd.Wait(); err != nil {
   130  		if _, ok := err.(*exec.ExitError); ok {
   131  			c.mu.Lock()
   132  			c.err = errors.New(c.stderr.String())
   133  			c.mu.Unlock()
   134  		}
   135  	}
   136  }
   137  
   138  func (c *binaryProcessor) File() *os.File {
   139  	return c.r
   140  }
   141  
   142  func (c *binaryProcessor) MediaType() string {
   143  	return c.mt
   144  }
   145  
   146  func (c *binaryProcessor) Read(p []byte) (int, error) {
   147  	return c.r.Read(p)
   148  }
   149  
   150  func (c *binaryProcessor) Close() error {
   151  	err := c.r.Close()
   152  	if kerr := c.cmd.Process.Kill(); err == nil {
   153  		err = kerr
   154  	}
   155  	return err
   156  }
   157  
   158  func getUiqPath() (string, error) {
   159  	dir, err := ioutil.TempDir("", "")
   160  	if err != nil {
   161  		return "", err
   162  	}
   163  	os.Remove(dir)
   164  	return filepath.Base(dir), nil
   165  }