Repository: opencontainers/runc
Pull request #3375 (2022-02-14 18:55:42)

libct: rm BaseContainer and Container interfaces
~~_Based on and currently includes #3373. Draft until that one is merged._~~

The only implementation of `BaseContainer` and `Container` is `linuxContainer`. It does not make
sense to have an interface with a single implementation, and we do not
foresee other types of containers being added to runc.
    
 - Remove `BaseContainer` and`Container` interfaces, moving their methods
   documentation to `linuxContainer`.
 
- Rename `linuxContainer` to `Container`.
    
- Adopt users from using interface to using struct.

Continuing the spring cleaning started in #3354. Loosely based on parts of #3316.

thaJeztah: @kolyshkin this one can be rebased (was considering "we should remove those interfaces", then recalled: "we had a PR for that") 😂 

Patch:
diff --git a/delete.go b/delete.go
index 746b0df5..dd3041f8 100644
--- a/delete.go
+++ b/delete.go
@@ -13,7 +13,7 @@ import (
 	"golang.org/x/sys/unix"
 )
 
-func killContainer(container libcontainer.Container) error {
+func killContainer(container *libcontainer.Container) error {
 	_ = container.Signal(unix.SIGKILL, false)
 	for i := 0; i < 100; i++ {
 		time.Sleep(100 * time.Millisecond)
diff --git a/libcontainer/container.go b/libcontainer/container.go
index c23281fb..c4aa99ec 100644
--- a/libcontainer/container.go
+++ b/libcontainer/container.go
@@ -5,11 +5,9 @@
 package libcontainer
 
 import (
-	"os"
 	"time"
 
 	"github.com/opencontainers/runc/libcontainer/configs"
-	"github.com/opencontainers/runtime-spec/specs-go"
 )
 
 // Status is the status of a container.
@@ -59,68 +57,3 @@ type BaseState struct {
 	// Config is the container's configuration.
 	Config configs.Config `json:"config"`
 }
-
-// BaseContainer is a libcontainer container object.
-//
-// Each container is thread-safe within the same process. Since a container can
-// be destroyed by a separate process, any function may return that the container
-// was not found. BaseContainer includes methods that are platform agnostic.
-type BaseContainer interface {
-	// Returns the ID of the container
-	ID() string
-
-	// Returns the current status of the container.
-	Status() (Status, error)
-
-	// State returns the current container's state information.
-	State() (*State, error)
-
-	// OCIState returns the current container's state information.
-	OCIState() (*specs.State, error)
-
-	// Returns the current config of the container.
-	Config() configs.Config
-
-	// Returns the PIDs inside this container. The PIDs are in the namespace of the calling process.
-	//
-	// Some of the returned PIDs may no longer refer to processes in the Container, unless
-	// the Container state is PAUSED in which case every PID in the slice is valid.
-	Processes() ([]int, error)
-
-	// Returns statistics for the container.
-	Stats() (*Stats, error)
-
-	// Set resources of container as configured
-	//
-	// We can use this to change resources when containers are running.
-	//
-	Set(config configs.Config) error
-
-	// Start a process inside the container. Returns error if process fails to
-	// start. You can track process lifecycle with passed Process structure.
-	Start(process *Process) (err error)
-
-	// Run immediately starts the process inside the container.  Returns error if process
-	// fails to start.  It does not block waiting for the exec fifo  after start returns but
-	// opens the fifo after start returns.
-	Run(process *Process) (err error)
-
-	// Destroys the container, if its in a valid state, after killing any
-	// remaining running processes.
-	//
-	// Any event registrations are removed before the container is destroyed.
-	// No error is returned if the container is already destroyed.
-	//
-	// Running containers must first be stopped using Signal(..).
-	// Paused containers must first be resumed using Resume(..).
-	Destroy() error
-
-	// Signal sends the provided signal code to the container's initial process.
-	//
-	// If all is specified the signal is sent to all processes in the container
-	// including the initial process.
-	Signal(s os.Signal, all bool) error
-
-	// Exec signals the container to exec the users process at the end of the init.
-	Exec() error
-}
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index d50c0c2e..a2ceb785 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -36,7 +36,8 @@ import (
 
 const stdioFdCount = 3
 
-type linuxContainer struct {
+// Container is a libcontainer container object.
+type Container struct {
 	id                   string
 	root                 string
 	config               *configs.Config
@@ -80,69 +81,44 @@ type State struct {
 	IntelRdtPath string `json:"intel_rdt_path"`
 }
 
-// Container is a libcontainer container object.
-//
-// Each container is thread-safe within the same process. Since a container can
-// be destroyed by a separate process, any function may return that the container
-// was not found.
-type Container interface {
-	BaseContainer
-
-	// Methods below here are platform specific
-
-	// Checkpoint checkpoints the running container's state to disk using the criu(8) utility.
-	Checkpoint(criuOpts *CriuOpts) error
-
-	// Restore restores the checkpointed container to a running state using the criu(8) utility.
-	Restore(process *Process, criuOpts *CriuOpts) error
-
-	// If the Container state is RUNNING or CREATED, sets the Container state to PAUSED and pauses
-	// the execution of any user processes. Asynchronously, when the container finished being paused the
-	// state is changed to PAUSED.
-	// If the Container state is PAUSED, do nothing.
-	Pause() error
-
-	// If the Container state is PAUSED, resumes the execution of any user processes in the
-	// Container before setting the Container state to RUNNING.
-	// If the Container state is RUNNING, do nothing.
-	Resume() error
-
-	// NotifyOOM returns a read-only channel signaling when the container receives an OOM notification.
-	NotifyOOM() (<-chan struct{}, error)
-
-	// NotifyMemoryPressure returns a read-only channel signaling when the container reaches a given pressure level
-	NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error)
-}
-
 // ID returns the container's unique ID
-func (c *linuxContainer) ID() string {
+func (c *Container) ID() string {
 	return c.id
 }
 
 // Config returns the container's configuration
-func (c *linuxContainer) Config() configs.Config {
+func (c *Container) Config() configs.Config {
 	return *c.config
 }
 
-func (c *linuxContainer) Status() (Status, error) {
+// Status returns the current status of the container.
+func (c *Container) Status() (Status, error) {
 	c.m.Lock()
 	defer c.m.Unlock()
 	return c.currentStatus()
 }
 
-func (c *linuxContainer) State() (*State, error) {
+// State returns the current container's state information.
+func (c *Container) State() (*State, error) {
 	c.m.Lock()
 	defer c.m.Unlock()
 	return c.currentState()
 }
 
-func (c *linuxContainer) OCIState() (*specs.State, error) {
+// OCIState returns the current container's state information.
+func (c *Container) OCIState() (*specs.State, error) {
 	c.m.Lock()
 	defer c.m.Unlock()
 	return c.currentOCIState()
 }
 
-func (c *linuxContainer) Processes() ([]int, error) {
+// Processes returns the PIDs inside this container. The PIDs are in the
+// namespace of the calling process.
+//
+// Some of the returned PIDs may no longer refer to processes in the container,
+// unless the container state is PAUSED in which case every PID in the slice is
+// valid.
+func (c *Container) Processes() ([]int, error) {
 	var pids []int
 	status, err := c.currentStatus()
 	if err != nil {
@@ -160,7 +136,8 @@ func (c *linuxContainer) Processes() ([]int, error) {
 	return pids, nil
 }
 
-func (c *linuxContainer) Stats() (*Stats, error) {
+// Stats returns statistics for the container.
+func (c *Container) Stats() (*Stats, error) {
 	var (
 		err   error
 		stats = &Stats{}
@@ -186,7 +163,9 @@ func (c *linuxContainer) Stats() (*Stats, error) {
 	return stats, nil
 }
 
-func (c *linuxContainer) Set(config configs.Config) error {
+// Set resources of container as configured. Can be used to change resources
+// when the container is running.
+func (c *Container) Set(config configs.Config) error {
 	c.m.Lock()
 	defer c.m.Unlock()
 	status, err := c.currentStatus()
@@ -221,7 +200,9 @@ func (c *linuxContainer) Set(config configs.Config) error {
 	return err
 }
 
-func (c *linuxContainer) Start(process *Process) error {
+// Start starts a process inside the container. Returns error if process fails
+// to start. You can track process lifecycle with passed Process structure.
+func (c *Container) Start(process *Process) error {
 	c.m.Lock()
 	defer c.m.Unlock()
 	if c.config.Cgroups.Resources.SkipDevices {
@@ -241,7 +222,10 @@ func (c *linuxContainer) Start(process *Process) error {
 	return nil
 }
 
-func (c *linuxContainer) Run(process *Process) error {
+// Run immediately starts the process inside the container. Returns an error if
+// the process fails to start. It does not block waiting for the exec fifo
+// after start returns but opens the fifo after start returns.
+func (c *Container) Run(process *Process) error {
 	if err := c.Start(process); err != nil {
 		return err
 	}
@@ -251,13 +235,14 @@ func (c *linuxContainer) Run(process *Process) error {
 	return nil
 }
 
-func (c *linuxContainer) Exec() error {
+// Exec signals the container to exec the users process at the end of the init.
+func (c *Container) Exec() error {
 	c.m.Lock()
 	defer c.m.Unlock()
 	return c.exec()
 }
 
-func (c *linuxContainer) exec() error {
+func (c *Container) exec() error {
 	path := filepath.Join(c.root, execFifoFilename)
 	pid := c.initProcess.pid()
 	blockingFifoOpenCh := awaitFifoOpen(path)
@@ -329,7 +314,7 @@ type openResult struct {
 	err  error
 }
 
-func (c *linuxContainer) start(process *Process) (retErr error) {
+func (c *Container) start(process *Process) (retErr error) {
 	parent, err := c.newParentProcess(process)
 	if err != nil {
 		return fmt.Errorf("unable to create new parent process: %w", err)
@@ -370,7 +355,7 @@ func (c *linuxContainer) start(process *Process) (retErr error) {
 	return nil
 }
 
-func (c *linuxContainer) Signal(s os.Signal, all bool) error {
+func (c *Container) Signal(s os.Signal, all bool) error {
 	c.m.Lock()
 	defer c.m.Unlock()
 	status, err := c.currentStatus()
@@ -402,7 +387,7 @@ func (c *linuxContainer) Signal(s os.Signal, all bool) error {
 	return ErrNotRunning
 }
 
-func (c *linuxContainer) createExecFifo() error {
+func (c *Container) createExecFifo() error {
 	rootuid, err := c.Config().HostRootUID()
 	if err != nil {
 		return err
@@ -425,7 +410,7 @@ func (c *linuxContainer) createExecFifo() error {
 	return os.Chown(fifoName, rootuid, rootgid)
 }
 
-func (c *linuxContainer) deleteExecFifo() {
+func (c *Container) deleteExecFifo() {
 	fifoName := filepath.Join(c.root, execFifoFilename)
 	os.Remove(fifoName)
 }
@@ -434,7 +419,7 @@ func (c *linuxContainer) deleteExecFifo() {
 // container cannot access the statedir (and the FIFO itself remains
 // un-opened). It then adds the FifoFd to the given exec.Cmd as an inherited
 // fd, with _LIBCONTAINER_FIFOFD set to its fd number.
-func (c *linuxContainer) includeExecFifo(cmd *exec.Cmd) error {
+func (c *Container) includeExecFifo(cmd *exec.Cmd) error {
 	fifoName := filepath.Join(c.root, execFifoFilename)
 	fifo, err := os.OpenFile(fifoName, unix.O_PATH|unix.O_CLOEXEC, 0)
 	if err != nil {
@@ -448,7 +433,7 @@ func (c *linuxContainer) includeExecFifo(cmd *exec.Cmd) error {
 	return nil
 }
 
-func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
+func (c *Container) newParentProcess(p *Process) (parentProcess, error) {
 	parentInitPipe, childInitPipe, err := utils.NewSockPair("init")
 	if err != nil {
 		return nil, fmt.Errorf("unable to create init pipe: %w", err)
@@ -477,7 +462,7 @@ func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
 	return c.newInitProcess(p, cmd, messageSockPair, logFilePair)
 }
 
-func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, childLogPipe *os.File) *exec.Cmd {
+func (c *Container) commandTemplate(p *Process, childInitPipe *os.File, childLogPipe *os.File) *exec.Cmd {
 	cmd := exec.Command("/proc/self/exe", "init")
 	cmd.Args[0] = os.Args[0]
 	cmd.Stdin = p.Stdin
@@ -519,7 +504,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, chi
 // shouldSendMountSources says whether the child process must setup bind mounts with
 // the source pre-opened (O_PATH) in the host user namespace.
 // See https://github.com/opencontainers/runc/issues/2484
-func (c *linuxContainer) shouldSendMountSources() bool {
+func (c *Container) shouldSendMountSources() bool {
 	// Passing the mount sources via SCM_RIGHTS is only necessary when
 	// both userns and mntns are active.
 	if !c.config.Namespaces.Contains(configs.NEWUSER) ||
@@ -543,7 +528,7 @@ func (c *linuxContainer) shouldSendMountSources() bool {
 	return false
 }
 
-func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*initProcess, error) {
+func (c *Container) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*initProcess, error) {
 	cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard))
 	nsMaps := make(map[configs.NamespaceType]string)
 	for _, ns := range c.config.Namespaces {
@@ -602,7 +587,7 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPa
 	return init, nil
 }
 
-func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*setnsProcess, error) {
+func (c *Container) newSetnsProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*setnsProcess, error) {
 	cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns))
 	state, err := c.currentState()
 	if err != nil {
@@ -659,7 +644,7 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, messageSockP
 	return proc, nil
 }
 
-func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
+func (c *Container) newInitConfig(process *Process) *initConfig {
 	cfg := &initConfig{
 		Config:           c.config,
 		Args:             process.Args,
@@ -699,13 +684,23 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
 	return cfg
 }
 
-func (c *linuxContainer) Destroy() error {
+// Destroy destroys the container, if its in a valid state, after killing any
+// remaining running processes.
+//
+// Any event registrations are removed before the container is destroyed.
+// No error is returned if the container is already destroyed.
+//
+// Running containers must first be stopped using Signal.
+// Paused containers must first be resumed using Resume.
+func (c *Container) Destroy() error {
 	c.m.Lock()
 	defer c.m.Unlock()
 	return c.state.destroy()
 }
 
-func (c *linuxContainer) Pause() error {
+// Pause pauses the container, if its state is RUNNING or CREATED, changing
+// its state to PAUSED. If the state is already PAUSED, does nothing.
+func (c *Container) Pause() error {
 	c.m.Lock()
 	defer c.m.Unlock()
 	status, err := c.currentStatus()
@@ -724,7 +719,11 @@ func (c *linuxContainer) Pause() error {
 	return ErrNotRunning
 }
 
-func (c *linuxContainer) Resume() error {
+// Resume resumes the execution of any user processes in the
+// container before setting the container state to RUNNING.
+// This is only performed if the current state is PAUSED.
+// If the Container state is RUNNING, does nothing.
+func (c *Container) Resume() error {
 	c.m.Lock()
 	defer c.m.Unlock()
 	status, err := c.currentStatus()
@@ -742,7 +741,9 @@ func (c *linuxContainer) Resume() error {
 	})
 }
 
-func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
+// NotifyOOM returns a read-only channel signaling when the container receives
+// an OOM notification.
+func (c *Container) NotifyOOM() (<-chan struct{}, error) {
 	// XXX(cyphar): This requires cgroups.
 	if c.config.RootlessCgroups {
 		logrus.Warn("getting OOM notifications may fail if you don't have the full access to cgroups")
@@ -754,7 +755,9 @@ func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
 	return notifyOnOOM(path)
 }
 
-func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
+// NotifyMemoryPressure returns a read-only channel signaling when the
+// container reaches a given pressure level.
+func (c *Container) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
 	// XXX(cyphar): This requires cgroups.
 	if c.config.RootlessCgroups {
 		logrus.Warn("getting memory pressure notifications may fail if you don't have the full access to cgroups")
@@ -764,7 +767,7 @@ func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struc
 
 var criuFeatures *criurpc.CriuFeatures
 
-func (c *linuxContainer) checkCriuFeatures(criuOpts *CriuOpts, rpcOpts *criurpc.CriuOpts, criuFeat *criurpc.CriuFeatures) error {
+func (c *Container) checkCriuFeatures(criuOpts *CriuOpts, rpcOpts *criurpc.CriuOpts, criuFeat *criurpc.CriuFeatures) error {
 	t := criurpc.CriuReqType_FEATURE_CHECK
 
 	// make sure the features we are looking for are really not from
@@ -824,8 +827,8 @@ func compareCriuVersion(criuVersion int, minVersion int) error {
 	return nil
 }
 
-// checkCriuVersion checks Criu version greater than or equal to minVersion
-func (c *linuxContainer) checkCriuVersion(minVersion int) error {
+// checkCriuVersion checks CRIU version greater than or equal to minVersion.
+func (c *Container) checkCriuVersion(minVersion int) error {
 	// If the version of criu has already been determined there is no need
 	// to ask criu for the version again. Use the value from c.criuVersion.
 	if c.criuVersion != 0 {
@@ -844,7 +847,7 @@ func (c *linuxContainer) checkCriuVersion(minVersion int) error {
 
 const descriptorsFilename = "descriptors.json"
 
-func (c *linuxContainer) addCriuDumpMount(req *criurpc.CriuReq, m *configs.Mount) {
+func (c *Container) addCriuDumpMount(req *criurpc.CriuReq, m *configs.Mount) {
 	mountDest := strings.TrimPrefix(m.Destination, c.config.Rootfs)
 	if dest, err := securejoin.SecureJoin(c.config.Rootfs, mountDest); err == nil {
 		mountDest = dest[len(c.config.Rootfs):]
@@ -856,7 +859,7 @@ func (c *linuxContainer) addCriuDumpMount(req *criurpc.CriuReq, m *configs.Mount
 	req.Opts.ExtMnt = append(req.Opts.ExtMnt, extMnt)
 }
 
-func (c *linuxContainer) addMaskPaths(req *criurpc.CriuReq) error {
+func (c *Container) addMaskPaths(req *criurpc.CriuReq) error {
 	for _, path := range c.config.MaskPaths {
 		fi, err := os.Stat(fmt.Sprintf("/proc/%d/root/%s", c.initProcess.pid(), path))
 		if err != nil {
@@ -878,7 +881,7 @@ func (c *linuxContainer) addMaskPaths(req *criurpc.CriuReq) error {
 	return nil
 }
 
-func (c *linuxContainer) handleCriuConfigurationFile(rpcOpts *criurpc.CriuOpts) {
+func (c *Container) handleCriuConfigurationFile(rpcOpts *criurpc.CriuOpts) {
 	// CRIU will evaluate a configuration starting with release 3.11.
 	// Settings in the configuration file will overwrite RPC settings.
 	// Look for annotations. The annotation 'org.criu.config'
@@ -903,7 +906,7 @@ func (c *linuxContainer) handleCriuConfigurationFile(rpcOpts *criurpc.CriuOpts)
 	}
 }
 
-func (c *linuxContainer) criuSupportsExtNS(t configs.NamespaceType) bool {
+func (c *Container) criuSupportsExtNS(t configs.NamespaceType) bool {
 	var minVersion int
 	switch t {
 	case configs.NEWNET:
@@ -923,7 +926,7 @@ func criuNsToKey(t configs.NamespaceType) string {
 	return "extRoot" + strings.Title(configs.NsName(t)) + "NS" //nolint:staticcheck // SA1019: strings.Title is deprecated
 }
 
-func (c *linuxContainer) handleCheckpointingExternalNamespaces(rpcOpts *criurpc.CriuOpts, t configs.NamespaceType) error {
+func (c *Container) handleCheckpointingExternalNamespaces(rpcOpts *criurpc.CriuOpts, t configs.NamespaceType) error {
 	if !c.criuSupportsExtNS(t) {
 		return nil
 	}
@@ -945,7 +948,7 @@ func (c *linuxContainer) handleCheckpointingExternalNamespaces(rpcOpts *criurpc.
 	return nil
 }
 
-func (c *linuxContainer) handleRestoringNamespaces(rpcOpts *criurpc.CriuOpts, extraFiles *[]*os.File) error {
+func (c *Container) handleRestoringNamespaces(rpcOpts *criurpc.CriuOpts, extraFiles *[]*os.File) error {
 	for _, ns := range c.config.Namespaces {
 		switch ns.Type {
 		case configs.NEWNET, configs.NEWPID:
@@ -983,7 +986,7 @@ func (c *linuxContainer) handleRestoringNamespaces(rpcOpts *criurpc.CriuOpts, ex
 	return nil
 }
 
-func (c *linuxContainer) handleRestoringExternalNamespaces(rpcOpts *criurpc.CriuOpts, extraFiles *[]*os.File, t configs.NamespaceType) error {
+func (c *Container) handleRestoringExternalNamespaces(rpcOpts *criurpc.CriuOpts, extraFiles *[]*os.File, t configs.NamespaceType) error {
 	if !c.criuSupportsExtNS(t) {
 		return nil
 	}
@@ -1014,7 +1017,7 @@ func (c *linuxContainer) handleRestoringExternalNamespaces(rpcOpts *criurpc.Criu
 	return nil
 }
 
-func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
+func (c *Container) Checkpoint(criuOpts *CriuOpts) error {
 	c.m.Lock()
 	defer c.m.Unlock()
 
@@ -1222,7 +1225,7 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
 	return nil
 }
 
-func (c *linuxContainer) addCriuRestoreMount(req *criurpc.CriuReq, m *configs.Mount) {
+func (c *Container) addCriuRestoreMount(req *criurpc.CriuReq, m *configs.Mount) {
 	mountDest := strings.TrimPrefix(m.Destination, c.config.Rootfs)
 	if dest, err := securejoin.SecureJoin(c.config.Rootfs, mountDest); err == nil {
 		mountDest = dest[len(c.config.Rootfs):]
@@ -1234,7 +1237,7 @@ func (c *linuxContainer) addCriuRestoreMount(req *criurpc.CriuReq, m *configs.Mo
 	req.Opts.ExtMnt = append(req.Opts.ExtMnt, extMnt)
 }
 
-func (c *linuxContainer) restoreNetwork(req *criurpc.CriuReq, criuOpts *CriuOpts) {
+func (c *Container) restoreNetwork(req *criurpc.CriuReq, criuOpts *CriuOpts) {
 	for _, iface := range c.config.Networks {
 		switch iface.Type {
 		case "veth":
@@ -1257,7 +1260,7 @@ func (c *linuxContainer) restoreNetwork(req *criurpc.CriuReq, criuOpts *CriuOpts
 // makeCriuRestoreMountpoints makes the actual mountpoints for the
 // restore using CRIU. This function is inspired from the code in
 // rootfs_linux.go
-func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
+func (c *Container) makeCriuRestoreMountpoints(m *configs.Mount) error {
 	switch m.Device {
 	case "cgroup":
 		// No mount point(s) need to be created:
@@ -1309,7 +1312,7 @@ func isPathInPrefixList(path string, prefix []string) bool {
 // runc modifies the rootfs to add mountpoints which do not exist.
 // This function also creates missing mountpoints as long as they
 // are not on top of a tmpfs, as CRIU will restore tmpfs content anyway.
-func (c *linuxContainer) prepareCriuRestoreMounts(mounts []*configs.Mount) error {
+func (c *Container) prepareCriuRestoreMounts(mounts []*configs.Mount) error {
 	// First get a list of a all tmpfs mounts
 	tmpfs := []string{}
 	for _, m := range mounts {
@@ -1366,7 +1369,9 @@ func (c *linuxContainer) prepareCriuRestoreMounts(mounts []*configs.Mount) error
 	return nil
 }
 
-func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
+// Restore restores the checkpointed container to a running state using the
+// criu(8) utility.
+func (c *Container) Restore(process *Process, criuOpts *CriuOpts) error {
 	c.m.Lock()
 	defer c.m.Unlock()
 
@@ -1540,7 +1545,7 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
 	return err
 }
 
-func (c *linuxContainer) criuApplyCgroups(pid int, req *criurpc.CriuReq) error {
+func (c *Container) criuApplyCgroups(pid int, req *criurpc.CriuReq) error {
 	// need to apply cgroups only on restore
 	if req.GetType() != criurpc.CriuReqType_RESTORE {
 		return nil
@@ -1577,7 +1582,7 @@ func (c *linuxContainer) criuApplyCgroups(pid int, req *criurpc.CriuReq) error {
 	return nil
 }
 
-func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *CriuOpts, extraFiles []*os.File) error {
+func (c *Container) criuSwrk(process *Process, req *criurpc.CriuReq, opts *CriuOpts, extraFiles []*os.File) error {
 	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0)
 	if err != nil {
 		return err
@@ -1795,7 +1800,7 @@ func unlockNetwork(config *configs.Config) error {
 	return nil
 }
 
-func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Process, cmd *exec.Cmd, opts *CriuOpts, fds []string, oob []byte) error {
+func (c *Container) criuNotifications(resp *criurpc.CriuResp, process *Process, cmd *exec.Cmd, opts *CriuOpts, fds []string, oob []byte) error {
 	notify := resp.GetNotify()
 	if notify == nil {
 		return fmt.Errorf("invalid response: %s", resp.String())
@@ -1893,7 +1898,7 @@ func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Proc
 	return nil
 }
 
-func (c *linuxContainer) updateState(process parentProcess) (*State, error) {
+func (c *Container) updateState(process parentProcess) (*State, error) {
 	if process != nil {
 		c.initProcess = process
 	}
@@ -1908,7 +1913,7 @@ func (c *linuxContainer) updateState(process parentProcess) (*State, error) {
 	return state, nil
 }
 
-func (c *linuxContainer) saveState(s *State) (retErr error) {
+func (c *Container) saveState(s *State) (retErr error) {
 	tmpFile, err := os.CreateTemp(c.root, "state-")
 	if err != nil {
 		return err
@@ -1934,7 +1939,7 @@ func (c *linuxContainer) saveState(s *State) (retErr error) {
 	return os.Rename(tmpFile.Name(), stateFilePath)
 }
 
-func (c *linuxContainer) currentStatus() (Status, error) {
+func (c *Container) currentStatus() (Status, error) {
 	if err := c.refreshState(); err != nil {
 		return -1, err
 	}
@@ -1945,7 +1950,7 @@ func (c *linuxContainer) currentStatus() (Status, error) {
 // container is what is true.  Because consumers of libcontainer can use it
 // out of process we need to verify the container's status based on runtime
 // information and not rely on our in process info.
-func (c *linuxContainer) refreshState() error {
+func (c *Container) refreshState() error {
 	paused, err := c.isPaused()
 	if err != nil {
 		return err
@@ -1963,7 +1968,7 @@ func (c *linuxContainer) refreshState() error {
 	return c.state.transition(&stoppedState{c: c})
 }
 
-func (c *linuxContainer) runType() Status {
+func (c *Container) runType() Status {
 	if c.initProcess == nil {
 		return Stopped
 	}
@@ -1983,7 +1988,7 @@ func (c *linuxContainer) runType() Status {
 	return Running
 }
 
-func (c *linuxContainer) isPaused() (bool, error) {
+func (c *Container) isPaused() (bool, error) {
 	state, err := c.cgroupManager.GetFreezerState()
 	if err != nil {
 		return false, err
@@ -1991,7 +1996,7 @@ func (c *linuxContainer) isPaused() (bool, error) {
 	return state == configs.Frozen, nil
 }
 
-func (c *linuxContainer) currentState() (*State, error) {
+func (c *Container) currentState() (*State, error) {
 	var (
 		startTime           uint64
 		externalDescriptors []string
@@ -2038,7 +2043,7 @@ func (c *linuxContainer) currentState() (*State, error) {
 	return state, nil
 }
 
-func (c *linuxContainer) currentOCIState() (*specs.State, error) {
+func (c *Container) currentOCIState() (*specs.State, error) {
 	bundle, annotations := utils.Annotations(c.config.Labels)
 	state := &specs.State{
 		Version:     specs.Version,
@@ -2061,7 +2066,7 @@ func (c *linuxContainer) currentOCIState() (*specs.State, error) {
 
 // orderNamespacePaths sorts namespace paths into a list of paths that we
 // can setns in order.
-func (c *linuxContainer) orderNamespacePaths(namespaces map[configs.NamespaceType]string) ([]string, error) {
+func (c *Container) orderNamespacePaths(namespaces map[configs.NamespaceType]string) ([]string, error) {
 	paths := []string{}
 	for _, ns := range configs.NamespaceTypes() {
 
@@ -2114,7 +2119,7 @@ type netlinkError struct{ error }
 // such as one that uses nsenter package to bootstrap the container's
 // init process correctly, i.e. with correct namespaces, uid/gid
 // mapping etc.
-func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string, it initType) (_ io.Reader, Err error) {
+func (c *Container) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string, it initType) (_ io.Reader, Err error) {
 	// create the netlink message
 	r := nl.NewNetlinkRequest(int(InitMsg), 0)
 
diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go
index 9d4bc4cf..10ca0eee 100644
--- a/libcontainer/factory_linux.go
+++ b/libcontainer/factory_linux.go
@@ -37,7 +37,7 @@ var idRegex = regexp.MustCompile(`^[\w+-\.]+$`)
 // The id must not be empty and consist of only the following characters:
 // ASCII letters, digits, underscore, plus, minus, period. The id must be
 // unique and non-existent for the given root path.
-func Create(root, id string, config *configs.Config) (Container, error) {
+func Create(root, id string, config *configs.Config) (*Container, error) {
 	if root == "" {
 		return nil, errors.New("root not set")
 	}
@@ -98,7 +98,7 @@ func Create(root, id string, config *configs.Config) (Container, error) {
 	if err := os.Mkdir(containerRoot, 0o711); err != nil {
 		return nil, err
 	}
-	c := &linuxContainer{
+	c := &Container{
 		id:              id,
 		root:            containerRoot,
 		config:          config,
@@ -112,7 +112,7 @@ func Create(root, id string, config *configs.Config) (Container, error) {
 // Load takes a path to the state directory (root) and an id of an existing
 // container, and returns a Container object reconstructed from the saved
 // state. This presents a read only view of the container.
-func Load(root, id string) (Container, error) {
+func Load(root, id string) (*Container, error) {
 	if root == "" {
 		return nil, errors.New("root not set")
 	}
@@ -137,7 +137,7 @@ func Load(root, id string) (Container, error) {
 	if err != nil {
 		return nil, err
 	}
-	c := &linuxContainer{
+	c := &Container{
 		initProcess:          r,
 		initProcessStartTime: state.InitProcessStartTime,
 		id:                   id,
diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go
index c2ee2da6..ea3223ae 100644
--- a/libcontainer/process_linux.go
+++ b/libcontainer/process_linux.go
@@ -300,7 +300,7 @@ type initProcess struct {
 	config          *initConfig
 	manager         cgroups.Manager
 	intelRdtManager *intelrdt.Manager
-	container       *linuxContainer
+	container       *Container
 	fds             []string
 	process         *Process
 	bootstrapData   io.Reader
diff --git a/libcontainer/state_linux.go b/libcontainer/state_linux.go
index aa6259b1..4895612e 100644
--- a/libcontainer/state_linux.go
+++ b/libcontainer/state_linux.go
@@ -35,7 +35,7 @@ type containerState interface {
 	status() Status
 }
 
-func destroy(c *linuxContainer) error {
+func destroy(c *Container) error {
 	if !c.config.Namespaces.Contains(configs.NEWPID) ||
 		c.config.Namespaces.PathOf(configs.NEWPID) != "" {
 		if err := signalAllProcesses(c.cgroupManager, unix.SIGKILL); err != nil {
@@ -59,7 +59,7 @@ func destroy(c *linuxContainer) error {
 	return err
 }
 
-func runPoststopHooks(c *linuxContainer) error {
+func runPoststopHooks(c *Container) error {
 	hooks := c.config.Hooks
 	if hooks == nil {
 		return nil
@@ -80,7 +80,7 @@ func runPoststopHooks(c *linuxContainer) error {
 
 // stoppedState represents a container is a stopped/destroyed state.
 type stoppedState struct {
-	c *linuxContainer
+	c *Container
 }
 
 func (b *stoppedState) status() Status {
@@ -104,7 +104,7 @@ func (b *stoppedState) destroy() error {
 
 // runningState represents a container that is currently running.
 type runningState struct {
-	c *linuxContainer
+	c *Container
 }
 
 func (r *runningState) status() Status {
@@ -136,7 +136,7 @@ func (r *runningState) destroy() error {
 }
 
 type createdState struct {
-	c *linuxContainer
+	c *Container
 }
 
 func (i *createdState) status() Status {
@@ -162,7 +162,7 @@ func (i *createdState) destroy() error {
 // 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
+	c *Container
 }
 
 func (p *pausedState) status() Status {
@@ -195,7 +195,7 @@ func (p *pausedState) destroy() error {
 // information that maybe need destroyed when the container is stopped and destroy is called.
 type restoredState struct {
 	imageDir string
-	c        *linuxContainer
+	c        *Container
 }
 
 func (r *restoredState) status() Status {
@@ -222,7 +222,7 @@ func (r *restoredState) destroy() error {
 // 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
+	c *Container
 	s Status
 }
 
diff --git a/notify_socket.go b/notify_socket.go
index 9dde506c..28c6c0ae 100644
--- a/notify_socket.go
+++ b/notify_socket.go
@@ -91,7 +91,7 @@ func notifySocketStart(context *cli.Context, notifySocketHost, id string) (*noti
 	return notifySocket, nil
 }
 
-func (s *notifySocket) waitForContainer(container libcontainer.Container) error {
+func (s *notifySocket) waitForContainer(container *libcontainer.Container) error {
 	state, err := container.State()
 	if err != nil {
 		return err
diff --git a/utils_linux.go b/utils_linux.go
index 4124d9a7..4c00b209 100644
--- a/utils_linux.go
+++ b/utils_linux.go
@@ -25,7 +25,7 @@ var errEmptyID = errors.New("container id cannot be empty")
 
 // getContainer returns the specified container instance by loading it from
 // a state directory (root).
-func getContainer(context *cli.Context) (libcontainer.Container, error) {
+func getContainer(context *cli.Context) (*libcontainer.Container, error) {
 	id := context.Args().First()
 	if id == "" {
 		return nil, errEmptyID
@@ -82,7 +82,7 @@ func newProcess(p specs.Process) (*libcontainer.Process, error) {
 	return lp, nil
 }
 
-func destroy(container libcontainer.Container) {
+func destroy(container *libcontainer.Container) {
 	if err := container.Destroy(); err != nil {
 		logrus.Error(err)
 	}
@@ -162,7 +162,7 @@ func createPidFile(path string, process *libcontainer.Process) error {
 	return os.Rename(tmpName, path)
 }
 
-func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
+func createContainer(context *cli.Context, id string, spec *specs.Spec) (*libcontainer.Container, error) {
 	rootlessCg, err := shouldUseRootlessCgroupManager(context)
 	if err != nil {
 		return nil, err
@@ -193,7 +193,7 @@ type runner struct {
 	preserveFDs     int
 	pidFile         string
 	consoleSocket   string
-	container       libcontainer.Container
+	container       *libcontainer.Container
 	action          CtAct
 	notifySocket    *notifySocket
 	criuOpts        *libcontainer.CriuOpts


Test patch:
diff --git a/libcontainer/container_linux_test.go b/libcontainer/container_linux_test.go
index 27eee624..6551de80 100644
--- a/libcontainer/container_linux_test.go
+++ b/libcontainer/container_linux_test.go
@@ -115,7 +115,7 @@ func TestGetContainerPids(t *testing.T) {
 	if err != nil {
 		t.Fatalf("can't stat pid %d, got %v", pid, err)
 	}
-	container := &linuxContainer{
+	container := &Container{
 		id:     "myid",
 		config: &configs.Config{},
 		cgroupManager: &mockCgroupManager{
@@ -148,7 +148,7 @@ func TestGetContainerState(t *testing.T) {
 		expectedMemoryPath  = "/sys/fs/cgroup/memory/myid"
 		expectedNetworkPath = fmt.Sprintf("/proc/%d/ns/net", pid)
 	)
-	container := &linuxContainer{
+	container := &Container{
 		id: "myid",
 		config: &configs.Config{
 			Namespaces: []configs.Namespace{
@@ -232,7 +232,7 @@ func TestGetContainerStateAfterUpdate(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	container := &linuxContainer{
+	container := &Container{
 		root: t.TempDir(),
 		id:   "myid",
 		config: &configs.Config{
diff --git a/libcontainer/factory_linux_test.go b/libcontainer/factory_linux_test.go
index c9e2b0e0..889ae034 100644
--- a/libcontainer/factory_linux_test.go
+++ b/libcontainer/factory_linux_test.go
@@ -75,12 +75,8 @@ func TestFactoryLoadContainer(t *testing.T) {
 	if !reflect.DeepEqual(config.Hooks, expectedHooks) {
 		t.Fatalf("expects hooks %q but received %q", expectedHooks, config.Hooks)
 	}
-	lcontainer, ok := container.(*linuxContainer)
-	if !ok {
-		t.Fatal("expected linux container on linux based systems")
-	}
-	if lcontainer.initProcess.pid() != expectedState.InitProcessPid {
-		t.Fatalf("expected init pid %d but received %d", expectedState.InitProcessPid, lcontainer.initProcess.pid())
+	if container.initProcess.pid() != expectedState.InitProcessPid {
+		t.Fatalf("expected init pid %d but received %d", expectedState.InitProcessPid, container.initProcess.pid())
 	}
 }
 
diff --git a/libcontainer/integration/utils_test.go b/libcontainer/integration/utils_test.go
index c54a40bb..3bacf93d 100644
--- a/libcontainer/integration/utils_test.go
+++ b/libcontainer/integration/utils_test.go
@@ -165,7 +165,7 @@ func copyBusybox(dest string) error {
 	return nil
 }
 
-func newContainer(t *testing.T, config *configs.Config) (libcontainer.Container, error) {
+func newContainer(t *testing.T, config *configs.Config) (*libcontainer.Container, error) {
 	name := strings.ReplaceAll(t.Name(), "/", "_") + strconv.FormatInt(-int64(time.Now().Nanosecond()), 35)
 	root := t.TempDir()
 
@@ -212,6 +212,6 @@ func runContainer(t *testing.T, config *configs.Config, args ...string) (buffers
 	return
 }
 
-func destroyContainer(container libcontainer.Container) {
+func destroyContainer(container *libcontainer.Container) {
 	_ = container.Destroy()
 }
diff --git a/libcontainer/state_linux_test.go b/libcontainer/state_linux_test.go
index 413626d5..b57e6eff 100644
--- a/libcontainer/state_linux_test.go
+++ b/libcontainer/state_linux_test.go
@@ -53,7 +53,7 @@ func testTransitions(t *testing.T, initialState containerState, valid []containe
 func TestStoppedStateTransition(t *testing.T) {
 	testTransitions(
 		t,
-		&stoppedState{c: &linuxContainer{}},
+		&stoppedState{c: &Container{}},
 		[]containerState{
 			&stoppedState{},
 			&runningState{},
@@ -65,7 +65,7 @@ func TestStoppedStateTransition(t *testing.T) {
 func TestPausedStateTransition(t *testing.T) {
 	testTransitions(
 		t,
-		&pausedState{c: &linuxContainer{}},
+		&pausedState{c: &Container{}},
 		[]containerState{
 			&pausedState{},
 			&runningState{},
@@ -77,7 +77,7 @@ func TestPausedStateTransition(t *testing.T) {
 func TestRestoredStateTransition(t *testing.T) {
 	testTransitions(
 		t,
-		&restoredState{c: &linuxContainer{}},
+		&restoredState{c: &Container{}},
 		[]containerState{
 			&stoppedState{},
 			&runningState{},
@@ -88,7 +88,7 @@ func TestRestoredStateTransition(t *testing.T) {
 func TestRunningStateTransition(t *testing.T) {
 	testTransitions(
 		t,
-		&runningState{c: &linuxContainer{}},
+		&runningState{c: &Container{}},
 		[]containerState{
 			&stoppedState{},
 			&pausedState{},
@@ -100,7 +100,7 @@ func TestRunningStateTransition(t *testing.T) {
 func TestCreatedStateTransition(t *testing.T) {
 	testTransitions(
 		t,
-		&createdState{c: &linuxContainer{}},
+		&createdState{c: &Container{}},
 		[]containerState{
 			&stoppedState{},
 			&pausedState{},