github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/container_exec.go (about)

     1  package libpod
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/containers/common/pkg/capabilities"
    11  	"github.com/containers/libpod/libpod/define"
    12  	"github.com/containers/libpod/libpod/events"
    13  	"github.com/containers/storage/pkg/stringid"
    14  	"github.com/pkg/errors"
    15  	"github.com/sirupsen/logrus"
    16  	"k8s.io/client-go/tools/remotecommand"
    17  )
    18  
    19  // ExecConfig contains the configuration of an exec session
    20  type ExecConfig struct {
    21  	// Command the the command that will be invoked in the exec session.
    22  	// Must not be empty.
    23  	Command []string `json:"command"`
    24  	// Terminal is whether the exec session will allocate a pseudoterminal.
    25  	Terminal bool `json:"terminal,omitempty"`
    26  	// AttachStdin is whether the STDIN stream will be forwarded to the exec
    27  	// session's first process when attaching. Only available if Terminal is
    28  	// false.
    29  	AttachStdin bool `json:"attachStdin,omitempty"`
    30  	// AttachStdout is whether the STDOUT stream will be forwarded to the
    31  	// exec session's first process when attaching. Only available if
    32  	// Terminal is false.
    33  	AttachStdout bool `json:"attachStdout,omitempty"`
    34  	// AttachStderr is whether the STDERR stream will be forwarded to the
    35  	// exec session's first process when attaching. Only available if
    36  	// Terminal is false.
    37  	AttachStderr bool `json:"attachStderr,omitempty"`
    38  	// DetachKeys are keys that will be used to detach from the exec
    39  	// session. Here, nil will use the default detach keys, where a pointer
    40  	// to the empty string ("") will disable detaching via detach keys.
    41  	DetachKeys *string `json:"detachKeys,omitempty"`
    42  	// Environment is a set of environment variables that will be set for
    43  	// the first process started by the exec session.
    44  	Environment map[string]string `json:"environment,omitempty"`
    45  	// Privileged is whether the exec session will be privileged - that is,
    46  	// will be granted additional capabilities.
    47  	Privileged bool `json:"privileged,omitempty"`
    48  	// User is the user the exec session will be run as.
    49  	// If set to "" the exec session will be started as the same user the
    50  	// container was started as.
    51  	User string `json:"user,omitempty"`
    52  	// WorkDir is the working directory for the first process that will be
    53  	// launched by the exec session.
    54  	// If set to "" the exec session will be started in / within the
    55  	// container.
    56  	WorkDir string `json:"workDir,omitempty"`
    57  	// PreserveFDs indicates that a number of extra FDs from the process
    58  	// running libpod will be passed into the container. These are assumed
    59  	// to begin at 3 (immediately after the standard streams). The number
    60  	// given is the number that will be passed into the exec session,
    61  	// starting at 3.
    62  	PreserveFDs uint `json:"preserveFds,omitempty"`
    63  }
    64  
    65  // ExecSession contains information on a single exec session attached to a given
    66  // container.
    67  type ExecSession struct {
    68  	// Id is the ID of the exec session.
    69  	// Named somewhat strangely to not conflict with ID().
    70  	Id string `json:"id"`
    71  	// ContainerId is the ID of the container this exec session belongs to.
    72  	// Named somewhat strangely to not conflict with ContainerID().
    73  	ContainerId string `json:"containerId"`
    74  
    75  	// State is the state of the exec session.
    76  	State define.ContainerExecStatus `json:"state"`
    77  	// PID is the PID of the process created by the exec session.
    78  	PID int `json:"pid,omitempty"`
    79  	// ExitCode is the exit code of the exec session, if it has exited.
    80  	ExitCode int `json:"exitCode,omitempty"`
    81  
    82  	// Config is the configuration of this exec session.
    83  	// Cannot be empty.
    84  	Config *ExecConfig `json:"config"`
    85  }
    86  
    87  // ID returns the ID of an exec session.
    88  func (e *ExecSession) ID() string {
    89  	return e.Id
    90  }
    91  
    92  // ContainerID returns the ID of the container this exec session was started in.
    93  func (e *ExecSession) ContainerID() string {
    94  	return e.ContainerId
    95  }
    96  
    97  // Inspect inspects the given exec session and produces detailed output on its
    98  // configuration and current state.
    99  func (e *ExecSession) Inspect() (*define.InspectExecSession, error) {
   100  	if e.Config == nil {
   101  		return nil, errors.Wrapf(define.ErrInternal, "given exec session does not have a configuration block")
   102  	}
   103  
   104  	output := new(define.InspectExecSession)
   105  	output.CanRemove = e.State != define.ExecStateRunning
   106  	output.ContainerID = e.ContainerId
   107  	if e.Config.DetachKeys != nil {
   108  		output.DetachKeys = *e.Config.DetachKeys
   109  	}
   110  	output.ExitCode = e.ExitCode
   111  	output.ID = e.Id
   112  	output.OpenStderr = e.Config.AttachStderr
   113  	output.OpenStdin = e.Config.AttachStdin
   114  	output.OpenStdout = e.Config.AttachStdout
   115  	output.Running = e.State == define.ExecStateRunning
   116  	output.Pid = e.PID
   117  	output.ProcessConfig = new(define.InspectExecProcess)
   118  	if len(e.Config.Command) > 0 {
   119  		output.ProcessConfig.Entrypoint = e.Config.Command[0]
   120  		if len(e.Config.Command) > 1 {
   121  			output.ProcessConfig.Arguments = make([]string, 0, len(e.Config.Command)-1)
   122  			output.ProcessConfig.Arguments = append(output.ProcessConfig.Arguments, e.Config.Command[1:]...)
   123  		}
   124  	}
   125  	output.ProcessConfig.Privileged = e.Config.Privileged
   126  	output.ProcessConfig.Tty = e.Config.Terminal
   127  	output.ProcessConfig.User = e.Config.User
   128  
   129  	return output, nil
   130  }
   131  
   132  // legacyExecSession contains information on an active exec session. It is a
   133  // holdover from a previous Podman version and is DEPRECATED.
   134  type legacyExecSession struct {
   135  	ID      string   `json:"id"`
   136  	Command []string `json:"command"`
   137  	PID     int      `json:"pid"`
   138  }
   139  
   140  // ExecCreate creates a new exec session for the container.
   141  // The session is not started. The ID of the new exec session will be returned.
   142  func (c *Container) ExecCreate(config *ExecConfig) (string, error) {
   143  	if !c.batched {
   144  		c.lock.Lock()
   145  		defer c.lock.Unlock()
   146  
   147  		if err := c.syncContainer(); err != nil {
   148  			return "", err
   149  		}
   150  	}
   151  
   152  	// Verify our config
   153  	if config == nil {
   154  		return "", errors.Wrapf(define.ErrInvalidArg, "must provide a configuration to ExecCreate")
   155  	}
   156  	if len(config.Command) == 0 {
   157  		return "", errors.Wrapf(define.ErrInvalidArg, "must provide a non-empty command to start an exec session")
   158  	}
   159  	if config.Terminal && (config.AttachStdin || config.AttachStdout || config.AttachStderr) {
   160  		return "", errors.Wrapf(define.ErrInvalidArg, "cannot specify streams to attach to when exec session has a pseudoterminal")
   161  	}
   162  
   163  	// Verify that we are in a good state to continue
   164  	if !c.ensureState(define.ContainerStateRunning) {
   165  		return "", errors.Wrapf(define.ErrCtrStateInvalid, "can only create exec sessions on running containers")
   166  	}
   167  
   168  	// Generate an ID for our new exec session
   169  	sessionID := stringid.GenerateNonCryptoID()
   170  	found := true
   171  	// This really ought to be a do-while, but Go doesn't have those...
   172  	for found {
   173  		found = false
   174  		for id := range c.state.ExecSessions {
   175  			if id == sessionID {
   176  				found = true
   177  				break
   178  			}
   179  		}
   180  		if found {
   181  			sessionID = stringid.GenerateNonCryptoID()
   182  		}
   183  	}
   184  
   185  	// Make our new exec session
   186  	session := new(ExecSession)
   187  	session.Id = sessionID
   188  	session.ContainerId = c.ID()
   189  	session.State = define.ExecStateCreated
   190  	session.Config = new(ExecConfig)
   191  	if err := JSONDeepCopy(config, session.Config); err != nil {
   192  		return "", errors.Wrapf(err, "error copying exec configuration into exec session")
   193  	}
   194  
   195  	if c.state.ExecSessions == nil {
   196  		c.state.ExecSessions = make(map[string]*ExecSession)
   197  	}
   198  
   199  	// Need to add to container state and exec session registry
   200  	c.state.ExecSessions[session.ID()] = session
   201  	if err := c.save(); err != nil {
   202  		return "", err
   203  	}
   204  	if err := c.runtime.state.AddExecSession(c, session); err != nil {
   205  		return "", err
   206  	}
   207  
   208  	logrus.Infof("Created exec session %s in container %s", session.ID(), c.ID())
   209  
   210  	return sessionID, nil
   211  }
   212  
   213  // ExecStart starts an exec session in the container, but does not attach to it.
   214  // Returns immediately upon starting the exec session.
   215  func (c *Container) ExecStart(sessionID string) error {
   216  	// Will be implemented in part 2, migrating Start and implementing
   217  	// detached Start.
   218  	return define.ErrNotImplemented
   219  }
   220  
   221  // ExecStartAndAttach starts and attaches to an exec session in a container.
   222  // TODO: Should we include detach keys in the signature to allow override?
   223  // TODO: How do we handle AttachStdin/AttachStdout/AttachStderr?
   224  func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams) error {
   225  	if !c.batched {
   226  		c.lock.Lock()
   227  		defer c.lock.Unlock()
   228  
   229  		if err := c.syncContainer(); err != nil {
   230  			return err
   231  		}
   232  	}
   233  
   234  	// Verify that we are in a good state to continue
   235  	if !c.ensureState(define.ContainerStateRunning) {
   236  		return errors.Wrapf(define.ErrCtrStateInvalid, "can only start exec sessions when their container is running")
   237  	}
   238  
   239  	session, ok := c.state.ExecSessions[sessionID]
   240  	if !ok {
   241  		return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID)
   242  	}
   243  
   244  	if session.State != define.ExecStateCreated {
   245  		return errors.Wrapf(define.ErrExecSessionStateInvalid, "can only start created exec sessions, while container %s session %s state is %q", c.ID(), session.ID(), session.State.String())
   246  	}
   247  
   248  	logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID())
   249  
   250  	// TODO: check logic here - should we set Privileged if the container is
   251  	// privileged?
   252  	var capList []string
   253  	if session.Config.Privileged || c.config.Privileged {
   254  		capList = capabilities.AllCapabilities()
   255  	}
   256  
   257  	user := c.config.User
   258  	if session.Config.User != "" {
   259  		user = session.Config.User
   260  	}
   261  
   262  	if err := c.createExecBundle(session.ID()); err != nil {
   263  		return err
   264  	}
   265  
   266  	opts := new(ExecOptions)
   267  	opts.Cmd = session.Config.Command
   268  	opts.CapAdd = capList
   269  	opts.Env = session.Config.Environment
   270  	opts.Terminal = session.Config.Terminal
   271  	opts.Cwd = session.Config.WorkDir
   272  	opts.User = user
   273  	opts.Streams = streams
   274  	opts.PreserveFDs = session.Config.PreserveFDs
   275  	opts.DetachKeys = session.Config.DetachKeys
   276  
   277  	pid, attachChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts)
   278  	if err != nil {
   279  		return err
   280  	}
   281  
   282  	c.newContainerEvent(events.Exec)
   283  	logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())
   284  
   285  	var lastErr error
   286  
   287  	// Update and save session to reflect PID/running
   288  	session.PID = pid
   289  	session.State = define.ExecStateRunning
   290  
   291  	if err := c.save(); err != nil {
   292  		lastErr = err
   293  	}
   294  
   295  	// Unlock so other processes can use the container
   296  	if !c.batched {
   297  		c.lock.Unlock()
   298  	}
   299  
   300  	tmpErr := <-attachChan
   301  	if lastErr != nil {
   302  		logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
   303  	}
   304  	lastErr = tmpErr
   305  
   306  	exitCode, err := c.readExecExitCode(session.ID())
   307  	if err != nil {
   308  		if lastErr != nil {
   309  			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
   310  		}
   311  		lastErr = err
   312  	}
   313  
   314  	logrus.Debugf("Container %s exec session %s completed with exit code %d", c.ID(), session.ID(), exitCode)
   315  
   316  	// Lock again
   317  	if !c.batched {
   318  		c.lock.Lock()
   319  	}
   320  
   321  	// Sync the container to pick up state changes
   322  	if err := c.syncContainer(); err != nil {
   323  		if lastErr != nil {
   324  			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
   325  		}
   326  		return errors.Wrapf(err, "error syncing container %s state to remove exec session %s", c.ID(), session.ID())
   327  	}
   328  
   329  	// Update status
   330  	// Since we did a syncContainer, the old session has been overwritten.
   331  	// Grab a fresh one from the database.
   332  	session, ok = c.state.ExecSessions[sessionID]
   333  	if !ok {
   334  		// Exec session already removed.
   335  		logrus.Infof("Container %s exec session %s already removed from database", c.ID(), sessionID)
   336  		return nil
   337  	}
   338  	session.State = define.ExecStateStopped
   339  	session.ExitCode = exitCode
   340  	session.PID = 0
   341  
   342  	if err := c.save(); err != nil {
   343  		if lastErr != nil {
   344  			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
   345  		}
   346  		lastErr = err
   347  	}
   348  
   349  	// Clean up after ourselves
   350  	if err := c.cleanupExecBundle(session.ID()); err != nil {
   351  		if lastErr != nil {
   352  			logrus.Errorf("Container %s exec session %s error: %v", c.ID(), session.ID(), lastErr)
   353  		}
   354  		lastErr = err
   355  	}
   356  
   357  	return lastErr
   358  }
   359  
   360  // ExecHTTPStartAndAttach starts and performs an HTTP attach to an exec session.
   361  func (c *Container) ExecHTTPStartAndAttach(sessionID string) error {
   362  	// Will be implemented in part 2, migrating Start.
   363  	return define.ErrNotImplemented
   364  }
   365  
   366  // ExecStop stops an exec session in the container.
   367  // If a timeout is provided, it will be used; otherwise, the timeout will
   368  // default to the stop timeout of the container.
   369  // Cleanup will be invoked automatically once the session is stopped.
   370  func (c *Container) ExecStop(sessionID string, timeout *uint) error {
   371  	if !c.batched {
   372  		c.lock.Lock()
   373  		defer c.lock.Unlock()
   374  
   375  		if err := c.syncContainer(); err != nil {
   376  			return err
   377  		}
   378  	}
   379  
   380  	session, ok := c.state.ExecSessions[sessionID]
   381  	if !ok {
   382  		return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID)
   383  	}
   384  
   385  	if session.State != define.ExecStateRunning {
   386  		return errors.Wrapf(define.ErrExecSessionStateInvalid, "container %s exec session %s is %q, can only stop running sessions", c.ID(), session.ID(), session.State.String())
   387  	}
   388  
   389  	logrus.Infof("Stopping container %s exec session %s", c.ID(), session.ID())
   390  
   391  	finalTimeout := c.StopTimeout()
   392  	if timeout != nil {
   393  		finalTimeout = *timeout
   394  	}
   395  
   396  	// Stop the session
   397  	if err := c.ociRuntime.ExecStopContainer(c, session.ID(), finalTimeout); err != nil {
   398  		return err
   399  	}
   400  
   401  	var cleanupErr error
   402  
   403  	// Retrieve exit code and update status
   404  	exitCode, err := c.readExecExitCode(session.ID())
   405  	if err != nil {
   406  		cleanupErr = err
   407  	}
   408  	session.ExitCode = exitCode
   409  	session.PID = 0
   410  	session.State = define.ExecStateStopped
   411  
   412  	if err := c.save(); err != nil {
   413  		if cleanupErr != nil {
   414  			logrus.Errorf("Error stopping container %s exec session %s: %v", c.ID(), session.ID(), cleanupErr)
   415  		}
   416  		cleanupErr = err
   417  	}
   418  
   419  	if err := c.cleanupExecBundle(session.ID()); err != nil {
   420  		if cleanupErr != nil {
   421  			logrus.Errorf("Error stopping container %s exec session %s: %v", c.ID(), session.ID(), cleanupErr)
   422  		}
   423  		cleanupErr = err
   424  	}
   425  
   426  	return cleanupErr
   427  }
   428  
   429  // ExecCleanup cleans up an exec session in the container, removing temporary
   430  // files associated with it.
   431  func (c *Container) ExecCleanup(sessionID string) error {
   432  	if !c.batched {
   433  		c.lock.Lock()
   434  		defer c.lock.Unlock()
   435  
   436  		if err := c.syncContainer(); err != nil {
   437  			return err
   438  		}
   439  	}
   440  
   441  	session, ok := c.state.ExecSessions[sessionID]
   442  	if !ok {
   443  		return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID)
   444  	}
   445  
   446  	if session.State == define.ExecStateRunning {
   447  		return errors.Wrapf(define.ErrExecSessionStateInvalid, "cannot clean up container %s exec session %s as it is running", c.ID(), session.ID())
   448  	}
   449  
   450  	logrus.Infof("Cleaning up container %s exec session %s", c.ID(), session.ID())
   451  
   452  	return c.cleanupExecBundle(session.ID())
   453  }
   454  
   455  // ExecRemove removes an exec session in the container.
   456  // If force is given, the session will be stopped first if it is running.
   457  func (c *Container) ExecRemove(sessionID string, force bool) error {
   458  	if !c.batched {
   459  		c.lock.Lock()
   460  		defer c.lock.Unlock()
   461  
   462  		if err := c.syncContainer(); err != nil {
   463  			return err
   464  		}
   465  	}
   466  
   467  	session, ok := c.state.ExecSessions[sessionID]
   468  	if !ok {
   469  		return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID)
   470  	}
   471  
   472  	logrus.Infof("Removing container %s exec session %s", c.ID(), session.ID())
   473  
   474  	// Update status of exec session if running, so we cna check if it
   475  	// stopped in the meantime.
   476  	if session.State == define.ExecStateRunning {
   477  		stopped, err := c.ociRuntime.ExecUpdateStatus(c, session.ID())
   478  		if err != nil {
   479  			return err
   480  		}
   481  		if stopped {
   482  			session.State = define.ExecStateStopped
   483  			// TODO: should we retrieve exit code here?
   484  			// TODO: Might be worth saving state here.
   485  		}
   486  	}
   487  
   488  	if session.State == define.ExecStateRunning {
   489  		if !force {
   490  			return errors.Wrapf(define.ErrExecSessionStateInvalid, "container %s exec session %s is still running, cannot remove", c.ID(), session.ID())
   491  		}
   492  
   493  		// Stop the session
   494  		if err := c.ociRuntime.ExecStopContainer(c, session.ID(), c.StopTimeout()); err != nil {
   495  			return err
   496  		}
   497  
   498  		if err := c.cleanupExecBundle(session.ID()); err != nil {
   499  			return err
   500  		}
   501  	}
   502  
   503  	// First remove exec session from DB.
   504  	if err := c.runtime.state.RemoveExecSession(session); err != nil {
   505  		return err
   506  	}
   507  	// Next, remove it from the container and save state
   508  	delete(c.state.ExecSessions, sessionID)
   509  	if err := c.save(); err != nil {
   510  		return err
   511  	}
   512  
   513  	logrus.Debugf("Successfully removed container %s exec session %s", c.ID(), session.ID())
   514  
   515  	return nil
   516  }
   517  
   518  // ExecResize resizes the TTY of the given exec session. Only available if the
   519  // exec session created a TTY.
   520  func (c *Container) ExecResize(sessionID string, newSize remotecommand.TerminalSize) error {
   521  	if !c.batched {
   522  		c.lock.Lock()
   523  		defer c.lock.Unlock()
   524  
   525  		if err := c.syncContainer(); err != nil {
   526  			return err
   527  		}
   528  	}
   529  
   530  	session, ok := c.state.ExecSessions[sessionID]
   531  	if !ok {
   532  		return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID)
   533  	}
   534  
   535  	logrus.Infof("Resizing container %s exec session %s to %+v", c.ID(), session.ID(), newSize)
   536  
   537  	if session.State != define.ExecStateRunning {
   538  		return errors.Wrapf(define.ErrExecSessionStateInvalid, "cannot resize container %s exec session %s as it is not running", c.ID(), session.ID())
   539  	}
   540  
   541  	return c.ociRuntime.ExecAttachResize(c, sessionID, newSize)
   542  }
   543  
   544  // Exec emulates the old Libpod exec API, providing a single call to create,
   545  // run, and remove an exec session. Returns exit code and error. Exit code is
   546  // not guaranteed to be set sanely if error is not nil.
   547  func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan remotecommand.TerminalSize) (int, error) {
   548  	sessionID, err := c.ExecCreate(config)
   549  	if err != nil {
   550  		return -1, err
   551  	}
   552  
   553  	// Start resizing if we have a resize channel.
   554  	// This goroutine may likely leak, given that we cannot close it here.
   555  	// Not a big deal, since it should run for as long as the Podman process
   556  	// does. Could be a big deal for `podman service` but we don't need this
   557  	// API there.
   558  	// TODO: Refactor so this is closed here, before we remove the exec
   559  	// session.
   560  	if resize != nil {
   561  		go func() {
   562  			logrus.Debugf("Sending resize events to exec session %s", sessionID)
   563  			for resizeRequest := range resize {
   564  				if err := c.ExecResize(sessionID, resizeRequest); err != nil {
   565  					// Assume the exec session went down.
   566  					logrus.Warnf("Error resizing exec session %s: %v", sessionID, err)
   567  					return
   568  				}
   569  			}
   570  		}()
   571  	}
   572  
   573  	if err := c.ExecStartAndAttach(sessionID, streams); err != nil {
   574  		return -1, err
   575  	}
   576  
   577  	session, err := c.ExecSession(sessionID)
   578  	if err != nil {
   579  		return -1, err
   580  	}
   581  	exitCode := session.ExitCode
   582  	if err := c.ExecRemove(sessionID, false); err != nil {
   583  		return -1, err
   584  	}
   585  
   586  	if exitCode != 0 {
   587  		return exitCode, errors.Wrapf(define.ErrOCIRuntime, "exec session exited with non-zero exit code %d", exitCode)
   588  	}
   589  
   590  	return exitCode, nil
   591  }
   592  
   593  // cleanup an exec session after its done
   594  func (c *Container) cleanupExecBundle(sessionID string) error {
   595  	if err := os.RemoveAll(c.execBundlePath(sessionID)); err != nil && !os.IsNotExist(err) {
   596  		return err
   597  	}
   598  
   599  	return c.ociRuntime.ExecContainerCleanup(c, sessionID)
   600  }
   601  
   602  // the path to a containers exec session bundle
   603  func (c *Container) execBundlePath(sessionID string) string {
   604  	return filepath.Join(c.bundlePath(), sessionID)
   605  }
   606  
   607  // Get PID file path for a container's exec session
   608  func (c *Container) execPidPath(sessionID string) string {
   609  	return filepath.Join(c.execBundlePath(sessionID), "exec_pid")
   610  }
   611  
   612  // the log path for an exec session
   613  func (c *Container) execLogPath(sessionID string) string {
   614  	return filepath.Join(c.execBundlePath(sessionID), "exec_log")
   615  }
   616  
   617  // the socket conmon creates for an exec session
   618  func (c *Container) execAttachSocketPath(sessionID string) (string, error) {
   619  	return c.ociRuntime.ExecAttachSocketPath(c, sessionID)
   620  }
   621  
   622  // execExitFileDir gets the path to the container's exit file
   623  func (c *Container) execExitFileDir(sessionID string) string {
   624  	return filepath.Join(c.execBundlePath(sessionID), "exit")
   625  }
   626  
   627  // execOCILog returns the file path for the exec sessions oci log
   628  func (c *Container) execOCILog(sessionID string) string {
   629  	if !c.ociRuntime.SupportsJSONErrors() {
   630  		return ""
   631  	}
   632  	return filepath.Join(c.execBundlePath(sessionID), "oci-log")
   633  }
   634  
   635  // create a bundle path and associated files for an exec session
   636  func (c *Container) createExecBundle(sessionID string) (err error) {
   637  	bundlePath := c.execBundlePath(sessionID)
   638  	if createErr := os.MkdirAll(bundlePath, execDirPermission); createErr != nil {
   639  		return createErr
   640  	}
   641  	defer func() {
   642  		if err != nil {
   643  			if err2 := os.RemoveAll(bundlePath); err != nil {
   644  				logrus.Warnf("error removing exec bundle after creation caused another error: %v", err2)
   645  			}
   646  		}
   647  	}()
   648  	if err2 := os.MkdirAll(c.execExitFileDir(sessionID), execDirPermission); err2 != nil {
   649  		// The directory is allowed to exist
   650  		if !os.IsExist(err2) {
   651  			err = errors.Wrapf(err2, "error creating OCI runtime exit file path %s", c.execExitFileDir(sessionID))
   652  		}
   653  	}
   654  	return
   655  }
   656  
   657  // readExecExitCode reads the exit file for an exec session and returns
   658  // the exit code
   659  func (c *Container) readExecExitCode(sessionID string) (int, error) {
   660  	exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID())
   661  	chWait := make(chan error)
   662  	defer close(chWait)
   663  
   664  	_, err := WaitForFile(exitFile, chWait, time.Second*5)
   665  	if err != nil {
   666  		return -1, err
   667  	}
   668  	ec, err := ioutil.ReadFile(exitFile)
   669  	if err != nil {
   670  		return -1, err
   671  	}
   672  	ecInt, err := strconv.Atoi(string(ec))
   673  	if err != nil {
   674  		return -1, err
   675  	}
   676  	return ecInt, nil
   677  }
   678  
   679  // getExecSessionPID gets the PID of an active exec session
   680  func (c *Container) getExecSessionPID(sessionID string) (int, error) {
   681  	session, ok := c.state.ExecSessions[sessionID]
   682  	if ok {
   683  		return session.PID, nil
   684  	}
   685  	oldSession, ok := c.state.LegacyExecSessions[sessionID]
   686  	if ok {
   687  		return oldSession.PID, nil
   688  	}
   689  
   690  	return -1, errors.Wrapf(define.ErrNoSuchExecSession, "no exec session with ID %s found in container %s", sessionID, c.ID())
   691  }
   692  
   693  // getKnownExecSessions gets a list of all exec sessions we think are running,
   694  // but does not verify their current state.
   695  // Please use getActiveExecSessions() outside of container_exec.go, as this
   696  // function performs further checks to return an accurate list.
   697  func (c *Container) getKnownExecSessions() []string {
   698  	knownSessions := []string{}
   699  	// First check legacy sessions.
   700  	// TODO: This is DEPRECATED and will be removed in a future major
   701  	// release.
   702  	for sessionID := range c.state.LegacyExecSessions {
   703  		knownSessions = append(knownSessions, sessionID)
   704  	}
   705  	// Next check new exec sessions, but only if in running state
   706  	for sessionID, session := range c.state.ExecSessions {
   707  		if session.State == define.ExecStateRunning {
   708  			knownSessions = append(knownSessions, sessionID)
   709  		}
   710  	}
   711  
   712  	return knownSessions
   713  }
   714  
   715  // getActiveExecSessions checks if there are any active exec sessions in the
   716  // current container. Returns an array of active exec sessions.
   717  // Will continue through errors where possible.
   718  // Currently handles both new and legacy, deprecated exec sessions.
   719  func (c *Container) getActiveExecSessions() ([]string, error) {
   720  	activeSessions := []string{}
   721  	knownSessions := c.getKnownExecSessions()
   722  
   723  	// Instead of saving once per iteration, do it once at the end.
   724  	var lastErr error
   725  	needSave := false
   726  	for _, id := range knownSessions {
   727  		alive, err := c.ociRuntime.ExecUpdateStatus(c, id)
   728  		if err != nil {
   729  			if lastErr != nil {
   730  				logrus.Errorf("Error checking container %s exec sessions: %v", c.ID(), lastErr)
   731  			}
   732  			lastErr = err
   733  			continue
   734  		}
   735  		if !alive {
   736  			if err := c.cleanupExecBundle(id); err != nil {
   737  				if lastErr != nil {
   738  					logrus.Errorf("Error checking container %s exec sessions: %v", c.ID(), lastErr)
   739  				}
   740  				lastErr = err
   741  			}
   742  
   743  			_, isLegacy := c.state.LegacyExecSessions[id]
   744  			if isLegacy {
   745  				delete(c.state.LegacyExecSessions, id)
   746  				needSave = true
   747  			} else {
   748  				session := c.state.ExecSessions[id]
   749  				exitCode, err := c.readExecExitCode(session.ID())
   750  				if err != nil {
   751  					if lastErr != nil {
   752  						logrus.Errorf("Error checking container %s exec sessions: %v", c.ID(), lastErr)
   753  					}
   754  					lastErr = err
   755  				}
   756  				session.ExitCode = exitCode
   757  				session.PID = 0
   758  				session.State = define.ExecStateStopped
   759  
   760  				needSave = true
   761  			}
   762  		} else {
   763  			activeSessions = append(activeSessions, id)
   764  		}
   765  	}
   766  	if needSave {
   767  		if err := c.save(); err != nil {
   768  			if lastErr != nil {
   769  				logrus.Errorf("Error reaping exec sessions for container %s: %v", c.ID(), lastErr)
   770  			}
   771  			lastErr = err
   772  		}
   773  	}
   774  
   775  	return activeSessions, lastErr
   776  }
   777  
   778  // removeAllExecSessions stops and removes all the container's exec sessions
   779  func (c *Container) removeAllExecSessions() error {
   780  	knownSessions := c.getKnownExecSessions()
   781  
   782  	var lastErr error
   783  	for _, id := range knownSessions {
   784  		if err := c.ociRuntime.ExecStopContainer(c, id, c.StopTimeout()); err != nil {
   785  			if lastErr != nil {
   786  				logrus.Errorf("Error stopping container %s exec sessions: %v", c.ID(), lastErr)
   787  			}
   788  			lastErr = err
   789  			continue
   790  		}
   791  
   792  		if err := c.cleanupExecBundle(id); err != nil {
   793  			if lastErr != nil {
   794  				logrus.Errorf("Error stopping container %s exec sessions: %v", c.ID(), lastErr)
   795  			}
   796  			lastErr = err
   797  		}
   798  	}
   799  	// Delete all exec sessions
   800  	if err := c.runtime.state.RemoveContainerExecSessions(c); err != nil {
   801  		if lastErr != nil {
   802  			logrus.Errorf("Error stopping container %s exec sessions: %v", c.ID(), lastErr)
   803  		}
   804  		lastErr = err
   805  	}
   806  	c.state.ExecSessions = nil
   807  	c.state.LegacyExecSessions = nil
   808  	if err := c.save(); err != nil {
   809  		if lastErr != nil {
   810  			logrus.Errorf("Error stopping container %s exec sessions: %v", c.ID(), lastErr)
   811  		}
   812  		lastErr = err
   813  	}
   814  
   815  	return lastErr
   816  }