package main import ( "encoding/json" "fmt" "io" "io/ioutil" "net" "os" "strings" "sync" "time" winio "github.com/Microsoft/go-winio" "github.com/Microsoft/hcsshim/internal/appargs" "github.com/Microsoft/hcsshim/internal/hcs" "github.com/Microsoft/hcsshim/internal/lcow" "github.com/Microsoft/hcsshim/internal/runhcs" "github.com/Microsoft/hcsshim/internal/schema2" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/urfave/cli" "golang.org/x/sys/windows" ) func containerPipePath(id string) string { return runhcs.SafePipePath("runhcs-shim-" + id) } func newFile(context *cli.Context, param string) *os.File { fd := uintptr(context.Int(param)) if fd == 0 { return nil } return os.NewFile(fd, "") } var shimCommand = cli.Command{ Name: "shim", Usage: `launch the process and proxy stdio (do not call it outside of runhcs)`, Hidden: true, Flags: []cli.Flag{ &cli.IntFlag{Name: "stdin", Hidden: true}, &cli.IntFlag{Name: "stdout", Hidden: true}, &cli.IntFlag{Name: "stderr", Hidden: true}, &cli.BoolFlag{Name: "exec", Hidden: true}, cli.StringFlag{Name: "log-pipe", Hidden: true}, }, Before: appargs.Validate(argID), Action: func(context *cli.Context) error { logPipe := context.String("log-pipe") if logPipe != "" { lpc, err := winio.DialPipe(logPipe, nil) if err != nil { return err } defer lpc.Close() logrus.SetOutput(lpc) } else { logrus.SetOutput(os.Stderr) } fatalWriter.Writer = os.Stdout id := context.Args().First() c, err := getContainer(id, true) if err != nil { return err } defer c.Close() // Asynchronously wait for the container to exit. containerExitCh := make(chan error) go func() { containerExitCh <- c.hc.Wait() }() // Get File objects for the open stdio files passed in as arguments. stdin := newFile(context, "stdin") stdout := newFile(context, "stdout") stderr := newFile(context, "stderr") exec := context.Bool("exec") terminateOnFailure := false errorOut := io.WriteCloser(os.Stdout) var spec *specs.Process if exec { // Read the process spec from stdin. specj, err := ioutil.ReadAll(os.Stdin) if err != nil { return err } os.Stdin.Close() spec = new(specs.Process) err = json.Unmarshal(specj, spec) if err != nil { return err } } else { // Stdin is not used. os.Stdin.Close() // Listen on the named pipe associated with this container. l, err := winio.ListenPipe(c.ShimPipePath(), nil) if err != nil { return err } // Alert the parent process that initialization has completed // successfully. errorOut.Write(runhcs.ShimSuccess) errorOut.Close() fatalWriter.Writer = ioutil.Discard // When this process exits, clear this process's pid in the registry. defer func() { stateKey.Set(id, keyShimPid, 0) }() defer func() { if terminateOnFailure { if err = c.hc.Terminate(); hcs.IsPending(err) { <-containerExitCh } } }() terminateOnFailure = true // Wait for a connection to the named pipe, exiting if the container // exits before this happens. var pipe net.Conn pipeCh := make(chan error) go func() { var err error pipe, err = l.Accept() pipeCh <- err }() select { case err = <-pipeCh: if err != nil { return err } case err = <-containerExitCh: if err != nil { return err } return cli.NewExitError("", 1) } // The next set of errors goes to the open pipe connection. errorOut = pipe fatalWriter.Writer = pipe // The process spec comes from the original container spec. spec = c.Spec.Process } // Create the process in the container. var wpp *hcsschema.ProcessParameters // Windows Process Parameters var lpp *lcow.ProcessParameters // Linux Process Parameters var p *hcs.Process if c.Spec.Linux == nil { environment := make(map[string]string) for _, v := range spec.Env { s := strings.SplitN(v, "=", 2) if len(s) == 2 && len(s[1]) > 0 { environment[s[0]] = s[1] } } wpp = &hcsschema.ProcessParameters{ WorkingDirectory: spec.Cwd, EmulateConsole: spec.Terminal, Environment: environment, User: spec.User.Username, } for i, arg := range spec.Args { e := windows.EscapeArg(arg) if i == 0 { wpp.CommandLine = e } else { wpp.CommandLine += " " + e } } if spec.ConsoleSize != nil { wpp.ConsoleSize = []int32{ int32(spec.ConsoleSize.Height), int32(spec.ConsoleSize.Width), } } wpp.CreateStdInPipe = stdin != nil wpp.CreateStdOutPipe = stdout != nil wpp.CreateStdErrPipe = stderr != nil p, err = c.hc.CreateProcess(wpp) } else { lpp = &lcow.ProcessParameters{} if exec { lpp.OCIProcess = spec } lpp.CreateStdInPipe = stdin != nil lpp.CreateStdOutPipe = stdout != nil lpp.CreateStdErrPipe = stderr != nil p, err = c.hc.CreateProcess(lpp) } if err != nil { return err } cstdin, cstdout, cstderr, err := p.Stdio() if err != nil { return err } if !exec { err = stateKey.Set(c.ID, keyInitPid, p.Pid()) if err != nil { return err } } // Store the Guest pid map err = stateKey.Set(c.ID, fmt.Sprintf(keyPidMapFmt, os.Getpid()), p.Pid()) if err != nil { return err } defer func() { // Remove the Guest pid map when this process is cleaned up stateKey.Clear(c.ID, fmt.Sprintf(keyPidMapFmt, os.Getpid())) }() terminateOnFailure = false // Alert the connected process that the process was launched // successfully. errorOut.Write(runhcs.ShimSuccess) errorOut.Close() fatalWriter.Writer = ioutil.Discard // Relay stdio. var wg sync.WaitGroup if cstdin != nil { go func() { io.Copy(cstdin, stdin) cstdin.Close() p.CloseStdin() }() } if cstdout != nil { wg.Add(1) go func() { io.Copy(stdout, cstdout) stdout.Close() cstdout.Close() wg.Done() }() } if cstderr != nil { wg.Add(1) go func() { io.Copy(stderr, cstderr) stderr.Close() cstderr.Close() wg.Done() }() } err = p.Wait() wg.Wait() // Attempt to get the exit code from the process. code := 1 if err == nil { code, err = p.ExitCode() if err != nil { code = 1 } } if !exec { // Shutdown the container, waiting 5 minutes before terminating is // forcefully. const shutdownTimeout = time.Minute * 5 waited := false err = c.hc.Shutdown() if hcs.IsPending(err) { select { case err = <-containerExitCh: waited = true case <-time.After(shutdownTimeout): err = hcs.ErrTimeout } } if hcs.IsAlreadyStopped(err) { err = nil } if err != nil { err = c.hc.Terminate() if waited { err = c.hc.Wait() } else { err = <-containerExitCh } } } return cli.NewExitError("", code) }, }