Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
| ... | ... |
@@ -407,7 +407,7 @@ func (b *Builder) run(c *daemon.Container) error {
|
| 407 | 407 |
// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach |
| 408 | 408 |
// but without hijacking for stdin. Also, with attach there can be race |
| 409 | 409 |
// condition because of some output already was printed before it. |
| 410 |
- return <-b.Daemon.Attach(c, nil, nil, b.OutStream, b.ErrStream) |
|
| 410 |
+ return <-b.Daemon.Attach(c, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream) |
|
| 411 | 411 |
}) |
| 412 | 412 |
} |
| 413 | 413 |
|
| ... | ... |
@@ -8,9 +8,9 @@ import ( |
| 8 | 8 |
"time" |
| 9 | 9 |
|
| 10 | 10 |
"github.com/docker/docker/engine" |
| 11 |
- "github.com/docker/docker/pkg/ioutils" |
|
| 12 | 11 |
"github.com/docker/docker/pkg/jsonlog" |
| 13 | 12 |
"github.com/docker/docker/pkg/log" |
| 13 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 14 | 14 |
"github.com/docker/docker/utils" |
| 15 | 15 |
) |
| 16 | 16 |
|
| ... | ... |
@@ -103,7 +103,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
| 103 | 103 |
cStderr = job.Stderr |
| 104 | 104 |
} |
| 105 | 105 |
|
| 106 |
- <-daemon.Attach(container, cStdin, cStdinCloser, cStdout, cStderr) |
|
| 106 |
+ <-daemon.Attach(container, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr) |
|
| 107 | 107 |
// If we are in stdinonce mode, wait for the process to end |
| 108 | 108 |
// otherwise, simply return |
| 109 | 109 |
if container.Config.StdinOnce && !container.Config.Tty {
|
| ... | ... |
@@ -119,15 +119,17 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
| 119 | 119 |
// Attach and ContainerAttach. |
| 120 | 120 |
// |
| 121 | 121 |
// This method is in use by builder/builder.go. |
| 122 |
-func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
|
| 122 |
+func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
|
| 123 | 123 |
var ( |
| 124 | 124 |
cStdout, cStderr io.ReadCloser |
| 125 | 125 |
nJobs int |
| 126 | 126 |
errors = make(chan error, 3) |
| 127 | 127 |
) |
| 128 | 128 |
|
| 129 |
- if stdin != nil && container.Config.OpenStdin {
|
|
| 130 |
- nJobs++ |
|
| 129 |
+ // Connect stdin of container to the http conn. |
|
| 130 |
+ if stdin != nil && openStdin {
|
|
| 131 |
+ nJobs += 1 |
|
| 132 |
+ // Get the stdin pipe. |
|
| 131 | 133 |
if cStdin, err := container.StdinPipe(); err != nil {
|
| 132 | 134 |
errors <- err |
| 133 | 135 |
} else {
|
| ... | ... |
@@ -135,7 +137,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 135 | 135 |
log.Debugf("attach: stdin: begin")
|
| 136 | 136 |
defer log.Debugf("attach: stdin: end")
|
| 137 | 137 |
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr |
| 138 |
- if container.Config.StdinOnce && !container.Config.Tty {
|
|
| 138 |
+ if stdinOnce && !tty {
|
|
| 139 | 139 |
defer cStdin.Close() |
| 140 | 140 |
} else {
|
| 141 | 141 |
defer func() {
|
| ... | ... |
@@ -147,10 +149,11 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 147 | 147 |
} |
| 148 | 148 |
}() |
| 149 | 149 |
} |
| 150 |
- if container.Config.Tty {
|
|
| 150 |
+ if tty {
|
|
| 151 | 151 |
_, err = utils.CopyEscapable(cStdin, stdin) |
| 152 | 152 |
} else {
|
| 153 | 153 |
_, err = io.Copy(cStdin, stdin) |
| 154 |
+ |
|
| 154 | 155 |
} |
| 155 | 156 |
if err == io.ErrClosedPipe {
|
| 156 | 157 |
err = nil |
| ... | ... |
@@ -163,7 +166,8 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 163 | 163 |
} |
| 164 | 164 |
} |
| 165 | 165 |
if stdout != nil {
|
| 166 |
- nJobs++ |
|
| 166 |
+ nJobs += 1 |
|
| 167 |
+ // Get a reader end of a pipe that is attached as stdout to the container. |
|
| 167 | 168 |
if p, err := container.StdoutPipe(); err != nil {
|
| 168 | 169 |
errors <- err |
| 169 | 170 |
} else {
|
| ... | ... |
@@ -172,7 +176,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 172 | 172 |
log.Debugf("attach: stdout: begin")
|
| 173 | 173 |
defer log.Debugf("attach: stdout: end")
|
| 174 | 174 |
// If we are in StdinOnce mode, then close stdin |
| 175 |
- if container.Config.StdinOnce && stdin != nil {
|
|
| 175 |
+ if stdinOnce && stdin != nil {
|
|
| 176 | 176 |
defer stdin.Close() |
| 177 | 177 |
} |
| 178 | 178 |
if stdinCloser != nil {
|
| ... | ... |
@@ -189,6 +193,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 189 | 189 |
}() |
| 190 | 190 |
} |
| 191 | 191 |
} else {
|
| 192 |
+ // Point stdout of container to a no-op writer. |
|
| 192 | 193 |
go func() {
|
| 193 | 194 |
if stdinCloser != nil {
|
| 194 | 195 |
defer stdinCloser.Close() |
| ... | ... |
@@ -201,7 +206,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 201 | 201 |
}() |
| 202 | 202 |
} |
| 203 | 203 |
if stderr != nil {
|
| 204 |
- nJobs++ |
|
| 204 |
+ nJobs += 1 |
|
| 205 | 205 |
if p, err := container.StderrPipe(); err != nil {
|
| 206 | 206 |
errors <- err |
| 207 | 207 |
} else {
|
| ... | ... |
@@ -210,7 +215,8 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 210 | 210 |
log.Debugf("attach: stderr: begin")
|
| 211 | 211 |
defer log.Debugf("attach: stderr: end")
|
| 212 | 212 |
// If we are in StdinOnce mode, then close stdin |
| 213 |
- if container.Config.StdinOnce && stdin != nil {
|
|
| 213 |
+ // Why are we closing stdin here and above while handling stdout? |
|
| 214 |
+ if stdinOnce && stdin != nil {
|
|
| 214 | 215 |
defer stdin.Close() |
| 215 | 216 |
} |
| 216 | 217 |
if stdinCloser != nil {
|
| ... | ... |
@@ -223,10 +229,12 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 223 | 223 |
if err != nil {
|
| 224 | 224 |
log.Errorf("attach: stderr: %s", err)
|
| 225 | 225 |
} |
| 226 |
+ log.Debugf("stdout attach end")
|
|
| 226 | 227 |
errors <- err |
| 227 | 228 |
}() |
| 228 | 229 |
} |
| 229 | 230 |
} else {
|
| 231 |
+ // Point stderr at a no-op writer. |
|
| 230 | 232 |
go func() {
|
| 231 | 233 |
if stdinCloser != nil {
|
| 232 | 234 |
defer stdinCloser.Close() |
| ... | ... |
@@ -252,7 +260,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo |
| 252 | 252 |
|
| 253 | 253 |
// FIXME: how to clean up the stdin goroutine without the unwanted side effect |
| 254 | 254 |
// of closing the passed stdin? Add an intermediary io.Pipe? |
| 255 |
- for i := 0; i < nJobs; i++ {
|
|
| 255 |
+ for i := 0; i < nJobs; i += 1 {
|
|
| 256 | 256 |
log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
| 257 | 257 |
if err := <-errors; err != nil {
|
| 258 | 258 |
log.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err)
|
| ... | ... |
@@ -42,6 +42,8 @@ type TtyTerminal interface {
|
| 42 | 42 |
|
| 43 | 43 |
type Driver interface {
|
| 44 | 44 |
Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code |
| 45 |
+ // Exec executes the process in an existing container, blocks until the process exits and returns the exit code |
|
| 46 |
+ Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error) |
|
| 45 | 47 |
Kill(c *Command, sig int) error |
| 46 | 48 |
Pause(c *Command) error |
| 47 | 49 |
Unpause(c *Command) error |
| ... | ... |
@@ -527,3 +527,7 @@ func (t *TtyConsole) Close() error {
|
| 527 | 527 |
t.SlavePty.Close() |
| 528 | 528 |
return t.MasterPty.Close() |
| 529 | 529 |
} |
| 530 |
+ |
|
| 531 |
+func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
|
| 532 |
+ return -1, fmt.Errorf("Unsupported: Exec is not supported by the lxc driver")
|
|
| 533 |
+} |
| 530 | 534 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,68 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package native |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "log" |
|
| 7 |
+ "os" |
|
| 8 |
+ "os/exec" |
|
| 9 |
+ "path/filepath" |
|
| 10 |
+ "runtime" |
|
| 11 |
+ |
|
| 12 |
+ "github.com/docker/libcontainer" |
|
| 13 |
+ "github.com/docker/libcontainer/namespaces" |
|
| 14 |
+ "github.com/docker/docker/daemon/execdriver" |
|
| 15 |
+ "github.com/docker/docker/reexec" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+const commandName = "nsenter-exec" |
|
| 19 |
+ |
|
| 20 |
+func init() {
|
|
| 21 |
+ reexec.Register(commandName, nsenterExec) |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func nsenterExec() {
|
|
| 25 |
+ runtime.LockOSThread() |
|
| 26 |
+ |
|
| 27 |
+ userArgs := findUserArgs() |
|
| 28 |
+ |
|
| 29 |
+ config, err := loadConfigFromFd() |
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ log.Fatalf("docker-exec: unable to receive config from sync pipe: %s", err)
|
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ if err := namespaces.FinalizeSetns(config, userArgs); err != nil {
|
|
| 35 |
+ log.Fatalf("docker-exec: failed to exec: %s", err)
|
|
| 36 |
+ } |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
|
| 40 |
+ active := d.activeContainers[c.ID] |
|
| 41 |
+ if active == nil {
|
|
| 42 |
+ return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
|
|
| 43 |
+ } |
|
| 44 |
+ state, err := libcontainer.GetState(filepath.Join(d.root, c.ID)) |
|
| 45 |
+ if err != nil {
|
|
| 46 |
+ return -1, fmt.Errorf("State unavailable for container with ID %s. The container may have been cleaned up already. Error: %s", c.ID, err)
|
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ var term execdriver.Terminal |
|
| 50 |
+ |
|
| 51 |
+ if processConfig.Tty {
|
|
| 52 |
+ term, err = NewTtyConsole(processConfig, pipes) |
|
| 53 |
+ } else {
|
|
| 54 |
+ term, err = execdriver.NewStdConsole(processConfig, pipes) |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ processConfig.Terminal = term |
|
| 58 |
+ |
|
| 59 |
+ args := append([]string{processConfig.Entrypoint}, processConfig.Arguments...)
|
|
| 60 |
+ |
|
| 61 |
+ return namespaces.ExecIn(active.container, state, args, os.Args[0], "exec", processConfig.Stdin, processConfig.Stdout, processConfig.Stderr, processConfig.Console, |
|
| 62 |
+ func(cmd *exec.Cmd) {
|
|
| 63 |
+ if startCallback != nil {
|
|
| 64 |
+ startCallback(&c.ProcessConfig, cmd.Process.Pid) |
|
| 65 |
+ } |
|
| 66 |
+ }) |
|
| 67 |
+} |
| 0 | 68 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,40 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package native |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "os" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/libcontainer" |
|
| 8 |
+ "github.com/docker/libcontainer/syncpipe" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func findUserArgs() []string {
|
|
| 12 |
+ i := 0 |
|
| 13 |
+ for _, a := range os.Args {
|
|
| 14 |
+ i++ |
|
| 15 |
+ |
|
| 16 |
+ if a == "--" {
|
|
| 17 |
+ break |
|
| 18 |
+ } |
|
| 19 |
+ } |
|
| 20 |
+ |
|
| 21 |
+ return os.Args[i:] |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+// loadConfigFromFd loads a container's config from the sync pipe that is provided by |
|
| 25 |
+// fd 3 when running a process |
|
| 26 |
+func loadConfigFromFd() (*libcontainer.Config, error) {
|
|
| 27 |
+ syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3) |
|
| 28 |
+ if err != nil {
|
|
| 29 |
+ return nil, err |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ var config *libcontainer.Config |
|
| 33 |
+ if err := syncPipe.ReadFromParent(&config); err != nil {
|
|
| 34 |
+ return nil, err |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ return config, nil |
|
| 38 |
+} |
|
| 39 |
+ |