mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
e918d02139
This removes libcontainer's own error wrapping system, consisting of a few types and functions, aimed at typization, wrapping and unwrapping of errors, as well as saving error stack traces. Since Go 1.13 now provides its own error wrapping mechanism and a few related functions, it makes sense to switch to it. While doing that, improve some error messages so that they start with "error", "unable to", or "can't". A few things that are worth mentioning: 1. We lose stack traces (which were never shown anyway). 2. Users of libcontainer that relied on particular errors (like ContainerNotExists) need to switch to using errors.Is with the new errors defined in error.go. 3. encoding/json is unable to unmarshal the built-in error type, so we have to introduce initError and wrap the errors into it (basically passing the error as a string). This is the same as it was before, just a tad simpler (actually the initError is a type that got removed in commit afa844311; also suddenly ierr variable name makes sense now). Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
246 lines
5.0 KiB
Go
246 lines
5.0 KiB
Go
// +build linux
|
|
|
|
package libcontainer
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/sirupsen/logrus"
|
|
"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 *linuxContainer) error {
|
|
if !c.config.Namespaces.Contains(configs.NEWPID) ||
|
|
c.config.Namespaces.PathOf(configs.NEWPID) != "" {
|
|
if err := signalAllProcesses(c.cgroupManager, unix.SIGKILL); err != nil {
|
|
logrus.Warn(err)
|
|
}
|
|
}
|
|
err := c.cgroupManager.Destroy()
|
|
if c.intelRdtManager != nil {
|
|
if ierr := c.intelRdtManager.Destroy(); err == nil {
|
|
err = ierr
|
|
}
|
|
}
|
|
if rerr := os.RemoveAll(c.root); err == nil {
|
|
err = rerr
|
|
}
|
|
c.initProcess = nil
|
|
if herr := runPoststopHooks(c); err == nil {
|
|
err = herr
|
|
}
|
|
c.state = &stoppedState{c: c}
|
|
return err
|
|
}
|
|
|
|
func runPoststopHooks(c *linuxContainer) error {
|
|
hooks := c.config.Hooks
|
|
if hooks == nil {
|
|
return nil
|
|
}
|
|
|
|
s, err := c.currentOCIState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.Status = specs.StateStopped
|
|
|
|
if err := hooks[configs.Poststop].RunHooks(s); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// stoppedState represents a container is a stopped/destroyed state.
|
|
type stoppedState struct {
|
|
c *linuxContainer
|
|
}
|
|
|
|
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 *linuxContainer
|
|
}
|
|
|
|
func (r *runningState) status() Status {
|
|
return Running
|
|
}
|
|
|
|
func (r *runningState) transition(s containerState) error {
|
|
switch s.(type) {
|
|
case *stoppedState:
|
|
if r.c.runType() == Running {
|
|
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.runType() == Running {
|
|
return ErrRunning
|
|
}
|
|
return destroy(r.c)
|
|
}
|
|
|
|
type createdState struct {
|
|
c *linuxContainer
|
|
}
|
|
|
|
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 *linuxContainer
|
|
}
|
|
|
|
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 {
|
|
t := p.c.runType()
|
|
if t != Running && t != Created {
|
|
if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil {
|
|
return err
|
|
}
|
|
return destroy(p.c)
|
|
}
|
|
return ErrPaused
|
|
}
|
|
|
|
// 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 *linuxContainer
|
|
}
|
|
|
|
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.root, "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 *linuxContainer
|
|
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()
|
|
}
|