aboutsummaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2020-08-11 15:00:57 -0400
committerGitHub <noreply@github.com>2020-08-11 15:00:57 -0400
commit1deb4d1d70efb6d62f4fe5e735c94523f930b6d7 (patch)
tree394020b1a48f76cd283e450d6952cf08a4f53dde /libpod
parent9a9ad853cb6781460829cb139cecbf9aff37896d (diff)
parent3f2cab86433859a1facf1996ad68dac23c9899b9 (diff)
downloadpodman-1deb4d1d70efb6d62f4fe5e735c94523f930b6d7.tar.gz
podman-1deb4d1d70efb6d62f4fe5e735c94523f930b6d7.tar.bz2
podman-1deb4d1d70efb6d62f4fe5e735c94523f930b6d7.zip
Merge pull request #7289 from vrothberg/v2-backports
V2 backports
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container.go8
-rw-r--r--libpod/container_exec.go14
-rw-r--r--libpod/container_internal_linux.go102
-rw-r--r--libpod/container_internal_linux_test.go42
-rw-r--r--libpod/define/errors.go3
-rw-r--r--libpod/image/filters.go20
-rw-r--r--libpod/image/image.go133
-rw-r--r--libpod/image/layer_tree.go222
-rw-r--r--libpod/image/prune.go11
-rw-r--r--libpod/logs/log.go12
-rw-r--r--libpod/logs/reversereader/reversereader.go4
-rw-r--r--libpod/oci_conmon_exec_linux.go17
-rw-r--r--libpod/options.go14
13 files changed, 448 insertions, 154 deletions
diff --git a/libpod/container.go b/libpod/container.go
index 6a87ad3a9..9ad938a5c 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -277,6 +277,9 @@ type ContainerConfig struct {
User string `json:"user,omitempty"`
// Additional groups to add
Groups []string `json:"groups,omitempty"`
+ // AddCurrentUserPasswdEntry indicates that the current user passwd entry
+ // should be added to the /etc/passwd within the container
+ AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"`
// Namespace Config
// IDs of container to share namespaces with
@@ -774,7 +777,10 @@ func (c *Container) Hostname() string {
// WorkingDir returns the containers working dir
func (c *Container) WorkingDir() string {
- return c.config.Spec.Process.Cwd
+ if c.config.Spec.Process != nil {
+ return c.config.Spec.Process.Cwd
+ }
+ return "/"
}
// State Accessors
diff --git a/libpod/container_exec.go b/libpod/container_exec.go
index bd04ee9b9..23bf71d9e 100644
--- a/libpod/container_exec.go
+++ b/libpod/container_exec.go
@@ -415,6 +415,13 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, httpCon net.Conn, h
execOpts, err := prepareForExec(c, session)
if err != nil {
+ session.State = define.ExecStateStopped
+ session.ExitCode = define.ExecErrorCodeGeneric
+
+ if err := c.save(); err != nil {
+ logrus.Errorf("Error saving container %s exec session %s after failure to prepare: %v", err, c.ID(), session.ID())
+ }
+
return err
}
@@ -427,6 +434,13 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, httpCon net.Conn, h
pid, attachChan, err := c.ociRuntime.ExecContainerHTTP(c, session.ID(), execOpts, httpCon, httpBuf, streams, cancel)
if err != nil {
+ session.State = define.ExecStateStopped
+ session.ExitCode = define.TranslateExecErrorToExitCode(define.ExecErrorCodeGeneric, err)
+
+ if err := c.save(); err != nil {
+ logrus.Errorf("Error saving container %s exec session %s after failure to start: %v", err, c.ID(), session.ID())
+ }
+
return err
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index cfcf9b823..d72e3ad47 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -9,6 +9,7 @@ import (
"io/ioutil"
"net"
"os"
+ "os/user"
"path"
"path/filepath"
"strconv"
@@ -33,7 +34,7 @@ import (
"github.com/containers/libpod/v2/pkg/util"
"github.com/containers/storage/pkg/archive"
securejoin "github.com/cyphar/filepath-securejoin"
- "github.com/opencontainers/runc/libcontainer/user"
+ User "github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
@@ -1420,9 +1421,38 @@ func (c *Container) getHosts() string {
return hosts
}
-// generatePasswd generates a container specific passwd file,
-// iff g.config.User is a number
-func (c *Container) generatePasswd() (string, error) {
+// generateCurrentUserPasswdEntry generates an /etc/passwd entry for the user
+// running the container engine
+func (c *Container) generateCurrentUserPasswdEntry() (string, error) {
+ uid := rootless.GetRootlessUID()
+ if uid == 0 {
+ return "", nil
+ }
+
+ u, err := user.LookupId(strconv.Itoa(rootless.GetRootlessUID()))
+ if err != nil {
+ return "", errors.Wrapf(err, "failed to get current user")
+ }
+
+ // Lookup the user to see if it exists in the container image.
+ _, err = lookup.GetUser(c.state.Mountpoint, u.Username)
+ if err != User.ErrNoPasswdEntries {
+ return "", err
+ }
+
+ // If the user's actual home directory exists, or was mounted in - use
+ // that.
+ homeDir := c.WorkingDir()
+ if MountExists(c.config.Spec.Mounts, u.HomeDir) {
+ homeDir = u.HomeDir
+ }
+
+ return fmt.Sprintf("%s:x:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Username, homeDir), nil
+}
+
+// generateUserPasswdEntry generates an /etc/passwd entry for the container user
+// to run in the container.
+func (c *Container) generateUserPasswdEntry() (string, error) {
var (
groupspec string
gid int
@@ -1440,14 +1470,13 @@ func (c *Container) generatePasswd() (string, error) {
if err != nil {
return "", nil
}
+
// Lookup the user to see if it exists in the container image
_, err = lookup.GetUser(c.state.Mountpoint, userspec)
- if err != nil && err != user.ErrNoPasswdEntries {
+ if err != User.ErrNoPasswdEntries {
return "", err
}
- if err == nil {
- return "", nil
- }
+
if groupspec != "" {
ugid, err := strconv.ParseUint(groupspec, 10, 32)
if err == nil {
@@ -1460,14 +1489,65 @@ func (c *Container) generatePasswd() (string, error) {
gid = group.Gid
}
}
+ return fmt.Sprintf("%d:x:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), nil
+}
+
+// generatePasswd generates a container specific passwd file,
+// iff g.config.User is a number
+func (c *Container) generatePasswd() (string, error) {
+ if !c.config.AddCurrentUserPasswdEntry && c.config.User == "" {
+ return "", nil
+ }
+ pwd := ""
+ if c.config.User != "" {
+ entry, err := c.generateUserPasswdEntry()
+ if err != nil {
+ return "", err
+ }
+ pwd += entry
+ }
+ if c.config.AddCurrentUserPasswdEntry {
+ entry, err := c.generateCurrentUserPasswdEntry()
+ if err != nil {
+ return "", err
+ }
+ pwd += entry
+ }
+ if pwd == "" {
+ return "", nil
+ }
+
+ // If we are *not* read-only - edit /etc/passwd in the container.
+ // This is *gross* (shows up in changes to the container, will be
+ // committed to images based on the container) but it actually allows us
+ // to add users to the container (a bind mount breaks useradd).
+ // We should never get here twice, because generateUserPasswdEntry will
+ // not return anything if the user already exists in /etc/passwd.
+ if !c.IsReadOnly() {
+ containerPasswd, err := securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd")
+ if err != nil {
+ return "", errors.Wrapf(err, "error looking up location of container %s /etc/passwd", c.ID())
+ }
+
+ f, err := os.OpenFile(containerPasswd, os.O_APPEND|os.O_WRONLY, 0600)
+ if err != nil {
+ return "", errors.Wrapf(err, "error opening container %s /etc/passwd", c.ID())
+ }
+ defer f.Close()
+
+ if _, err := f.WriteString(pwd); err != nil {
+ return "", errors.Wrapf(err, "unable to append to container %s /etc/passwd", c.ID())
+ }
+
+ return "", nil
+ }
+
originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd")
orig, err := ioutil.ReadFile(originPasswdFile)
if err != nil && !os.IsNotExist(err) {
return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile)
}
-
- pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir())
- passwdFile, err := c.writeStringToRundir("passwd", pwd)
+ passwdFile, err := c.writeStringToRundir("passwd", string(orig)+pwd)
if err != nil {
return "", errors.Wrapf(err, "failed to create temporary passwd file")
}
diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go
new file mode 100644
index 000000000..078cc53a7
--- /dev/null
+++ b/libpod/container_internal_linux_test.go
@@ -0,0 +1,42 @@
+// +build linux
+
+package libpod
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGenerateUserPasswdEntry(t *testing.T) {
+ dir, err := ioutil.TempDir("", "libpod_test_")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ c := Container{
+ config: &ContainerConfig{
+ User: "123:456",
+ Spec: &spec.Spec{},
+ },
+ state: &ContainerState{
+ Mountpoint: "/does/not/exist/tmp/",
+ },
+ }
+ user, err := c.generateUserPasswdEntry()
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, user, "123:x:123:456:container user:/:/bin/sh\n")
+
+ c.config.User = "567"
+ user, err = c.generateUserPasswdEntry()
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, user, "567:x:567:0:container user:/:/bin/sh\n")
+}
diff --git a/libpod/define/errors.go b/libpod/define/errors.go
index 1e9179353..5cadce396 100644
--- a/libpod/define/errors.go
+++ b/libpod/define/errors.go
@@ -23,6 +23,9 @@ var (
// ErrNoSuchVolume indicates the requested volume does not exist
ErrNoSuchVolume = errors.New("no such volume")
+ // ErrNoSuchNetwork indicates the requested network does not exist
+ ErrNoSuchNetwork = errors.New("network not found")
+
// ErrNoSuchExecSession indicates that the requested exec session does
// not exist.
ErrNoSuchExecSession = errors.New("no such exec session")
diff --git a/libpod/image/filters.go b/libpod/image/filters.go
index 11d081ec3..24e39a67b 100644
--- a/libpod/image/filters.go
+++ b/libpod/image/filters.go
@@ -29,6 +29,26 @@ func CreatedBeforeFilter(createTime time.Time) ResultFilter {
}
}
+// IntermediateFilter returns filter for intermediate images (i.e., images
+// with children and no tags).
+func (ir *Runtime) IntermediateFilter(ctx context.Context, images []*Image) (ResultFilter, error) {
+ tree, err := ir.layerTree()
+ if err != nil {
+ return nil, err
+ }
+ return func(i *Image) bool {
+ if len(i.Names()) > 0 {
+ return true
+ }
+ children, err := tree.children(ctx, i, false)
+ if err != nil {
+ logrus.Error(err.Error())
+ return false
+ }
+ return len(children) == 0
+ }, nil
+}
+
// CreatedAfterFilter allows you to filter on images created after
// the given time.Time
func CreatedAfterFilter(createTime time.Time) ResultFilter {
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 048ec825d..6a87380c0 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -855,26 +855,6 @@ func (i *Image) Dangling() bool {
return len(i.Names()) == 0
}
-// Intermediate returns true if the image is cache or intermediate image.
-// Cache image has parent and child.
-func (i *Image) Intermediate(ctx context.Context) (bool, error) {
- parent, err := i.IsParent(ctx)
- if err != nil {
- return false, err
- }
- if !parent {
- return false, nil
- }
- img, err := i.GetParent(ctx)
- if err != nil {
- return false, err
- }
- if img != nil {
- return true, nil
- }
- return false, nil
-}
-
// User returns the image's user
func (i *Image) User(ctx context.Context) (string, error) {
imgInspect, err := i.inspect(ctx, false)
@@ -1213,7 +1193,7 @@ func splitString(input string) string {
// the parent of any other layer in store. Double check that image with that
// layer exists as well.
func (i *Image) IsParent(ctx context.Context) (bool, error) {
- children, err := i.getChildren(ctx, 1)
+ children, err := i.getChildren(ctx, false)
if err != nil {
if errors.Cause(err) == ErrImageIsBareList {
return false, nil
@@ -1288,63 +1268,16 @@ func areParentAndChild(parent, child *imgspecv1.Image) bool {
// GetParent returns the image ID of the parent. Return nil if a parent is not found.
func (i *Image) GetParent(ctx context.Context) (*Image, error) {
- var childLayer *storage.Layer
- images, err := i.imageruntime.GetImages()
+ tree, err := i.imageruntime.layerTree()
if err != nil {
return nil, err
}
- if i.TopLayer() != "" {
- if childLayer, err = i.imageruntime.store.Layer(i.TopLayer()); err != nil {
- return nil, err
- }
- }
- // fetch the configuration for the child image
- child, err := i.ociv1Image(ctx)
- if err != nil {
- if errors.Cause(err) == ErrImageIsBareList {
- return nil, nil
- }
- return nil, err
- }
- for _, img := range images {
- if img.ID() == i.ID() {
- continue
- }
- candidateLayer := img.TopLayer()
- // as a child, our top layer, if we have one, is either the
- // candidate parent's layer, or one that's derived from it, so
- // skip over any candidate image where we know that isn't the
- // case
- if childLayer != nil {
- // The child has at least one layer, so a parent would
- // have a top layer that's either the same as the child's
- // top layer or the top layer's recorded parent layer,
- // which could be an empty value.
- if candidateLayer != childLayer.Parent && candidateLayer != childLayer.ID {
- continue
- }
- } else {
- // The child has no layers, but the candidate does.
- if candidateLayer != "" {
- continue
- }
- }
- // fetch the configuration for the candidate image
- candidate, err := img.ociv1Image(ctx)
- if err != nil {
- return nil, err
- }
- // compare them
- if areParentAndChild(candidate, child) {
- return img, nil
- }
- }
- return nil, nil
+ return tree.parent(ctx, i)
}
// GetChildren returns a list of the imageIDs that depend on the image
func (i *Image) GetChildren(ctx context.Context) ([]string, error) {
- children, err := i.getChildren(ctx, 0)
+ children, err := i.getChildren(ctx, true)
if err != nil {
if errors.Cause(err) == ErrImageIsBareList {
return nil, nil
@@ -1354,62 +1287,15 @@ func (i *Image) GetChildren(ctx context.Context) ([]string, error) {
return children, nil
}
-// getChildren returns a list of at most "max" imageIDs that depend on the image
-func (i *Image) getChildren(ctx context.Context, max int) ([]string, error) {
- var children []string
-
- if _, err := i.toImageRef(ctx); err != nil {
- return nil, nil
- }
-
- images, err := i.imageruntime.GetImages()
- if err != nil {
- return nil, err
- }
-
- // fetch the configuration for the parent image
- parent, err := i.ociv1Image(ctx)
+// getChildren returns a list of imageIDs that depend on the image. If all is
+// false, only the first child image is returned.
+func (i *Image) getChildren(ctx context.Context, all bool) ([]string, error) {
+ tree, err := i.imageruntime.layerTree()
if err != nil {
return nil, err
}
- parentLayer := i.TopLayer()
- for _, img := range images {
- if img.ID() == i.ID() {
- continue
- }
- if img.TopLayer() == "" {
- if parentLayer != "" {
- // this image has no layers, but we do, so
- // it can't be derived from this one
- continue
- }
- } else {
- candidateLayer, err := img.Layer()
- if err != nil {
- return nil, err
- }
- // if this image's top layer is not our top layer, and is not
- // based on our top layer, we can skip it
- if candidateLayer.Parent != parentLayer && candidateLayer.ID != parentLayer {
- continue
- }
- }
- // fetch the configuration for the candidate image
- candidate, err := img.ociv1Image(ctx)
- if err != nil {
- return nil, err
- }
- // compare them
- if areParentAndChild(parent, candidate) {
- children = append(children, img.ID())
- }
- // if we're not building an exhaustive list, maybe we're done?
- if max > 0 && len(children) >= max {
- break
- }
- }
- return children, nil
+ return tree.children(ctx, i, all)
}
// InputIsID returns a bool if the user input for an image
@@ -1608,6 +1494,7 @@ type LayerInfo struct {
// GetLayersMapWithImageInfo returns map of image-layers, with associated information like RepoTags, parent and list of child layers.
func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, error) {
+ // TODO: evaluate if we can reuse `layerTree` here.
// Memory allocated to store map of layers with key LayerID.
// Map will build dependency chain with ParentID and ChildID(s)
diff --git a/libpod/image/layer_tree.go b/libpod/image/layer_tree.go
new file mode 100644
index 000000000..3699655fd
--- /dev/null
+++ b/libpod/image/layer_tree.go
@@ -0,0 +1,222 @@
+package image
+
+import (
+ "context"
+
+ ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
+ "github.com/pkg/errors"
+)
+
+// layerTree is an internal representation of local layers.
+type layerTree struct {
+ // nodes is the actual layer tree with layer IDs being keys.
+ nodes map[string]*layerNode
+ // ociCache is a cache for Image.ID -> OCI Image. Translations are done
+ // on-demand.
+ ociCache map[string]*ociv1.Image
+}
+
+// node returns a layerNode for the specified layerID.
+func (t *layerTree) node(layerID string) *layerNode {
+ node, exists := t.nodes[layerID]
+ if !exists {
+ node = &layerNode{}
+ t.nodes[layerID] = node
+ }
+ return node
+}
+
+// toOCI returns an OCI image for the specified image.
+func (t *layerTree) toOCI(ctx context.Context, i *Image) (*ociv1.Image, error) {
+ var err error
+ oci, exists := t.ociCache[i.ID()]
+ if !exists {
+ oci, err = i.ociv1Image(ctx)
+ t.ociCache[i.ID()] = oci
+ }
+ return oci, err
+}
+
+// layerNode is a node in a layerTree. It's ID is the key in a layerTree.
+type layerNode struct {
+ children []*layerNode
+ images []*Image
+ parent *layerNode
+}
+
+// layerTree extracts a layerTree from the layers in the local storage and
+// relates them to the specified images.
+func (ir *Runtime) layerTree() (*layerTree, error) {
+ layers, err := ir.store.Layers()
+ if err != nil {
+ return nil, err
+ }
+
+ images, err := ir.GetImages()
+ if err != nil {
+ return nil, err
+ }
+
+ tree := layerTree{
+ nodes: make(map[string]*layerNode),
+ ociCache: make(map[string]*ociv1.Image),
+ }
+
+ // First build a tree purely based on layer information.
+ for _, layer := range layers {
+ node := tree.node(layer.ID)
+ if layer.Parent == "" {
+ continue
+ }
+ parent := tree.node(layer.Parent)
+ node.parent = parent
+ parent.children = append(parent.children, node)
+ }
+
+ // Now assign the images to each (top) layer.
+ for i := range images {
+ img := images[i] // do not leak loop variable outside the scope
+ topLayer := img.TopLayer()
+ if topLayer == "" {
+ continue
+ }
+ node, exists := tree.nodes[topLayer]
+ if !exists {
+ return nil, errors.Errorf("top layer %s of image %s not found in layer tree", img.TopLayer(), img.ID())
+ }
+ node.images = append(node.images, img)
+ }
+
+ return &tree, nil
+}
+
+// children returns the image IDs of children . Child images are images
+// with either the same top layer as parent or parent being the true parent
+// layer. Furthermore, the history of the parent and child images must match
+// with the parent having one history item less.
+// If all is true, all images are returned. Otherwise, the first image is
+// returned.
+func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]string, error) {
+ if parent.TopLayer() == "" {
+ return nil, nil
+ }
+
+ var children []string
+
+ parentNode, exists := t.nodes[parent.TopLayer()]
+ if !exists {
+ return nil, errors.Errorf("layer not found in layer tree: %q", parent.TopLayer())
+ }
+
+ parentID := parent.ID()
+ parentOCI, err := t.toOCI(ctx, parent)
+ if err != nil {
+ return nil, err
+ }
+
+ // checkParent returns true if child and parent are in such a relation.
+ checkParent := func(child *Image) (bool, error) {
+ if parentID == child.ID() {
+ return false, nil
+ }
+ childOCI, err := t.toOCI(ctx, child)
+ if err != nil {
+ return false, err
+ }
+ // History check.
+ return areParentAndChild(parentOCI, childOCI), nil
+ }
+
+ // addChildrenFrom adds child images of parent to children. Returns
+ // true if any image is a child of parent.
+ addChildrenFromNode := func(node *layerNode) (bool, error) {
+ foundChildren := false
+ for _, childImage := range node.images {
+ isChild, err := checkParent(childImage)
+ if err != nil {
+ return foundChildren, err
+ }
+ if isChild {
+ foundChildren = true
+ children = append(children, childImage.ID())
+ if all {
+ return foundChildren, nil
+ }
+ }
+ }
+ return foundChildren, nil
+ }
+
+ // First check images where parent's top layer is also the parent
+ // layer.
+ for _, childNode := range parentNode.children {
+ found, err := addChildrenFromNode(childNode)
+ if err != nil {
+ return nil, err
+ }
+ if found && all {
+ return children, nil
+ }
+ }
+
+ // Now check images with the same top layer.
+ if _, err := addChildrenFromNode(parentNode); err != nil {
+ return nil, err
+ }
+
+ return children, nil
+}
+
+// parent returns the parent image or nil if no parent image could be found.
+func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) {
+ if child.TopLayer() == "" {
+ return nil, nil
+ }
+
+ node, exists := t.nodes[child.TopLayer()]
+ if !exists {
+ return nil, errors.Errorf("layer not found in layer tree: %q", child.TopLayer())
+ }
+
+ childOCI, err := t.toOCI(ctx, child)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check images from the parent node (i.e., parent layer) and images
+ // with the same layer (i.e., same top layer).
+ childID := child.ID()
+ images := node.images
+ if node.parent != nil {
+ images = append(images, node.parent.images...)
+ }
+ for _, parent := range images {
+ if parent.ID() == childID {
+ continue
+ }
+ parentOCI, err := t.toOCI(ctx, parent)
+ if err != nil {
+ return nil, err
+ }
+ // History check.
+ if areParentAndChild(parentOCI, childOCI) {
+ return parent, nil
+ }
+ }
+
+ return nil, nil
+}
+
+// hasChildrenAndParent returns true if the specified image has children and a
+// parent.
+func (t *layerTree) hasChildrenAndParent(ctx context.Context, i *Image) (bool, error) {
+ children, err := t.children(ctx, i, false)
+ if err != nil {
+ return false, err
+ }
+ if len(children) == 0 {
+ return false, nil
+ }
+ parent, err := t.parent(ctx, i)
+ return parent != nil, err
+}
diff --git a/libpod/image/prune.go b/libpod/image/prune.go
index 5ad7a9a5e..2448f7c87 100644
--- a/libpod/image/prune.go
+++ b/libpod/image/prune.go
@@ -66,6 +66,12 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I
if err != nil {
return nil, err
}
+
+ tree, err := ir.layerTree()
+ if err != nil {
+ return nil, err
+ }
+
for _, i := range allImages {
// filter the images based on this.
for _, filterFunc := range filterFuncs {
@@ -85,8 +91,9 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I
}
}
- //skip the cache or intermediate images
- intermediate, err := i.Intermediate(ctx)
+ // skip the cache (i.e., with parent) and intermediate (i.e.,
+ // with children) images
+ intermediate, err := tree.hasChildrenAndParent(ctx, i)
if err != nil {
return nil, err
}
diff --git a/libpod/logs/log.go b/libpod/logs/log.go
index 03acadb18..f66cbbadd 100644
--- a/libpod/logs/log.go
+++ b/libpod/logs/log.go
@@ -101,11 +101,14 @@ func getTailLog(path string, tail int) ([]*LogLine, error) {
if err != nil {
if errors.Cause(err) == io.EOF {
inputs <- []string{leftover}
- close(inputs)
- break
+ } else {
+ logrus.Error(err)
}
- logrus.Error(err)
close(inputs)
+ if err := f.Close(); err != nil {
+ logrus.Error(err)
+ }
+ break
}
line := strings.Split(s+leftover, "\n")
if len(line) > 1 {
@@ -136,9 +139,6 @@ func getTailLog(path string, tail int) ([]*LogLine, error) {
}
// if we have enough loglines, we can hangup
if nllCounter >= tail {
- if err := f.Close(); err != nil {
- logrus.Error(err)
- }
break
}
}
diff --git a/libpod/logs/reversereader/reversereader.go b/libpod/logs/reversereader/reversereader.go
index 72d9ad975..4fa1a3f88 100644
--- a/libpod/logs/reversereader/reversereader.go
+++ b/libpod/logs/reversereader/reversereader.go
@@ -60,7 +60,7 @@ func (r *ReverseReader) Read() (string, error) {
if int64(n) < r.readSize {
b = b[0:n]
}
- // Set to the next page boundary
- r.offset = -r.readSize
+ // Move the offset one pagesize up
+ r.offset -= r.readSize
return string(b), nil
}
diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go
index 1a6fe827b..9fe761638 100644
--- a/libpod/oci_conmon_exec_linux.go
+++ b/libpod/oci_conmon_exec_linux.go
@@ -449,9 +449,12 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex
return nil, nil, err
}
+ var filesToClose []*os.File
if options.PreserveFDs > 0 {
for fd := 3; fd < int(3+options.PreserveFDs); fd++ {
- execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)))
+ f := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))
+ filesToClose = append(filesToClose, f)
+ execCmd.ExtraFiles = append(execCmd.ExtraFiles, f)
}
}
@@ -483,14 +486,10 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex
return nil, nil, err
}
- if options.PreserveFDs > 0 {
- for fd := 3; fd < int(3+options.PreserveFDs); fd++ {
- // These fds were passed down to the runtime. Close them
- // and not interfere
- if err := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close(); err != nil {
- logrus.Debugf("unable to close file fd-%d", fd)
- }
- }
+ // These fds were passed down to the runtime. Close them
+ // and not interfere
+ for _, f := range filesToClose {
+ errorhandling.CloseQuiet(f)
}
return execCmd, pipes, nil
diff --git a/libpod/options.go b/libpod/options.go
index bff3f3c18..560b406e2 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -844,6 +844,20 @@ func WithPIDNSFrom(nsCtr *Container) CtrCreateOption {
}
}
+// WithAddCurrentUserPasswdEntry indicates that container should add current
+// user entry to /etc/passwd, since the UID will be mapped into the container,
+// via user namespace
+func WithAddCurrentUserPasswdEntry() CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+
+ ctr.config.AddCurrentUserPasswdEntry = true
+ return nil
+ }
+}
+
// WithUserNSFrom indicates the the container should join the user namespace of
// the given container.
// If the container has joined a pod, it can only join the namespaces of