package libcontainer import ( "fmt" "os" "path/filepath" "github.com/opencontainers/cgroups" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" ) func newStateTransitionError(from, to containerState) error { return &stateTransitionError{ From: from.status().String(), To: to.status().String(), } } // stateTransitionError is returned when an invalid state transition happens from one // state to another. type stateTransitionError struct { From string To string } func (s *stateTransitionError) Error() string { return fmt.Sprintf("invalid state transition from %s to %s", s.From, s.To) } type containerState interface { transition(containerState) error destroy() error status() Status } func destroy(c *Container) error { // Usually, when a container init is gone, all other processes in its // cgroup are killed by the kernel. This is not the case for a shared // PID namespace container, which may have some processes left after // its init is killed or exited. // // As the container without init process running is considered stopped, // and destroy is supposed to remove all the container resources, we need // to kill those processes here. if !c.config.Namespaces.IsPrivate(configs.NEWPID) { // Likely to fail when c.config.RootlessCgroups is true _ = signalAllProcesses(c.cgroupManager, unix.SIGKILL) } if err := c.cgroupManager.Destroy(); err != nil { return fmt.Errorf("unable to remove container's cgroup: %w", err) } if c.intelRdtManager != nil { if err := c.intelRdtManager.Destroy(); err != nil { return fmt.Errorf("unable to remove container's IntelRDT group: %w", err) } } if err := os.RemoveAll(c.stateDir); err != nil { return fmt.Errorf("unable to remove container state dir: %w", err) } c.initProcess = nil err := runPoststopHooks(c) c.state = &stoppedState{c: c} return err } func runPoststopHooks(c *Container) error { hooks := c.config.Hooks if hooks == nil { return nil } s, err := c.currentOCIState() if err != nil { return err } s.Status = specs.StateStopped return hooks.Run(configs.Poststop, s) } // stoppedState represents a container is a stopped/destroyed state. type stoppedState struct { c *Container } func (b *stoppedState) status() Status { return Stopped } func (b *stoppedState) transition(s containerState) error { switch s.(type) { case *runningState, *restoredState: b.c.state = s return nil case *stoppedState: return nil } return newStateTransitionError(b, s) } func (b *stoppedState) destroy() error { return destroy(b.c) } // runningState represents a container that is currently running. type runningState struct { c *Container } func (r *runningState) status() Status { return Running } func (r *runningState) transition(s containerState) error { switch s.(type) { case *stoppedState: if r.c.hasInit() { return ErrRunning } r.c.state = s return nil case *pausedState: r.c.state = s return nil case *runningState: return nil } return newStateTransitionError(r, s) } func (r *runningState) destroy() error { if r.c.hasInit() { return ErrRunning } return destroy(r.c) } type createdState struct { c *Container } func (i *createdState) status() Status { return Created } func (i *createdState) transition(s containerState) error { switch s.(type) { case *runningState, *pausedState, *stoppedState: i.c.state = s return nil case *createdState: return nil } return newStateTransitionError(i, s) } func (i *createdState) destroy() error { _ = i.c.initProcess.signal(unix.SIGKILL) return destroy(i.c) } // pausedState represents a container that is currently pause. It cannot be destroyed in a // paused state and must transition back to running first. type pausedState struct { c *Container } func (p *pausedState) status() Status { return Paused } func (p *pausedState) transition(s containerState) error { switch s.(type) { case *runningState, *stoppedState: p.c.state = s return nil case *pausedState: return nil } return newStateTransitionError(p, s) } func (p *pausedState) destroy() error { if p.c.hasInit() { return ErrPaused } if err := p.c.cgroupManager.Freeze(cgroups.Thawed); err != nil { return err } return destroy(p.c) } // restoredState is the same as the running state but also has associated checkpoint // information that maybe need destroyed when the container is stopped and destroy is called. type restoredState struct { imageDir string c *Container } func (r *restoredState) status() Status { return Running } func (r *restoredState) transition(s containerState) error { switch s.(type) { case *stoppedState, *runningState: return nil } return newStateTransitionError(r, s) } func (r *restoredState) destroy() error { if _, err := os.Stat(filepath.Join(r.c.stateDir, "checkpoint")); err != nil { if !os.IsNotExist(err) { return err } } return destroy(r.c) } // loadedState is used whenever a container is restored, loaded, or setting additional // processes inside and it should not be destroyed when it is exiting. type loadedState struct { c *Container s Status } func (n *loadedState) status() Status { return n.s } func (n *loadedState) transition(s containerState) error { n.c.state = s return nil } func (n *loadedState) destroy() error { if err := n.c.refreshState(); err != nil { return err } return n.c.state.destroy() }