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