exec.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. // +build windows
  2. package windows
  3. import (
  4. "errors"
  5. "fmt"
  6. "github.com/Sirupsen/logrus"
  7. "github.com/docker/docker/daemon/execdriver"
  8. "github.com/docker/docker/pkg/stringid"
  9. "github.com/microsoft/hcsshim"
  10. "github.com/natefinch/npipe"
  11. )
  12. // Exec implements the exec driver Driver interface.
  13. func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
  14. var (
  15. inListen, outListen, errListen *npipe.PipeListener
  16. term execdriver.Terminal
  17. err error
  18. randomID = stringid.GenerateNonCryptoID()
  19. serverPipeFormat, clientPipeFormat string
  20. pid uint32
  21. exitCode int32
  22. )
  23. active := d.activeContainers[c.ID]
  24. if active == nil {
  25. return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID)
  26. }
  27. createProcessParms := hcsshim.CreateProcessParams{
  28. EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty
  29. WorkingDirectory: c.WorkingDir,
  30. }
  31. // Configure the environment for the process // Note NOT c.ProcessConfig.Tty
  32. createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env)
  33. // We use another unique ID here for each exec instance otherwise it
  34. // may conflict with the pipe name being used by RUN.
  35. // We use a different pipe name between real and dummy mode in the HCS
  36. if dummyMode {
  37. clientPipeFormat = `\\.\pipe\docker-exec-%[1]s-%[2]s-%[3]s`
  38. serverPipeFormat = clientPipeFormat
  39. } else {
  40. clientPipeFormat = `\\.\pipe\docker-exec-%[2]s-%[3]s`
  41. serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-exec-%[2]s-%[3]s`
  42. }
  43. // Connect stdin
  44. if pipes.Stdin != nil {
  45. stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stdin")
  46. createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stdin")
  47. // Listen on the named pipe
  48. inListen, err = npipe.Listen(stdInPipe)
  49. if err != nil {
  50. logrus.Errorf("stdin failed to listen on %s %s ", stdInPipe, err)
  51. return -1, err
  52. }
  53. defer inListen.Close()
  54. // Launch a goroutine to do the accept. We do this so that we can
  55. // cause an otherwise blocking goroutine to gracefully close when
  56. // the caller (us) closes the listener
  57. go stdinAccept(inListen, stdInPipe, pipes.Stdin)
  58. }
  59. // Connect stdout
  60. stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stdout")
  61. createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stdout")
  62. outListen, err = npipe.Listen(stdOutPipe)
  63. if err != nil {
  64. logrus.Errorf("stdout failed to listen on %s %s", stdOutPipe, err)
  65. return -1, err
  66. }
  67. defer outListen.Close()
  68. go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout)
  69. // No stderr on TTY. Note NOT c.ProcessConfig.Tty
  70. if !processConfig.Tty {
  71. // Connect stderr
  72. stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stderr")
  73. createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stderr")
  74. errListen, err = npipe.Listen(stdErrPipe)
  75. if err != nil {
  76. logrus.Errorf("Stderr failed to listen on %s %s", stdErrPipe, err)
  77. return -1, err
  78. }
  79. defer errListen.Close()
  80. go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr)
  81. }
  82. // While this should get caught earlier, just in case, validate that we
  83. // have something to run.
  84. if processConfig.Entrypoint == "" {
  85. err = errors.New("No entrypoint specified")
  86. logrus.Error(err)
  87. return -1, err
  88. }
  89. // Build the command line of the process
  90. createProcessParms.CommandLine = processConfig.Entrypoint
  91. for _, arg := range processConfig.Arguments {
  92. logrus.Debugln("appending ", arg)
  93. createProcessParms.CommandLine += " " + arg
  94. }
  95. logrus.Debugln("commandLine: ", createProcessParms.CommandLine)
  96. // Start the command running in the container.
  97. pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms)
  98. if err != nil {
  99. logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
  100. return -1, err
  101. }
  102. // Note NOT c.ProcessConfig.Tty
  103. if processConfig.Tty {
  104. term = NewTtyConsole(c.ID, pid)
  105. } else {
  106. term = NewStdConsole()
  107. }
  108. processConfig.Terminal = term
  109. // Invoke the start callback
  110. if startCallback != nil {
  111. startCallback(&c.ProcessConfig, int(pid))
  112. }
  113. if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil {
  114. logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err)
  115. return -1, err
  116. }
  117. // TODO Windows - Do something with this exit code
  118. logrus.Debugln("Exiting Run() with ExitCode 0", c.ID)
  119. return int(exitCode), nil
  120. }