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