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