...

Source file src/github.com/Microsoft/hcsshim/cmd/runhcs/shim.go

Documentation: github.com/Microsoft/hcsshim/cmd/runhcs

     1  //go:build windows
     2  
     3  package main
     4  
     5  import (
     6  	gcontext "context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"net"
    11  	"os"
    12  	"time"
    13  
    14  	winio "github.com/Microsoft/go-winio"
    15  	"github.com/Microsoft/hcsshim/internal/appargs"
    16  	cmdpkg "github.com/Microsoft/hcsshim/internal/cmd"
    17  	"github.com/Microsoft/hcsshim/internal/hcs"
    18  	"github.com/Microsoft/hcsshim/internal/runhcs"
    19  	specs "github.com/opencontainers/runtime-spec/specs-go"
    20  	"github.com/sirupsen/logrus"
    21  	"github.com/urfave/cli"
    22  )
    23  
    24  func newFile(context *cli.Context, param string) *os.File {
    25  	fd := uintptr(context.Int(param))
    26  	if fd == 0 {
    27  		return nil
    28  	}
    29  	return os.NewFile(fd, "")
    30  }
    31  
    32  var shimCommand = cli.Command{
    33  	Name:   "shim",
    34  	Usage:  `launch the process and proxy stdio (do not call it outside of runhcs)`,
    35  	Hidden: true,
    36  	Flags: []cli.Flag{
    37  		&cli.IntFlag{Name: "stdin", Hidden: true},
    38  		&cli.IntFlag{Name: "stdout", Hidden: true},
    39  		&cli.IntFlag{Name: "stderr", Hidden: true},
    40  		&cli.BoolFlag{Name: "exec", Hidden: true},
    41  		cli.StringFlag{Name: "log-pipe", Hidden: true},
    42  	},
    43  	Before: appargs.Validate(argID),
    44  	Action: func(context *cli.Context) error {
    45  		logPipe := context.String("log-pipe")
    46  		if logPipe != "" {
    47  			lpc, err := winio.DialPipe(logPipe, nil)
    48  			if err != nil {
    49  				return err
    50  			}
    51  			defer lpc.Close()
    52  			logrus.SetOutput(lpc)
    53  		} else {
    54  			logrus.SetOutput(os.Stderr)
    55  		}
    56  		fatalWriter.Writer = os.Stdout
    57  
    58  		id := context.Args().First()
    59  		c, err := getContainer(id, true)
    60  		if err != nil {
    61  			return err
    62  		}
    63  		defer c.Close()
    64  
    65  		// Asynchronously wait for the container to exit.
    66  		containerExitCh := make(chan struct{})
    67  		var containerExitErr error
    68  		go func() {
    69  			containerExitErr = c.hc.Wait()
    70  			close(containerExitCh)
    71  		}()
    72  
    73  		// Get File objects for the open stdio files passed in as arguments.
    74  		stdin := newFile(context, "stdin")
    75  		stdout := newFile(context, "stdout")
    76  		stderr := newFile(context, "stderr")
    77  
    78  		exec := context.Bool("exec")
    79  		terminateOnFailure := false
    80  
    81  		errorOut := io.WriteCloser(os.Stdout)
    82  
    83  		var spec *specs.Process
    84  
    85  		if exec {
    86  			// Read the process spec from stdin.
    87  			specj, err := io.ReadAll(os.Stdin)
    88  			if err != nil {
    89  				return err
    90  			}
    91  			os.Stdin.Close()
    92  
    93  			spec = new(specs.Process)
    94  			err = json.Unmarshal(specj, spec)
    95  			if err != nil {
    96  				return err
    97  			}
    98  
    99  		} else {
   100  			// Stdin is not used.
   101  			os.Stdin.Close()
   102  
   103  			// Listen on the named pipe associated with this container.
   104  			l, err := winio.ListenPipe(c.ShimPipePath(), nil)
   105  			if err != nil {
   106  				return err
   107  			}
   108  
   109  			// Alert the parent process that initialization has completed
   110  			// successfully.
   111  			_, _ = errorOut.Write(runhcs.ShimSuccess)
   112  			errorOut.Close()
   113  			fatalWriter.Writer = io.Discard
   114  
   115  			// When this process exits, clear this process's pid in the registry.
   116  			defer func() {
   117  				_ = stateKey.Set(id, keyShimPid, 0)
   118  			}()
   119  
   120  			defer func() {
   121  				if terminateOnFailure {
   122  					_ = c.hc.Terminate(gcontext.Background())
   123  					<-containerExitCh
   124  				}
   125  			}()
   126  			terminateOnFailure = true
   127  
   128  			// Wait for a connection to the named pipe, exiting if the container
   129  			// exits before this happens.
   130  			var pipe net.Conn
   131  			pipeCh := make(chan error)
   132  			go func() {
   133  				var err error
   134  				pipe, err = l.Accept()
   135  				pipeCh <- err
   136  			}()
   137  
   138  			select {
   139  			case err = <-pipeCh:
   140  				if err != nil {
   141  					return err
   142  				}
   143  			case <-containerExitCh:
   144  				err = containerExitErr
   145  				if err != nil {
   146  					return err
   147  				}
   148  				return cli.NewExitError("", 1)
   149  			}
   150  
   151  			// The next set of errors goes to the open pipe connection.
   152  			errorOut = pipe
   153  			fatalWriter.Writer = pipe
   154  
   155  			// The process spec comes from the original container spec.
   156  			spec = c.Spec.Process
   157  		}
   158  
   159  		// Create the process in the container.
   160  		cmd := &cmdpkg.Cmd{
   161  			Host:   c.hc,
   162  			Stdin:  stdin,
   163  			Stdout: stdout,
   164  			Stderr: stderr,
   165  		}
   166  		if c.Spec.Linux == nil || exec {
   167  			cmd.Spec = spec
   168  		}
   169  
   170  		err = cmd.Start()
   171  		if err != nil {
   172  			return err
   173  		}
   174  		pid := cmd.Process.Pid()
   175  		if !exec {
   176  			err = stateKey.Set(c.ID, keyInitPid, pid)
   177  			if err != nil {
   178  				stdin.Close()
   179  				_, _ = cmd.Process.Kill(gcontext.Background())
   180  				_ = cmd.Wait()
   181  				return err
   182  			}
   183  		}
   184  
   185  		// Store the Guest pid map
   186  		err = stateKey.Set(c.ID, fmt.Sprintf(keyPidMapFmt, os.Getpid()), pid)
   187  
   188  		if err != nil {
   189  			stdin.Close()
   190  			_, _ = cmd.Process.Kill(gcontext.Background())
   191  			_ = cmd.Wait()
   192  			return err
   193  		}
   194  		defer func() {
   195  			// Remove the Guest pid map when this process is cleaned up
   196  			_ = stateKey.Clear(c.ID, fmt.Sprintf(keyPidMapFmt, os.Getpid()))
   197  		}()
   198  
   199  		terminateOnFailure = false
   200  
   201  		// Alert the connected process that the process was launched
   202  		// successfully.
   203  		_, _ = errorOut.Write(runhcs.ShimSuccess)
   204  		errorOut.Close()
   205  		fatalWriter.Writer = io.Discard
   206  
   207  		_ = cmd.Wait()
   208  		code := cmd.ExitState.ExitCode()
   209  		if !exec {
   210  			// Shutdown the container, waiting 5 minutes before terminating is
   211  			// forcefully.
   212  			const shutdownTimeout = time.Minute * 5
   213  			err := c.hc.Shutdown(gcontext.Background())
   214  			if err != nil {
   215  				select {
   216  				case <-containerExitCh:
   217  					err = containerExitErr
   218  				case <-time.After(shutdownTimeout):
   219  					err = hcs.ErrTimeout
   220  				}
   221  			}
   222  
   223  			if err != nil {
   224  				_ = c.hc.Terminate(gcontext.Background())
   225  			}
   226  			<-containerExitCh
   227  		}
   228  
   229  		return cli.NewExitError("", code)
   230  	},
   231  }
   232  

View as plain text