5130fe5d |
package daemon
import ( |
a793564b |
"fmt" |
5130fe5d |
"io" |
ab30e19b |
"strings" |
5f017bba |
"time" |
5130fe5d |
|
7bb815e2 |
"golang.org/x/net/context"
|
6f4d8470 |
"github.com/Sirupsen/logrus" |
6bb0d181 |
"github.com/docker/docker/container" |
9ca2e4e8 |
"github.com/docker/docker/daemon/exec" |
a793564b |
"github.com/docker/docker/errors" |
9c4570a9 |
"github.com/docker/docker/libcontainerd" |
c1477db0 |
"github.com/docker/docker/pkg/pools" |
b6c7becb |
"github.com/docker/docker/pkg/signal" |
15aa2a66 |
"github.com/docker/docker/pkg/term" |
907407d0 |
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/strslice" |
5130fe5d |
)
|
b6c7becb |
// Seconds to wait after sending TERM before trying KILL
const termProcessTimeout = 10
|
6bb0d181 |
func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) { |
5b794c41 |
// Storing execs in container in order to kill them gracefully whenever the container is stopped or removed. |
6bb0d181 |
container.ExecCommands.Add(config.ID, config) |
bfebdfde |
// Storing execs in daemon for easy access via remote API. |
9ca2e4e8 |
d.execCommands.Add(config.ID, config) |
bfebdfde |
}
|
2d43d934 |
// ExecExists looks up the exec instance and returns a bool if it exists or not. |
6bb0d181 |
// It will also return the error produced by `getConfig` |
2d43d934 |
func (d *Daemon) ExecExists(name string) (bool, error) {
if _, err := d.getExecConfig(name); err != nil {
return false, err
}
return true, nil
}
// getExecConfig looks up the exec instance by name. If the container associated
// with the exec instance is stopped or paused, it will return an error. |
9ca2e4e8 |
func (d *Daemon) getExecConfig(name string) (*exec.Config, error) { |
2d43d934 |
ec := d.execCommands.Get(name) |
d841b779 |
// If the exec is found but its container is not in the daemon's list of |
01b86d61 |
// containers then it must have been deleted, in which case instead of |
d841b779 |
// saying the container isn't running, we should return a 404 so that
// the user sees the same error now that they will after the
// 5 minute clean-up loop is run which erases old/dead execs.
|
9ca2e4e8 |
if ec != nil {
if container := d.containers.Get(ec.ContainerID); container != nil {
if !container.IsRunning() { |
a793564b |
return nil, fmt.Errorf("Container %s is not running: %s", container.ID, container.State.String()) |
9ca2e4e8 |
} |
6bb0d181 |
if container.IsPaused() { |
a793564b |
return nil, errExecPaused(container.ID) |
9ca2e4e8 |
} |
1d2208fe |
if container.IsRestarting() { |
a793564b |
return nil, errContainerIsRestarting(container.ID) |
1d2208fe |
} |
9ca2e4e8 |
return ec, nil |
bfebdfde |
} |
5130fe5d |
}
|
a793564b |
return nil, errExecNotFound(name) |
bfebdfde |
}
|
6bb0d181 |
func (d *Daemon) unregisterExecCommand(container *container.Container, execConfig *exec.Config) {
container.ExecCommands.Delete(execConfig.ID) |
9ca2e4e8 |
d.execCommands.Delete(execConfig.ID) |
bfebdfde |
} |
5130fe5d |
|
6bb0d181 |
func (d *Daemon) getActiveContainer(name string) (*container.Container, error) { |
d7d512bb |
container, err := d.GetContainer(name) |
d25a6537 |
if err != nil {
return nil, err |
5130fe5d |
}
|
669561c2 |
if !container.IsRunning() { |
a793564b |
return nil, errNotRunning{container.ID} |
5130fe5d |
} |
6bb0d181 |
if container.IsPaused() { |
a793564b |
return nil, errExecPaused(name) |
1bb02117 |
} |
1d2208fe |
if container.IsRestarting() { |
a793564b |
return nil, errContainerIsRestarting(container.ID) |
1d2208fe |
} |
bfebdfde |
return container, nil
} |
5130fe5d |
|
abd72d40 |
// ContainerExecCreate sets up an exec in a running container. |
b9c94b70 |
func (d *Daemon) ContainerExecCreate(name string, config *types.ExecConfig) (string, error) {
container, err := d.getActiveContainer(name) |
c8a3d313 |
if err != nil { |
24425021 |
return "", err |
c8a3d313 |
} |
bfebdfde |
|
53b0d626 |
cmd := strslice.StrSlice(config.Cmd)
entrypoint, args := d.getEntrypointAndArgs(strslice.StrSlice{}, cmd) |
5130fe5d |
|
15aa2a66 |
keys := []byte{}
if config.DetachKeys != "" {
keys, err = term.ToBytes(config.DetachKeys)
if err != nil { |
91e5bb95 |
err = fmt.Errorf("Invalid escape keys (%s) provided", config.DetachKeys)
return "", err |
15aa2a66 |
}
}
|
9ca2e4e8 |
execConfig := exec.NewConfig()
execConfig.OpenStdin = config.AttachStdin
execConfig.OpenStdout = config.AttachStdout
execConfig.OpenStderr = config.AttachStderr
execConfig.ContainerID = container.ID |
15aa2a66 |
execConfig.DetachKeys = keys |
9c4570a9 |
execConfig.Entrypoint = entrypoint
execConfig.Args = args
execConfig.Tty = config.Tty
execConfig.Privileged = config.Privileged
execConfig.User = config.User
if len(execConfig.User) == 0 {
execConfig.User = container.Config.User
} |
bfebdfde |
|
9ca2e4e8 |
d.registerExecCommand(container, execConfig) |
bfebdfde |
|
9c4570a9 |
d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " ")) |
bfebdfde |
|
9ca2e4e8 |
return execConfig.ID, nil |
bfebdfde |
}
|
abd72d40 |
// ContainerExecStart starts a previously set up exec instance. The
// std streams are set up. |
b6c7becb |
// If ctx is cancelled, the process is terminated.
func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) (err error) { |
bfebdfde |
var (
cStdin io.ReadCloser
cStdout, cStderr io.Writer
)
|
2d43d934 |
ec, err := d.getExecConfig(name) |
39030382 |
if err != nil { |
a793564b |
return errExecNotFound(name) |
bfebdfde |
}
|
2d43d934 |
ec.Lock() |
1a60a805 |
if ec.ExitCode != nil {
ec.Unlock() |
a793564b |
err := fmt.Errorf("Error: Exec command %s has already run", ec.ID)
return errors.NewRequestConflictError(err) |
1a60a805 |
}
|
2d43d934 |
if ec.Running {
ec.Unlock() |
a793564b |
return fmt.Errorf("Error: Exec command %s is already running", ec.ID) |
bfebdfde |
} |
2d43d934 |
ec.Running = true |
9c4570a9 |
defer func() {
if err != nil {
ec.Running = false
exitCode := 126
ec.ExitCode = &exitCode
}
}() |
2d43d934 |
ec.Unlock() |
bfebdfde |
|
6bb0d181 |
c := d.containers.Get(ec.ContainerID)
logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID) |
9c4570a9 |
d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " ")) |
e3d813f3 |
|
fb0ac1af |
if ec.OpenStdin && stdin != nil { |
bfebdfde |
r, w := io.Pipe()
go func() {
defer w.Close() |
a72b45db |
defer logrus.Debug("Closing buffered stdin pipe") |
c1477db0 |
pools.Copy(w, stdin) |
bfebdfde |
}()
cStdin = r
} |
2d43d934 |
if ec.OpenStdout { |
24425021 |
cStdout = stdout |
bfebdfde |
} |
2d43d934 |
if ec.OpenStderr { |
24425021 |
cStderr = stderr |
5130fe5d |
}
|
2d43d934 |
if ec.OpenStdin { |
9ca2e4e8 |
ec.NewInputPipes() |
5130fe5d |
} else { |
9ca2e4e8 |
ec.NewNopInputPipe() |
5130fe5d |
}
|
9c4570a9 |
p := libcontainerd.Process{
Args: append([]string{ec.Entrypoint}, ec.Args...),
Terminal: ec.Tty,
} |
561005e5 |
|
9c4570a9 |
if err := execSetPlatformOpt(c, ec, &p); err != nil { |
afc64c2d |
return err |
5130fe5d |
}
|
b6c7becb |
attachErr := container.AttachStreams(ctx, ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) |
90928eb1 |
|
afc64c2d |
if err := d.containerd.AddProcess(ctx, c.ID, name, p); err != nil { |
9c4570a9 |
return err |
90928eb1 |
}
|
b6c7becb |
select {
case <-ctx.Done():
logrus.Debugf("Sending TERM signal to process %v in container %v", name, c.ID)
d.containerd.SignalProcess(c.ID, name, int(signal.SignalMap["TERM"]))
select {
case <-time.After(termProcessTimeout * time.Second):
logrus.Infof("Container %v, process %v failed to exit within %d seconds of signal TERM - using the force", c.ID, name, termProcessTimeout)
d.containerd.SignalProcess(c.ID, name, int(signal.SignalMap["KILL"]))
case <-attachErr:
// TERM signal worked
}
return fmt.Errorf("context cancelled")
case err := <-attachErr:
if err != nil { |
3accde6d |
if _, ok := err.(container.DetachError); !ok {
return fmt.Errorf("exec attach failed with error: %v", err) |
83ad006d |
} |
3accde6d |
d.LogContainerEvent(c, "exec_detach") |
b6c7becb |
} |
9c4570a9 |
}
return nil |
5130fe5d |
} |
5f017bba |
// execCommandGC runs a ticker to clean up the daemon references
// of exec configs that are no longer part of the container.
func (d *Daemon) execCommandGC() {
for range time.Tick(5 * time.Minute) {
var (
cleaned int
liveExecCommands = d.containerExecIds()
) |
9ca2e4e8 |
for id, config := range d.execCommands.Commands() {
if config.CanRemove { |
5f017bba |
cleaned++
d.execCommands.Delete(id) |
34ab8c43 |
} else {
if _, exists := liveExecCommands[id]; !exists { |
9ca2e4e8 |
config.CanRemove = true |
34ab8c43 |
} |
5f017bba |
}
} |
b271593c |
if cleaned > 0 {
logrus.Debugf("clean %d unused exec commands", cleaned)
} |
5f017bba |
}
}
// containerExecIds returns a list of all the current exec ids that are in use
// and running inside a container.
func (d *Daemon) containerExecIds() map[string]struct{} {
ids := map[string]struct{}{}
for _, c := range d.containers.List() { |
6bb0d181 |
for _, id := range c.ExecCommands.List() { |
5f017bba |
ids[id] = struct{}{}
}
}
return ids
} |