...

Source file src/github.com/Microsoft/hcsshim/pkg/go-runhcs/runhcs.go

Documentation: github.com/Microsoft/hcsshim/pkg/go-runhcs

     1  //go:build windows
     2  
     3  package runhcs
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  	"sync/atomic"
    16  
    17  	irunhcs "github.com/Microsoft/hcsshim/internal/runhcs"
    18  	"github.com/containerd/go-runc"
    19  )
    20  
    21  // Format is the type of log formatting options available.
    22  type Format string
    23  
    24  const (
    25  	none Format = ""
    26  	// Text is the default text log output.
    27  	Text Format = "text"
    28  	// JSON is the JSON formatted log output.
    29  	JSON Format = "json"
    30  )
    31  
    32  var runhcsPath atomic.Value
    33  
    34  func getCommandPath() string {
    35  	const command = "runhcs.exe"
    36  
    37  	pathi := runhcsPath.Load()
    38  	if pathi == nil {
    39  		path, err := exec.LookPath(command)
    40  		if err != nil {
    41  			if errors.Is(err, exec.ErrDot) {
    42  				err = nil
    43  			}
    44  		}
    45  		if err != nil {
    46  			// LookPath only finds current directory matches based on the
    47  			// callers current directory but the caller is not likely in the
    48  			// same directory as the containerd executables. Instead match the
    49  			// calling binaries path (a containerd shim usually) and see if they
    50  			// are side by side. If so execute the runhcs.exe found there.
    51  			if self, serr := os.Executable(); serr == nil {
    52  				testPath := filepath.Join(filepath.Dir(self), command)
    53  				if _, serr := os.Stat(testPath); serr == nil {
    54  					path = testPath
    55  				}
    56  			}
    57  			if path == "" {
    58  				// Failed to look up command just use it directly and let the
    59  				// Windows loader find it.
    60  				path = command
    61  			}
    62  			runhcsPath.Store(path)
    63  			return path
    64  		}
    65  		apath, err := filepath.Abs(path)
    66  		if err != nil {
    67  			// We couldnt make `path` an `AbsPath`. Just use `path` directly and
    68  			// let the Windows loader find it.
    69  			apath = path
    70  		}
    71  		runhcsPath.Store(apath)
    72  		return apath
    73  	}
    74  	return pathi.(string)
    75  }
    76  
    77  var bytesBufferPool = sync.Pool{
    78  	New: func() interface{} {
    79  		return bytes.NewBuffer(nil)
    80  	},
    81  }
    82  
    83  func getBuf() *bytes.Buffer {
    84  	return bytesBufferPool.Get().(*bytes.Buffer)
    85  }
    86  
    87  func putBuf(b *bytes.Buffer) {
    88  	b.Reset()
    89  	bytesBufferPool.Put(b)
    90  }
    91  
    92  // Runhcs is the client to the runhcs cli
    93  type Runhcs struct {
    94  	// Debug enables debug output for logging.
    95  	Debug bool
    96  	// Log sets the log file path or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-log) where internal debug information is written.
    97  	Log string
    98  	// LogFormat sets the format used by logs.
    99  	LogFormat Format
   100  	// Owner sets the compute system owner property.
   101  	Owner string
   102  	// Root is the registry key root for storage of runhcs container state.
   103  	Root string
   104  }
   105  
   106  func (r *Runhcs) args() []string {
   107  	var out []string
   108  	if r.Debug {
   109  		out = append(out, "--debug")
   110  	}
   111  	if r.Log != "" {
   112  		if strings.HasPrefix(r.Log, irunhcs.SafePipePrefix) {
   113  			out = append(out, "--log", r.Log)
   114  		} else {
   115  			abs, err := filepath.Abs(r.Log)
   116  			if err == nil {
   117  				out = append(out, "--log", abs)
   118  			}
   119  		}
   120  	}
   121  	if r.LogFormat != none {
   122  		out = append(out, "--log-format", string(r.LogFormat))
   123  	}
   124  	if r.Owner != "" {
   125  		out = append(out, "--owner", r.Owner)
   126  	}
   127  	if r.Root != "" {
   128  		out = append(out, "--root", r.Root)
   129  	}
   130  	return out
   131  }
   132  
   133  func (r *Runhcs) command(context context.Context, args ...string) *exec.Cmd {
   134  	cmd := exec.CommandContext(context, getCommandPath(), append(r.args(), args...)...)
   135  	cmd.Env = os.Environ()
   136  	return cmd
   137  }
   138  
   139  // runOrError will run the provided command.  If an error is
   140  // encountered and neither Stdout or Stderr was set the error and the
   141  // stderr of the command will be returned in the format of <error>:
   142  // <stderr>
   143  func (r *Runhcs) runOrError(cmd *exec.Cmd) error {
   144  	if cmd.Stdout != nil || cmd.Stderr != nil {
   145  		ec, err := runc.Monitor.Start(cmd)
   146  		if err != nil {
   147  			return err
   148  		}
   149  		status, err := runc.Monitor.Wait(cmd, ec)
   150  		if err == nil && status != 0 {
   151  			err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
   152  		}
   153  		return err
   154  	}
   155  	data, err := cmdOutput(cmd, true)
   156  	if err != nil {
   157  		return fmt.Errorf("%s: %s", err, data)
   158  	}
   159  	return nil
   160  }
   161  
   162  func cmdOutput(cmd *exec.Cmd, combined bool) ([]byte, error) {
   163  	b := getBuf()
   164  	defer putBuf(b)
   165  
   166  	cmd.Stdout = b
   167  	if combined {
   168  		cmd.Stderr = b
   169  	}
   170  	ec, err := runc.Monitor.Start(cmd)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	status, err := runc.Monitor.Wait(cmd, ec)
   176  	if err == nil && status != 0 {
   177  		err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
   178  	}
   179  
   180  	return b.Bytes(), err
   181  }
   182  

View as plain text