// Run implements the exec driver Driver interface func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { var ( term execdriver.Terminal err error inListen, outListen, errListen *npipe.PipeListener ) // Make sure the client isn't asking for options which aren't supported err = checkSupportedOptions(c) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } cu := &containerInit{ SystemType: "Container", Name: c.ID, IsDummy: dummyMode, VolumePath: c.Rootfs, IgnoreFlushesDuringBoot: c.FirstStart, LayerFolderPath: c.LayerFolder, } for i := 0; i < len(c.LayerPaths); i++ { cu.Layers = append(cu.Layers, layer{ ID: hcsshim.NewGUID(c.LayerPaths[i]).ToString(), Path: c.LayerPaths[i], }) } // TODO Windows. At some point, when there is CLI on docker run to // enable the IP Address of the container to be passed into docker run, // the IP Address needs to be wired through to HCS in the JSON. It // would be present in c.Network.Interface.IPAddress. See matching // TODO in daemon\container_windows.go, function populateCommand. if c.Network.Interface != nil { var pbs []portBinding // Enumerate through the port bindings specified by the user and convert // them into the internal structure matching the JSON blob that can be // understood by the HCS. for i, v := range c.Network.Interface.PortBindings { proto := strings.ToUpper(i.Proto()) if proto != "TCP" && proto != "UDP" { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto()) } if len(v) > 1 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings") } for _, v2 := range v { var ( iPort, ePort int err error ) if len(v2.HostIP) != 0 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings") } if ePort, err = strconv.Atoi(v2.HostPort); err != nil { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err) } if iPort, err = strconv.Atoi(i.Port()); err != nil { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err) } if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range") } pbs = append(pbs, portBinding{ExternalPort: ePort, InternalPort: iPort, Protocol: proto}) } } // TODO Windows: TP3 workaround. Allow the user to override the name of // the Container NAT device through an environment variable. This will // ultimately be a global daemon parameter on Windows, similar to -b // for the name of the virtual switch (aka bridge). cn := os.Getenv("DOCKER_CONTAINER_NAT") if len(cn) == 0 { cn = defaultContainerNAT } dev := device{ DeviceType: "Network", Connection: &networkConnection{ NetworkName: c.Network.Interface.Bridge, // TODO Windows: Fixme, next line. Needs HCS fix. EnableNat: false, Nat: natSettings{ Name: cn, PortBindings: pbs, }, }, } if c.Network.Interface.MacAddress != "" { windowsStyleMAC := strings.Replace( c.Network.Interface.MacAddress, ":", "-", -1) dev.Settings = networkSettings{ MacAddress: windowsStyleMAC, } } cu.Devices = append(cu.Devices, dev) } else { logrus.Debugln("No network interface") } configurationb, err := json.Marshal(cu) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } configuration := string(configurationb) err = hcsshim.CreateComputeSystem(c.ID, configuration) if err != nil { logrus.Debugln("Failed to create temporary container ", err) return execdriver.ExitStatus{ExitCode: -1}, err } // Start the container logrus.Debugln("Starting container ", c.ID) err = hcsshim.StartComputeSystem(c.ID) if err != nil { logrus.Errorf("Failed to start compute system: %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } defer func() { // Stop the container if terminateMode { logrus.Debugf("Terminating container %s", c.ID) if err := hcsshim.TerminateComputeSystem(c.ID); err != nil { // IMPORTANT: Don't fail if fails to change state. It could already // have been stopped through kill(). // Otherwise, the docker daemon will hang in job wait() logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err) } } else { logrus.Debugf("Shutting down container %s", c.ID) if err := hcsshim.ShutdownComputeSystem(c.ID); err != nil { // IMPORTANT: Don't fail if fails to change state. It could already // have been stopped through kill(). // Otherwise, the docker daemon will hang in job wait() logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err) } } }() // We use a different pipe name between real and dummy mode in the HCS var serverPipeFormat, clientPipeFormat string if dummyMode { clientPipeFormat = `\\.\pipe\docker-run-%[1]s-%[2]s` serverPipeFormat = clientPipeFormat } else { clientPipeFormat = `\\.\pipe\docker-run-%[2]s` serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-run-%[2]s` } createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: c.ProcessConfig.Tty, WorkingDirectory: c.WorkingDir, ConsoleSize: c.ProcessConfig.ConsoleSize, } // Configure the environment for the process createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env) // Connect stdin if pipes.Stdin != nil { stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdin") createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdin") // Listen on the named pipe inListen, err = npipe.Listen(stdInPipe) if err != nil { logrus.Errorf("stdin failed to listen on %s err=%s", stdInPipe, err) return execdriver.ExitStatus{ExitCode: -1}, err } defer inListen.Close() // Launch a goroutine to do the accept. We do this so that we can // cause an otherwise blocking goroutine to gracefully close when // the caller (us) closes the listener go stdinAccept(inListen, stdInPipe, pipes.Stdin) } // Connect stdout stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdout") createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdout") outListen, err = npipe.Listen(stdOutPipe) if err != nil { logrus.Errorf("stdout failed to listen on %s err=%s", stdOutPipe, err) return execdriver.ExitStatus{ExitCode: -1}, err } defer outListen.Close() go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout) // No stderr on TTY. if !c.ProcessConfig.Tty { // Connect stderr stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stderr") createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stderr") errListen, err = npipe.Listen(stdErrPipe) if err != nil { logrus.Errorf("stderr failed to listen on %s err=%s", stdErrPipe, err) return execdriver.ExitStatus{ExitCode: -1}, err } defer errListen.Close() go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr) } // This should get caught earlier, but just in case - validate that we // have something to run if c.ProcessConfig.Entrypoint == "" { err = errors.New("No entrypoint specified") logrus.Error(err) return execdriver.ExitStatus{ExitCode: -1}, err } // Build the command line of the process createProcessParms.CommandLine = c.ProcessConfig.Entrypoint for _, arg := range c.ProcessConfig.Arguments { logrus.Debugln("appending ", arg) createProcessParms.CommandLine += " " + arg } logrus.Debugf("CommandLine: %s", createProcessParms.CommandLine) // Start the command running in the container. var pid uint32 pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms) if err != nil { logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } //Save the PID as we'll need this in Kill() logrus.Debugf("PID %d", pid) c.ContainerPid = int(pid) if c.ProcessConfig.Tty { term = NewTtyConsole(c.ID, pid) } else { term = NewStdConsole() } c.ProcessConfig.Terminal = term // Maintain our list of active containers. We'll need this later for exec // and other commands. d.Lock() d.activeContainers[c.ID] = &activeContainer{ command: c, } d.Unlock() // Invoke the start callback if startCallback != nil { startCallback(&c.ProcessConfig, int(pid)) } var exitCode int32 exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid) if err != nil { logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID) return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil }
// Run implements the exec driver Driver interface func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) { var ( term execdriver.Terminal err error ) cu := &containerInit{ SystemType: "Container", Name: c.ID, Owner: defaultOwner, IsDummy: dummyMode, VolumePath: c.Rootfs, IgnoreFlushesDuringBoot: c.FirstStart, LayerFolderPath: c.LayerFolder, ProcessorWeight: c.Resources.CPUShares, HostName: c.Hostname, } cu.HvPartition = c.HvPartition if cu.HvPartition { cu.SandboxPath = filepath.Dir(c.LayerFolder) } else { cu.VolumePath = c.Rootfs cu.LayerFolderPath = c.LayerFolder } for _, layerPath := range c.LayerPaths { _, filename := filepath.Split(layerPath) g, err := hcsshim.NameToGuid(filename) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } cu.Layers = append(cu.Layers, layer{ ID: g.ToString(), Path: layerPath, }) } // Add the mounts (volumes, bind mounts etc) to the structure mds := make([]mappedDir, len(c.Mounts)) for i, mount := range c.Mounts { mds[i] = mappedDir{ HostPath: mount.Source, ContainerPath: mount.Destination, ReadOnly: !mount.Writable} } cu.MappedDirectories = mds // TODO Windows. At some point, when there is CLI on docker run to // enable the IP Address of the container to be passed into docker run, // the IP Address needs to be wired through to HCS in the JSON. It // would be present in c.Network.Interface.IPAddress. See matching // TODO in daemon\container_windows.go, function populateCommand. if c.Network.Interface != nil { var pbs []portBinding // Enumerate through the port bindings specified by the user and convert // them into the internal structure matching the JSON blob that can be // understood by the HCS. for i, v := range c.Network.Interface.PortBindings { proto := strings.ToUpper(i.Proto()) if proto != "TCP" && proto != "UDP" { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto()) } if len(v) > 1 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings") } for _, v2 := range v { var ( iPort, ePort int err error ) if len(v2.HostIP) != 0 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings") } if ePort, err = strconv.Atoi(v2.HostPort); err != nil { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err) } if iPort, err = strconv.Atoi(i.Port()); err != nil { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err) } if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range") } pbs = append(pbs, portBinding{ExternalPort: ePort, InternalPort: iPort, Protocol: proto}) } } // TODO Windows: TP3 workaround. Allow the user to override the name of // the Container NAT device through an environment variable. This will // ultimately be a global daemon parameter on Windows, similar to -b // for the name of the virtual switch (aka bridge). cn := os.Getenv("DOCKER_CONTAINER_NAT") if len(cn) == 0 { cn = defaultContainerNAT } dev := device{ DeviceType: "Network", Connection: &networkConnection{ NetworkName: c.Network.Interface.Bridge, // TODO Windows: Fixme, next line. Needs HCS fix. EnableNat: false, Nat: natSettings{ Name: cn, PortBindings: pbs, }, }, } if c.Network.Interface.MacAddress != "" { windowsStyleMAC := strings.Replace( c.Network.Interface.MacAddress, ":", "-", -1) dev.Settings = networkSettings{ MacAddress: windowsStyleMAC, } } cu.Devices = append(cu.Devices, dev) } else { logrus.Debugln("No network interface") } configurationb, err := json.Marshal(cu) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } configuration := string(configurationb) // TODO Windows TP5 timeframe. Remove when TP4 is no longer supported. // The following a workaround for Windows TP4 which has a networking // bug which fairly frequently returns an error. Back off and retry. maxAttempts := 1 if TP4RetryHack { maxAttempts = 5 } i := 0 for i < maxAttempts { i++ err = hcsshim.CreateComputeSystem(c.ID, configuration) if err != nil { if TP4RetryHack { if !strings.Contains(err.Error(), `Win32 API call returned error r1=0x800401f3`) && // Invalid class string !strings.Contains(err.Error(), `Win32 API call returned error r1=0x80070490`) && // Element not found !strings.Contains(err.Error(), `Win32 API call returned error r1=0x80070002`) && // The system cannot find the file specified !strings.Contains(err.Error(), `Win32 API call returned error r1=0x800704c6`) && // The network is not present or not started !strings.Contains(err.Error(), `Win32 API call returned error r1=0x800700a1`) { // The specified path is invalid logrus.Debugln("Failed to create temporary container ", err) return execdriver.ExitStatus{ExitCode: -1}, err } logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1) time.Sleep(50 * time.Millisecond) } } else { break } } // Start the container logrus.Debugln("Starting container ", c.ID) err = hcsshim.StartComputeSystem(c.ID) if err != nil { logrus.Errorf("Failed to start compute system: %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } defer func() { // Stop the container if forceKill { logrus.Debugf("Forcibly terminating container %s", c.ID) if errno, err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { logrus.Warnf("Ignoring error from TerminateComputeSystem 0x%X %s", errno, err) } } else { logrus.Debugf("Shutting down container %s", c.ID) if errno, err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { if errno != hcsshim.Win32SystemShutdownIsInProgress && errno != hcsshim.Win32SpecifiedPathInvalid && errno != hcsshim.Win32SystemCannotFindThePathSpecified { logrus.Warnf("Ignoring error from ShutdownComputeSystem 0x%X %s", errno, err) } } } }() createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: c.ProcessConfig.Tty, WorkingDirectory: c.WorkingDir, ConsoleSize: c.ProcessConfig.ConsoleSize, } // Configure the environment for the process createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env) createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } // Start the command running in the container. pid, stdin, stdout, stderr, _, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms) if err != nil { logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } // Now that the process has been launched, begin copying data to and from // the named pipes for the std handles. setupPipes(stdin, stdout, stderr, pipes) //Save the PID as we'll need this in Kill() logrus.Debugf("PID %d", pid) c.ContainerPid = int(pid) if c.ProcessConfig.Tty { term = NewTtyConsole(c.ID, pid) } else { term = NewStdConsole() } c.ProcessConfig.Terminal = term // Maintain our list of active containers. We'll need this later for exec // and other commands. d.Lock() d.activeContainers[c.ID] = &activeContainer{ command: c, } d.Unlock() if hooks.Start != nil { // A closed channel for OOM is returned here as it will be // non-blocking and return the correct result when read. chOOM := make(chan struct{}) close(chOOM) hooks.Start(&c.ProcessConfig, int(pid), chOOM) } var ( exitCode int32 errno uint32 ) exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite) if err != nil { if errno != hcsshim.Win32PipeHasBeenEnded { logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) } // Do NOT return err here as the container would have // started, otherwise docker will deadlock. It's perfectly legitimate // for WaitForProcessInComputeSystem to fail in situations such // as the container being killed on another thread. return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil } logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID) return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil }
// Exec implements the exec driver Driver interface. func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) { var ( term execdriver.Terminal err error exitCode int32 errno uint32 ) active := d.activeContainers[c.ID] if active == nil { return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID) } createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty WorkingDirectory: c.WorkingDir, } // Configure the environment for the process // Note NOT c.ProcessConfig.Env createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env) // Create the commandline for the process // Note NOT c.ProcessConfig createProcessParms.CommandLine, err = createCommandLine(processConfig, false) if err != nil { return -1, err } // Start the command running in the container. pid, stdin, stdout, stderr, rc, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !processConfig.Tty, createProcessParms) if err != nil { // TODO Windows: TP4 Workaround. In Hyper-V containers, there is a limitation // of one exec per container. This should be fixed post TP4. CreateProcessInComputeSystem // will return a specific error which we handle here to give a good error message // back to the user instead of an inactionable "An invalid argument was supplied" if rc == hcsshim.Win32InvalidArgument { return -1, fmt.Errorf("The limit of docker execs per Hyper-V container has been exceeded") } logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) return -1, err } // Now that the process has been launched, begin copying data to and from // the named pipes for the std handles. setupPipes(stdin, stdout, stderr, pipes) // Note NOT c.ProcessConfig.Tty if processConfig.Tty { term = NewTtyConsole(c.ID, pid) } else { term = NewStdConsole() } processConfig.Terminal = term // Invoke the start callback if hooks.Start != nil { // A closed channel for OOM is returned here as it will be // non-blocking and return the correct result when read. chOOM := make(chan struct{}) close(chOOM) hooks.Start(&c.ProcessConfig, int(pid), chOOM) } if exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite); err != nil { if errno == hcsshim.Win32PipeHasBeenEnded { logrus.Debugf("Exiting Run() after WaitForProcessInComputeSystem failed with recognised error 0x%X", errno) return hcsshim.WaitErrExecFailed, nil } logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): 0x%X %s", errno, err) return -1, err } logrus.Debugln("Exiting Run()", c.ID) return int(exitCode), nil }
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { var ( inListen, outListen, errListen *npipe.PipeListener term execdriver.Terminal err error randomID string = stringid.GenerateNonCryptoID() serverPipeFormat, clientPipeFormat string pid uint32 exitCode int32 ) active := d.activeContainers[c.ID] if active == nil { return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID) } createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty WorkingDirectory: c.WorkingDir, } // Configure the environment for the process // Note NOT c.ProcessConfig.Tty createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env) // We use another unique ID here for each exec instance otherwise it // may conflict with the pipe name being used by RUN. // We use a different pipe name between real and dummy mode in the HCS if dummyMode { clientPipeFormat = `\\.\pipe\docker-exec-%[1]s-%[2]s-%[3]s` serverPipeFormat = clientPipeFormat } else { clientPipeFormat = `\\.\pipe\docker-exec-%[2]s-%[3]s` serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-exec-%[2]s-%[3]s` } // Connect stdin if pipes.Stdin != nil { stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stdin") createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stdin") // Listen on the named pipe inListen, err = npipe.Listen(stdInPipe) if err != nil { logrus.Errorf("stdin failed to listen on %s %s ", stdInPipe, err) return -1, err } defer inListen.Close() // Launch a goroutine to do the accept. We do this so that we can // cause an otherwise blocking goroutine to gracefully close when // the caller (us) closes the listener go stdinAccept(inListen, stdInPipe, pipes.Stdin) } // Connect stdout stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stdout") createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stdout") outListen, err = npipe.Listen(stdOutPipe) if err != nil { logrus.Errorf("stdout failed to listen on %s %s", stdOutPipe, err) return -1, err } defer outListen.Close() go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout) // No stderr on TTY. Note NOT c.ProcessConfig.Tty if !processConfig.Tty { // Connect stderr stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stderr") createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stderr") errListen, err = npipe.Listen(stdErrPipe) if err != nil { logrus.Errorf("Stderr failed to listen on %s %s", stdErrPipe, err) return -1, err } defer errListen.Close() go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr) } // While this should get caught earlier, just in case, validate that we // have something to run. if processConfig.Entrypoint == "" { err = errors.New("No entrypoint specified") logrus.Error(err) return -1, err } // Build the command line of the process createProcessParms.CommandLine = processConfig.Entrypoint for _, arg := range processConfig.Arguments { logrus.Debugln("appending ", arg) createProcessParms.CommandLine += " " + arg } logrus.Debugln("commandLine: ", createProcessParms.CommandLine) // Start the command running in the container. pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms) if err != nil { logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) return -1, err } // Note NOT c.ProcessConfig.Tty if processConfig.Tty { term = NewTtyConsole(c.ID, pid) } else { term = NewStdConsole() } processConfig.Terminal = term // Invoke the start callback if startCallback != nil { startCallback(&c.ProcessConfig, int(pid)) } if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil { logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err) return -1, err } // TODO Windows - Do something with this exit code logrus.Debugln("Exiting Run() with ExitCode 0", c.ID) return int(exitCode), nil }
// Exec implements the exec driver Driver interface. func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { var ( term execdriver.Terminal err error exitCode int32 ) active := d.activeContainers[c.ID] if active == nil { return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID) } createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty WorkingDirectory: c.WorkingDir, } // Configure the environment for the process // Note NOT c.ProcessConfig.Tty createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env) // While this should get caught earlier, just in case, validate that we // have something to run. if processConfig.Entrypoint == "" { err = errors.New("No entrypoint specified") logrus.Error(err) return -1, err } // Build the command line of the process createProcessParms.CommandLine = processConfig.Entrypoint for _, arg := range processConfig.Arguments { logrus.Debugln("appending ", arg) createProcessParms.CommandLine += " " + arg } logrus.Debugln("commandLine: ", createProcessParms.CommandLine) // Start the command running in the container. pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !processConfig.Tty, createProcessParms) if err != nil { logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) return -1, err } // Now that the process has been launched, begin copying data to and from // the named pipes for the std handles. setupPipes(stdin, stdout, stderr, pipes) // Note NOT c.ProcessConfig.Tty if processConfig.Tty { term = NewTtyConsole(c.ID, pid) } else { term = NewStdConsole() } processConfig.Terminal = term // Invoke the start callback if startCallback != nil { startCallback(&c.ProcessConfig, int(pid)) } if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil { logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err) return -1, err } // TODO Windows - Do something with this exit code logrus.Debugln("Exiting Run() with ExitCode 0", c.ID) return int(exitCode), nil }
// waitExit runs as a goroutine waiting for the process to exit. It's // equivalent to (in the linux containerd world) where events come in for // state change notifications from containerd. func (ctr *container) waitExit(pid uint32, processFriendlyName string, isFirstProcessToStart bool) error { logrus.Debugln("waitExit on pid", pid) // Block indefinitely for the process to exit. exitCode, err := hcsshim.WaitForProcessInComputeSystem(ctr.containerID, pid, hcsshim.TimeoutInfinite) if err != nil { if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) } // Fall through here, do not return. This ensures we attempt to continue the // shutdown in HCS nad tell the docker engine that the process/container // has exited to avoid a container being dropped on the floor. } // Assume the container has exited si := StateInfo{ State: StateExit, ExitCode: uint32(exitCode), Pid: pid, ProcessID: processFriendlyName, } // But it could have been an exec'd process which exited if !isFirstProcessToStart { si.State = StateExitProcess } // If this is the init process, always call into vmcompute.dll to // shutdown the container after we have completed. if isFirstProcessToStart { logrus.Debugf("Shutting down container %s", ctr.containerID) // Explicit timeout here rather than hcsshim.TimeoutInfinte to avoid a // (remote) possibility that ShutdownComputeSystem hangs indefinitely. const shutdownTimeout = 5 * 60 * 1000 // 5 minutes if err := hcsshim.ShutdownComputeSystem(ctr.containerID, shutdownTimeout, "waitExit"); err != nil { if herr, ok := err.(*hcsshim.HcsError); !ok || (herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS && herr.Err != ErrorBadPathname && herr.Err != syscall.ERROR_PATH_NOT_FOUND) { logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err) } } else { logrus.Debugf("Completed shutting down container %s", ctr.containerID) } // BUGBUG - Is taking the lock necessary here? Should it just be taken for // the deleteContainer call, not for the restart logic? @jhowardmsft ctr.client.lock(ctr.containerID) defer ctr.client.unlock(ctr.containerID) if si.State == StateExit && ctr.restartManager != nil { restart, wait, err := ctr.restartManager.ShouldRestart(uint32(exitCode)) if err != nil { logrus.Error(err) } else if restart { si.State = StateRestart ctr.restarting = true go func() { err := <-wait ctr.restarting = false if err != nil { si.State = StateExit if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil { logrus.Error(err) } logrus.Error(err) } else { ctr.client.Create(ctr.containerID, ctr.ociSpec, ctr.options...) } }() } } // Remove process from list if we have exited // We need to do so here in case the Message Handler decides to restart it. if si.State == StateExit { ctr.client.deleteContainer(ctr.friendlyName) } } // Call into the backend to notify it of the state change. logrus.Debugf("waitExit() calling backend.StateChanged %v", si) if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil { logrus.Error(err) } logrus.Debugln("waitExit() completed OK") return nil }
// Exec implements the exec driver Driver interface. func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) { var ( term execdriver.Terminal err error exitCode int32 errno uint32 ) active := d.activeContainers[c.ID] if active == nil { return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID) } createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty WorkingDirectory: c.WorkingDir, } // Configure the environment for the process // Note NOT c.ProcessConfig.Tty createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env) // While this should get caught earlier, just in case, validate that we // have something to run. if processConfig.Entrypoint == "" { err = errors.New("No entrypoint specified") logrus.Error(err) return -1, err } // Build the command line of the process createProcessParms.CommandLine = processConfig.Entrypoint for _, arg := range processConfig.Arguments { logrus.Debugln("appending ", arg) createProcessParms.CommandLine += " " + arg } logrus.Debugln("commandLine: ", createProcessParms.CommandLine) // Start the command running in the container. pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !processConfig.Tty, createProcessParms) if err != nil { logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) return -1, err } // Now that the process has been launched, begin copying data to and from // the named pipes for the std handles. setupPipes(stdin, stdout, stderr, pipes) // Note NOT c.ProcessConfig.Tty if processConfig.Tty { term = NewTtyConsole(c.ID, pid) } else { term = NewStdConsole() } processConfig.Terminal = term // Invoke the start callback if hooks.Start != nil { // A closed channel for OOM is returned here as it will be // non-blocking and return the correct result when read. chOOM := make(chan struct{}) close(chOOM) hooks.Start(&c.ProcessConfig, int(pid), chOOM) } if exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite); err != nil { if errno == hcsshim.Win32PipeHasBeenEnded { logrus.Debugf("Exiting Run() after WaitForProcessInComputeSystem failed with recognised error 0x%X", errno) return hcsshim.WaitErrExecFailed, nil } logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): 0x%X %s", errno, err) return -1, err } logrus.Debugln("Exiting Run()", c.ID) return int(exitCode), nil }
// waitExit runs as a goroutine waiting for the process to exit. It's // equivalent to (in the linux containerd world) where events come in for // state change notifications from containerd. func (ctr *container) waitExit(pid uint32, processFriendlyName string, isFirstProcessToStart bool) error { logrus.Debugln("waitExit on pid", pid) // Block indefinitely for the process to exit. exitCode, err := hcsshim.WaitForProcessInComputeSystem(ctr.containerID, pid, hcsshim.TimeoutInfinite) if err != nil { if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE { logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) } // Fall through here, do not return. This ensures we attempt to continue the // shutdown in HCS nad tell the docker engine that the process/container // has exited to avoid a container being dropped on the floor. } // Assume the container has exited si := StateInfo{ CommonStateInfo: CommonStateInfo{ State: StateExit, ExitCode: uint32(exitCode), Pid: pid, ProcessID: processFriendlyName, }, UpdatePending: false, } // But it could have been an exec'd process which exited if !isFirstProcessToStart { si.State = StateExitProcess } else { // Since this is the init process, always call into vmcompute.dll to // shutdown the container after we have completed. propertyCheckFlag := 1 // Include update pending check. csProperties, err := hcsshim.GetComputeSystemProperties(ctr.containerID, uint32(propertyCheckFlag)) if err != nil { logrus.Warnf("GetComputeSystemProperties failed (container may have been killed): %s", err) } else { si.UpdatePending = csProperties.AreUpdatesPending } logrus.Debugf("Shutting down container %s", ctr.containerID) // Explicit timeout here rather than hcsshim.TimeoutInfinte to avoid a // (remote) possibility that ShutdownComputeSystem hangs indefinitely. const shutdownTimeout = 5 * 60 * 1000 // 5 minutes if err := hcsshim.ShutdownComputeSystem(ctr.containerID, shutdownTimeout, "waitExit"); err != nil { if herr, ok := err.(*hcsshim.HcsError); !ok || (herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS && herr.Err != ErrorBadPathname && herr.Err != syscall.ERROR_PATH_NOT_FOUND) { logrus.Debugf("waitExit - error from ShutdownComputeSystem on %s %v. Calling TerminateComputeSystem", ctr.containerCommon, err) if err := hcsshim.TerminateComputeSystem(ctr.containerID, shutdownTimeout, "waitExit"); err != nil { logrus.Debugf("waitExit - ignoring error from TerminateComputeSystem %s %v", ctr.containerID, err) } else { logrus.Debugf("Successful TerminateComputeSystem after failed ShutdownComputeSystem on %s in waitExit", ctr.containerID) } } } else { logrus.Debugf("Completed shutting down container %s", ctr.containerID) } if !ctr.manualStopRequested && ctr.restartManager != nil { restart, wait, err := ctr.restartManager.ShouldRestart(uint32(exitCode), false, time.Since(ctr.startedAt)) if err != nil { logrus.Error(err) } else if restart { si.State = StateRestart ctr.restarting = true go func() { err := <-wait ctr.restarting = false ctr.client.deleteContainer(ctr.friendlyName) if err != nil { si.State = StateExit if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil { logrus.Error(err) } logrus.Error(err) } else { ctr.client.Create(ctr.containerID, ctr.ociSpec, ctr.options...) } }() } } // Remove process from list if we have exited // We need to do so here in case the Message Handler decides to restart it. if si.State == StateExit { ctr.client.deleteContainer(ctr.friendlyName) } } // Call into the backend to notify it of the state change. logrus.Debugf("waitExit() calling backend.StateChanged %v", si) if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil { logrus.Error(err) } logrus.Debugln("waitExit() completed OK") return nil }
// Run implements the exec driver Driver interface func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) { var ( term execdriver.Terminal err error ) // Make sure the client isn't asking for options which aren't supported err = checkSupportedOptions(c) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } cu := &containerInit{ SystemType: "Container", Name: c.ID, Owner: defaultOwner, IsDummy: dummyMode, VolumePath: c.Rootfs, IgnoreFlushesDuringBoot: c.FirstStart, LayerFolderPath: c.LayerFolder, ProcessorWeight: c.Resources.CPUShares, HostName: c.Hostname, } for i := 0; i < len(c.LayerPaths); i++ { _, filename := filepath.Split(c.LayerPaths[i]) g, err := hcsshim.NameToGuid(filename) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } cu.Layers = append(cu.Layers, layer{ ID: g.ToString(), Path: c.LayerPaths[i], }) } // TODO Windows. At some point, when there is CLI on docker run to // enable the IP Address of the container to be passed into docker run, // the IP Address needs to be wired through to HCS in the JSON. It // would be present in c.Network.Interface.IPAddress. See matching // TODO in daemon\container_windows.go, function populateCommand. if c.Network.Interface != nil { var pbs []portBinding // Enumerate through the port bindings specified by the user and convert // them into the internal structure matching the JSON blob that can be // understood by the HCS. for i, v := range c.Network.Interface.PortBindings { proto := strings.ToUpper(i.Proto()) if proto != "TCP" && proto != "UDP" { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto()) } if len(v) > 1 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings") } for _, v2 := range v { var ( iPort, ePort int err error ) if len(v2.HostIP) != 0 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings") } if ePort, err = strconv.Atoi(v2.HostPort); err != nil { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err) } if iPort, err = strconv.Atoi(i.Port()); err != nil { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err) } if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 { return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range") } pbs = append(pbs, portBinding{ExternalPort: ePort, InternalPort: iPort, Protocol: proto}) } } // TODO Windows: TP3 workaround. Allow the user to override the name of // the Container NAT device through an environment variable. This will // ultimately be a global daemon parameter on Windows, similar to -b // for the name of the virtual switch (aka bridge). cn := os.Getenv("DOCKER_CONTAINER_NAT") if len(cn) == 0 { cn = defaultContainerNAT } dev := device{ DeviceType: "Network", Connection: &networkConnection{ NetworkName: c.Network.Interface.Bridge, // TODO Windows: Fixme, next line. Needs HCS fix. EnableNat: false, Nat: natSettings{ Name: cn, PortBindings: pbs, }, }, } if c.Network.Interface.MacAddress != "" { windowsStyleMAC := strings.Replace( c.Network.Interface.MacAddress, ":", "-", -1) dev.Settings = networkSettings{ MacAddress: windowsStyleMAC, } } cu.Devices = append(cu.Devices, dev) } else { logrus.Debugln("No network interface") } configurationb, err := json.Marshal(cu) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } configuration := string(configurationb) err = hcsshim.CreateComputeSystem(c.ID, configuration) if err != nil { logrus.Debugln("Failed to create temporary container ", err) return execdriver.ExitStatus{ExitCode: -1}, err } // Start the container logrus.Debugln("Starting container ", c.ID) err = hcsshim.StartComputeSystem(c.ID) if err != nil { logrus.Errorf("Failed to start compute system: %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } defer func() { // Stop the container if forceKill { logrus.Debugf("Forcibly terminating container %s", c.ID) if errno, err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { logrus.Warnf("Ignoring error from TerminateComputeSystem 0x%X %s", errno, err) } } else { logrus.Debugf("Shutting down container %s", c.ID) if errno, err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { if errno != hcsshim.Win32SystemShutdownIsInProgress && errno != hcsshim.Win32SpecifiedPathInvalid && errno != hcsshim.Win32SystemCannotFindThePathSpecified { logrus.Warnf("Ignoring error from ShutdownComputeSystem 0x%X %s", errno, err) } } } }() createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: c.ProcessConfig.Tty, WorkingDirectory: c.WorkingDir, ConsoleSize: c.ProcessConfig.ConsoleSize, } // Configure the environment for the process createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env) // This should get caught earlier, but just in case - validate that we // have something to run if c.ProcessConfig.Entrypoint == "" { err = errors.New("No entrypoint specified") logrus.Error(err) return execdriver.ExitStatus{ExitCode: -1}, err } // Build the command line of the process createProcessParms.CommandLine = c.ProcessConfig.Entrypoint for _, arg := range c.ProcessConfig.Arguments { logrus.Debugln("appending ", arg) createProcessParms.CommandLine += " " + syscall.EscapeArg(arg) } logrus.Debugf("CommandLine: %s", createProcessParms.CommandLine) // Start the command running in the container. pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms) if err != nil { logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } // Now that the process has been launched, begin copying data to and from // the named pipes for the std handles. setupPipes(stdin, stdout, stderr, pipes) //Save the PID as we'll need this in Kill() logrus.Debugf("PID %d", pid) c.ContainerPid = int(pid) if c.ProcessConfig.Tty { term = NewTtyConsole(c.ID, pid) } else { term = NewStdConsole() } c.ProcessConfig.Terminal = term // Maintain our list of active containers. We'll need this later for exec // and other commands. d.Lock() d.activeContainers[c.ID] = &activeContainer{ command: c, } d.Unlock() if hooks.Start != nil { // A closed channel for OOM is returned here as it will be // non-blocking and return the correct result when read. chOOM := make(chan struct{}) close(chOOM) hooks.Start(&c.ProcessConfig, int(pid), chOOM) } var ( exitCode int32 errno uint32 ) exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite) if err != nil { if errno != hcsshim.Win32PipeHasBeenEnded { logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) } // Do NOT return err here as the container would have // started, otherwise docker will deadlock. It's perfectly legitimate // for WaitForProcessInComputeSystem to fail in situations such // as the container being killed on another thread. return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil } logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID) return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil }
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { var ( term execdriver.Terminal err error inListen, outListen, errListen *npipe.PipeListener ) // Make sure the client isn't asking for options which aren't supported err = checkSupportedOptions(c) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } cu := &containerInit{ SystemType: "Container", Name: c.ID, IsDummy: dummyMode, VolumePath: c.Rootfs, IgnoreFlushesDuringBoot: c.FirstStart, LayerFolderPath: c.LayerFolder, } for i := 0; i < len(c.LayerPaths); i++ { cu.Layers = append(cu.Layers, layer{ Id: hcsshim.NewGUID(c.LayerPaths[i]).ToString(), Path: c.LayerPaths[i], }) } if c.Network.Interface != nil { dev := device{ DeviceType: "Network", Connection: &networkConnection{ NetworkName: c.Network.Interface.Bridge, EnableNat: false, }, } if c.Network.Interface.MacAddress != "" { windowsStyleMAC := strings.Replace( c.Network.Interface.MacAddress, ":", "-", -1) dev.Settings = networkSettings{ MacAddress: windowsStyleMAC, } } logrus.Debugf("Virtual switch '%s', mac='%s'", c.Network.Interface.Bridge, c.Network.Interface.MacAddress) cu.Devices = append(cu.Devices, dev) } else { logrus.Debugln("No network interface") } configurationb, err := json.Marshal(cu) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } configuration := string(configurationb) err = hcsshim.CreateComputeSystem(c.ID, configuration) if err != nil { logrus.Debugln("Failed to create temporary container ", err) return execdriver.ExitStatus{ExitCode: -1}, err } // Start the container logrus.Debugln("Starting container ", c.ID) err = hcsshim.StartComputeSystem(c.ID) if err != nil { logrus.Errorf("Failed to start compute system: %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } defer func() { // Stop the container if terminateMode { logrus.Debugf("Terminating container %s", c.ID) if err := hcsshim.TerminateComputeSystem(c.ID); err != nil { // IMPORTANT: Don't fail if fails to change state. It could already // have been stopped through kill(). // Otherwise, the docker daemon will hang in job wait() logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err) } } else { logrus.Debugf("Shutting down container %s", c.ID) if err := hcsshim.ShutdownComputeSystem(c.ID); err != nil { // IMPORTANT: Don't fail if fails to change state. It could already // have been stopped through kill(). // Otherwise, the docker daemon will hang in job wait() logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err) } } }() // We use a different pipe name between real and dummy mode in the HCS var serverPipeFormat, clientPipeFormat string if dummyMode { clientPipeFormat = `\\.\pipe\docker-run-%[1]s-%[2]s` serverPipeFormat = clientPipeFormat } else { clientPipeFormat = `\\.\pipe\docker-run-%[2]s` serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-run-%[2]s` } createProcessParms := hcsshim.CreateProcessParams{ EmulateConsole: c.ProcessConfig.Tty, WorkingDirectory: c.WorkingDir, ConsoleSize: c.ProcessConfig.ConsoleSize, } // Configure the environment for the process createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env) // Connect stdin if pipes.Stdin != nil { stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdin") createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdin") // Listen on the named pipe inListen, err = npipe.Listen(stdInPipe) if err != nil { logrus.Errorf("stdin failed to listen on %s err=%s", stdInPipe, err) return execdriver.ExitStatus{ExitCode: -1}, err } defer inListen.Close() // Launch a goroutine to do the accept. We do this so that we can // cause an otherwise blocking goroutine to gracefully close when // the caller (us) closes the listener go stdinAccept(inListen, stdInPipe, pipes.Stdin) } // Connect stdout stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdout") createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdout") outListen, err = npipe.Listen(stdOutPipe) if err != nil { logrus.Errorf("stdout failed to listen on %s err=%s", stdOutPipe, err) return execdriver.ExitStatus{ExitCode: -1}, err } defer outListen.Close() go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout) // No stderr on TTY. if !c.ProcessConfig.Tty { // Connect stderr stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stderr") createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stderr") errListen, err = npipe.Listen(stdErrPipe) if err != nil { logrus.Errorf("stderr failed to listen on %s err=%s", stdErrPipe, err) return execdriver.ExitStatus{ExitCode: -1}, err } defer errListen.Close() go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr) } // This should get caught earlier, but just in case - validate that we // have something to run if c.ProcessConfig.Entrypoint == "" { err = errors.New("No entrypoint specified") logrus.Error(err) return execdriver.ExitStatus{ExitCode: -1}, err } // Build the command line of the process createProcessParms.CommandLine = c.ProcessConfig.Entrypoint for _, arg := range c.ProcessConfig.Arguments { logrus.Debugln("appending ", arg) createProcessParms.CommandLine += " " + arg } logrus.Debugf("CommandLine: %s", createProcessParms.CommandLine) // Start the command running in the container. var pid uint32 pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms) if err != nil { logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } //Save the PID as we'll need this in Kill() logrus.Debugf("PID %d", pid) c.ContainerPid = int(pid) if c.ProcessConfig.Tty { term = NewTtyConsole(c.ID, pid) } else { term = NewStdConsole() } c.ProcessConfig.Terminal = term // Maintain our list of active containers. We'll need this later for exec // and other commands. d.Lock() d.activeContainers[c.ID] = &activeContainer{ command: c, } d.Unlock() // Invoke the start callback if startCallback != nil { startCallback(&c.ProcessConfig, int(pid)) } var exitCode int32 exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid) if err != nil { logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err) return execdriver.ExitStatus{ExitCode: -1}, err } logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID) return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil }