github.com/amylindburg/docker@v1.7.0/daemon/exec.go (about)

     1  package daemon
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/Sirupsen/logrus"
    11  	"github.com/docker/docker/daemon/execdriver"
    12  	"github.com/docker/docker/pkg/broadcastwriter"
    13  	"github.com/docker/docker/pkg/ioutils"
    14  	"github.com/docker/docker/pkg/stringid"
    15  	"github.com/docker/docker/runconfig"
    16  )
    17  
    18  type execConfig struct {
    19  	sync.Mutex
    20  	ID            string
    21  	Running       bool
    22  	ExitCode      int
    23  	ProcessConfig execdriver.ProcessConfig
    24  	StreamConfig
    25  	OpenStdin  bool
    26  	OpenStderr bool
    27  	OpenStdout bool
    28  	Container  *Container
    29  }
    30  
    31  type execStore struct {
    32  	s map[string]*execConfig
    33  	sync.RWMutex
    34  }
    35  
    36  func newExecStore() *execStore {
    37  	return &execStore{s: make(map[string]*execConfig, 0)}
    38  }
    39  
    40  func (e *execStore) Add(id string, execConfig *execConfig) {
    41  	e.Lock()
    42  	e.s[id] = execConfig
    43  	e.Unlock()
    44  }
    45  
    46  func (e *execStore) Get(id string) *execConfig {
    47  	e.RLock()
    48  	res := e.s[id]
    49  	e.RUnlock()
    50  	return res
    51  }
    52  
    53  func (e *execStore) Delete(id string) {
    54  	e.Lock()
    55  	delete(e.s, id)
    56  	e.Unlock()
    57  }
    58  
    59  func (e *execStore) List() []string {
    60  	var IDs []string
    61  	e.RLock()
    62  	for id := range e.s {
    63  		IDs = append(IDs, id)
    64  	}
    65  	e.RUnlock()
    66  	return IDs
    67  }
    68  
    69  func (execConfig *execConfig) Resize(h, w int) error {
    70  	return execConfig.ProcessConfig.Terminal.Resize(h, w)
    71  }
    72  
    73  func (d *Daemon) registerExecCommand(execConfig *execConfig) {
    74  	// Storing execs in container in order to kill them gracefully whenever the container is stopped or removed.
    75  	execConfig.Container.execCommands.Add(execConfig.ID, execConfig)
    76  	// Storing execs in daemon for easy access via remote API.
    77  	d.execCommands.Add(execConfig.ID, execConfig)
    78  }
    79  
    80  func (d *Daemon) getExecConfig(name string) (*execConfig, error) {
    81  	if execConfig := d.execCommands.Get(name); execConfig != nil {
    82  		if !execConfig.Container.IsRunning() {
    83  			return nil, fmt.Errorf("Container %s is not running", execConfig.Container.ID)
    84  		}
    85  		return execConfig, nil
    86  	}
    87  
    88  	return nil, fmt.Errorf("No such exec instance '%s' found in daemon", name)
    89  }
    90  
    91  func (d *Daemon) unregisterExecCommand(execConfig *execConfig) {
    92  	execConfig.Container.execCommands.Delete(execConfig.ID)
    93  	d.execCommands.Delete(execConfig.ID)
    94  }
    95  
    96  func (d *Daemon) getActiveContainer(name string) (*Container, error) {
    97  	container, err := d.Get(name)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	if !container.IsRunning() {
   103  		return nil, fmt.Errorf("Container %s is not running", name)
   104  	}
   105  	if container.IsPaused() {
   106  		return nil, fmt.Errorf("Container %s is paused, unpause the container before exec", name)
   107  	}
   108  	return container, nil
   109  }
   110  
   111  func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, error) {
   112  
   113  	// Not all drivers support Exec (LXC for example)
   114  	if err := checkExecSupport(d.execDriver.Name()); err != nil {
   115  		return "", err
   116  	}
   117  
   118  	container, err := d.getActiveContainer(config.Container)
   119  	if err != nil {
   120  		return "", err
   121  	}
   122  
   123  	cmd := runconfig.NewCommand(config.Cmd...)
   124  	entrypoint, args := d.getEntrypointAndArgs(runconfig.NewEntrypoint(), cmd)
   125  
   126  	processConfig := execdriver.ProcessConfig{
   127  		Tty:        config.Tty,
   128  		Entrypoint: entrypoint,
   129  		Arguments:  args,
   130  		User:       config.User,
   131  	}
   132  
   133  	execConfig := &execConfig{
   134  		ID:            stringid.GenerateRandomID(),
   135  		OpenStdin:     config.AttachStdin,
   136  		OpenStdout:    config.AttachStdout,
   137  		OpenStderr:    config.AttachStderr,
   138  		StreamConfig:  StreamConfig{},
   139  		ProcessConfig: processConfig,
   140  		Container:     container,
   141  		Running:       false,
   142  	}
   143  
   144  	container.LogEvent("exec_create: " + execConfig.ProcessConfig.Entrypoint + " " + strings.Join(execConfig.ProcessConfig.Arguments, " "))
   145  
   146  	d.registerExecCommand(execConfig)
   147  
   148  	return execConfig.ID, nil
   149  
   150  }
   151  
   152  func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error {
   153  
   154  	var (
   155  		cStdin           io.ReadCloser
   156  		cStdout, cStderr io.Writer
   157  	)
   158  
   159  	execConfig, err := d.getExecConfig(execName)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	func() {
   165  		execConfig.Lock()
   166  		defer execConfig.Unlock()
   167  		if execConfig.Running {
   168  			err = fmt.Errorf("Error: Exec command %s is already running", execName)
   169  		}
   170  		execConfig.Running = true
   171  	}()
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	logrus.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID)
   177  	container := execConfig.Container
   178  
   179  	container.LogEvent("exec_start: " + execConfig.ProcessConfig.Entrypoint + " " + strings.Join(execConfig.ProcessConfig.Arguments, " "))
   180  
   181  	if execConfig.OpenStdin {
   182  		r, w := io.Pipe()
   183  		go func() {
   184  			defer w.Close()
   185  			defer logrus.Debugf("Closing buffered stdin pipe")
   186  			io.Copy(w, stdin)
   187  		}()
   188  		cStdin = r
   189  	}
   190  	if execConfig.OpenStdout {
   191  		cStdout = stdout
   192  	}
   193  	if execConfig.OpenStderr {
   194  		cStderr = stderr
   195  	}
   196  
   197  	execConfig.StreamConfig.stderr = broadcastwriter.New()
   198  	execConfig.StreamConfig.stdout = broadcastwriter.New()
   199  	// Attach to stdin
   200  	if execConfig.OpenStdin {
   201  		execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdinPipe = io.Pipe()
   202  	} else {
   203  		execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
   204  	}
   205  
   206  	attachErr := attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr)
   207  
   208  	execErr := make(chan error)
   209  
   210  	// Note, the execConfig data will be removed when the container
   211  	// itself is deleted.  This allows us to query it (for things like
   212  	// the exitStatus) even after the cmd is done running.
   213  
   214  	go func() {
   215  		if err := container.Exec(execConfig); err != nil {
   216  			execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err)
   217  		}
   218  	}()
   219  
   220  	select {
   221  	case err := <-attachErr:
   222  		if err != nil {
   223  			return fmt.Errorf("attach failed with error: %s", err)
   224  		}
   225  		break
   226  	case err := <-execErr:
   227  		return err
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
   234  	exitStatus, err := d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
   235  
   236  	// On err, make sure we don't leave ExitCode at zero
   237  	if err != nil && exitStatus == 0 {
   238  		exitStatus = 128
   239  	}
   240  
   241  	execConfig.ExitCode = exitStatus
   242  	execConfig.Running = false
   243  
   244  	return exitStatus, err
   245  }