2018-01-18 16:37:41 +00:00
package libpod
import (
2021-01-13 06:17:27 +00:00
"bufio"
2018-05-31 18:47:17 +00:00
"bytes"
2018-04-18 20:48:35 +00:00
"context"
2018-01-18 16:37:41 +00:00
"fmt"
"io"
2018-01-19 14:51:59 +00:00
"io/ioutil"
2018-01-18 16:37:41 +00:00
"os"
"path/filepath"
2018-10-09 11:54:37 +00:00
"strconv"
2018-01-19 14:51:59 +00:00
"strings"
2018-10-19 20:02:14 +00:00
"time"
2018-01-18 16:37:41 +00:00
2021-02-25 16:43:18 +00:00
metadata "github.com/checkpoint-restore/checkpointctl/lib"
2021-02-10 14:46:12 +00:00
"github.com/containers/buildah/copier"
2021-01-15 06:27:23 +00:00
"github.com/containers/common/pkg/secrets"
2021-02-20 13:28:38 +00:00
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/events"
"github.com/containers/podman/v3/pkg/cgroups"
"github.com/containers/podman/v3/pkg/ctime"
"github.com/containers/podman/v3/pkg/hooks"
"github.com/containers/podman/v3/pkg/hooks/exec"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/selinux"
2018-01-18 16:37:41 +00:00
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
2020-06-24 09:25:47 +00:00
"github.com/containers/storage/pkg/idtools"
2018-08-10 18:46:59 +00:00
"github.com/containers/storage/pkg/mount"
2020-06-19 13:29:34 +00:00
"github.com/coreos/go-systemd/v22/daemon"
2020-01-13 12:01:45 +00:00
securejoin "github.com/cyphar/filepath-securejoin"
2018-01-18 16:37:41 +00:00
spec "github.com/opencontainers/runtime-spec/specs-go"
2019-02-06 19:17:25 +00:00
"github.com/opencontainers/runtime-tools/generate"
2021-01-15 06:27:23 +00:00
"github.com/opencontainers/selinux/go-selinux/label"
2018-01-18 16:37:41 +00:00
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
2018-01-18 16:46:10 +00:00
const (
// name of the directory holding the artifacts
2019-07-01 17:55:03 +00:00
artifactsDir = "artifacts"
execDirPermission = 0755
2018-01-18 16:46:10 +00:00
)
2018-01-18 16:37:41 +00:00
// rootFsSize gets the size of the container's root filesystem
// A container FS is split into two parts. The first is the top layer, a
// mutable layer, and the rest is the RootFS: the set of immutable layers
// that make up the image on which the container is based.
func ( c * Container ) rootFsSize ( ) ( int64 , error ) {
2018-04-26 15:21:48 +00:00
if c . config . Rootfs != "" {
return 0 , nil
}
2019-03-19 09:38:56 +00:00
if c . runtime . store == nil {
return 0 , nil
}
2018-04-26 15:21:48 +00:00
2018-01-18 16:37:41 +00:00
container , err := c . runtime . store . Container ( c . ID ( ) )
if err != nil {
return 0 , err
}
// Ignore the size of the top layer. The top layer is a mutable RW layer
// and is not considered a part of the rootfs
rwLayer , err := c . runtime . store . Layer ( container . LayerID )
if err != nil {
return 0 , err
}
layer , err := c . runtime . store . Layer ( rwLayer . Parent )
if err != nil {
return 0 , err
}
size := int64 ( 0 )
for layer . Parent != "" {
layerSize , err := c . runtime . store . DiffSize ( layer . Parent , layer . ID )
if err != nil {
return size , errors . Wrapf ( err , "getting diffsize of layer %q and its parent %q" , layer . ID , layer . Parent )
}
size += layerSize
layer , err = c . runtime . store . Layer ( layer . Parent )
if err != nil {
return 0 , err
}
}
// Get the size of the last layer. Has to be outside of the loop
2018-05-25 00:50:37 +00:00
// because the parent of the last layer is "", and lstore.Get("")
2018-01-18 16:37:41 +00:00
// will return an error.
layerSize , err := c . runtime . store . DiffSize ( layer . Parent , layer . ID )
return size + layerSize , err
}
2019-12-27 22:42:38 +00:00
// rwSize gets the size of the mutable top layer of the container.
2018-01-18 16:37:41 +00:00
func ( c * Container ) rwSize ( ) ( int64 , error ) {
2018-04-26 15:21:48 +00:00
if c . config . Rootfs != "" {
var size int64
err := filepath . Walk ( c . config . Rootfs , func ( path string , info os . FileInfo , err error ) error {
if err != nil {
return err
}
size += info . Size ( )
return nil
} )
return size , err
}
2018-01-18 16:37:41 +00:00
container , err := c . runtime . store . Container ( c . ID ( ) )
if err != nil {
return 0 , err
}
2019-12-27 22:42:38 +00:00
// The top layer of a container is
// the only readable/writeable layer, all others are immutable.
rwLayer , err := c . runtime . store . Layer ( container . LayerID )
2018-01-18 16:37:41 +00:00
if err != nil {
return 0 , err
}
2019-12-27 22:42:38 +00:00
// Get the size of the top layer by calculating the size of the diff
// between the layer and its parent.
return c . runtime . store . DiffSize ( rwLayer . Parent , rwLayer . ID )
2018-01-18 16:37:41 +00:00
}
2018-05-16 17:38:17 +00:00
// bundlePath returns the path to the container's root filesystem - where the OCI spec will be
2018-01-18 16:37:41 +00:00
// placed, amongst other things
func ( c * Container ) bundlePath ( ) string {
return c . config . StaticDir
}
2018-05-16 17:38:17 +00:00
// ControlSocketPath returns the path to the containers control socket for things like tty
// resizing
func ( c * Container ) ControlSocketPath ( ) string {
return filepath . Join ( c . bundlePath ( ) , "ctl" )
}
2018-09-18 09:56:19 +00:00
// CheckpointPath returns the path to the directory containing the checkpoint
func ( c * Container ) CheckpointPath ( ) string {
2021-02-25 16:43:18 +00:00
return filepath . Join ( c . bundlePath ( ) , metadata . CheckpointDirectory )
2018-09-18 09:56:19 +00:00
}
2021-01-10 10:12:12 +00:00
// PreCheckpointPath returns the path to the directory containing the pre-checkpoint-images
func ( c * Container ) PreCheckPointPath ( ) string {
return filepath . Join ( c . bundlePath ( ) , "pre-checkpoint" )
}
2018-05-16 17:38:17 +00:00
// AttachSocketPath retrieves the path of the container's attach socket
2019-10-08 17:53:36 +00:00
func ( c * Container ) AttachSocketPath ( ) ( string , error ) {
return c . ociRuntime . AttachSocketPath ( c )
2018-01-18 16:37:41 +00:00
}
2019-07-01 17:55:03 +00:00
// exitFilePath gets the path to the container's exit file
2019-10-08 17:53:36 +00:00
func ( c * Container ) exitFilePath ( ) ( string , error ) {
return c . ociRuntime . ExitFilePath ( c )
2019-07-01 17:55:03 +00:00
}
2018-10-19 20:02:14 +00:00
// Wait for the container's exit file to appear.
// When it does, update our state based on it.
func ( c * Container ) waitForExitFileAndSync ( ) error {
2019-10-08 17:53:36 +00:00
exitFile , err := c . exitFilePath ( )
if err != nil {
return err
}
2018-10-19 20:02:14 +00:00
2019-05-21 08:01:29 +00:00
chWait := make ( chan error )
defer close ( chWait )
2019-10-08 17:53:36 +00:00
_ , err = WaitForFile ( exitFile , chWait , time . Second * 5 )
2018-10-19 20:02:14 +00:00
if err != nil {
// Exit file did not appear
// Reset our state
c . state . ExitCode = - 1
c . state . FinishedTime = time . Now ( )
2019-06-25 13:40:19 +00:00
c . state . State = define . ContainerStateStopped
2018-10-19 20:02:14 +00:00
if err2 := c . save ( ) ; err2 != nil {
logrus . Errorf ( "Error saving container %s state: %v" , c . ID ( ) , err2 )
}
return err
}
2019-10-15 19:11:26 +00:00
if err := c . checkExitFile ( ) ; err != nil {
2018-10-19 20:02:14 +00:00
return err
}
return c . save ( )
}
// Handle the container exit file.
// The exit file is used to supply container exit time and exit code.
// This assumes the exit file already exists.
func ( c * Container ) handleExitFile ( exitFile string , fi os . FileInfo ) error {
c . state . FinishedTime = ctime . Created ( fi )
statusCodeStr , err := ioutil . ReadFile ( exitFile )
if err != nil {
return errors . Wrapf ( err , "failed to read exit file for container %s" , c . ID ( ) )
}
statusCode , err := strconv . Atoi ( string ( statusCodeStr ) )
if err != nil {
2018-10-29 17:20:26 +00:00
return errors . Wrapf ( err , "error converting exit status code (%q) for container %s to int" ,
c . ID ( ) , statusCodeStr )
2018-10-19 20:02:14 +00:00
}
c . state . ExitCode = int32 ( statusCode )
oomFilePath := filepath . Join ( c . bundlePath ( ) , "oom" )
if _ , err = os . Stat ( oomFilePath ) ; err == nil {
c . state . OOMKilled = true
}
c . state . Exited = true
2019-03-12 20:12:09 +00:00
// Write an event for the container's death
c . newContainerExitedEvent ( c . state . ExitCode )
2018-10-19 20:02:14 +00:00
return nil
}
2020-11-06 14:55:40 +00:00
func ( c * Container ) shouldRestart ( ) bool {
// If we did not get a restart policy match, return false
2019-05-03 14:35:48 +00:00
// Do the same if we're not a policy that restarts.
if ! c . state . RestartPolicyMatch ||
2021-03-15 11:55:06 +00:00
c . config . RestartPolicy == define . RestartPolicyNo ||
c . config . RestartPolicy == define . RestartPolicyNone {
2020-11-06 14:55:40 +00:00
return false
2019-05-03 14:35:48 +00:00
}
// If we're RestartPolicyOnFailure, we need to check retries and exit
// code.
2021-03-15 11:55:06 +00:00
if c . config . RestartPolicy == define . RestartPolicyOnFailure {
2019-05-03 14:35:48 +00:00
if c . state . ExitCode == 0 {
2020-11-06 14:55:40 +00:00
return false
2019-05-03 14:35:48 +00:00
}
// If we don't have a max retries set, continue
if c . config . RestartRetries > 0 {
2020-11-06 14:55:40 +00:00
if c . state . RestartCount >= c . config . RestartRetries {
return false
2019-05-03 14:35:48 +00:00
}
}
}
2020-11-06 14:55:40 +00:00
return true
}
2019-05-03 14:35:48 +00:00
2020-11-06 14:55:40 +00:00
// Handle container restart policy.
// This is called when a container has exited, and was not explicitly stopped by
// an API call to stop the container or pod it is in.
func ( c * Container ) handleRestartPolicy ( ctx context . Context ) ( _ bool , retErr error ) {
if ! c . shouldRestart ( ) {
return false , nil
}
2019-04-01 19:22:32 +00:00
logrus . Debugf ( "Restarting container %s due to restart policy %s" , c . ID ( ) , c . config . RestartPolicy )
// Need to check if dependencies are alive.
2020-07-09 17:50:01 +00:00
if err := c . checkDependenciesAndHandleError ( ) ; err != nil {
2019-05-03 14:35:48 +00:00
return false , err
2019-04-01 19:22:32 +00:00
}
2019-04-03 14:24:35 +00:00
// Is the container running again?
// If so, we don't have to do anything
2019-10-28 17:09:01 +00:00
if c . ensureState ( define . ContainerStateRunning , define . ContainerStatePaused ) {
2019-05-03 14:35:48 +00:00
return false , nil
2019-06-25 13:40:19 +00:00
} else if c . state . State == define . ContainerStateUnknown {
2019-06-24 20:48:34 +00:00
return false , errors . Wrapf ( define . ErrInternal , "invalid container state encountered in restart attempt!" )
2019-04-03 14:24:35 +00:00
}
2019-04-03 18:17:02 +00:00
c . newContainerEvent ( events . Restart )
2019-04-01 23:20:03 +00:00
// Increment restart count
2021-02-11 21:28:35 +00:00
c . state . RestartCount ++
2019-04-01 23:20:03 +00:00
logrus . Debugf ( "Container %s now on retry %d" , c . ID ( ) , c . state . RestartCount )
if err := c . save ( ) ; err != nil {
2019-05-03 14:35:48 +00:00
return false , err
2019-04-01 23:20:03 +00:00
}
2019-04-01 19:22:32 +00:00
defer func ( ) {
2020-07-09 17:50:01 +00:00
if retErr != nil {
if err := c . cleanup ( ctx ) ; err != nil {
logrus . Errorf ( "error cleaning up container %s: %v" , c . ID ( ) , err )
2019-04-01 19:22:32 +00:00
}
}
} ( )
if err := c . prepare ( ) ; err != nil {
2019-05-03 14:35:48 +00:00
return false , err
2019-04-01 19:22:32 +00:00
}
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateStopped {
2019-04-01 19:22:32 +00:00
// Reinitialize the container if we need to
2019-04-01 23:20:03 +00:00
if err := c . reinit ( ctx , true ) ; err != nil {
2019-05-03 14:35:48 +00:00
return false , err
2019-04-01 19:22:32 +00:00
}
2019-10-28 17:09:01 +00:00
} else if c . ensureState ( define . ContainerStateConfigured , define . ContainerStateExited ) {
2019-04-01 19:22:32 +00:00
// Initialize the container
2019-04-01 23:20:03 +00:00
if err := c . init ( ctx , true ) ; err != nil {
2019-05-03 14:35:48 +00:00
return false , err
2019-04-01 19:22:32 +00:00
}
}
2019-05-03 14:35:48 +00:00
if err := c . start ( ) ; err != nil {
return false , err
}
return true , nil
2019-04-01 19:22:32 +00:00
}
2019-10-28 17:09:01 +00:00
// Ensure that the container is in a specific state or state.
// Returns true if the container is in one of the given states,
// or false otherwise.
func ( c * Container ) ensureState ( states ... define . ContainerStatus ) bool {
for _ , state := range states {
if state == c . state . State {
return true
}
}
return false
}
2018-02-28 14:29:46 +00:00
// Sync this container with on-disk state and runtime status
2018-01-18 16:37:41 +00:00
// Should only be called with container lock held
// This function should suffice to ensure a container's state is accurate and
// it is valid for use.
func ( c * Container ) syncContainer ( ) error {
if err := c . runtime . state . UpdateContainer ( c ) ; err != nil {
return err
}
2018-02-28 14:29:46 +00:00
// If runtime knows about the container, update its status in runtime
2018-01-18 16:37:41 +00:00
// And then save back to disk
2019-10-28 17:09:01 +00:00
if c . ensureState ( define . ContainerStateCreated , define . ContainerStateRunning , define . ContainerStateStopped , define . ContainerStatePaused ) {
2018-01-18 16:37:41 +00:00
oldState := c . state . State
2019-10-15 19:11:26 +00:00
if err := c . checkExitFile ( ) ; err != nil {
2018-01-18 16:37:41 +00:00
return err
}
2019-10-15 19:11:26 +00:00
2018-01-18 16:37:41 +00:00
// Only save back to DB if state changed
if c . state . State != oldState {
2019-04-01 19:22:32 +00:00
// Check for a restart policy match
2021-03-15 11:55:06 +00:00
if c . config . RestartPolicy != define . RestartPolicyNone && c . config . RestartPolicy != define . RestartPolicyNo &&
2019-06-25 13:40:19 +00:00
( oldState == define . ContainerStateRunning || oldState == define . ContainerStatePaused ) &&
( c . state . State == define . ContainerStateStopped || c . state . State == define . ContainerStateExited ) &&
2019-04-01 19:22:32 +00:00
! c . state . StoppedByUser {
c . state . RestartPolicyMatch = true
}
2018-01-18 16:37:41 +00:00
if err := c . save ( ) ; err != nil {
return err
}
}
}
if ! c . valid {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrRemoved , "container %s is not valid" , c . ID ( ) )
2018-01-18 16:37:41 +00:00
}
return nil
}
2020-02-24 16:38:06 +00:00
func ( c * Container ) setupStorageMapping ( dest , from * storage . IDMappingOptions ) {
if c . config . Rootfs != "" {
return
}
* dest = * from
if dest . AutoUserNs {
overrides := c . getUserOverrides ( )
dest . AutoUserNsOpts . PasswdFile = overrides . ContainerEtcPasswdPath
dest . AutoUserNsOpts . GroupFile = overrides . ContainerEtcGroupPath
if c . config . User != "" {
initialSize := uint32 ( 0 )
parts := strings . Split ( c . config . User , ":" )
for _ , p := range parts {
s , err := strconv . ParseUint ( p , 10 , 32 )
if err == nil && uint32 ( s ) > initialSize {
initialSize = uint32 ( s )
}
}
dest . AutoUserNsOpts . InitialSize = initialSize + 1
}
2020-06-24 09:25:47 +00:00
} else if c . config . Spec . Linux != nil {
dest . UIDMap = nil
for _ , r := range c . config . Spec . Linux . UIDMappings {
u := idtools . IDMap {
ContainerID : int ( r . ContainerID ) ,
HostID : int ( r . HostID ) ,
Size : int ( r . Size ) ,
}
dest . UIDMap = append ( dest . UIDMap , u )
}
dest . GIDMap = nil
for _ , r := range c . config . Spec . Linux . GIDMappings {
g := idtools . IDMap {
ContainerID : int ( r . ContainerID ) ,
HostID : int ( r . HostID ) ,
Size : int ( r . Size ) ,
}
dest . GIDMap = append ( dest . GIDMap , g )
}
2020-09-09 22:19:39 +00:00
dest . HostUIDMapping = false
dest . HostGIDMapping = false
2020-02-24 16:38:06 +00:00
}
}
2018-01-18 16:37:41 +00:00
// Create container root filesystem for use
2018-04-18 20:48:35 +00:00
func ( c * Container ) setupStorage ( ctx context . Context ) error {
2018-01-18 16:37:41 +00:00
if ! c . valid {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrRemoved , "container %s is not valid" , c . ID ( ) )
2018-01-18 16:37:41 +00:00
}
2019-06-25 13:40:19 +00:00
if c . state . State != define . ContainerStateConfigured {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "container %s must be in Configured state to have storage set up" , c . ID ( ) )
2018-01-18 16:37:41 +00:00
}
// Need both an image ID and image name, plus a bool telling us whether to use the image configuration
2018-04-26 15:21:48 +00:00
if c . config . Rootfs == "" && ( c . config . RootfsImageID == "" || c . config . RootfsImageName == "" ) {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrInvalidArg , "must provide image ID and image name to use an image" )
2018-01-18 16:37:41 +00:00
}
2018-10-18 19:50:11 +00:00
options := storage . ContainerOptions {
IDMappingOptions : storage . IDMappingOptions {
HostUIDMapping : true ,
HostGIDMapping : true ,
} ,
LabelOpts : c . config . LabelOpts ,
}
2019-06-25 12:36:05 +00:00
if c . restoreFromCheckpoint {
// If restoring from a checkpoint, the root file-system
// needs to be mounted with the same SELinux labels as
// it was mounted previously.
if options . Flags == nil {
options . Flags = make ( map [ string ] interface { } )
}
options . Flags [ "ProcessLabel" ] = c . config . ProcessLabel
options . Flags [ "MountLabel" ] = c . config . MountLabel
}
2018-11-16 11:51:26 +00:00
if c . config . Privileged {
privOpt := func ( opt string ) bool {
for _ , privopt := range [ ] string { "nodev" , "nosuid" , "noexec" } {
if opt == privopt {
return true
}
}
return false
}
2019-05-31 02:01:25 +00:00
defOptions , err := storage . GetMountOptions ( c . runtime . store . GraphDriverName ( ) , c . runtime . store . GraphOptions ( ) )
2018-11-16 11:51:26 +00:00
if err != nil {
return errors . Wrapf ( err , "error getting default mount options" )
}
var newOptions [ ] string
for _ , opt := range defOptions {
if ! privOpt ( opt ) {
newOptions = append ( newOptions , opt )
}
}
options . MountOpts = newOptions
}
2018-06-03 19:08:07 +00:00
2021-05-05 13:44:29 +00:00
options . Volatile = c . config . Volatile
2020-02-24 16:38:06 +00:00
c . setupStorageMapping ( & options . IDMappingOptions , & c . config . IDMappings )
2018-10-18 19:50:11 +00:00
containerInfo , err := c . runtime . storageService . CreateContainerStorage ( ctx , c . runtime . imageContext , c . config . RootfsImageName , c . config . RootfsImageID , c . config . Name , c . config . ID , options )
2018-01-18 16:37:41 +00:00
if err != nil {
return errors . Wrapf ( err , "error creating container storage" )
}
2020-02-24 16:38:06 +00:00
c . config . IDMappings . UIDMap = containerInfo . UIDMap
c . config . IDMappings . GIDMap = containerInfo . GIDMap
2020-04-15 18:48:53 +00:00
processLabel := containerInfo . ProcessLabel
switch {
case c . ociRuntime . SupportsKVM ( ) :
2020-04-23 19:55:35 +00:00
processLabel , err = selinux . KVMLabel ( processLabel )
2020-04-15 18:48:53 +00:00
if err != nil {
return err
}
case c . config . Systemd :
2020-04-23 19:55:35 +00:00
processLabel , err = selinux . InitLabel ( processLabel )
2020-04-15 18:48:53 +00:00
if err != nil {
return err
}
}
c . config . ProcessLabel = processLabel
2020-02-24 16:38:06 +00:00
c . config . MountLabel = containerInfo . MountLabel
c . config . StaticDir = containerInfo . Dir
c . state . RunDir = containerInfo . RunDir
2019-05-01 16:49:04 +00:00
if len ( c . config . IDMappings . UIDMap ) != 0 || len ( c . config . IDMappings . GIDMap ) != 0 {
2019-03-21 11:18:42 +00:00
if err := os . Chown ( containerInfo . RunDir , c . RootUID ( ) , c . RootGID ( ) ) ; err != nil {
2018-04-24 14:41:42 +00:00
return err
}
2019-03-21 11:18:42 +00:00
if err := os . Chown ( containerInfo . Dir , c . RootUID ( ) , c . RootGID ( ) ) ; err != nil {
return err
2018-04-24 14:41:42 +00:00
}
}
2018-05-01 16:08:52 +00:00
// Set the default Entrypoint and Command
2019-03-14 20:10:25 +00:00
if containerInfo . Config != nil {
2020-08-22 10:15:53 +00:00
// Set CMD in the container to the default configuration only if ENTRYPOINT is not set by the user.
if c . config . Entrypoint == nil && c . config . Command == nil {
c . config . Command = containerInfo . Config . Config . Cmd
}
2019-03-14 20:10:25 +00:00
if c . config . Entrypoint == nil {
c . config . Entrypoint = containerInfo . Config . Config . Entrypoint
}
2018-05-23 15:33:22 +00:00
}
2018-05-01 16:08:52 +00:00
2018-01-18 16:37:41 +00:00
artifacts := filepath . Join ( c . config . StaticDir , artifactsDir )
if err := os . MkdirAll ( artifacts , 0755 ) ; err != nil {
2020-10-05 19:33:53 +00:00
return errors . Wrap ( err , "error creating artifacts directory" )
2018-01-18 16:37:41 +00:00
}
return nil
}
// Tear down a container's storage prior to removal
func ( c * Container ) teardownStorage ( ) error {
2019-10-28 17:09:01 +00:00
if c . ensureState ( define . ContainerStateRunning , define . ContainerStatePaused ) {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "cannot remove storage for container %s as it is running or paused" , c . ID ( ) )
2018-01-18 16:37:41 +00:00
}
artifacts := filepath . Join ( c . config . StaticDir , artifactsDir )
if err := os . RemoveAll ( artifacts ) ; err != nil {
2019-04-11 13:51:26 +00:00
return errors . Wrapf ( err , "error removing container %s artifacts %q" , c . ID ( ) , artifacts )
2018-01-18 16:37:41 +00:00
}
if err := c . cleanupStorage ( ) ; err != nil {
return errors . Wrapf ( err , "failed to cleanup container %s storage" , c . ID ( ) )
}
if err := c . runtime . storageService . DeleteContainer ( c . ID ( ) ) ; err != nil {
2018-04-01 01:27:28 +00:00
// If the container has already been removed, warn but do not
// error - we wanted it gone, it is already gone.
// Potentially another tool using containers/storage already
// removed it?
2019-10-14 17:49:06 +00:00
if errors . Cause ( err ) == storage . ErrNotAContainer || errors . Cause ( err ) == storage . ErrContainerUnknown {
2020-12-01 21:15:14 +00:00
logrus . Infof ( "Storage for container %s already removed" , c . ID ( ) )
2018-04-01 01:27:28 +00:00
return nil
}
2018-01-18 16:37:41 +00:00
return errors . Wrapf ( err , "error removing container %s root filesystem" , c . ID ( ) )
}
return nil
}
2020-03-09 20:06:43 +00:00
// Reset resets state fields to default values.
// It is performed before a refresh and clears the state after a reboot.
// It does not save the results - assumes the database will do that for us.
2020-06-11 18:40:38 +00:00
func resetState ( state * ContainerState ) {
2018-06-21 13:45:03 +00:00
state . PID = 0
2019-07-02 22:52:55 +00:00
state . ConmonPID = 0
2018-06-21 13:45:03 +00:00
state . Mountpoint = ""
state . Mounted = false
2019-06-25 13:40:19 +00:00
if state . State != define . ContainerStateExited {
state . State = define . ContainerStateConfigured
2019-02-05 20:37:56 +00:00
}
2018-06-21 13:45:03 +00:00
state . ExecSessions = make ( map [ string ] * ExecSession )
2020-02-04 21:56:07 +00:00
state . LegacyExecSessions = nil
2018-06-21 13:45:03 +00:00
state . BindMounts = make ( map [ string ] string )
2019-04-01 17:30:28 +00:00
state . StoppedByUser = false
2019-04-01 19:22:32 +00:00
state . RestartPolicyMatch = false
2019-04-01 23:20:03 +00:00
state . RestartCount = 0
2018-06-21 13:45:03 +00:00
}
2018-08-23 19:13:41 +00:00
// Refresh refreshes the container's state after a restart.
// Refresh cannot perform any operations that would lock another container.
// We cannot guarantee any other container has a valid lock at the time it is
// running.
2018-01-18 16:37:41 +00:00
func ( c * Container ) refresh ( ) error {
2018-07-31 13:26:06 +00:00
// Don't need a full sync, but we do need to update from the database to
// pick up potentially-missing container state
if err := c . runtime . state . UpdateContainer ( c ) ; err != nil {
return err
}
2018-01-18 16:37:41 +00:00
if ! c . valid {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrRemoved , "container %s is not valid - may have been removed" , c . ID ( ) )
2018-01-18 16:37:41 +00:00
}
// We need to get the container's temporary directory from c/storage
// It was lost in the reboot and must be recreated
dir , err := c . runtime . storageService . GetRunDir ( c . ID ( ) )
if err != nil {
return errors . Wrapf ( err , "error retrieving temporary directory for container %s" , c . ID ( ) )
}
2019-03-21 11:18:42 +00:00
c . state . RunDir = dir
2018-04-24 14:41:42 +00:00
if len ( c . config . IDMappings . UIDMap ) != 0 || len ( c . config . IDMappings . GIDMap ) != 0 {
2020-03-27 14:13:51 +00:00
info , err := os . Stat ( c . runtime . config . Engine . TmpDir )
2018-04-24 14:41:42 +00:00
if err != nil {
2020-10-28 17:16:42 +00:00
return err
2018-04-24 14:41:42 +00:00
}
2020-03-27 14:13:51 +00:00
if err := os . Chmod ( c . runtime . config . Engine . TmpDir , info . Mode ( ) | 0111 ) ; err != nil {
2020-10-28 17:16:42 +00:00
return err
2018-04-24 14:41:42 +00:00
}
2020-03-27 14:13:51 +00:00
root := filepath . Join ( c . runtime . config . Engine . TmpDir , "containers-root" , c . ID ( ) )
2018-04-24 14:41:42 +00:00
if err := os . MkdirAll ( root , 0755 ) ; err != nil {
return errors . Wrapf ( err , "error creating userNS tmpdir for container %s" , c . ID ( ) )
}
if err := os . Chown ( root , c . RootUID ( ) , c . RootGID ( ) ) ; err != nil {
return err
}
}
2018-01-18 16:37:41 +00:00
2018-08-23 19:13:41 +00:00
// We need to pick up a new lock
2019-05-06 17:44:01 +00:00
lock , err := c . runtime . lockManager . AllocateAndRetrieveLock ( c . config . LockID )
2018-08-23 19:13:41 +00:00
if err != nil {
2019-06-21 20:00:39 +00:00
return errors . Wrapf ( err , "error acquiring lock %d for container %s" , c . config . LockID , c . ID ( ) )
2018-08-23 19:13:41 +00:00
}
c . lock = lock
2020-03-09 20:06:43 +00:00
// Try to delete any lingering IP allocations.
// If this fails, just log and ignore.
// I'm a little concerned that this is so far down in refresh() and we
// could fail before getting to it - but the worst that would happen is
// that Inspect() would return info on IPs we no longer own.
if len ( c . state . NetworkStatus ) > 0 {
if err := c . removeIPv4Allocations ( ) ; err != nil {
logrus . Errorf ( "Error removing IP allocations for container %s: %v" , c . ID ( ) , err )
}
}
c . state . NetworkStatus = nil
2018-04-24 14:41:42 +00:00
if err := c . save ( ) ; err != nil {
2018-01-18 16:37:41 +00:00
return errors . Wrapf ( err , "error refreshing state for container %s" , c . ID ( ) )
}
2018-03-13 15:49:24 +00:00
// Remove ctl and attach files, which may persist across reboot
if err := c . removeConmonFiles ( ) ; err != nil {
return err
}
2020-03-09 20:06:43 +00:00
return nil
}
// Try and remove IP address allocations. Presently IPv4 only.
// Should be safe as rootless because NetworkStatus should only be populated if
// CNI is running.
func ( c * Container ) removeIPv4Allocations ( ) error {
cniNetworksDir , err := getCNINetworksDir ( )
if err != nil {
return err
}
if len ( c . state . NetworkStatus ) == 0 {
2019-10-01 12:10:04 +00:00
return nil
}
2020-03-09 20:06:43 +00:00
cniDefaultNetwork := ""
if c . runtime . netPlugin != nil {
cniDefaultNetwork = c . runtime . netPlugin . GetDefaultNetworkName ( )
}
2020-11-20 18:49:40 +00:00
networks , _ , err := c . networks ( )
Add support for network connect / disconnect to DB
Convert the existing network aliases set/remove code to network
connect and disconnect. We can no longer modify aliases for an
existing network, but we can add and remove entire networks. As
part of this, we need to add a new function to retrieve current
aliases the container is connected to (we had a table for this
as of the first aliases PR, but it was not externally exposed).
At the same time, remove all deconflicting logic for aliases.
Docker does absolutely no checks of this nature, and allows two
containers to have the same aliases, aliases that conflict with
container names, etc - it's just left to DNS to return all the
IP addresses, and presumably we round-robin from there? Most
tests for the existing code had to be removed because of this.
Convert all uses of the old container config.Networks field,
which previously included all networks in the container, to use
the new DB table. This ensures we actually get an up-to-date list
of in-use networks. Also, add network aliases to the output of
`podman inspect`.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-11-10 19:54:09 +00:00
if err != nil {
return err
}
2020-11-20 18:49:40 +00:00
if len ( networks ) != len ( c . state . NetworkStatus ) {
Add support for network connect / disconnect to DB
Convert the existing network aliases set/remove code to network
connect and disconnect. We can no longer modify aliases for an
existing network, but we can add and remove entire networks. As
part of this, we need to add a new function to retrieve current
aliases the container is connected to (we had a table for this
as of the first aliases PR, but it was not externally exposed).
At the same time, remove all deconflicting logic for aliases.
Docker does absolutely no checks of this nature, and allows two
containers to have the same aliases, aliases that conflict with
container names, etc - it's just left to DNS to return all the
IP addresses, and presumably we round-robin from there? Most
tests for the existing code had to be removed because of this.
Convert all uses of the old container config.Networks field,
which previously included all networks in the container, to use
the new DB table. This ensures we actually get an up-to-date list
of in-use networks. Also, add network aliases to the output of
`podman inspect`.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-11-10 19:54:09 +00:00
return errors . Wrapf ( define . ErrInternal , "network mismatch: asked to join %d CNI networks but got %d CNI results" , len ( networks ) , len ( c . state . NetworkStatus ) )
2020-03-09 20:06:43 +00:00
}
for index , result := range c . state . NetworkStatus {
for _ , ctrIP := range result . IPs {
if ctrIP . Version != "4" {
continue
}
candidate := ""
Add support for network connect / disconnect to DB
Convert the existing network aliases set/remove code to network
connect and disconnect. We can no longer modify aliases for an
existing network, but we can add and remove entire networks. As
part of this, we need to add a new function to retrieve current
aliases the container is connected to (we had a table for this
as of the first aliases PR, but it was not externally exposed).
At the same time, remove all deconflicting logic for aliases.
Docker does absolutely no checks of this nature, and allows two
containers to have the same aliases, aliases that conflict with
container names, etc - it's just left to DNS to return all the
IP addresses, and presumably we round-robin from there? Most
tests for the existing code had to be removed because of this.
Convert all uses of the old container config.Networks field,
which previously included all networks in the container, to use
the new DB table. This ensures we actually get an up-to-date list
of in-use networks. Also, add network aliases to the output of
`podman inspect`.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-11-10 19:54:09 +00:00
if len ( networks ) > 0 {
2020-03-09 20:06:43 +00:00
// CNI returns networks in order we passed them.
// So our index into results should be our index
// into networks.
Add support for network connect / disconnect to DB
Convert the existing network aliases set/remove code to network
connect and disconnect. We can no longer modify aliases for an
existing network, but we can add and remove entire networks. As
part of this, we need to add a new function to retrieve current
aliases the container is connected to (we had a table for this
as of the first aliases PR, but it was not externally exposed).
At the same time, remove all deconflicting logic for aliases.
Docker does absolutely no checks of this nature, and allows two
containers to have the same aliases, aliases that conflict with
container names, etc - it's just left to DNS to return all the
IP addresses, and presumably we round-robin from there? Most
tests for the existing code had to be removed because of this.
Convert all uses of the old container config.Networks field,
which previously included all networks in the container, to use
the new DB table. This ensures we actually get an up-to-date list
of in-use networks. Also, add network aliases to the output of
`podman inspect`.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-11-10 19:54:09 +00:00
candidate = filepath . Join ( cniNetworksDir , networks [ index ] , ctrIP . Address . IP . String ( ) )
2020-03-09 20:06:43 +00:00
} else {
candidate = filepath . Join ( cniNetworksDir , cniDefaultNetwork , ctrIP . Address . IP . String ( ) )
}
logrus . Debugf ( "Going to try removing IP address reservation file %q for container %s" , candidate , c . ID ( ) )
if err := os . Remove ( candidate ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "error removing CNI IP reservation file %q for container %s" , candidate , c . ID ( ) )
}
}
}
return nil
2018-03-13 15:49:24 +00:00
}
// Remove conmon attach socket and terminal resize FIFO
// This is necessary for restarting containers
func ( c * Container ) removeConmonFiles ( ) error {
// Files are allowed to not exist, so ignore ENOENT
2021-01-14 17:26:34 +00:00
attachFile , err := c . AttachSocketPath ( )
if err != nil {
return errors . Wrapf ( err , "failed to get attach socket path for container %s" , c . ID ( ) )
}
2018-03-13 15:49:24 +00:00
if err := os . Remove ( attachFile ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "error removing container %s attach file" , c . ID ( ) )
}
ctlFile := filepath . Join ( c . bundlePath ( ) , "ctl" )
if err := os . Remove ( ctlFile ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "error removing container %s ctl file" , c . ID ( ) )
}
2019-11-15 17:44:15 +00:00
winszFile := filepath . Join ( c . bundlePath ( ) , "winsz" )
if err := os . Remove ( winszFile ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "error removing container %s winsz file" , c . ID ( ) )
}
2018-03-13 15:49:24 +00:00
oomFile := filepath . Join ( c . bundlePath ( ) , "oom" )
if err := os . Remove ( oomFile ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "error removing container %s OOM file" , c . ID ( ) )
}
podman: fix memleak caused by renaming and not deleting
the exit file
If the container exit code needs to be retained, it cannot be retained
in tmpfs, because libpod runs in a memcg itself so it can't leave
traces with a daemon-less design.
This wasn't a memleak detectable by kmemleak for example. The kernel
never lost track of the memory and there was no erroneous refcounting
either. The reference count dependencies however are not easy to track
because when a refcount is increased, there's no way to tell who's
still holding the reference. In this case it was a single page of
tmpfs pagecache holding a refcount that kept pinned a whole hierarchy
of dying memcg, slab kmem, cgropups, unrechable kernfs nodes and the
respective dentries and inodes. Such a problem wouldn't happen if the
exit file was stored in a regular filesystem because the pagecache
could be reclaimed in such case under memory pressure. The tmpfs page
can be swapped out, but that's not enough to release the memcg with
CONFIG_MEMCG_SWAP_ENABLED=y.
No amount of more aggressive kernel slab shrinking could have solved
this. Not even assigning slab kmem of dying cgroups to alive cgroup
would fully solve this. The only way to free the memory of a dying
cgroup when a struct page still references it, would be to loop over
all "struct page" in the kernel to find which one is associated with
the dying cgroup which is a O(N) operation (where N is the number of
pages and can reach billions). Linking all the tmpfs pages to the
memcg would cost less during memcg offlining, but it would waste lots
of memory and CPU globally. So this can't be optimized in the kernel.
A cronjob running this command can act as workaround and will allow
all slab cache to be released, not just the single tmpfs pages.
rm -f /run/libpod/exits/*
This patch solved the memleak with a reproducer, booting with
cgroup.memory=nokmem and with selinux disabled. The reason memcg kmem
and selinux were disabled for testing of this fix, is because kmem
greatly decreases the kernel effectiveness in reusing partial slab
objects. cgroup.memory=nokmem is strongly recommended at least for
workstation usage. selinux needs to be further analyzed because it
causes further slab allocations.
The upstream podman commit used for testing is
1fe2965e4f672674f7b66648e9973a0ed5434bb4 (v1.4.4).
The upstream kernel commit used for testing is
f16fea666898dbdd7812ce94068c76da3e3fcf1e (v5.2-rc6).
Reported-by: Michele Baldessari <michele@redhat.com>
Signed-off-by: Andrea Arcangeli <aarcange@redhat.com>
<Applied with small tweaks to comments>
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2019-07-10 19:09:33 +00:00
// Remove the exit file so we don't leak memory in tmpfs
2019-10-08 17:53:36 +00:00
exitFile , err := c . exitFilePath ( )
if err != nil {
return err
}
2019-09-20 05:22:19 +00:00
if err := os . Remove ( exitFile ) ; err != nil && ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "error removing container %s exit file" , c . ID ( ) )
2018-03-13 15:49:24 +00:00
}
2018-01-18 16:37:41 +00:00
return nil
}
func ( c * Container ) export ( path string ) error {
mountPoint := c . state . Mountpoint
if ! c . state . Mounted {
2019-07-03 20:37:17 +00:00
containerMount , err := c . runtime . store . Mount ( c . ID ( ) , c . config . MountLabel )
2018-01-18 16:37:41 +00:00
if err != nil {
return errors . Wrapf ( err , "error mounting container %q" , c . ID ( ) )
}
2019-07-03 20:37:17 +00:00
mountPoint = containerMount
2018-01-18 16:37:41 +00:00
defer func ( ) {
2018-07-08 11:55:30 +00:00
if _ , err := c . runtime . store . Unmount ( c . ID ( ) , false ) ; err != nil {
2018-01-18 16:37:41 +00:00
logrus . Errorf ( "error unmounting container %q: %v" , c . ID ( ) , err )
}
} ( )
}
input , err := archive . Tar ( mountPoint , archive . Uncompressed )
if err != nil {
return errors . Wrapf ( err , "error reading container directory %q" , c . ID ( ) )
}
outFile , err := os . Create ( path )
if err != nil {
return errors . Wrapf ( err , "error creating file %q" , path )
}
defer outFile . Close ( )
_ , err = io . Copy ( outFile , input )
return err
}
// Get path of artifact with a given name for this container
func ( c * Container ) getArtifactPath ( name string ) string {
return filepath . Join ( c . config . StaticDir , artifactsDir , name )
}
// Used with Wait() to determine if a container has exited
2021-02-01 19:21:03 +00:00
func ( c * Container ) isStopped ( ) ( bool , int32 , error ) {
2018-04-14 21:32:49 +00:00
if ! c . batched {
2018-01-18 16:37:41 +00:00
c . lock . Lock ( )
defer c . lock . Unlock ( )
}
err := c . syncContainer ( )
if err != nil {
2021-02-01 19:21:03 +00:00
return true , - 1 , err
2018-01-18 16:37:41 +00:00
}
Add ContainerStateRemoving
When Libpod removes a container, there is the possibility that
removal will not fully succeed. The most notable problems are
storage issues, where the container cannot be removed from
c/storage.
When this occurs, we were faced with a choice. We can keep the
container in the state, appearing in `podman ps` and available for
other API operations, but likely unable to do any of them as it's
been partially removed. Or we can remove it very early and clean
up after it's already gone. We have, until now, used the second
approach.
The problem that arises is intermittent problems removing
storage. We end up removing a container, failing to remove its
storage, and ending up with a container permanently stuck in
c/storage that we can't remove with the normal Podman CLI, can't
use the name of, and generally can't interact with. A notable
cause is when Podman is hit by a SIGKILL midway through removal,
which can consistently cause `podman rm` to fail to remove
storage.
We now add a new state for containers that are in the process of
being removed, ContainerStateRemoving. We set this at the
beginning of the removal process. It notifies Podman that the
container cannot be used anymore, but preserves it in the DB
until it is fully removed. This will allow Remove to be run on
these containers again, which should successfully remove storage
if it fails.
Fixes #3906
Signed-off-by: Matthew Heon <mheon@redhat.com>
2019-11-11 14:52:13 +00:00
2021-02-01 19:21:03 +00:00
return ! c . ensureState ( define . ContainerStateRunning , define . ContainerStatePaused , define . ContainerStateStopping ) , c . state . ExitCode , nil
2018-01-18 16:37:41 +00:00
}
// save container state to the database
func ( c * Container ) save ( ) error {
if err := c . runtime . state . SaveContainer ( c ) ; err != nil {
return errors . Wrapf ( err , "error saving container %s state" , c . ID ( ) )
}
return nil
}
2019-02-15 21:39:24 +00:00
// Checks the container is in the right state, then initializes the container in preparation to start the container.
// If recursive is true, each of the containers dependencies will be started.
// Otherwise, this function will return with error if there are dependencies of this container that aren't running.
2020-07-09 17:50:01 +00:00
func ( c * Container ) prepareToStart ( ctx context . Context , recursive bool ) ( retErr error ) {
2019-02-15 21:39:24 +00:00
// Container must be created or stopped to be started
2019-10-28 17:09:01 +00:00
if ! c . ensureState ( define . ContainerStateConfigured , define . ContainerStateCreated , define . ContainerStateStopped , define . ContainerStateExited ) {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "container %s must be in Created or Stopped state to be started" , c . ID ( ) )
2019-02-15 21:39:24 +00:00
}
if ! recursive {
2020-06-11 18:40:38 +00:00
if err := c . checkDependenciesAndHandleError ( ) ; err != nil {
2019-02-15 21:39:24 +00:00
return err
}
} else {
if err := c . startDependencies ( ctx ) ; err != nil {
return err
}
}
defer func ( ) {
2020-07-09 17:50:01 +00:00
if retErr != nil {
if err := c . cleanup ( ctx ) ; err != nil {
logrus . Errorf ( "error cleaning up container %s: %v" , c . ID ( ) , err )
2019-02-15 21:39:24 +00:00
}
}
} ( )
if err := c . prepare ( ) ; err != nil {
return err
}
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateStopped {
2019-02-15 21:39:24 +00:00
// Reinitialize the container if we need to
2019-04-01 23:20:03 +00:00
if err := c . reinit ( ctx , false ) ; err != nil {
2019-02-15 21:39:24 +00:00
return err
}
2019-10-28 17:09:01 +00:00
} else if c . ensureState ( define . ContainerStateConfigured , define . ContainerStateExited ) {
2019-02-15 21:39:24 +00:00
// Or initialize it if necessary
2019-04-01 23:20:03 +00:00
if err := c . init ( ctx , false ) ; err != nil {
2019-02-15 21:39:24 +00:00
return err
}
}
return nil
}
// checks dependencies are running and prints a helpful message
2020-06-11 18:40:38 +00:00
func ( c * Container ) checkDependenciesAndHandleError ( ) error {
2019-02-15 21:39:24 +00:00
notRunning , err := c . checkDependenciesRunning ( )
if err != nil {
return errors . Wrapf ( err , "error checking dependencies for container %s" , c . ID ( ) )
}
if len ( notRunning ) > 0 {
depString := strings . Join ( notRunning , "," )
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "some dependencies of container %s are not started: %s" , c . ID ( ) , depString )
2019-02-15 21:39:24 +00:00
}
return nil
}
// Recursively start all dependencies of a container so the container can be started.
func ( c * Container ) startDependencies ( ctx context . Context ) error {
depCtrIDs := c . Dependencies ( )
if len ( depCtrIDs ) == 0 {
return nil
}
depVisitedCtrs := make ( map [ string ] * Container )
if err := c . getAllDependencies ( depVisitedCtrs ) ; err != nil {
return errors . Wrapf ( err , "error starting dependency for container %s" , c . ID ( ) )
}
// Because of how Go handles passing slices through functions, a slice cannot grow between function calls
// without clunky syntax. Circumnavigate this by translating the map to a slice for buildContainerGraph
depCtrs := make ( [ ] * Container , 0 )
for _ , ctr := range depVisitedCtrs {
depCtrs = append ( depCtrs , ctr )
}
// Build a dependency graph of containers
2019-08-13 11:06:37 +00:00
graph , err := BuildContainerGraph ( depCtrs )
2019-02-15 21:39:24 +00:00
if err != nil {
return errors . Wrapf ( err , "error generating dependency graph for container %s" , c . ID ( ) )
}
// If there are no containers without dependencies, we can't start
// Error out
if len ( graph . noDepNodes ) == 0 {
2019-02-19 14:27:26 +00:00
// we have no dependencies that need starting, go ahead and return
if len ( graph . nodes ) == 0 {
return nil
}
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrNoSuchCtr , "All dependencies have dependencies of %s" , c . ID ( ) )
2019-02-15 21:39:24 +00:00
}
2019-02-19 14:27:26 +00:00
ctrErrors := make ( map [ string ] error )
ctrsVisited := make ( map [ string ] bool )
2019-02-15 21:39:24 +00:00
// Traverse the graph beginning at nodes with no dependencies
for _ , node := range graph . noDepNodes {
startNode ( ctx , node , false , ctrErrors , ctrsVisited , true )
}
if len ( ctrErrors ) > 0 {
logrus . Errorf ( "error starting some container dependencies" )
for _ , e := range ctrErrors {
logrus . Errorf ( "%q" , e )
}
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrInternal , "error starting some containers" )
2019-02-15 21:39:24 +00:00
}
return nil
}
// getAllDependencies is a precursor to starting dependencies.
// To start a container with all of its dependencies, we need to recursively find all dependencies
// a container has, as well as each of those containers' dependencies, and so on
2020-12-21 22:48:43 +00:00
// To do so, keep track of containers already visited (so there aren't redundant state lookups),
2019-02-15 21:39:24 +00:00
// and recursively search until we have reached the leafs of every dependency node.
2020-12-21 22:48:43 +00:00
// Since we need to start all dependencies for our original container to successfully start, we propagate any errors
2019-02-15 21:39:24 +00:00
// in looking up dependencies.
// Note: this function is currently meant as a robust solution to a narrow problem: start an infra-container when
// a container in the pod is run. It has not been tested for performance past one level, so expansion of recursive start
// must be tested first.
func ( c * Container ) getAllDependencies ( visited map [ string ] * Container ) error {
depIDs := c . Dependencies ( )
if len ( depIDs ) == 0 {
return nil
}
for _ , depID := range depIDs {
if _ , ok := visited [ depID ] ; ! ok {
2019-04-12 14:21:45 +00:00
dep , err := c . runtime . state . Container ( depID )
2019-02-15 21:39:24 +00:00
if err != nil {
return err
}
2019-02-19 14:27:26 +00:00
status , err := dep . State ( )
if err != nil {
2019-02-15 21:39:24 +00:00
return err
}
2019-02-19 14:27:26 +00:00
// if the dependency is already running, we can assume its dependencies are also running
// so no need to add them to those we need to start
2019-06-25 13:40:19 +00:00
if status != define . ContainerStateRunning {
2019-02-19 14:27:26 +00:00
visited [ depID ] = dep
if err := dep . getAllDependencies ( visited ) ; err != nil {
return err
}
}
2019-02-15 21:39:24 +00:00
}
}
return nil
}
2018-04-02 16:23:19 +00:00
// Check if a container's dependencies are running
// Returns a []string containing the IDs of dependencies that are not running
func ( c * Container ) checkDependenciesRunning ( ) ( [ ] string , error ) {
2018-04-01 00:53:05 +00:00
deps := c . Dependencies ( )
2018-04-02 16:23:19 +00:00
notRunning := [ ] string { }
// We were not passed a set of dependency containers
// Make it ourselves
2018-04-01 00:53:05 +00:00
depCtrs := make ( map [ string ] * Container , len ( deps ) )
for _ , dep := range deps {
// Get the dependency container
depCtr , err := c . runtime . state . Container ( dep )
if err != nil {
2018-04-02 16:23:19 +00:00
return nil , errors . Wrapf ( err , "error retrieving dependency %s of container %s from state" , dep , c . ID ( ) )
2018-04-01 00:53:05 +00:00
}
// Check the status
state , err := depCtr . State ( )
if err != nil {
2018-04-02 16:23:19 +00:00
return nil , errors . Wrapf ( err , "error retrieving state of dependency %s of container %s" , dep , c . ID ( ) )
2018-04-01 00:53:05 +00:00
}
2019-06-25 13:40:19 +00:00
if state != define . ContainerStateRunning {
2018-04-02 16:23:19 +00:00
notRunning = append ( notRunning , dep )
2018-04-01 00:53:05 +00:00
}
depCtrs [ dep ] = depCtr
2018-04-02 16:23:19 +00:00
}
return notRunning , nil
}
2018-04-20 16:59:19 +00:00
func ( c * Container ) completeNetworkSetup ( ) error {
2020-02-19 20:14:41 +00:00
var outResolvConf [ ] string
2018-12-06 19:56:57 +00:00
netDisabled , err := c . NetworkDisabled ( )
if err != nil {
return err
}
if ! c . config . PostConfigureNetNS || netDisabled {
2018-04-20 16:59:19 +00:00
return nil
}
if err := c . syncContainer ( ) ; err != nil {
return err
}
2021-02-18 13:53:53 +00:00
if c . config . NetMode . IsSlirp4netns ( ) {
2020-08-26 09:07:51 +00:00
return c . runtime . setupSlirp4netns ( c )
2018-07-25 13:15:13 +00:00
}
2020-02-19 20:14:41 +00:00
if err := c . runtime . setupNetNS ( c ) ; err != nil {
return err
}
state := c . state
// collect any dns servers that cni tells us to use (dnsname)
for _ , cni := range state . NetworkStatus {
if cni . DNS . Nameservers != nil {
for _ , server := range cni . DNS . Nameservers {
outResolvConf = append ( outResolvConf , fmt . Sprintf ( "nameserver %s" , server ) )
}
}
}
2020-10-06 11:04:18 +00:00
// check if we have a bindmount for /etc/hosts
if hostsBindMount , ok := state . BindMounts [ "/etc/hosts" ] ; ok && len ( c . cniHosts ( ) ) > 0 {
ctrHostPath := filepath . Join ( c . state . RunDir , "hosts" )
if hostsBindMount == ctrHostPath {
// read the existing hosts
b , err := ioutil . ReadFile ( hostsBindMount )
if err != nil {
return err
}
if err := ioutil . WriteFile ( hostsBindMount , append ( b , [ ] byte ( c . cniHosts ( ) ) ... ) , 0644 ) ; err != nil {
return err
}
}
}
2020-02-19 20:14:41 +00:00
// check if we have a bindmount for resolv.conf
resolvBindMount := state . BindMounts [ "/etc/resolv.conf" ]
if len ( outResolvConf ) < 1 || resolvBindMount == "" || len ( c . config . NetNsCtr ) > 0 {
return nil
}
// read the existing resolv.conf
b , err := ioutil . ReadFile ( resolvBindMount )
if err != nil {
return err
}
for _ , line := range strings . Split ( string ( b ) , "\n" ) {
2020-03-07 15:30:44 +00:00
// only keep things that don't start with nameserver from the old
2020-02-19 20:14:41 +00:00
// resolv.conf file
if ! strings . HasPrefix ( line , "nameserver" ) {
outResolvConf = append ( [ ] string { line } , outResolvConf ... )
}
}
// write and return
return ioutil . WriteFile ( resolvBindMount , [ ] byte ( strings . Join ( outResolvConf , "\n" ) ) , 0644 )
2018-04-20 16:59:19 +00:00
}
2020-10-06 11:04:18 +00:00
func ( c * Container ) cniHosts ( ) string {
var hosts string
if len ( c . state . NetworkStatus ) > 0 && len ( c . state . NetworkStatus [ 0 ] . IPs ) > 0 {
ipAddress := strings . Split ( c . state . NetworkStatus [ 0 ] . IPs [ 0 ] . Address . String ( ) , "/" ) [ 0 ]
hosts += fmt . Sprintf ( "%s\t%s %s\n" , ipAddress , c . Hostname ( ) , c . Config ( ) . Name )
}
return hosts
}
2018-04-02 16:23:19 +00:00
// Initialize a container, creating it in the runtime
2019-04-01 23:20:03 +00:00
func ( c * Container ) init ( ctx context . Context , retainRetries bool ) error {
2019-09-20 05:22:19 +00:00
// Unconditionally remove conmon temporary files.
// We've been running into far too many issues where they block startup.
if err := c . removeConmonFiles ( ) ; err != nil {
return err
}
2019-07-03 20:37:17 +00:00
// Generate the OCI newSpec
newSpec , err := c . generateSpec ( ctx )
2018-03-12 19:32:10 +00:00
if err != nil {
return err
}
2019-07-03 20:37:17 +00:00
// Save the OCI newSpec to disk
if err := c . saveSpec ( newSpec ) ; err != nil {
2018-03-12 19:32:10 +00:00
return err
}
2020-06-24 12:44:28 +00:00
for _ , v := range c . config . NamedVolumes {
if err := c . chownVolume ( v . Name ) ; err != nil {
return err
}
}
2019-07-01 17:55:03 +00:00
// With the spec complete, do an OCI create
2019-10-08 17:53:36 +00:00
if err := c . ociRuntime . CreateContainer ( c , nil ) ; err != nil {
// Fedora 31 is carrying a patch to display improved error
// messages to better handle the V2 transition. This is NOT
// upstream in any OCI runtime.
// TODO: Remove once runc supports cgroupsv2
2019-10-08 00:11:42 +00:00
if strings . Contains ( err . Error ( ) , "this version of runc doesn't work on cgroups v2" ) {
2019-10-08 17:53:36 +00:00
logrus . Errorf ( "oci runtime %q does not support CGroups V2: use system migrate to mitigate" , c . ociRuntime . Name ( ) )
2019-10-08 00:11:42 +00:00
}
2018-03-12 19:32:10 +00:00
return err
}
logrus . Debugf ( "Created container %s in OCI runtime" , c . ID ( ) )
2020-05-13 13:17:36 +00:00
// Remove any exec sessions leftover from a potential prior run.
if len ( c . state . ExecSessions ) > 0 {
if err := c . runtime . state . RemoveContainerExecSessions ( c ) ; err != nil {
2020-05-14 21:01:49 +00:00
logrus . Errorf ( "Error removing container %s exec sessions from DB: %v" , c . ID ( ) , err )
2020-05-13 13:17:36 +00:00
}
c . state . ExecSessions = make ( map [ string ] * ExecSession )
}
2018-09-23 22:04:29 +00:00
c . state . ExitCode = 0
c . state . Exited = false
2019-06-25 13:40:19 +00:00
c . state . State = define . ContainerStateCreated
2019-04-01 17:30:28 +00:00
c . state . StoppedByUser = false
2019-04-01 19:22:32 +00:00
c . state . RestartPolicyMatch = false
2018-03-12 19:32:10 +00:00
2019-04-01 23:20:03 +00:00
if ! retainRetries {
c . state . RestartCount = 0
}
2018-04-20 16:59:19 +00:00
if err := c . save ( ) ; err != nil {
return err
}
2019-03-14 20:14:18 +00:00
if c . config . HealthCheckConfig != nil {
if err := c . createTimer ( ) ; err != nil {
logrus . Error ( err )
}
}
2019-02-28 20:15:56 +00:00
defer c . newContainerEvent ( events . Init )
2018-04-20 16:59:19 +00:00
return c . completeNetworkSetup ( )
2018-03-12 19:32:10 +00:00
}
2018-09-23 22:04:29 +00:00
// Clean up a container in the OCI runtime.
// Deletes the container in the runtime, and resets its state to Exited.
// The container can be restarted cleanly after this.
func ( c * Container ) cleanupRuntime ( ctx context . Context ) error {
2019-04-29 14:37:50 +00:00
// If the container is not ContainerStateStopped or
// ContainerStateCreated, do nothing.
2019-10-28 17:09:01 +00:00
if ! c . ensureState ( define . ContainerStateStopped , define . ContainerStateCreated ) {
2018-09-23 22:04:29 +00:00
return nil
}
2018-03-15 14:14:57 +00:00
2018-03-14 19:14:49 +00:00
// If necessary, delete attach and ctl files
if err := c . removeConmonFiles ( ) ; err != nil {
return err
}
2018-05-31 18:47:17 +00:00
if err := c . delete ( ctx ) ; err != nil {
return err
2018-03-14 19:14:49 +00:00
}
2018-05-31 18:47:17 +00:00
2019-04-29 14:37:50 +00:00
// If we were Stopped, we are now Exited, as we've removed ourself
// from the runtime.
// If we were Created, we are now Configured.
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateStopped {
c . state . State = define . ContainerStateExited
} else if c . state . State == define . ContainerStateCreated {
c . state . State = define . ContainerStateConfigured
2019-04-29 14:37:50 +00:00
}
2018-10-02 17:39:33 +00:00
if c . valid {
if err := c . save ( ) ; err != nil {
return err
}
2018-03-14 19:14:49 +00:00
}
2018-03-15 14:14:57 +00:00
logrus . Debugf ( "Successfully cleaned up container %s" , c . ID ( ) )
2018-09-23 22:04:29 +00:00
return nil
}
// Reinitialize a container.
// Deletes and recreates a container in the runtime.
// Should only be done on ContainerStateStopped containers.
// Not necessary for ContainerStateExited - the container has already been
// removed from the runtime, so init() can proceed freely.
2019-04-01 23:20:03 +00:00
func ( c * Container ) reinit ( ctx context . Context , retainRetries bool ) error {
2018-09-23 22:04:29 +00:00
logrus . Debugf ( "Recreating container %s in OCI runtime" , c . ID ( ) )
if err := c . cleanupRuntime ( ctx ) ; err != nil {
return err
}
2018-03-14 19:14:49 +00:00
// Initialize the container again
2019-04-01 23:20:03 +00:00
return c . init ( ctx , retainRetries )
2018-03-14 19:14:49 +00:00
}
2018-03-12 19:32:10 +00:00
// Initialize (if necessary) and start a container
// Performs all necessary steps to start a container that is not running
// Does not lock or check validity
2020-07-09 17:50:01 +00:00
func ( c * Container ) initAndStart ( ctx context . Context ) ( retErr error ) {
2018-03-12 19:32:10 +00:00
// If we are ContainerStateUnknown, throw an error
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateUnknown {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "container %s is in an unknown state" , c . ID ( ) )
Add ContainerStateRemoving
When Libpod removes a container, there is the possibility that
removal will not fully succeed. The most notable problems are
storage issues, where the container cannot be removed from
c/storage.
When this occurs, we were faced with a choice. We can keep the
container in the state, appearing in `podman ps` and available for
other API operations, but likely unable to do any of them as it's
been partially removed. Or we can remove it very early and clean
up after it's already gone. We have, until now, used the second
approach.
The problem that arises is intermittent problems removing
storage. We end up removing a container, failing to remove its
storage, and ending up with a container permanently stuck in
c/storage that we can't remove with the normal Podman CLI, can't
use the name of, and generally can't interact with. A notable
cause is when Podman is hit by a SIGKILL midway through removal,
which can consistently cause `podman rm` to fail to remove
storage.
We now add a new state for containers that are in the process of
being removed, ContainerStateRemoving. We set this at the
beginning of the removal process. It notifies Podman that the
container cannot be used anymore, but preserves it in the DB
until it is fully removed. This will allow Remove to be run on
these containers again, which should successfully remove storage
if it fails.
Fixes #3906
Signed-off-by: Matthew Heon <mheon@redhat.com>
2019-11-11 14:52:13 +00:00
} else if c . state . State == define . ContainerStateRemoving {
return errors . Wrapf ( define . ErrCtrStateInvalid , "cannot start container %s as it is being removed" , c . ID ( ) )
2018-03-12 19:32:10 +00:00
}
// If we are running, do nothing
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateRunning {
2018-03-12 19:32:10 +00:00
return nil
}
// If we are paused, throw an error
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStatePaused {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "cannot start paused container %s" , c . ID ( ) )
2018-03-12 19:32:10 +00:00
}
defer func ( ) {
2020-07-09 17:50:01 +00:00
if retErr != nil {
if err := c . cleanup ( ctx ) ; err != nil {
logrus . Errorf ( "error cleaning up container %s: %v" , c . ID ( ) , err )
2018-03-12 19:32:10 +00:00
}
}
} ( )
2018-11-07 16:44:33 +00:00
if err := c . prepare ( ) ; err != nil {
return err
}
2018-03-13 15:49:24 +00:00
// If we are ContainerStateStopped we need to remove from runtime
// And reset to ContainerStateConfigured
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateStopped {
2018-03-15 14:14:57 +00:00
logrus . Debugf ( "Recreating container %s in OCI runtime" , c . ID ( ) )
2019-04-01 23:20:03 +00:00
if err := c . reinit ( ctx , false ) ; err != nil {
2018-03-13 15:49:24 +00:00
return err
}
2019-10-28 17:09:01 +00:00
} else if c . ensureState ( define . ContainerStateConfigured , define . ContainerStateExited ) {
2019-04-01 23:20:03 +00:00
if err := c . init ( ctx , false ) ; err != nil {
2018-03-12 19:32:10 +00:00
return err
}
}
// Now start the container
return c . start ( )
}
// Internal, non-locking function to start a container
func ( c * Container ) start ( ) error {
2019-01-02 17:11:50 +00:00
if c . config . Spec . Process != nil {
logrus . Debugf ( "Starting container %s with command %v" , c . ID ( ) , c . config . Spec . Process . Args )
}
2019-10-08 17:53:36 +00:00
if err := c . ociRuntime . StartContainer ( c ) ; err != nil {
2018-03-12 19:32:10 +00:00
return err
}
logrus . Debugf ( "Started container %s" , c . ID ( ) )
2019-06-25 13:40:19 +00:00
c . state . State = define . ContainerStateRunning
2018-03-12 19:32:10 +00:00
2020-06-19 13:29:34 +00:00
if c . config . SdNotifyMode != define . SdNotifyModeIgnore {
payload := fmt . Sprintf ( "MAINPID=%d" , c . state . ConmonPID )
if c . config . SdNotifyMode == define . SdNotifyModeConmon {
payload += "\n"
payload += daemon . SdNotifyReady
}
if sent , err := daemon . SdNotify ( false , payload ) ; err != nil {
logrus . Errorf ( "Error notifying systemd of Conmon PID: %s" , err . Error ( ) )
} else if sent {
logrus . Debugf ( "Notify sent successfully" )
}
}
2019-03-14 20:14:18 +00:00
if c . config . HealthCheckConfig != nil {
2020-05-20 19:43:01 +00:00
if err := c . updateHealthStatus ( define . HealthCheckStarting ) ; err != nil {
2019-03-14 20:14:18 +00:00
logrus . Error ( err )
}
if err := c . startTimer ( ) ; err != nil {
logrus . Error ( err )
}
}
2019-03-12 20:12:09 +00:00
defer c . newContainerEvent ( events . Start )
2018-03-12 19:32:10 +00:00
return c . save ( )
}
2018-01-29 16:59:33 +00:00
// Internal, non-locking function to stop container
2019-12-18 14:42:01 +00:00
func ( c * Container ) stop ( timeout uint ) error {
2019-03-05 23:11:28 +00:00
logrus . Debugf ( "Stopping ctr %s (timeout %d)" , c . ID ( ) , timeout )
2018-01-29 16:59:33 +00:00
2019-12-18 14:42:01 +00:00
// If the container is running in a PID Namespace, then killing the
// primary pid is enough to kill the container. If it is not running in
// a pid namespace then the OCI Runtime needs to kill ALL processes in
// the containers cgroup in order to make sure the container is stopped.
all := ! c . hasNamespace ( spec . PIDNamespace )
2019-10-08 17:53:36 +00:00
// We can't use --all if CGroups aren't present.
// Rootless containers with CGroups v1 and NoCgroups are both cases
// where this can happen.
if all {
if c . config . NoCgroups {
all = false
} else if rootless . IsRootless ( ) {
// Only do this check if we need to
unified , err := cgroups . IsCgroup2UnifiedMode ( )
if err != nil {
return err
}
if ! unified {
all = false
}
}
}
Ensure Conmon is alive before waiting for exit file
This came out of a conversation with Valentin about
systemd-managed Podman. He discovered that unit files did not
properly handle cases where Conmon was dead - the ExecStopPost
`podman rm --force` line was not actually removing the container,
but interestingly, adding a `podman cleanup --rm` line would
remove it. Both of these commands do the same thing (minus the
`podman cleanup --rm` command not force-removing running
containers).
Without a running Conmon instance, the container process is still
running (assuming you killed Conmon with SIGKILL and it had no
chance to kill the container it managed), but you can still kill
the container itself with `podman stop` - Conmon is not involved,
only the OCI Runtime. (`podman rm --force` and `podman stop` use
the same code to kill the container). The problem comes when we
want to get the container's exit code - we expect Conmon to make
us an exit file, which it's obviously not going to do, being
dead. The first `podman rm` would fail because of this, but
importantly, it would (after failing to retrieve the exit code
correctly) set container status to Exited, so that the second
`podman cleanup` process would succeed.
To make sure the first `podman rm --force` succeeds, we need to
catch the case where Conmon is already dead, and instead of
waiting for an exit file that will never come, immediately set
the Stopped state and remove an error that can be caught and
handled.
Signed-off-by: Matthew Heon <mheon@redhat.com>
2020-06-08 17:34:12 +00:00
// Check if conmon is still alive.
// If it is not, we won't be getting an exit file.
conmonAlive , err := c . ociRuntime . CheckConmonRunning ( c )
if err != nil {
return err
}
2021-01-07 12:13:36 +00:00
// Set the container state to "stopping" and unlock the container
// before handing it over to conmon to unblock other commands. #8501
// demonstrates nicely that a high stop timeout will block even simple
// commands such as `podman ps` from progressing if the container lock
// is held when busy-waiting for the container to be stopped.
c . state . State = define . ContainerStateStopping
if err := c . save ( ) ; err != nil {
return errors . Wrapf ( err , "error saving container %s state before stopping" , c . ID ( ) )
}
if ! c . batched {
c . lock . Unlock ( )
}
2021-03-04 21:22:41 +00:00
stopErr := c . ociRuntime . StopContainer ( c , timeout , all )
2018-01-29 16:59:33 +00:00
2021-01-07 12:13:36 +00:00
if ! c . batched {
c . lock . Lock ( )
if err := c . syncContainer ( ) ; err != nil {
switch errors . Cause ( err ) {
// If the container has already been removed (e.g., via
// the cleanup process), there's nothing left to do.
case define . ErrNoSuchCtr , define . ErrCtrRemoved :
2021-03-04 21:22:41 +00:00
return stopErr
2021-01-07 12:13:36 +00:00
default :
2021-03-04 21:22:41 +00:00
if stopErr != nil {
logrus . Errorf ( "Error syncing container %s status: %v" , c . ID ( ) , err )
return stopErr
}
2021-01-07 12:13:36 +00:00
return err
}
}
}
2021-03-04 21:22:41 +00:00
// We have to check stopErr *after* we lock again - otherwise, we have a
2021-04-21 09:38:34 +00:00
// change of panicking on a double-unlock. Ref: GH Issue 9615
2021-03-04 21:22:41 +00:00
if stopErr != nil {
return stopErr
}
2021-01-07 12:13:36 +00:00
// Since we're now subject to a race condition with other processes who
// may have altered the state (and other data), let's check if the
// state has changed. If so, we should return immediately and log a
// warning.
if c . state . State != define . ContainerStateStopping {
logrus . Warnf (
"Container %q state changed from %q to %q while waiting for it to be stopped: discontinuing stop procedure as another process interfered" ,
c . ID ( ) , define . ContainerStateStopping , c . state . State ,
)
return nil
}
Ensure Conmon is alive before waiting for exit file
This came out of a conversation with Valentin about
systemd-managed Podman. He discovered that unit files did not
properly handle cases where Conmon was dead - the ExecStopPost
`podman rm --force` line was not actually removing the container,
but interestingly, adding a `podman cleanup --rm` line would
remove it. Both of these commands do the same thing (minus the
`podman cleanup --rm` command not force-removing running
containers).
Without a running Conmon instance, the container process is still
running (assuming you killed Conmon with SIGKILL and it had no
chance to kill the container it managed), but you can still kill
the container itself with `podman stop` - Conmon is not involved,
only the OCI Runtime. (`podman rm --force` and `podman stop` use
the same code to kill the container). The problem comes when we
want to get the container's exit code - we expect Conmon to make
us an exit file, which it's obviously not going to do, being
dead. The first `podman rm` would fail because of this, but
importantly, it would (after failing to retrieve the exit code
correctly) set container status to Exited, so that the second
`podman cleanup` process would succeed.
To make sure the first `podman rm --force` succeeds, we need to
catch the case where Conmon is already dead, and instead of
waiting for an exit file that will never come, immediately set
the Stopped state and remove an error that can be caught and
handled.
Signed-off-by: Matthew Heon <mheon@redhat.com>
2020-06-08 17:34:12 +00:00
c . newContainerEvent ( events . Stop )
2019-07-02 23:10:51 +00:00
c . state . PID = 0
c . state . ConmonPID = 0
2019-04-01 17:30:28 +00:00
c . state . StoppedByUser = true
Ensure Conmon is alive before waiting for exit file
This came out of a conversation with Valentin about
systemd-managed Podman. He discovered that unit files did not
properly handle cases where Conmon was dead - the ExecStopPost
`podman rm --force` line was not actually removing the container,
but interestingly, adding a `podman cleanup --rm` line would
remove it. Both of these commands do the same thing (minus the
`podman cleanup --rm` command not force-removing running
containers).
Without a running Conmon instance, the container process is still
running (assuming you killed Conmon with SIGKILL and it had no
chance to kill the container it managed), but you can still kill
the container itself with `podman stop` - Conmon is not involved,
only the OCI Runtime. (`podman rm --force` and `podman stop` use
the same code to kill the container). The problem comes when we
want to get the container's exit code - we expect Conmon to make
us an exit file, which it's obviously not going to do, being
dead. The first `podman rm` would fail because of this, but
importantly, it would (after failing to retrieve the exit code
correctly) set container status to Exited, so that the second
`podman cleanup` process would succeed.
To make sure the first `podman rm --force` succeeds, we need to
catch the case where Conmon is already dead, and instead of
waiting for an exit file that will never come, immediately set
the Stopped state and remove an error that can be caught and
handled.
Signed-off-by: Matthew Heon <mheon@redhat.com>
2020-06-08 17:34:12 +00:00
if ! conmonAlive {
2020-09-11 10:12:58 +00:00
// Conmon is dead, so we can't expect an exit code.
Ensure Conmon is alive before waiting for exit file
This came out of a conversation with Valentin about
systemd-managed Podman. He discovered that unit files did not
properly handle cases where Conmon was dead - the ExecStopPost
`podman rm --force` line was not actually removing the container,
but interestingly, adding a `podman cleanup --rm` line would
remove it. Both of these commands do the same thing (minus the
`podman cleanup --rm` command not force-removing running
containers).
Without a running Conmon instance, the container process is still
running (assuming you killed Conmon with SIGKILL and it had no
chance to kill the container it managed), but you can still kill
the container itself with `podman stop` - Conmon is not involved,
only the OCI Runtime. (`podman rm --force` and `podman stop` use
the same code to kill the container). The problem comes when we
want to get the container's exit code - we expect Conmon to make
us an exit file, which it's obviously not going to do, being
dead. The first `podman rm` would fail because of this, but
importantly, it would (after failing to retrieve the exit code
correctly) set container status to Exited, so that the second
`podman cleanup` process would succeed.
To make sure the first `podman rm --force` succeeds, we need to
catch the case where Conmon is already dead, and instead of
waiting for an exit file that will never come, immediately set
the Stopped state and remove an error that can be caught and
handled.
Signed-off-by: Matthew Heon <mheon@redhat.com>
2020-06-08 17:34:12 +00:00
c . state . ExitCode = - 1
c . state . FinishedTime = time . Now ( )
c . state . State = define . ContainerStateStopped
if err := c . save ( ) ; err != nil {
logrus . Errorf ( "Error saving container %s status: %v" , c . ID ( ) , err )
}
return errors . Wrapf ( define . ErrConmonDead , "container %s conmon process missing, cannot retrieve exit code" , c . ID ( ) )
}
2019-04-01 17:30:28 +00:00
if err := c . save ( ) ; err != nil {
return errors . Wrapf ( err , "error saving container %s state after stopping" , c . ID ( ) )
}
2018-10-19 20:02:14 +00:00
// Wait until we have an exit file, and sync once we do
2019-07-31 21:22:08 +00:00
if err := c . waitForExitFileAndSync ( ) ; err != nil {
return err
}
return nil
2018-01-29 16:59:33 +00:00
}
2018-06-21 13:45:03 +00:00
// Internal, non-locking function to pause a container
func ( c * Container ) pause ( ) error {
2019-07-15 20:44:56 +00:00
if c . config . NoCgroups {
return errors . Wrapf ( define . ErrNoCgroups , "cannot pause without using CGroups" )
}
2019-09-12 16:37:22 +00:00
if rootless . IsRootless ( ) {
cgroupv2 , err := cgroups . IsCgroup2UnifiedMode ( )
if err != nil {
return errors . Wrap ( err , "failed to determine cgroupversion" )
}
if ! cgroupv2 {
return errors . Wrap ( define . ErrNoCgroups , "can not pause containers on rootless containers with cgroup V1" )
}
}
2019-10-08 17:53:36 +00:00
if err := c . ociRuntime . PauseContainer ( c ) ; err != nil {
2020-01-10 15:47:19 +00:00
// TODO when using docker-py there is some sort of race/incompatibility here
return err
2018-06-21 13:45:03 +00:00
}
logrus . Debugf ( "Paused container %s" , c . ID ( ) )
2019-06-25 13:40:19 +00:00
c . state . State = define . ContainerStatePaused
2018-06-21 13:45:03 +00:00
return c . save ( )
}
// Internal, non-locking function to unpause a container
func ( c * Container ) unpause ( ) error {
2019-07-15 20:44:56 +00:00
if c . config . NoCgroups {
return errors . Wrapf ( define . ErrNoCgroups , "cannot unpause without using CGroups" )
}
2019-10-08 17:53:36 +00:00
if err := c . ociRuntime . UnpauseContainer ( c ) ; err != nil {
2020-01-10 15:47:19 +00:00
// TODO when using docker-py there is some sort of race/incompatibility here
return err
2018-06-21 13:45:03 +00:00
}
logrus . Debugf ( "Unpaused container %s" , c . ID ( ) )
2019-06-25 13:40:19 +00:00
c . state . State = define . ContainerStateRunning
2018-06-21 13:45:03 +00:00
return c . save ( )
}
2018-07-23 19:56:12 +00:00
// Internal, non-locking function to restart a container
2020-07-09 17:50:01 +00:00
func ( c * Container ) restartWithTimeout ( ctx context . Context , timeout uint ) ( retErr error ) {
2019-10-28 17:09:01 +00:00
if ! c . ensureState ( define . ContainerStateConfigured , define . ContainerStateCreated , define . ContainerStateRunning , define . ContainerStateStopped , define . ContainerStateExited ) {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "unable to restart a container in a paused or unknown state" )
2018-07-23 19:56:12 +00:00
}
2019-04-03 18:17:02 +00:00
c . newContainerEvent ( events . Restart )
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateRunning {
2019-08-02 14:27:50 +00:00
conmonPID := c . state . ConmonPID
2019-12-18 14:42:01 +00:00
if err := c . stop ( timeout ) ; err != nil {
2018-07-23 19:56:12 +00:00
return err
}
2019-08-02 14:27:50 +00:00
// Old versions of conmon have a bug where they create the exit file before
// closing open file descriptors causing a race condition when restarting
// containers with open ports since we cannot bind the ports as they're not
// yet closed by conmon.
//
// Killing the old conmon PID is ~okay since it forces the FDs of old conmons
// to be closed, while it's a NOP for newer versions which should have
// exited already.
if conmonPID != 0 {
// Ignore errors from FindProcess() as conmon could already have exited.
p , err := os . FindProcess ( conmonPID )
if p != nil && err == nil {
if err = p . Kill ( ) ; err != nil {
logrus . Debugf ( "error killing conmon process: %v" , err )
}
}
}
2020-02-02 17:51:03 +00:00
// Ensure we tear down the container network so it will be
// recreated - otherwise, behavior of restart differs from stop
// and start
if err := c . cleanupNetwork ( ) ; err != nil {
return err
}
2018-07-23 19:56:12 +00:00
}
defer func ( ) {
2020-07-09 17:50:01 +00:00
if retErr != nil {
if err := c . cleanup ( ctx ) ; err != nil {
logrus . Errorf ( "error cleaning up container %s: %v" , c . ID ( ) , err )
2018-07-23 19:56:12 +00:00
}
}
} ( )
2018-11-07 16:44:33 +00:00
if err := c . prepare ( ) ; err != nil {
return err
}
2018-07-23 19:56:12 +00:00
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateStopped {
2018-07-23 19:56:12 +00:00
// Reinitialize the container if we need to
2019-04-01 23:20:03 +00:00
if err := c . reinit ( ctx , false ) ; err != nil {
2018-07-23 19:56:12 +00:00
return err
}
2019-06-25 13:40:19 +00:00
} else if c . state . State == define . ContainerStateConfigured ||
c . state . State == define . ContainerStateExited {
2018-09-23 22:04:29 +00:00
// Initialize the container
2019-04-01 23:20:03 +00:00
if err := c . init ( ctx , false ) ; err != nil {
2018-07-23 19:56:12 +00:00
return err
}
}
return c . start ( )
}
2018-01-18 16:37:41 +00:00
// mountStorage sets up the container's root filesystem
// It mounts the image and any other requested mounts
// TODO: Add ability to override mount label so we can use this for Mount() too
// TODO: Can we use this for export? Copying SHM into the export might not be
// good
2020-01-13 12:01:45 +00:00
func ( c * Container ) mountStorage ( ) ( _ string , deferredErr error ) {
2018-10-17 18:43:36 +00:00
var err error
2018-01-18 16:37:41 +00:00
// Container already mounted, nothing to do
if c . state . Mounted {
2018-10-17 18:43:36 +00:00
return c . state . Mountpoint , nil
2018-01-18 16:37:41 +00:00
}
2018-11-08 11:14:46 +00:00
mounted , err := mount . Mounted ( c . config . ShmDir )
if err != nil {
return "" , errors . Wrapf ( err , "unable to determine if %q is mounted" , c . config . ShmDir )
}
2018-06-01 11:10:14 +00:00
2018-12-24 11:55:24 +00:00
if ! mounted && ! MountExists ( c . config . Spec . Mounts , "/dev/shm" ) {
2018-11-08 11:14:46 +00:00
shmOptions := fmt . Sprintf ( "mode=1777,size=%d" , c . config . ShmSize )
if err := c . mountSHM ( shmOptions ) ; err != nil {
return "" , err
}
2018-04-24 00:42:53 +00:00
if err := os . Chown ( c . config . ShmDir , c . RootUID ( ) , c . RootGID ( ) ) ; err != nil {
2018-10-17 18:43:36 +00:00
return "" , errors . Wrapf ( err , "failed to chown %s" , c . config . ShmDir )
2018-04-24 00:42:53 +00:00
}
2019-09-03 19:03:44 +00:00
defer func ( ) {
2020-01-13 12:01:45 +00:00
if deferredErr != nil {
2019-09-03 19:03:44 +00:00
if err := c . unmountSHM ( c . config . ShmDir ) ; err != nil {
logrus . Errorf ( "Error unmounting SHM for container %s after mount error: %v" , c . ID ( ) , err )
}
}
} ( )
}
2019-09-06 17:52:13 +00:00
// We need to mount the container before volumes - to ensure the copyup
// works properly.
mountPoint := c . config . Rootfs
if mountPoint == "" {
mountPoint , err = c . mount ( )
if err != nil {
return "" , err
}
defer func ( ) {
2020-01-13 12:01:45 +00:00
if deferredErr != nil {
2019-09-06 17:52:13 +00:00
if err := c . unmount ( false ) ; err != nil {
logrus . Errorf ( "Error unmounting container %s after mount error: %v" , c . ID ( ) , err )
}
}
} ( )
}
2019-09-03 19:03:44 +00:00
// Request a mount of all named volumes
for _ , v := range c . config . NamedVolumes {
2019-09-06 17:52:13 +00:00
vol , err := c . mountNamedVolume ( v , mountPoint )
2019-09-03 19:03:44 +00:00
if err != nil {
2019-09-06 17:52:13 +00:00
return "" , err
2019-09-03 19:03:44 +00:00
}
2019-09-06 17:52:13 +00:00
defer func ( ) {
2020-01-13 12:01:45 +00:00
if deferredErr == nil {
2019-09-06 17:52:13 +00:00
return
}
2019-09-03 19:03:44 +00:00
vol . lock . Lock ( )
2019-09-06 17:52:13 +00:00
if err := vol . unmount ( false ) ; err != nil {
logrus . Errorf ( "Error unmounting volume %s after error mounting container %s: %v" , vol . Name ( ) , c . ID ( ) , err )
2019-09-03 19:03:44 +00:00
}
vol . lock . Unlock ( )
2019-09-06 17:52:13 +00:00
} ( )
2018-01-18 16:37:41 +00:00
}
2019-09-06 17:52:13 +00:00
return mountPoint , nil
}
// Mount a single named volume into the container.
// If necessary, copy up image contents into the volume.
// Does not verify that the name volume given is actually present in container
// config.
// Returns the volume that was mounted.
func ( c * Container ) mountNamedVolume ( v * ContainerNamedVolume , mountpoint string ) ( * Volume , error ) {
2020-11-11 20:57:06 +00:00
logrus . Debugf ( "Going to mount named volume %s" , v . Name )
2019-09-06 17:52:13 +00:00
vol , err := c . runtime . state . Volume ( v . Name )
if err != nil {
return nil , errors . Wrapf ( err , "error retrieving named volume %s for container %s" , v . Name , c . ID ( ) )
}
2020-02-23 18:25:12 +00:00
if vol . config . LockID == c . config . LockID {
return nil , errors . Wrapf ( define . ErrWillDeadlock , "container %s and volume %s share lock ID %d" , c . ID ( ) , vol . Name ( ) , c . config . LockID )
}
2019-09-06 17:52:13 +00:00
vol . lock . Lock ( )
defer vol . lock . Unlock ( )
if vol . needsMount ( ) {
if err := vol . mount ( ) ; err != nil {
return nil , errors . Wrapf ( err , "error mounting volume %s for container %s" , vol . Name ( ) , c . ID ( ) )
2018-04-26 15:21:48 +00:00
}
2018-01-18 16:37:41 +00:00
}
2019-09-06 17:52:13 +00:00
// The volume may need a copy-up. Check the state.
if err := vol . update ( ) ; err != nil {
return nil , err
}
if vol . state . NeedsCopyUp {
logrus . Debugf ( "Copying up contents from container %s to volume %s" , c . ID ( ) , vol . Name ( ) )
2020-02-10 18:37:38 +00:00
// Set NeedsCopyUp to false immediately, so we don't try this
// again when there are already files copied.
vol . state . NeedsCopyUp = false
if err := vol . save ( ) ; err != nil {
return nil , err
}
// If the volume is not empty, we should not copy up.
2021-02-10 14:46:12 +00:00
volMount := vol . mountPoint ( )
2020-02-10 18:37:38 +00:00
contents , err := ioutil . ReadDir ( volMount )
if err != nil {
return nil , errors . Wrapf ( err , "error listing contents of volume %s mountpoint when copying up from container %s" , vol . Name ( ) , c . ID ( ) )
}
if len ( contents ) > 0 {
// The volume is not empty. It was likely modified
// outside of Podman. For safety, let's not copy up into
// it. Fixes CVE-2020-1726.
return vol , nil
}
2019-09-06 17:52:13 +00:00
srcDir , err := securejoin . SecureJoin ( mountpoint , v . Dest )
if err != nil {
return nil , errors . Wrapf ( err , "error calculating destination path to copy up container %s volume %s" , c . ID ( ) , vol . Name ( ) )
}
2021-02-10 14:46:12 +00:00
// Do a manual stat on the source directory to verify existence.
// Skip the rest if it exists.
// TODO: Should this be stat or lstat? I'm using lstat because I
// think copy-up doesn't happen when the source is a link.
srcStat , err := os . Lstat ( srcDir )
if err != nil {
if os . IsNotExist ( err ) {
// Source does not exist, don't bother copying
// up.
return vol , nil
}
return nil , errors . Wrapf ( err , "error identifying source directory for copy up into volume %s" , vol . Name ( ) )
}
// If it's not a directory we're mounting over it.
if ! srcStat . IsDir ( ) {
return vol , nil
}
2021-02-15 16:58:24 +00:00
// Read contents, do not bother continuing if it's empty. Fixes
// a bizarre issue where something copier.Get will ENOENT on
// empty directories and sometimes it will not.
// RHBZ#1928643
srcContents , err := ioutil . ReadDir ( srcDir )
if err != nil {
return nil , errors . Wrapf ( err , "error reading contents of source directory for copy up into volume %s" , vol . Name ( ) )
}
if len ( srcContents ) == 0 {
return vol , nil
}
2021-02-10 14:46:12 +00:00
// Buildah Copier accepts a reader, so we'll need a pipe.
reader , writer := io . Pipe ( )
defer reader . Close ( )
errChan := make ( chan error , 1 )
logrus . Infof ( "About to copy up into volume %s" , vol . Name ( ) )
// Copy, container side: get a tar archive of what needs to be
// streamed into the volume.
go func ( ) {
defer writer . Close ( )
getOptions := copier . GetOptions {
KeepDirectoryNames : false ,
}
2021-02-17 19:09:28 +00:00
errChan <- copier . Get ( srcDir , "" , getOptions , [ ] string { "/." } , writer )
2021-02-10 14:46:12 +00:00
} ( )
// Copy, volume side: stream what we've written to the pipe, into
// the volume.
copyOpts := copier . PutOptions { }
if err := copier . Put ( volMount , "" , copyOpts , reader ) ; err != nil {
err2 := <- errChan
if err2 != nil {
logrus . Errorf ( "Error streaming contents of container %s directory for volume copy-up: %v" , c . ID ( ) , err2 )
}
return nil , errors . Wrapf ( err , "error copying up to volume %s" , vol . Name ( ) )
}
if err := <- errChan ; err != nil {
return nil , errors . Wrapf ( err , "error streaming container content for copy up into volume %s" , vol . Name ( ) )
2019-09-06 17:52:13 +00:00
}
}
return vol , nil
2018-01-18 16:37:41 +00:00
}
2020-06-24 12:44:28 +00:00
// Chown the specified volume if necessary.
func ( c * Container ) chownVolume ( volumeName string ) error {
vol , err := c . runtime . state . Volume ( volumeName )
if err != nil {
return errors . Wrapf ( err , "error retrieving named volume %s for container %s" , volumeName , c . ID ( ) )
}
vol . lock . Lock ( )
defer vol . lock . Unlock ( )
// The volume may need a copy-up. Check the state.
if err := vol . update ( ) ; err != nil {
return err
}
Initial implementation of volume plugins
This implements support for mounting and unmounting volumes
backed by volume plugins. Support for actually retrieving
plugins requires a pull request to land in containers.conf and
then that to be vendored, and as such is not yet ready. Given
this, this code is only compile tested. However, the code for
everything past retrieving the plugin has been written - there is
support for creating, removing, mounting, and unmounting volumes,
which should allow full functionality once the c/common PR is
merged.
A major change is the signature of the MountPoint function for
volumes, which now, by necessity, returns an error. Named volumes
managed by a plugin do not have a mountpoint we control; instead,
it is managed entirely by the plugin. As such, we need to cache
the path in the DB, and calls to retrieve it now need to access
the DB (and may fail as such).
Notably absent is support for SELinux relabelling and chowning
these volumes. Given that we don't manage the mountpoint for
these volumes, I am extremely reluctant to try and modify it - we
could easily break the plugin trying to chown or relabel it.
Also, we had no less than *5* separate implementations of
inspecting a volume floating around in pkg/infra/abi and
pkg/api/handlers/libpod. And none of them used volume.Inspect(),
the only correct way of inspecting volumes. Remove them all and
consolidate to using the correct way. Compat API is likely still
doing things the wrong way, but that is an issue for another day.
Fixes #4304
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-12-04 21:24:56 +00:00
// TODO: For now, I've disabled chowning volumes owned by non-Podman
// drivers. This may be safe, but it's really going to be a case-by-case
2021-04-21 09:38:34 +00:00
// thing, I think - safest to leave disabled now and re-enable later if
Initial implementation of volume plugins
This implements support for mounting and unmounting volumes
backed by volume plugins. Support for actually retrieving
plugins requires a pull request to land in containers.conf and
then that to be vendored, and as such is not yet ready. Given
this, this code is only compile tested. However, the code for
everything past retrieving the plugin has been written - there is
support for creating, removing, mounting, and unmounting volumes,
which should allow full functionality once the c/common PR is
merged.
A major change is the signature of the MountPoint function for
volumes, which now, by necessity, returns an error. Named volumes
managed by a plugin do not have a mountpoint we control; instead,
it is managed entirely by the plugin. As such, we need to cache
the path in the DB, and calls to retrieve it now need to access
the DB (and may fail as such).
Notably absent is support for SELinux relabelling and chowning
these volumes. Given that we don't manage the mountpoint for
these volumes, I am extremely reluctant to try and modify it - we
could easily break the plugin trying to chown or relabel it.
Also, we had no less than *5* separate implementations of
inspecting a volume floating around in pkg/infra/abi and
pkg/api/handlers/libpod. And none of them used volume.Inspect(),
the only correct way of inspecting volumes. Remove them all and
consolidate to using the correct way. Compat API is likely still
doing things the wrong way, but that is an issue for another day.
Fixes #4304
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-12-04 21:24:56 +00:00
// there is a demand.
if vol . state . NeedsChown && ! vol . UsesVolumeDriver ( ) {
2020-06-24 12:44:28 +00:00
vol . state . NeedsChown = false
2020-07-30 20:59:45 +00:00
uid := int ( c . config . Spec . Process . User . UID )
gid := int ( c . config . Spec . Process . User . GID )
if c . config . IDMappings . UIDMap != nil {
p := idtools . IDPair {
UID : uid ,
GID : gid ,
}
mappings := idtools . NewIDMappingsFromMaps ( c . config . IDMappings . UIDMap , c . config . IDMappings . GIDMap )
newPair , err := mappings . ToHost ( p )
if err != nil {
return errors . Wrapf ( err , "error mapping user %d:%d" , uid , gid )
}
uid = newPair . UID
gid = newPair . GID
}
2020-06-24 12:44:28 +00:00
vol . state . UIDChowned = uid
vol . state . GIDChowned = gid
if err := vol . save ( ) ; err != nil {
return err
}
2020-07-30 20:59:45 +00:00
Initial implementation of volume plugins
This implements support for mounting and unmounting volumes
backed by volume plugins. Support for actually retrieving
plugins requires a pull request to land in containers.conf and
then that to be vendored, and as such is not yet ready. Given
this, this code is only compile tested. However, the code for
everything past retrieving the plugin has been written - there is
support for creating, removing, mounting, and unmounting volumes,
which should allow full functionality once the c/common PR is
merged.
A major change is the signature of the MountPoint function for
volumes, which now, by necessity, returns an error. Named volumes
managed by a plugin do not have a mountpoint we control; instead,
it is managed entirely by the plugin. As such, we need to cache
the path in the DB, and calls to retrieve it now need to access
the DB (and may fail as such).
Notably absent is support for SELinux relabelling and chowning
these volumes. Given that we don't manage the mountpoint for
these volumes, I am extremely reluctant to try and modify it - we
could easily break the plugin trying to chown or relabel it.
Also, we had no less than *5* separate implementations of
inspecting a volume floating around in pkg/infra/abi and
pkg/api/handlers/libpod. And none of them used volume.Inspect(),
the only correct way of inspecting volumes. Remove them all and
consolidate to using the correct way. Compat API is likely still
doing things the wrong way, but that is an issue for another day.
Fixes #4304
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-12-04 21:24:56 +00:00
mountPoint , err := vol . MountPoint ( )
if err != nil {
return err
}
2020-07-30 20:59:45 +00:00
if err := os . Lchown ( mountPoint , uid , gid ) ; err != nil {
2020-06-24 12:44:28 +00:00
return err
}
}
return nil
}
2018-01-18 16:37:41 +00:00
// cleanupStorage unmounts and cleans up the container's root filesystem
func ( c * Container ) cleanupStorage ( ) error {
if ! c . state . Mounted {
// Already unmounted, do nothing
2019-03-05 23:11:28 +00:00
logrus . Debugf ( "Container %s storage is already unmounted, skipping..." , c . ID ( ) )
2018-01-18 16:37:41 +00:00
return nil
}
2019-03-05 23:11:28 +00:00
2019-09-05 14:00:50 +00:00
var cleanupErr error
2019-07-03 20:37:17 +00:00
for _ , containerMount := range c . config . Mounts {
if err := c . unmountSHM ( containerMount ) ; err != nil {
2019-09-05 14:00:50 +00:00
if cleanupErr != nil {
logrus . Errorf ( "Error unmounting container %s: %v" , c . ID ( ) , cleanupErr )
}
cleanupErr = err
2018-01-18 16:37:41 +00:00
}
}
2019-03-05 23:11:28 +00:00
2020-07-09 19:46:14 +00:00
if err := c . cleanupOverlayMounts ( ) ; err != nil {
// If the container can't remove content report the error
logrus . Errorf ( "Failed to cleanup overlay mounts for %s: %v" , c . ID ( ) , err )
cleanupErr = err
}
2018-04-26 15:21:48 +00:00
if c . config . Rootfs != "" {
2019-09-05 14:00:50 +00:00
return cleanupErr
2018-04-26 15:21:48 +00:00
}
2018-01-18 16:37:41 +00:00
2018-07-30 13:04:18 +00:00
if err := c . unmount ( false ) ; err != nil {
2018-05-14 14:17:24 +00:00
// If the container has already been removed, warn but don't
// error
// We still want to be able to kick the container out of the
// state
2019-09-16 13:22:26 +00:00
if errors . Cause ( err ) == storage . ErrNotAContainer || errors . Cause ( err ) == storage . ErrContainerUnknown || errors . Cause ( err ) == storage . ErrLayerNotMounted {
2018-05-14 14:17:24 +00:00
logrus . Errorf ( "Storage for container %s has been removed" , c . ID ( ) )
2019-09-05 14:00:50 +00:00
} else {
if cleanupErr != nil {
logrus . Errorf ( "Error cleaning up container %s storage: %v" , c . ID ( ) , cleanupErr )
}
cleanupErr = err
2018-05-14 14:17:24 +00:00
}
2018-01-18 16:37:41 +00:00
}
2019-09-03 19:03:44 +00:00
// Request an unmount of all named volumes
for _ , v := range c . config . NamedVolumes {
vol , err := c . runtime . state . Volume ( v . Name )
if err != nil {
if cleanupErr != nil {
logrus . Errorf ( "Error unmounting container %s: %v" , c . ID ( ) , cleanupErr )
}
cleanupErr = errors . Wrapf ( err , "error retrieving named volume %s for container %s" , v . Name , c . ID ( ) )
// We need to try and unmount every volume, so continue
// if they fail.
continue
}
if vol . needsMount ( ) {
vol . lock . Lock ( )
if err := vol . unmount ( false ) ; err != nil {
if cleanupErr != nil {
logrus . Errorf ( "Error unmounting container %s: %v" , c . ID ( ) , cleanupErr )
}
cleanupErr = errors . Wrapf ( err , "error unmounting volume %s for container %s" , vol . Name ( ) , c . ID ( ) )
}
vol . lock . Unlock ( )
}
}
2018-01-18 16:37:41 +00:00
c . state . Mountpoint = ""
c . state . Mounted = false
2018-06-04 21:31:49 +00:00
if c . valid {
2019-09-03 19:03:44 +00:00
if err := c . save ( ) ; err != nil {
if cleanupErr != nil {
logrus . Errorf ( "Error unmounting container %s: %v" , c . ID ( ) , cleanupErr )
}
cleanupErr = err
}
2018-06-04 21:31:49 +00:00
}
2019-09-03 19:03:44 +00:00
return cleanupErr
2018-01-18 16:37:41 +00:00
}
2018-03-14 19:14:49 +00:00
// Unmount the a container and free its resources
2018-09-23 22:04:29 +00:00
func ( c * Container ) cleanup ( ctx context . Context ) error {
2018-03-14 19:14:49 +00:00
var lastError error
2018-05-09 20:33:31 +00:00
logrus . Debugf ( "Cleaning up container %s" , c . ID ( ) )
2019-03-14 20:14:18 +00:00
// Remove healthcheck unit/timer file if it execs
if c . config . HealthCheckConfig != nil {
if err := c . removeTimer ( ) ; err != nil {
2019-04-11 13:51:26 +00:00
logrus . Errorf ( "Error removing timer for container %s healthcheck: %v" , c . ID ( ) , err )
2019-03-14 20:14:18 +00:00
}
}
2018-03-14 19:14:49 +00:00
// Clean up network namespace, if present
if err := c . cleanupNetwork ( ) ; err != nil {
2019-04-11 13:51:26 +00:00
lastError = errors . Wrapf ( err , "error removing container %s network" , c . ID ( ) )
2018-03-14 19:14:49 +00:00
}
2020-05-14 13:39:41 +00:00
// Remove the container from the runtime, if necessary.
// Do this *before* unmounting storage - some runtimes (e.g. Kata)
// apparently object to having storage removed while the container still
// exists.
if err := c . cleanupRuntime ( ctx ) ; err != nil {
2018-03-14 19:14:49 +00:00
if lastError != nil {
2020-05-14 13:39:41 +00:00
logrus . Errorf ( "Error removing container %s from OCI runtime: %v" , c . ID ( ) , err )
2018-03-14 19:14:49 +00:00
} else {
2020-05-14 13:39:41 +00:00
lastError = err
2018-03-14 19:14:49 +00:00
}
}
2020-05-14 13:39:41 +00:00
// Unmount storage
if err := c . cleanupStorage ( ) ; err != nil {
2018-09-23 22:04:29 +00:00
if lastError != nil {
2020-05-14 13:39:41 +00:00
logrus . Errorf ( "Error unmounting container %s storage: %v" , c . ID ( ) , err )
2018-09-23 22:04:29 +00:00
} else {
2020-05-14 13:39:41 +00:00
lastError = errors . Wrapf ( err , "error unmounting container %s storage" , c . ID ( ) )
2018-09-23 22:04:29 +00:00
}
}
2020-10-26 10:35:02 +00:00
// Unmount image volumes
for _ , v := range c . config . ImageVolumes {
2021-04-22 06:01:12 +00:00
img , _ , err := c . runtime . LibimageRuntime ( ) . LookupImage ( v . Source , nil )
2020-10-26 10:35:02 +00:00
if err != nil {
if lastError == nil {
lastError = err
continue
}
logrus . Errorf ( "error unmounting image volume %q:%q :%v" , v . Source , v . Dest , err )
}
if err := img . Unmount ( false ) ; err != nil {
if lastError == nil {
lastError = err
continue
}
logrus . Errorf ( "error unmounting image volume %q:%q :%v" , v . Source , v . Dest , err )
}
}
2018-03-14 19:14:49 +00:00
return lastError
}
2018-05-31 18:47:17 +00:00
// delete deletes the container and runs any configured poststop
// hooks.
2020-07-09 17:50:01 +00:00
func ( c * Container ) delete ( ctx context . Context ) error {
2019-10-08 17:53:36 +00:00
if err := c . ociRuntime . DeleteContainer ( c ) ; err != nil {
2018-05-31 18:47:17 +00:00
return errors . Wrapf ( err , "error removing container %s from runtime" , c . ID ( ) )
}
if err := c . postDeleteHooks ( ctx ) ; err != nil {
return errors . Wrapf ( err , "container %s poststop hooks" , c . ID ( ) )
}
return nil
}
// postDeleteHooks runs the poststop hooks (if any) as specified by
// the OCI Runtime Specification (which requires them to run
// post-delete, despite the stage name).
2020-07-09 17:50:01 +00:00
func ( c * Container ) postDeleteHooks ( ctx context . Context ) error {
2018-05-31 18:47:17 +00:00
if c . state . ExtensionStageHooks != nil {
extensionHooks , ok := c . state . ExtensionStageHooks [ "poststop" ]
if ok {
state , err := json . Marshal ( spec . State {
Version : spec . Version ,
ID : c . ID ( ) ,
Status : "stopped" ,
Bundle : c . bundlePath ( ) ,
Annotations : c . config . Spec . Annotations ,
} )
if err != nil {
return err
}
for i , hook := range extensionHooks {
2019-07-11 10:44:12 +00:00
hook := hook
2018-06-18 17:19:48 +00:00
logrus . Debugf ( "container %s: invoke poststop hook %d, path %s" , c . ID ( ) , i , hook . Path )
2018-05-31 18:47:17 +00:00
var stderr , stdout bytes . Buffer
hookErr , err := exec . Run ( ctx , & hook , state , & stdout , & stderr , exec . DefaultPostKillTimeout )
if err != nil {
logrus . Warnf ( "container %s: poststop hook %d: %v" , c . ID ( ) , i , err )
if hookErr != err {
logrus . Debugf ( "container %s: poststop hook %d (hook error): %v" , c . ID ( ) , i , hookErr )
}
stdoutString := stdout . String ( )
if stdoutString != "" {
logrus . Debugf ( "container %s: poststop hook %d: stdout:\n%s" , c . ID ( ) , i , stdoutString )
}
stderrString := stderr . String ( )
if stderrString != "" {
logrus . Debugf ( "container %s: poststop hook %d: stderr:\n%s" , c . ID ( ) , i , stderrString )
}
}
}
}
}
return nil
}
2020-07-14 20:20:22 +00:00
// writeStringToRundir writes the given string to a file with the given name in
// the container's temporary files directory. The file will be chown'd to the
// container's root user and have an appropriate SELinux label set.
// If a file with the same name already exists, it will be deleted and recreated
// with the new contents.
// Returns the full path to the new file.
func ( c * Container ) writeStringToRundir ( destFile , contents string ) ( string , error ) {
2018-01-19 14:51:59 +00:00
destFileName := filepath . Join ( c . state . RunDir , destFile )
2018-04-24 14:41:42 +00:00
if err := os . Remove ( destFileName ) ; err != nil && ! os . IsNotExist ( err ) {
return "" , errors . Wrapf ( err , "error removing %s for container %s" , destFile , c . ID ( ) )
}
2020-07-14 20:20:22 +00:00
if err := writeStringToPath ( destFileName , contents , c . config . MountLabel , c . RootUID ( ) , c . RootGID ( ) ) ; err != nil {
2018-06-11 14:03:34 +00:00
return "" , err
2018-04-24 00:42:53 +00:00
}
2020-07-14 20:20:22 +00:00
return destFileName , nil
}
// writeStringToStaticDir writes the given string to a file with the given name
// in the container's permanent files directory. The file will be chown'd to the
// container's root user and have an appropriate SELinux label set.
// Unlike writeStringToRundir, will *not* delete and re-create if the file
// already exists (will instead error).
// Returns the full path to the new file.
func ( c * Container ) writeStringToStaticDir ( filename , contents string ) ( string , error ) {
destFileName := filepath . Join ( c . config . StaticDir , filename )
if err := writeStringToPath ( destFileName , contents , c . config . MountLabel , c . RootUID ( ) , c . RootGID ( ) ) ; err != nil {
2018-01-18 16:37:41 +00:00
return "" , err
}
2018-04-24 14:41:42 +00:00
2020-07-14 20:20:22 +00:00
return destFileName , nil
2018-01-18 16:37:41 +00:00
}
2018-01-19 14:51:59 +00:00
2021-01-13 06:17:27 +00:00
// appendStringToRunDir appends the provided string to the runtimedir file
func ( c * Container ) appendStringToRunDir ( destFile , output string ) ( string , error ) {
2019-03-04 03:54:41 +00:00
destFileName := filepath . Join ( c . state . RunDir , destFile )
2021-01-13 06:17:27 +00:00
f , err := os . OpenFile ( destFileName , os . O_APPEND | os . O_RDWR , 0600 )
2019-03-04 03:54:41 +00:00
if err != nil {
2020-10-05 19:33:53 +00:00
return "" , err
2019-03-04 03:54:41 +00:00
}
defer f . Close ( )
2021-01-13 06:17:27 +00:00
compareStr := strings . TrimRight ( output , "\n" )
scanner := bufio . NewScanner ( f )
scanner . Split ( bufio . ScanLines )
for scanner . Scan ( ) {
if strings . Compare ( scanner . Text ( ) , compareStr ) == 0 {
return filepath . Join ( c . state . RunDir , destFile ) , nil
}
}
2019-03-04 03:54:41 +00:00
if _ , err := f . WriteString ( output ) ; err != nil {
return "" , errors . Wrapf ( err , "unable to write %s" , destFileName )
}
2019-03-21 11:18:42 +00:00
return filepath . Join ( c . state . RunDir , destFile ) , nil
2019-03-04 03:54:41 +00:00
}
2019-02-06 19:22:46 +00:00
// saveSpec saves the OCI spec to disk, replacing any existing specs for the container
2018-03-07 16:15:00 +00:00
func ( c * Container ) saveSpec ( spec * spec . Spec ) error {
// If the OCI spec already exists, we need to replace it
// Cannot guarantee some things, e.g. network namespaces, have the same
// paths
jsonPath := filepath . Join ( c . bundlePath ( ) , "config.json" )
if _ , err := os . Stat ( jsonPath ) ; err != nil {
if ! os . IsNotExist ( err ) {
return errors . Wrapf ( err , "error doing stat on container %s spec" , c . ID ( ) )
}
// The spec does not exist, we're fine
} else {
// The spec exists, need to remove it
if err := os . Remove ( jsonPath ) ; err != nil {
return errors . Wrapf ( err , "error replacing runtime spec for container %s" , c . ID ( ) )
}
}
fileJSON , err := json . Marshal ( spec )
if err != nil {
return errors . Wrapf ( err , "error exporting runtime spec for container %s to JSON" , c . ID ( ) )
}
if err := ioutil . WriteFile ( jsonPath , fileJSON , 0644 ) ; err != nil {
return errors . Wrapf ( err , "error writing runtime spec JSON for container %s to disk" , c . ID ( ) )
}
logrus . Debugf ( "Created OCI spec for container %s at %s" , c . ID ( ) , jsonPath )
c . state . ConfigPath = jsonPath
return nil
}
2018-03-29 15:01:47 +00:00
2018-11-19 17:22:32 +00:00
// Warning: precreate hooks may alter 'config' in place.
2020-07-09 17:50:01 +00:00
func ( c * Container ) setupOCIHooks ( ctx context . Context , config * spec . Spec ) ( map [ string ] [ ] spec . Hook , error ) {
2018-11-19 17:22:32 +00:00
allHooks := make ( map [ string ] [ ] spec . Hook )
2020-03-27 14:13:51 +00:00
if c . runtime . config . Engine . HooksDir == nil {
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
if rootless . IsRootless ( ) {
2018-09-17 13:33:11 +00:00
return nil , nil
}
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
for _ , hDir := range [ ] string { hooks . DefaultDir , hooks . OverrideDir } {
2019-03-02 05:36:44 +00:00
manager , err := hooks . New ( ctx , [ ] string { hDir } , [ ] string { "precreate" , "poststop" } )
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
if err != nil {
if os . IsNotExist ( err ) {
continue
}
return nil , err
}
2019-07-03 20:37:17 +00:00
ociHooks , err := manager . Hooks ( config , c . Spec ( ) . Annotations , len ( c . config . UserVolumes ) > 0 )
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
if err != nil {
return nil , err
}
2019-07-03 20:37:17 +00:00
if len ( ociHooks ) > 0 || config . Hooks != nil {
logrus . Warnf ( "implicit hook directories are deprecated; set --ociHooks-dir=%q explicitly to continue to load ociHooks from this directory" , hDir )
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
}
2019-07-03 20:37:17 +00:00
for i , hook := range ociHooks {
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
allHooks [ i ] = hook
}
2018-09-17 13:33:11 +00:00
}
2018-11-19 17:22:32 +00:00
} else {
2020-03-27 14:13:51 +00:00
manager , err := hooks . New ( ctx , c . runtime . config . Engine . HooksDir , [ ] string { "precreate" , "poststop" } )
2018-11-19 17:22:32 +00:00
if err != nil {
return nil , err
}
allHooks , err = manager . Hooks ( config , c . Spec ( ) . Annotations , len ( c . config . UserVolumes ) > 0 )
if err != nil {
return nil , err
}
2018-03-29 15:01:47 +00:00
}
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
2018-11-19 17:22:32 +00:00
hookErr , err := exec . RuntimeConfigFilter ( ctx , allHooks [ "precreate" ] , config , exec . DefaultPostKillTimeout )
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
if err != nil {
2018-11-19 17:22:32 +00:00
logrus . Warnf ( "container %s: precreate hook: %v" , c . ID ( ) , err )
if hookErr != nil && hookErr != err {
logrus . Debugf ( "container %s: precreate hook (hook error): %v" , c . ID ( ) , hookErr )
2018-12-07 16:23:03 +00:00
}
libpod/container_internal: Deprecate implicit hook directories
Part of the motivation for 800eb863 (Hooks supports two directories,
process default and override, 2018-09-17, #1487) was [1]:
> We only use this for override. The reason this was caught is people
> are trying to get hooks to work with CoreOS. You are not allowed to
> write to /usr/share... on CoreOS, so they wanted podman to also look
> at /etc, where users and third parties can write.
But we'd also been disabling hooks completely for rootless users. And
even for root users, the override logic was tricky when folks actually
had content in both directories. For example, if you wanted to
disable a hook from the default directory, you'd have to add a no-op
hook to the override directory.
Also, the previous implementation failed to handle the case where
there hooks defined in the override directory but the default
directory did not exist:
$ podman version
Version: 0.11.2-dev
Go Version: go1.10.3
Git Commit: "6df7409cb5a41c710164c42ed35e33b28f3f7214"
Built: Sun Dec 2 21:30:06 2018
OS/Arch: linux/amd64
$ ls -l /etc/containers/oci/hooks.d/test.json
-rw-r--r--. 1 root root 184 Dec 2 16:27 /etc/containers/oci/hooks.d/test.json
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:31:19-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:31:19-08:00" level=warning msg="failed to load hooks: {}%!(EXTRA *os.PathError=open /usr/share/containers/oci/hooks.d: no such file or directory)"
With this commit:
$ podman --log-level=debug run --rm docker.io/library/alpine echo 'successful container' 2>&1 | grep -i hook
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /usr/share/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="reading hooks from /etc/containers/oci/hooks.d"
time="2018-12-02T21:33:07-08:00" level=debug msg="added hook /etc/containers/oci/hooks.d/test.json"
time="2018-12-02T21:33:07-08:00" level=debug msg="hook test.json matched; adding to stages [prestart]"
time="2018-12-02T21:33:07-08:00" level=warning msg="implicit hook directories are deprecated; set --hooks-dir="/etc/containers/oci/hooks.d" explicitly to continue to load hooks from this directory"
time="2018-12-02T21:33:07-08:00" level=error msg="container create failed: container_linux.go:336: starting container process caused "process_linux.go:399: container init caused \"process_linux.go:382: running prestart hook 0 caused \\\"error running hook: exit status 1, stdout: , stderr: oh, noes!\\\\n\\\"\""
(I'd setup the hook to error out). You can see that it's silenly
ignoring the ENOENT for /usr/share/containers/oci/hooks.d and
continuing on to load hooks from /etc/containers/oci/hooks.d.
When it loads the hook, it also logs a warning-level message
suggesting that callers explicitly configure their hook directories.
That will help consumers migrate, so we can drop the implicit hook
directories in some future release. When folks *do* explicitly
configure hook directories (via the newly-public --hooks-dir and
hooks_dir options), we error out if they're missing:
$ podman --hooks-dir /does/not/exist run --rm docker.io/library/alpine echo 'successful container'
error setting up OCI Hooks: open /does/not/exist: no such file or directory
I've dropped the trailing "path" from the old, hidden --hooks-dir-path
and hooks_dir_path because I think "dir(ectory)" is already enough
context for "we expect a path argument". I consider this name change
non-breaking because the old forms were undocumented.
Coming back to rootless users, I've enabled hooks now. I expect they
were previously disabled because users had no way to avoid
/usr/share/containers/oci/hooks.d which might contain hooks that
required root permissions. But now rootless users will have to
explicitly configure hook directories, and since their default config
is from ~/.config/containers/libpod.conf, it's a misconfiguration if
it contains hooks_dir entries which point at directories with hooks
that require root access. We error out so they can fix their
libpod.conf.
[1]: https://github.com/containers/libpod/pull/1487#discussion_r218149355
Signed-off-by: W. Trevor King <wking@tremily.us>
2018-12-03 05:22:08 +00:00
return nil , err
}
2018-11-19 17:22:32 +00:00
return allHooks , nil
2018-03-29 15:01:47 +00:00
}
2018-07-19 20:59:42 +00:00
// mount mounts the container's root filesystem
func ( c * Container ) mount ( ) ( string , error ) {
2021-03-02 08:20:53 +00:00
if c . state . State == define . ContainerStateRemoving {
return "" , errors . Wrapf ( define . ErrCtrStateInvalid , "cannot mount container %s as it is being removed" , c . ID ( ) )
}
2018-07-19 20:59:42 +00:00
mountPoint , err := c . runtime . storageService . MountContainerImage ( c . ID ( ) )
if err != nil {
return "" , errors . Wrapf ( err , "error mounting storage for container %s" , c . ID ( ) )
}
2018-08-31 15:20:13 +00:00
mountPoint , err = filepath . EvalSymlinks ( mountPoint )
if err != nil {
return "" , errors . Wrapf ( err , "error resolving storage path for container %s" , c . ID ( ) )
}
2019-03-21 11:18:42 +00:00
if err := os . Chown ( mountPoint , c . RootUID ( ) , c . RootGID ( ) ) ; err != nil {
return "" , errors . Wrapf ( err , "cannot chown %s to %d:%d" , mountPoint , c . RootUID ( ) , c . RootGID ( ) )
}
2018-07-19 20:59:42 +00:00
return mountPoint , nil
}
// unmount unmounts the container's root filesystem
2018-07-30 13:04:18 +00:00
func ( c * Container ) unmount ( force bool ) error {
2018-07-19 20:59:42 +00:00
// Also unmount storage
2018-07-30 13:04:18 +00:00
if _ , err := c . runtime . storageService . UnmountContainerImage ( c . ID ( ) , force ) ; err != nil {
2018-07-19 20:59:42 +00:00
return errors . Wrapf ( err , "error unmounting container %s root filesystem" , c . ID ( ) )
}
return nil
}
2018-10-01 17:10:46 +00:00
2019-05-10 16:42:14 +00:00
// checkReadyForRemoval checks whether the given container is ready to be
// removed.
// These checks are only used if force-remove is not specified.
// If it is, we'll remove the container anyways.
// Returns nil if safe to remove, or an error describing why it's unsafe if not.
func ( c * Container ) checkReadyForRemoval ( ) error {
2019-06-25 13:40:19 +00:00
if c . state . State == define . ContainerStateUnknown {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "container %s is in invalid state" , c . ID ( ) )
2019-05-10 16:42:14 +00:00
}
2019-10-28 17:09:01 +00:00
if c . ensureState ( define . ContainerStateRunning , define . ContainerStatePaused ) {
return errors . Wrapf ( define . ErrCtrStateInvalid , "cannot remove container %s as it is %s - running or paused containers cannot be removed without force" , c . ID ( ) , c . state . State . String ( ) )
2019-05-10 16:42:14 +00:00
}
2020-02-04 21:56:07 +00:00
// Check exec sessions
sessions , err := c . getActiveExecSessions ( )
if err != nil {
2019-12-12 21:19:36 +00:00
return err
}
2020-02-04 21:56:07 +00:00
if len ( sessions ) != 0 {
2019-06-24 20:48:34 +00:00
return errors . Wrapf ( define . ErrCtrStateInvalid , "cannot remove container %s as it has active exec sessions" , c . ID ( ) )
2019-05-10 16:42:14 +00:00
}
return nil
}
2019-02-06 19:17:25 +00:00
2021-01-10 10:12:12 +00:00
// canWithPrevious return the stat of the preCheckPoint dir
func ( c * Container ) canWithPrevious ( ) error {
_ , err := os . Stat ( c . PreCheckPointPath ( ) )
return err
}
2019-02-06 19:17:25 +00:00
// prepareCheckpointExport writes the config and spec to
// JSON files for later export
2020-07-09 17:50:01 +00:00
func ( c * Container ) prepareCheckpointExport ( ) error {
2019-02-06 19:17:25 +00:00
// save live config
2021-02-25 16:43:18 +00:00
if _ , err := metadata . WriteJSONFile ( c . Config ( ) , c . bundlePath ( ) , metadata . ConfigDumpFile ) ; err != nil {
2019-02-06 19:17:25 +00:00
return err
}
// save spec
jsonPath := filepath . Join ( c . bundlePath ( ) , "config.json" )
g , err := generate . NewFromFile ( jsonPath )
if err != nil {
logrus . Debugf ( "generating spec for container %q failed with %v" , c . ID ( ) , err )
return err
}
2021-02-25 16:43:18 +00:00
if _ , err := metadata . WriteJSONFile ( g . Config , c . bundlePath ( ) , metadata . SpecDumpFile ) ; err != nil {
2019-02-06 19:17:25 +00:00
return err
}
return nil
}
2019-07-02 15:40:14 +00:00
// sortUserVolumes sorts the volumes specified for a container
// between named and normal volumes
func ( c * Container ) sortUserVolumes ( ctrSpec * spec . Spec ) ( [ ] * ContainerNamedVolume , [ ] spec . Mount ) {
namedUserVolumes := [ ] * ContainerNamedVolume { }
userMounts := [ ] spec . Mount { }
// We need to parse all named volumes and mounts into maps, so we don't
// end up with repeated lookups for each user volume.
// Map destination to struct, as destination is what is stored in
// UserVolumes.
namedVolumes := make ( map [ string ] * ContainerNamedVolume )
mounts := make ( map [ string ] spec . Mount )
for _ , namedVol := range c . config . NamedVolumes {
namedVolumes [ namedVol . Dest ] = namedVol
}
for _ , mount := range ctrSpec . Mounts {
mounts [ mount . Destination ] = mount
}
for _ , vol := range c . config . UserVolumes {
if volume , ok := namedVolumes [ vol ] ; ok {
namedUserVolumes = append ( namedUserVolumes , volume )
} else if mount , ok := mounts [ vol ] ; ok {
userMounts = append ( userMounts , mount )
} else {
logrus . Warnf ( "Could not find mount at destination %q when parsing user volumes for container %s" , vol , c . ID ( ) )
}
}
return namedUserVolumes , userMounts
}
2019-10-15 19:11:26 +00:00
// Check for an exit file, and handle one if present
func ( c * Container ) checkExitFile ( ) error {
// If the container's not running, nothing to do.
2021-01-07 12:13:36 +00:00
if ! c . ensureState ( define . ContainerStateRunning , define . ContainerStatePaused , define . ContainerStateStopping ) {
2019-10-15 19:11:26 +00:00
return nil
}
exitFile , err := c . exitFilePath ( )
if err != nil {
return err
}
// Check for the exit file
info , err := os . Stat ( exitFile )
if err != nil {
if os . IsNotExist ( err ) {
// Container is still running, no error
return nil
}
return errors . Wrapf ( err , "error running stat on container %s exit file" , c . ID ( ) )
}
// Alright, it exists. Transition to Stopped state.
c . state . State = define . ContainerStateStopped
c . state . PID = 0
c . state . ConmonPID = 0
// Read the exit file to get our stopped time and exit code.
return c . handleExitFile ( exitFile , info )
}
2019-12-12 21:19:36 +00:00
2019-12-18 14:42:01 +00:00
func ( c * Container ) hasNamespace ( namespace spec . LinuxNamespaceType ) bool {
if c . config . Spec == nil || c . config . Spec . Linux == nil {
return false
}
for _ , n := range c . config . Spec . Linux . Namespaces {
if n . Type == namespace {
return true
}
}
return false
}
2021-01-15 06:27:23 +00:00
// extractSecretToStorage copies a secret's data from the secrets manager to the container's static dir
func ( c * Container ) extractSecretToCtrStorage ( name string ) error {
manager , err := secrets . NewManager ( c . runtime . GetSecretsStorageDir ( ) )
if err != nil {
return err
}
secr , data , err := manager . LookupSecretData ( name )
if err != nil {
return err
}
secretFile := filepath . Join ( c . config . SecretsPath , secr . Name )
err = ioutil . WriteFile ( secretFile , data , 0644 )
if err != nil {
return errors . Wrapf ( err , "unable to create %s" , secretFile )
}
if err := label . Relabel ( secretFile , c . config . MountLabel , false ) ; err != nil {
return err
}
return nil
}