exec.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. package daemon
  2. import (
  3. "fmt"
  4. "io"
  5. "io/ioutil"
  6. "strings"
  7. "sync"
  8. "github.com/Sirupsen/logrus"
  9. "github.com/docker/docker/daemon/execdriver"
  10. "github.com/docker/docker/daemon/execdriver/lxc"
  11. "github.com/docker/docker/pkg/broadcastwriter"
  12. "github.com/docker/docker/pkg/ioutils"
  13. "github.com/docker/docker/pkg/promise"
  14. "github.com/docker/docker/pkg/stringid"
  15. "github.com/docker/docker/runconfig"
  16. )
  17. type execConfig struct {
  18. sync.Mutex
  19. ID string
  20. Running bool
  21. ExitCode int
  22. ProcessConfig execdriver.ProcessConfig
  23. StreamConfig
  24. OpenStdin bool
  25. OpenStderr bool
  26. OpenStdout bool
  27. Container *Container
  28. }
  29. type execStore struct {
  30. s map[string]*execConfig
  31. sync.RWMutex
  32. }
  33. func newExecStore() *execStore {
  34. return &execStore{s: make(map[string]*execConfig, 0)}
  35. }
  36. func (e *execStore) Add(id string, execConfig *execConfig) {
  37. e.Lock()
  38. e.s[id] = execConfig
  39. e.Unlock()
  40. }
  41. func (e *execStore) Get(id string) *execConfig {
  42. e.RLock()
  43. res := e.s[id]
  44. e.RUnlock()
  45. return res
  46. }
  47. func (e *execStore) Delete(id string) {
  48. e.Lock()
  49. delete(e.s, id)
  50. e.Unlock()
  51. }
  52. func (e *execStore) List() []string {
  53. var IDs []string
  54. e.RLock()
  55. for id := range e.s {
  56. IDs = append(IDs, id)
  57. }
  58. e.RUnlock()
  59. return IDs
  60. }
  61. func (execConfig *execConfig) Resize(h, w int) error {
  62. return execConfig.ProcessConfig.Terminal.Resize(h, w)
  63. }
  64. func (d *Daemon) registerExecCommand(execConfig *execConfig) {
  65. // Storing execs in container in order to kill them gracefully whenever the container is stopped or removed.
  66. execConfig.Container.execCommands.Add(execConfig.ID, execConfig)
  67. // Storing execs in daemon for easy access via remote API.
  68. d.execCommands.Add(execConfig.ID, execConfig)
  69. }
  70. func (d *Daemon) getExecConfig(name string) (*execConfig, error) {
  71. if execConfig := d.execCommands.Get(name); execConfig != nil {
  72. if !execConfig.Container.IsRunning() {
  73. return nil, fmt.Errorf("Container %s is not running", execConfig.Container.ID)
  74. }
  75. return execConfig, nil
  76. }
  77. return nil, fmt.Errorf("No such exec instance '%s' found in daemon", name)
  78. }
  79. func (d *Daemon) unregisterExecCommand(execConfig *execConfig) {
  80. execConfig.Container.execCommands.Delete(execConfig.ID)
  81. d.execCommands.Delete(execConfig.ID)
  82. }
  83. func (d *Daemon) getActiveContainer(name string) (*Container, error) {
  84. container, err := d.Get(name)
  85. if err != nil {
  86. return nil, err
  87. }
  88. if !container.IsRunning() {
  89. return nil, fmt.Errorf("Container %s is not running", name)
  90. }
  91. if container.IsPaused() {
  92. return nil, fmt.Errorf("Container %s is paused, unpause the container before exec", name)
  93. }
  94. return container, nil
  95. }
  96. func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, error) {
  97. if strings.HasPrefix(d.execDriver.Name(), lxc.DriverName) {
  98. return "", lxc.ErrExec
  99. }
  100. container, err := d.getActiveContainer(config.Container)
  101. if err != nil {
  102. return "", err
  103. }
  104. cmd := runconfig.NewCommand(config.Cmd...)
  105. entrypoint, args := d.getEntrypointAndArgs(runconfig.NewEntrypoint(), cmd)
  106. processConfig := execdriver.ProcessConfig{
  107. Tty: config.Tty,
  108. Entrypoint: entrypoint,
  109. Arguments: args,
  110. User: config.User,
  111. Privileged: config.Privileged,
  112. }
  113. execConfig := &execConfig{
  114. ID: stringid.GenerateRandomID(),
  115. OpenStdin: config.AttachStdin,
  116. OpenStdout: config.AttachStdout,
  117. OpenStderr: config.AttachStderr,
  118. StreamConfig: StreamConfig{},
  119. ProcessConfig: processConfig,
  120. Container: container,
  121. Running: false,
  122. }
  123. container.LogEvent("exec_create: " + execConfig.ProcessConfig.Entrypoint + " " + strings.Join(execConfig.ProcessConfig.Arguments, " "))
  124. d.registerExecCommand(execConfig)
  125. return execConfig.ID, nil
  126. }
  127. func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error {
  128. var (
  129. cStdin io.ReadCloser
  130. cStdout, cStderr io.Writer
  131. )
  132. execConfig, err := d.getExecConfig(execName)
  133. if err != nil {
  134. return err
  135. }
  136. func() {
  137. execConfig.Lock()
  138. defer execConfig.Unlock()
  139. if execConfig.Running {
  140. err = fmt.Errorf("Error: Exec command %s is already running", execName)
  141. }
  142. execConfig.Running = true
  143. }()
  144. if err != nil {
  145. return err
  146. }
  147. logrus.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID)
  148. container := execConfig.Container
  149. container.LogEvent("exec_start: " + execConfig.ProcessConfig.Entrypoint + " " + strings.Join(execConfig.ProcessConfig.Arguments, " "))
  150. if execConfig.OpenStdin {
  151. r, w := io.Pipe()
  152. go func() {
  153. defer w.Close()
  154. defer logrus.Debugf("Closing buffered stdin pipe")
  155. io.Copy(w, stdin)
  156. }()
  157. cStdin = r
  158. }
  159. if execConfig.OpenStdout {
  160. cStdout = stdout
  161. }
  162. if execConfig.OpenStderr {
  163. cStderr = stderr
  164. }
  165. execConfig.StreamConfig.stderr = broadcastwriter.New()
  166. execConfig.StreamConfig.stdout = broadcastwriter.New()
  167. // Attach to stdin
  168. if execConfig.OpenStdin {
  169. execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdinPipe = io.Pipe()
  170. } else {
  171. execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
  172. }
  173. attachErr := attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr)
  174. execErr := make(chan error)
  175. // Note, the execConfig data will be removed when the container
  176. // itself is deleted. This allows us to query it (for things like
  177. // the exitStatus) even after the cmd is done running.
  178. go func() {
  179. if err := container.Exec(execConfig); err != nil {
  180. execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err)
  181. }
  182. }()
  183. select {
  184. case err := <-attachErr:
  185. if err != nil {
  186. return fmt.Errorf("attach failed with error: %s", err)
  187. }
  188. break
  189. case err := <-execErr:
  190. return err
  191. }
  192. return nil
  193. }
  194. func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
  195. exitStatus, err := d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
  196. // On err, make sure we don't leave ExitCode at zero
  197. if err != nil && exitStatus == 0 {
  198. exitStatus = 128
  199. }
  200. execConfig.ExitCode = exitStatus
  201. execConfig.Running = false
  202. return exitStatus, err
  203. }
  204. func (container *Container) GetExecIDs() []string {
  205. return container.execCommands.List()
  206. }
  207. func (container *Container) Exec(execConfig *execConfig) error {
  208. container.Lock()
  209. defer container.Unlock()
  210. waitStart := make(chan struct{})
  211. callback := func(processConfig *execdriver.ProcessConfig, pid int) {
  212. if processConfig.Tty {
  213. // The callback is called after the process Start()
  214. // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
  215. // which we close here.
  216. if c, ok := processConfig.Stdout.(io.Closer); ok {
  217. c.Close()
  218. }
  219. }
  220. close(waitStart)
  221. }
  222. // We use a callback here instead of a goroutine and an chan for
  223. // syncronization purposes
  224. cErr := promise.Go(func() error { return container.monitorExec(execConfig, callback) })
  225. // Exec should not return until the process is actually running
  226. select {
  227. case <-waitStart:
  228. case err := <-cErr:
  229. return err
  230. }
  231. return nil
  232. }
  233. func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error {
  234. var (
  235. err error
  236. exitCode int
  237. )
  238. pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin)
  239. exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback)
  240. if err != nil {
  241. logrus.Errorf("Error running command in existing container %s: %s", container.ID, err)
  242. }
  243. logrus.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
  244. if execConfig.OpenStdin {
  245. if err := execConfig.StreamConfig.stdin.Close(); err != nil {
  246. logrus.Errorf("Error closing stdin while running in %s: %s", container.ID, err)
  247. }
  248. }
  249. if err := execConfig.StreamConfig.stdout.Clean(); err != nil {
  250. logrus.Errorf("Error closing stdout while running in %s: %s", container.ID, err)
  251. }
  252. if err := execConfig.StreamConfig.stderr.Clean(); err != nil {
  253. logrus.Errorf("Error closing stderr while running in %s: %s", container.ID, err)
  254. }
  255. if execConfig.ProcessConfig.Terminal != nil {
  256. if err := execConfig.ProcessConfig.Terminal.Close(); err != nil {
  257. logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
  258. }
  259. }
  260. return err
  261. }