github.com/mephux/docker@v1.6.0-rc5/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/common" 16 "github.com/docker/docker/pkg/ioutils" 17 "github.com/docker/docker/pkg/promise" 18 "github.com/docker/docker/runconfig" 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 in order 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, err := d.Get(name) 101 if err != nil { 102 return nil, err 103 } 104 105 if !container.IsRunning() { 106 return nil, fmt.Errorf("Container %s is not running", name) 107 } 108 if container.IsPaused() { 109 return nil, fmt.Errorf("Container %s is paused, unpause the container before exec", name) 110 } 111 return container, nil 112 } 113 114 func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status { 115 if len(job.Args) != 1 { 116 return job.Errorf("Usage: %s [options] container command [args]", job.Name) 117 } 118 119 if strings.HasPrefix(d.execDriver.Name(), lxc.DriverName) { 120 return job.Error(lxc.ErrExec) 121 } 122 123 var name = job.Args[0] 124 125 container, err := d.getActiveContainer(name) 126 if err != nil { 127 return job.Error(err) 128 } 129 130 config, err := runconfig.ExecConfigFromJob(job) 131 if err != nil { 132 return job.Error(err) 133 } 134 135 entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd) 136 137 processConfig := execdriver.ProcessConfig{ 138 Tty: config.Tty, 139 Entrypoint: entrypoint, 140 Arguments: args, 141 } 142 143 execConfig := &execConfig{ 144 ID: common.GenerateRandomID(), 145 OpenStdin: config.AttachStdin, 146 OpenStdout: config.AttachStdout, 147 OpenStderr: config.AttachStderr, 148 StreamConfig: StreamConfig{}, 149 ProcessConfig: processConfig, 150 Container: container, 151 Running: false, 152 } 153 154 container.LogEvent("exec_create: " + execConfig.ProcessConfig.Entrypoint + " " + strings.Join(execConfig.ProcessConfig.Arguments, " ")) 155 156 d.registerExecCommand(execConfig) 157 158 job.Printf("%s\n", execConfig.ID) 159 160 return engine.StatusOK 161 } 162 163 func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status { 164 if len(job.Args) != 1 { 165 return job.Errorf("Usage: %s [options] exec", job.Name) 166 } 167 168 var ( 169 cStdin io.ReadCloser 170 cStdout, cStderr io.Writer 171 execName = job.Args[0] 172 ) 173 174 execConfig, err := d.getExecConfig(execName) 175 if err != nil { 176 return job.Error(err) 177 } 178 179 func() { 180 execConfig.Lock() 181 defer execConfig.Unlock() 182 if execConfig.Running { 183 err = fmt.Errorf("Error: Exec command %s is already running", execName) 184 } 185 execConfig.Running = true 186 }() 187 if err != nil { 188 return job.Error(err) 189 } 190 191 log.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID) 192 container := execConfig.Container 193 194 container.LogEvent("exec_start: " + execConfig.ProcessConfig.Entrypoint + " " + strings.Join(execConfig.ProcessConfig.Arguments, " ")) 195 196 if execConfig.OpenStdin { 197 r, w := io.Pipe() 198 go func() { 199 defer w.Close() 200 defer log.Debugf("Closing buffered stdin pipe") 201 io.Copy(w, job.Stdin) 202 }() 203 cStdin = r 204 } 205 if execConfig.OpenStdout { 206 cStdout = job.Stdout 207 } 208 if execConfig.OpenStderr { 209 cStderr = job.Stderr 210 } 211 212 execConfig.StreamConfig.stderr = broadcastwriter.New() 213 execConfig.StreamConfig.stdout = broadcastwriter.New() 214 // Attach to stdin 215 if execConfig.OpenStdin { 216 execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdinPipe = io.Pipe() 217 } else { 218 execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin 219 } 220 221 attachErr := d.Attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr) 222 223 execErr := make(chan error) 224 225 // Note, the execConfig data will be removed when the container 226 // itself is deleted. This allows us to query it (for things like 227 // the exitStatus) even after the cmd is done running. 228 229 go func() { 230 err := container.Exec(execConfig) 231 if err != nil { 232 execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err) 233 } 234 }() 235 236 select { 237 case err := <-attachErr: 238 if err != nil { 239 return job.Errorf("attach failed with error: %s", err) 240 } 241 break 242 case err := <-execErr: 243 return job.Error(err) 244 } 245 246 return engine.StatusOK 247 } 248 249 func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { 250 exitStatus, err := d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback) 251 252 // On err, make sure we don't leave ExitCode at zero 253 if err != nil && exitStatus == 0 { 254 exitStatus = 128 255 } 256 257 execConfig.ExitCode = exitStatus 258 execConfig.Running = false 259 260 return exitStatus, err 261 } 262 263 func (container *Container) GetExecIDs() []string { 264 return container.execCommands.List() 265 } 266 267 func (container *Container) Exec(execConfig *execConfig) error { 268 container.Lock() 269 defer container.Unlock() 270 271 waitStart := make(chan struct{}) 272 273 callback := func(processConfig *execdriver.ProcessConfig, pid int) { 274 if processConfig.Tty { 275 // The callback is called after the process Start() 276 // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave 277 // which we close here. 278 if c, ok := processConfig.Stdout.(io.Closer); ok { 279 c.Close() 280 } 281 } 282 close(waitStart) 283 } 284 285 // We use a callback here instead of a goroutine and an chan for 286 // syncronization purposes 287 cErr := promise.Go(func() error { return container.monitorExec(execConfig, callback) }) 288 289 // Exec should not return until the process is actually running 290 select { 291 case <-waitStart: 292 case err := <-cErr: 293 return err 294 } 295 296 return nil 297 } 298 299 func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error { 300 var ( 301 err error 302 exitCode int 303 ) 304 305 pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin) 306 exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback) 307 if err != nil { 308 log.Errorf("Error running command in existing container %s: %s", container.ID, err) 309 } 310 311 log.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode) 312 if execConfig.OpenStdin { 313 if err := execConfig.StreamConfig.stdin.Close(); err != nil { 314 log.Errorf("Error closing stdin while running in %s: %s", container.ID, err) 315 } 316 } 317 if err := execConfig.StreamConfig.stdout.Clean(); err != nil { 318 log.Errorf("Error closing stdout while running in %s: %s", container.ID, err) 319 } 320 if err := execConfig.StreamConfig.stderr.Clean(); err != nil { 321 log.Errorf("Error closing stderr while running in %s: %s", container.ID, err) 322 } 323 if execConfig.ProcessConfig.Terminal != nil { 324 if err := execConfig.ProcessConfig.Terminal.Close(); err != nil { 325 log.Errorf("Error closing terminal while running in container %s: %s", container.ID, err) 326 } 327 } 328 329 return err 330 }