github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/state_linux.go (about)

     1  package libcontainer
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  
     8  	"github.com/opencontainers/runc/libcontainer/configs"
     9  	"github.com/opencontainers/runtime-spec/specs-go"
    10  	"golang.org/x/sys/unix"
    11  )
    12  
    13  func newStateTransitionError(from, to containerState) error {
    14  	return &stateTransitionError{
    15  		From: from.status().String(),
    16  		To:   to.status().String(),
    17  	}
    18  }
    19  
    20  // stateTransitionError is returned when an invalid state transition happens from one
    21  // state to another.
    22  type stateTransitionError struct {
    23  	From string
    24  	To   string
    25  }
    26  
    27  func (s *stateTransitionError) Error() string {
    28  	return fmt.Sprintf("invalid state transition from %s to %s", s.From, s.To)
    29  }
    30  
    31  type containerState interface {
    32  	transition(containerState) error
    33  	destroy() error
    34  	status() Status
    35  }
    36  
    37  func destroy(c *Container) error {
    38  	// Usually, when a container init is gone, all other processes in its
    39  	// cgroup are killed by the kernel. This is not the case for a shared
    40  	// PID namespace container, which may have some processes left after
    41  	// its init is killed or exited.
    42  	//
    43  	// As the container without init process running is considered stopped,
    44  	// and destroy is supposed to remove all the container resources, we need
    45  	// to kill those processes here.
    46  	if !c.config.Namespaces.IsPrivate(configs.NEWPID) {
    47  		_ = signalAllProcesses(c.cgroupManager, unix.SIGKILL)
    48  	}
    49  	if err := c.cgroupManager.Destroy(); err != nil {
    50  		return fmt.Errorf("unable to remove container's cgroup: %w", err)
    51  	}
    52  	if c.intelRdtManager != nil {
    53  		if err := c.intelRdtManager.Destroy(); err != nil {
    54  			return fmt.Errorf("unable to remove container's IntelRDT group: %w", err)
    55  		}
    56  	}
    57  	if err := os.RemoveAll(c.stateDir); err != nil {
    58  		return fmt.Errorf("unable to remove container state dir: %w", err)
    59  	}
    60  	c.initProcess = nil
    61  	err := runPoststopHooks(c)
    62  	c.state = &stoppedState{c: c}
    63  	return err
    64  }
    65  
    66  func runPoststopHooks(c *Container) error {
    67  	hooks := c.config.Hooks
    68  	if hooks == nil {
    69  		return nil
    70  	}
    71  
    72  	s, err := c.currentOCIState()
    73  	if err != nil {
    74  		return err
    75  	}
    76  	s.Status = specs.StateStopped
    77  
    78  	return hooks.Run(configs.Poststop, s)
    79  }
    80  
    81  // stoppedState represents a container is a stopped/destroyed state.
    82  type stoppedState struct {
    83  	c *Container
    84  }
    85  
    86  func (b *stoppedState) status() Status {
    87  	return Stopped
    88  }
    89  
    90  func (b *stoppedState) transition(s containerState) error {
    91  	switch s.(type) {
    92  	case *runningState, *restoredState:
    93  		b.c.state = s
    94  		return nil
    95  	case *stoppedState:
    96  		return nil
    97  	}
    98  	return newStateTransitionError(b, s)
    99  }
   100  
   101  func (b *stoppedState) destroy() error {
   102  	return destroy(b.c)
   103  }
   104  
   105  // runningState represents a container that is currently running.
   106  type runningState struct {
   107  	c *Container
   108  }
   109  
   110  func (r *runningState) status() Status {
   111  	return Running
   112  }
   113  
   114  func (r *runningState) transition(s containerState) error {
   115  	switch s.(type) {
   116  	case *stoppedState:
   117  		if r.c.hasInit() {
   118  			return ErrRunning
   119  		}
   120  		r.c.state = s
   121  		return nil
   122  	case *pausedState:
   123  		r.c.state = s
   124  		return nil
   125  	case *runningState:
   126  		return nil
   127  	}
   128  	return newStateTransitionError(r, s)
   129  }
   130  
   131  func (r *runningState) destroy() error {
   132  	if r.c.hasInit() {
   133  		return ErrRunning
   134  	}
   135  	return destroy(r.c)
   136  }
   137  
   138  type createdState struct {
   139  	c *Container
   140  }
   141  
   142  func (i *createdState) status() Status {
   143  	return Created
   144  }
   145  
   146  func (i *createdState) transition(s containerState) error {
   147  	switch s.(type) {
   148  	case *runningState, *pausedState, *stoppedState:
   149  		i.c.state = s
   150  		return nil
   151  	case *createdState:
   152  		return nil
   153  	}
   154  	return newStateTransitionError(i, s)
   155  }
   156  
   157  func (i *createdState) destroy() error {
   158  	_ = i.c.initProcess.signal(unix.SIGKILL)
   159  	return destroy(i.c)
   160  }
   161  
   162  // pausedState represents a container that is currently pause.  It cannot be destroyed in a
   163  // paused state and must transition back to running first.
   164  type pausedState struct {
   165  	c *Container
   166  }
   167  
   168  func (p *pausedState) status() Status {
   169  	return Paused
   170  }
   171  
   172  func (p *pausedState) transition(s containerState) error {
   173  	switch s.(type) {
   174  	case *runningState, *stoppedState:
   175  		p.c.state = s
   176  		return nil
   177  	case *pausedState:
   178  		return nil
   179  	}
   180  	return newStateTransitionError(p, s)
   181  }
   182  
   183  func (p *pausedState) destroy() error {
   184  	if p.c.hasInit() {
   185  		return ErrPaused
   186  	}
   187  	if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil {
   188  		return err
   189  	}
   190  	return destroy(p.c)
   191  }
   192  
   193  // restoredState is the same as the running state but also has associated checkpoint
   194  // information that maybe need destroyed when the container is stopped and destroy is called.
   195  type restoredState struct {
   196  	imageDir string
   197  	c        *Container
   198  }
   199  
   200  func (r *restoredState) status() Status {
   201  	return Running
   202  }
   203  
   204  func (r *restoredState) transition(s containerState) error {
   205  	switch s.(type) {
   206  	case *stoppedState, *runningState:
   207  		return nil
   208  	}
   209  	return newStateTransitionError(r, s)
   210  }
   211  
   212  func (r *restoredState) destroy() error {
   213  	if _, err := os.Stat(filepath.Join(r.c.stateDir, "checkpoint")); err != nil {
   214  		if !os.IsNotExist(err) {
   215  			return err
   216  		}
   217  	}
   218  	return destroy(r.c)
   219  }
   220  
   221  // loadedState is used whenever a container is restored, loaded, or setting additional
   222  // processes inside and it should not be destroyed when it is exiting.
   223  type loadedState struct {
   224  	c *Container
   225  	s Status
   226  }
   227  
   228  func (n *loadedState) status() Status {
   229  	return n.s
   230  }
   231  
   232  func (n *loadedState) transition(s containerState) error {
   233  	n.c.state = s
   234  	return nil
   235  }
   236  
   237  func (n *loadedState) destroy() error {
   238  	if err := n.c.refreshState(); err != nil {
   239  		return err
   240  	}
   241  	return n.c.state.destroy()
   242  }