github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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  			err = fmt.Errorf("Invalid escape keys (%s) provided", config.DetachKeys)
   105  			return "", err
   106  		}
   107  	}
   108  
   109  	execConfig := exec.NewConfig()
   110  	execConfig.OpenStdin = config.AttachStdin
   111  	execConfig.OpenStdout = config.AttachStdout
   112  	execConfig.OpenStderr = config.AttachStderr
   113  	execConfig.ContainerID = container.ID
   114  	execConfig.DetachKeys = keys
   115  	execConfig.Entrypoint = entrypoint
   116  	execConfig.Args = args
   117  	execConfig.Tty = config.Tty
   118  	execConfig.Privileged = config.Privileged
   119  	execConfig.User = config.User
   120  	if len(execConfig.User) == 0 {
   121  		execConfig.User = container.Config.User
   122  	}
   123  
   124  	d.registerExecCommand(container, execConfig)
   125  
   126  	d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " "))
   127  
   128  	return execConfig.ID, nil
   129  }
   130  
   131  // ContainerExecStart starts a previously set up exec instance. The
   132  // std streams are set up.
   133  func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) (err error) {
   134  	var (
   135  		cStdin           io.ReadCloser
   136  		cStdout, cStderr io.Writer
   137  	)
   138  
   139  	ec, err := d.getExecConfig(name)
   140  	if err != nil {
   141  		return errExecNotFound(name)
   142  	}
   143  
   144  	ec.Lock()
   145  	if ec.ExitCode != nil {
   146  		ec.Unlock()
   147  		err := fmt.Errorf("Error: Exec command %s has already run", ec.ID)
   148  		return errors.NewRequestConflictError(err)
   149  	}
   150  
   151  	if ec.Running {
   152  		ec.Unlock()
   153  		return fmt.Errorf("Error: Exec command %s is already running", ec.ID)
   154  	}
   155  	ec.Running = true
   156  	defer func() {
   157  		if err != nil {
   158  			ec.Running = false
   159  			exitCode := 126
   160  			ec.ExitCode = &exitCode
   161  		}
   162  	}()
   163  	ec.Unlock()
   164  
   165  	c := d.containers.Get(ec.ContainerID)
   166  	logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID)
   167  	d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " "))
   168  
   169  	if ec.OpenStdin && stdin != nil {
   170  		r, w := io.Pipe()
   171  		go func() {
   172  			defer w.Close()
   173  			defer logrus.Debugf("Closing buffered stdin pipe")
   174  			pools.Copy(w, stdin)
   175  		}()
   176  		cStdin = r
   177  	}
   178  	if ec.OpenStdout {
   179  		cStdout = stdout
   180  	}
   181  	if ec.OpenStderr {
   182  		cStderr = stderr
   183  	}
   184  
   185  	if ec.OpenStdin {
   186  		ec.NewInputPipes()
   187  	} else {
   188  		ec.NewNopInputPipe()
   189  	}
   190  
   191  	p := libcontainerd.Process{
   192  		Args:     append([]string{ec.Entrypoint}, ec.Args...),
   193  		Terminal: ec.Tty,
   194  	}
   195  
   196  	if err := execSetPlatformOpt(c, ec, &p); err != nil {
   197  		return nil
   198  	}
   199  
   200  	attachErr := container.AttachStreams(context.Background(), ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys)
   201  
   202  	if err := d.containerd.AddProcess(c.ID, name, p); err != nil {
   203  		return err
   204  	}
   205  
   206  	err = <-attachErr
   207  	if err != nil {
   208  		return fmt.Errorf("attach failed with error: %v", err)
   209  	}
   210  	return nil
   211  }
   212  
   213  // execCommandGC runs a ticker to clean up the daemon references
   214  // of exec configs that are no longer part of the container.
   215  func (d *Daemon) execCommandGC() {
   216  	for range time.Tick(5 * time.Minute) {
   217  		var (
   218  			cleaned          int
   219  			liveExecCommands = d.containerExecIds()
   220  		)
   221  		for id, config := range d.execCommands.Commands() {
   222  			if config.CanRemove {
   223  				cleaned++
   224  				d.execCommands.Delete(id)
   225  			} else {
   226  				if _, exists := liveExecCommands[id]; !exists {
   227  					config.CanRemove = true
   228  				}
   229  			}
   230  		}
   231  		if cleaned > 0 {
   232  			logrus.Debugf("clean %d unused exec commands", cleaned)
   233  		}
   234  	}
   235  }
   236  
   237  // containerExecIds returns a list of all the current exec ids that are in use
   238  // and running inside a container.
   239  func (d *Daemon) containerExecIds() map[string]struct{} {
   240  	ids := map[string]struct{}{}
   241  	for _, c := range d.containers.List() {
   242  		for _, id := range c.ExecCommands.List() {
   243  			ids[id] = struct{}{}
   244  		}
   245  	}
   246  	return ids
   247  }