github.com/akerouanton/docker@v1.11.0-rc3/daemon/exec.go (about)

     1  package daemon
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"strings"
     7  	"time"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/Sirupsen/logrus"
    12  	"github.com/docker/docker/container"
    13  	"github.com/docker/docker/daemon/exec"
    14  	"github.com/docker/docker/errors"
    15  	"github.com/docker/docker/libcontainerd"
    16  	"github.com/docker/docker/pkg/pools"
    17  	"github.com/docker/docker/pkg/term"
    18  	"github.com/docker/engine-api/types"
    19  	"github.com/docker/engine-api/types/strslice"
    20  )
    21  
    22  func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) {
    23  	// Storing execs in container in order to kill them gracefully whenever the container is stopped or removed.
    24  	container.ExecCommands.Add(config.ID, config)
    25  	// Storing execs in daemon for easy access via remote API.
    26  	d.execCommands.Add(config.ID, config)
    27  }
    28  
    29  // ExecExists looks up the exec instance and returns a bool if it exists or not.
    30  // It will also return the error produced by `getConfig`
    31  func (d *Daemon) ExecExists(name string) (bool, error) {
    32  	if _, err := d.getExecConfig(name); err != nil {
    33  		return false, err
    34  	}
    35  	return true, nil
    36  }
    37  
    38  // getExecConfig looks up the exec instance by name. If the container associated
    39  // with the exec instance is stopped or paused, it will return an error.
    40  func (d *Daemon) getExecConfig(name string) (*exec.Config, error) {
    41  	ec := d.execCommands.Get(name)
    42  
    43  	// If the exec is found but its container is not in the daemon's list of
    44  	// containers then it must have been deleted, in which case instead of
    45  	// saying the container isn't running, we should return a 404 so that
    46  	// the user sees the same error now that they will after the
    47  	// 5 minute clean-up loop is run which erases old/dead execs.
    48  
    49  	if ec != nil {
    50  		if container := d.containers.Get(ec.ContainerID); container != nil {
    51  			if !container.IsRunning() {
    52  				return nil, fmt.Errorf("Container %s is not running: %s", container.ID, container.State.String())
    53  			}
    54  			if container.IsPaused() {
    55  				return nil, errExecPaused(container.ID)
    56  			}
    57  			if container.IsRestarting() {
    58  				return nil, errContainerIsRestarting(container.ID)
    59  			}
    60  			return ec, nil
    61  		}
    62  	}
    63  
    64  	return nil, errExecNotFound(name)
    65  }
    66  
    67  func (d *Daemon) unregisterExecCommand(container *container.Container, execConfig *exec.Config) {
    68  	container.ExecCommands.Delete(execConfig.ID)
    69  	d.execCommands.Delete(execConfig.ID)
    70  }
    71  
    72  func (d *Daemon) getActiveContainer(name string) (*container.Container, error) {
    73  	container, err := d.GetContainer(name)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	if !container.IsRunning() {
    79  		return nil, errNotRunning{container.ID}
    80  	}
    81  	if container.IsPaused() {
    82  		return nil, errExecPaused(name)
    83  	}
    84  	if container.IsRestarting() {
    85  		return nil, errContainerIsRestarting(container.ID)
    86  	}
    87  	return container, nil
    88  }
    89  
    90  // ContainerExecCreate sets up an exec in a running container.
    91  func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
    92  	container, err := d.getActiveContainer(config.Container)
    93  	if err != nil {
    94  		return "", err
    95  	}
    96  
    97  	cmd := strslice.StrSlice(config.Cmd)
    98  	entrypoint, args := d.getEntrypointAndArgs(strslice.StrSlice{}, cmd)
    99  
   100  	keys := []byte{}
   101  	if config.DetachKeys != "" {
   102  		keys, err = term.ToBytes(config.DetachKeys)
   103  		if err != nil {
   104  			logrus.Warnf("Wrong escape keys provided (%s, error: %s) using default : ctrl-p ctrl-q", config.DetachKeys, err.Error())
   105  		}
   106  	}
   107  
   108  	execConfig := exec.NewConfig()
   109  	execConfig.OpenStdin = config.AttachStdin
   110  	execConfig.OpenStdout = config.AttachStdout
   111  	execConfig.OpenStderr = config.AttachStderr
   112  	execConfig.ContainerID = container.ID
   113  	execConfig.DetachKeys = keys
   114  	execConfig.Entrypoint = entrypoint
   115  	execConfig.Args = args
   116  	execConfig.Tty = config.Tty
   117  	execConfig.Privileged = config.Privileged
   118  	execConfig.User = config.User
   119  	if len(execConfig.User) == 0 {
   120  		execConfig.User = container.Config.User
   121  	}
   122  
   123  	d.registerExecCommand(container, execConfig)
   124  
   125  	d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " "))
   126  
   127  	return execConfig.ID, nil
   128  }
   129  
   130  // ContainerExecStart starts a previously set up exec instance. The
   131  // std streams are set up.
   132  func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) (err error) {
   133  	var (
   134  		cStdin           io.ReadCloser
   135  		cStdout, cStderr io.Writer
   136  	)
   137  
   138  	ec, err := d.getExecConfig(name)
   139  	if err != nil {
   140  		return errExecNotFound(name)
   141  	}
   142  
   143  	ec.Lock()
   144  	if ec.ExitCode != nil {
   145  		ec.Unlock()
   146  		err := fmt.Errorf("Error: Exec command %s has already run", ec.ID)
   147  		return errors.NewRequestConflictError(err)
   148  	}
   149  
   150  	if ec.Running {
   151  		ec.Unlock()
   152  		return fmt.Errorf("Error: Exec command %s is already running", ec.ID)
   153  	}
   154  	ec.Running = true
   155  	defer func() {
   156  		if err != nil {
   157  			ec.Running = false
   158  			exitCode := 126
   159  			ec.ExitCode = &exitCode
   160  		}
   161  	}()
   162  	ec.Unlock()
   163  
   164  	c := d.containers.Get(ec.ContainerID)
   165  	logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID)
   166  	d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " "))
   167  
   168  	if ec.OpenStdin && stdin != nil {
   169  		r, w := io.Pipe()
   170  		go func() {
   171  			defer w.Close()
   172  			defer logrus.Debugf("Closing buffered stdin pipe")
   173  			pools.Copy(w, stdin)
   174  		}()
   175  		cStdin = r
   176  	}
   177  	if ec.OpenStdout {
   178  		cStdout = stdout
   179  	}
   180  	if ec.OpenStderr {
   181  		cStderr = stderr
   182  	}
   183  
   184  	if ec.OpenStdin {
   185  		ec.NewInputPipes()
   186  	} else {
   187  		ec.NewNopInputPipe()
   188  	}
   189  
   190  	p := libcontainerd.Process{
   191  		Args:     append([]string{ec.Entrypoint}, ec.Args...),
   192  		Terminal: ec.Tty,
   193  	}
   194  
   195  	if err := execSetPlatformOpt(c, ec, &p); err != nil {
   196  		return nil
   197  	}
   198  
   199  	attachErr := container.AttachStreams(context.Background(), ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys)
   200  
   201  	if err := d.containerd.AddProcess(c.ID, name, p); err != nil {
   202  		return err
   203  	}
   204  
   205  	err = <-attachErr
   206  	if err != nil {
   207  		return fmt.Errorf("attach failed with error: %v", err)
   208  	}
   209  	return nil
   210  }
   211  
   212  // execCommandGC runs a ticker to clean up the daemon references
   213  // of exec configs that are no longer part of the container.
   214  func (d *Daemon) execCommandGC() {
   215  	for range time.Tick(5 * time.Minute) {
   216  		var (
   217  			cleaned          int
   218  			liveExecCommands = d.containerExecIds()
   219  		)
   220  		for id, config := range d.execCommands.Commands() {
   221  			if config.CanRemove {
   222  				cleaned++
   223  				d.execCommands.Delete(id)
   224  			} else {
   225  				if _, exists := liveExecCommands[id]; !exists {
   226  					config.CanRemove = true
   227  				}
   228  			}
   229  		}
   230  		if cleaned > 0 {
   231  			logrus.Debugf("clean %d unused exec commands", cleaned)
   232  		}
   233  	}
   234  }
   235  
   236  // containerExecIds returns a list of all the current exec ids that are in use
   237  // and running inside a container.
   238  func (d *Daemon) containerExecIds() map[string]struct{} {
   239  	ids := map[string]struct{}{}
   240  	for _, c := range d.containers.List() {
   241  		for _, id := range c.ExecCommands.List() {
   242  			ids[id] = struct{}{}
   243  		}
   244  	}
   245  	return ids
   246  }