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  }