summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container_api.go29
-rw-r--r--libpod/container_internal_linux.go67
-rw-r--r--libpod/container_path_resolution.go166
-rw-r--r--libpod/image/manifests.go9
-rw-r--r--libpod/image/search.go28
-rw-r--r--libpod/network/create.go9
-rw-r--r--libpod/networking_linux.go22
-rw-r--r--libpod/runtime.go14
-rw-r--r--libpod/shutdown/handler.go10
9 files changed, 318 insertions, 36 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 0d62a2dd7..951227a4f 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -776,3 +776,32 @@ func (c *Container) ShouldRestart(ctx context.Context) bool {
}
return c.shouldRestart()
}
+
+// ResolvePath resolves the specified path on the root for the container. The
+// root must either be the mounted image of the container or the already
+// mounted container storage.
+//
+// It returns the resolved root and the resolved path. Note that the path may
+// resolve to the container's mount point or to a volume or bind mount.
+func (c *Container) ResolvePath(ctx context.Context, root string, path string) (string, string, error) {
+ logrus.Debugf("Resolving path %q (root %q) on container %s", path, root, c.ID())
+
+ // Minimal sanity checks.
+ if len(root)*len(path) == 0 {
+ return "", "", errors.Wrapf(define.ErrInternal, "ResolvePath: root (%q) and path (%q) must be non empty", root, path)
+ }
+ if _, err := os.Stat(root); err != nil {
+ return "", "", errors.Wrapf(err, "cannot locate root to resolve path on container %s", c.ID())
+ }
+
+ if !c.batched {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if err := c.syncContainer(); err != nil {
+ return "", "", err
+ }
+ }
+
+ return c.resolvePath(root, path)
+}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 0553cc59c..6c9489a08 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -174,25 +174,60 @@ func (c *Container) prepare() error {
return err
}
- // Ensure container entrypoint is created (if required)
- if c.config.CreateWorkingDir {
- workdir, err := securejoin.SecureJoin(c.state.Mountpoint, c.WorkingDir())
- if err != nil {
- return errors.Wrapf(err, "error creating path to container %s working dir", c.ID())
- }
- rootUID := c.RootUID()
- rootGID := c.RootGID()
+ // Make sure the workdir exists
+ if err := c.resolveWorkDir(); err != nil {
+ return err
+ }
- if err := os.MkdirAll(workdir, 0755); err != nil {
- if os.IsExist(err) {
- return nil
+ return nil
+}
+
+// resolveWorkDir resolves the container's workdir and, depending on the
+// configuration, will create it, or error out if it does not exist.
+// Note that the container must be mounted before.
+func (c *Container) resolveWorkDir() error {
+ workdir := c.WorkingDir()
+
+ // If the specified workdir is a subdir of a volume or mount,
+ // we don't need to do anything. The runtime is taking care of
+ // that.
+ if isPathOnVolume(c, workdir) || isPathOnBindMount(c, workdir) {
+ logrus.Debugf("Workdir %q resolved to a volume or mount", workdir)
+ return nil
+ }
+
+ _, resolvedWorkdir, err := c.resolvePath(c.state.Mountpoint, workdir)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("Workdir %q resolved to host path %q", workdir, resolvedWorkdir)
+
+ // No need to create it (e.g., `--workdir=/foo`), so let's make sure
+ // the path exists on the container.
+ if !c.config.CreateWorkingDir {
+ if _, err := os.Stat(resolvedWorkdir); err != nil {
+ if os.IsNotExist(err) {
+ return errors.Errorf("workdir %q does not exist on container %s", workdir, c.ID())
}
- return errors.Wrapf(err, "error creating container %s working dir", c.ID())
+ // This might be a serious error (e.g., permission), so
+ // we need to return the full error.
+ return errors.Wrapf(err, "error detecting workdir %q on container %s", workdir, c.ID())
}
+ }
+
+ // Ensure container entrypoint is created (if required).
+ rootUID := c.RootUID()
+ rootGID := c.RootGID()
- if err := os.Chown(workdir, rootUID, rootGID); err != nil {
- return errors.Wrapf(err, "error chowning container %s working directory to container root", c.ID())
+ if err := os.MkdirAll(resolvedWorkdir, 0755); err != nil {
+ if os.IsExist(err) {
+ return nil
}
+ return errors.Wrapf(err, "error creating container %s workdir", c.ID())
+ }
+
+ if err := os.Chown(resolvedWorkdir, rootUID, rootGID); err != nil {
+ return errors.Wrapf(err, "error chowning container %s workdir to container root", c.ID())
}
return nil
@@ -1700,7 +1735,7 @@ func (c *Container) generateResolvConf() (string, error) {
nameservers = resolvconf.GetNameservers(resolv.Content)
// slirp4netns has a built in DNS server.
if c.config.NetMode.IsSlirp4netns() {
- nameservers = append([]string{"10.0.2.3"}, nameservers...)
+ nameservers = append([]string{slirp4netnsDNS}, nameservers...)
}
}
@@ -1780,7 +1815,7 @@ func (c *Container) getHosts() string {
if c.Hostname() != "" {
if c.config.NetMode.IsSlirp4netns() {
// When using slirp4netns, the interface gets a static IP
- hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", "10.0.2.100", c.Hostname(), c.config.Name)
+ hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP, c.Hostname(), c.config.Name)
} else {
hasNetNS := false
netNone := false
diff --git a/libpod/container_path_resolution.go b/libpod/container_path_resolution.go
new file mode 100644
index 000000000..805b3b947
--- /dev/null
+++ b/libpod/container_path_resolution.go
@@ -0,0 +1,166 @@
+package libpod
+
+import (
+ "path/filepath"
+ "strings"
+
+ securejoin "github.com/cyphar/filepath-securejoin"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// resolveContainerPaths resolves the container's mount point and the container
+// path as specified by the user. Both may resolve to paths outside of the
+// container's mount point when the container path hits a volume or bind mount.
+//
+// It returns a bool, indicating whether containerPath resolves outside of
+// mountPoint (e.g., via a mount or volume), the resolved root (e.g., container
+// mount, bind mount or volume) and the resolved path on the root (absolute to
+// the host).
+func (container *Container) resolvePath(mountPoint string, containerPath string) (string, string, error) {
+ // Let's first make sure we have a path relative to the mount point.
+ pathRelativeToContainerMountPoint := containerPath
+ if !filepath.IsAbs(containerPath) {
+ // If the containerPath is not absolute, it's relative to the
+ // container's working dir. To be extra careful, let's first
+ // join the working dir with "/", and the add the containerPath
+ // to it.
+ pathRelativeToContainerMountPoint = filepath.Join(filepath.Join("/", container.WorkingDir()), containerPath)
+ }
+ resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint)
+ pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint)
+ pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint)
+
+ // Now we have an "absolute container Path" but not yet resolved on the
+ // host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to
+ // check if "/foo/bar/file.txt" is on a volume or bind mount. To do
+ // that, we need to walk *down* the paths to the root. Assuming
+ // volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar",
+ // we must select "/foo/bar". Once selected, we need to rebase the
+ // remainder (i.e, "/file.txt") on the volume's mount point on the
+ // host. Same applies to bind mounts.
+
+ searchPath := pathRelativeToContainerMountPoint
+ for {
+ volume, err := findVolume(container, searchPath)
+ if err != nil {
+ return "", "", err
+ }
+ if volume != nil {
+ logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath)
+
+ // TODO: We really need to force the volume to mount
+ // before doing this, but that API is not exposed
+ // externally right now and doing so is beyond the scope
+ // of this commit.
+ mountPoint, err := volume.MountPoint()
+ if err != nil {
+ return "", "", err
+ }
+ if mountPoint == "" {
+ return "", "", errors.Errorf("volume %s is not mounted, cannot copy into it", volume.Name())
+ }
+
+ // We found a matching volume for searchPath. We now
+ // need to first find the relative path of our input
+ // path to the searchPath, and then join it with the
+ // volume's mount point.
+ pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
+ absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume)
+ if err != nil {
+ return "", "", err
+ }
+ return mountPoint, absolutePathOnTheVolumeMount, nil
+ }
+
+ if mount := findBindMount(container, searchPath); mount != nil {
+ logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath)
+ // We found a matching bind mount for searchPath. We
+ // now need to first find the relative path of our
+ // input path to the searchPath, and then join it with
+ // the source of the bind mount.
+ pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
+ absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount)
+ if err != nil {
+ return "", "", err
+ }
+ return mount.Source, absolutePathOnTheBindMount, nil
+
+ }
+
+ if searchPath == "/" {
+ // Cannot go beyond "/", so we're done.
+ break
+ }
+
+ // Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar").
+ searchPath = filepath.Dir(searchPath)
+ }
+
+ // No volume, no bind mount but just a normal path on the container.
+ return mountPoint, resolvedPathOnTheContainerMountPoint, nil
+}
+
+// findVolume checks if the specified containerPath matches the destination
+// path of a Volume. Returns a matching Volume or nil.
+func findVolume(c *Container, containerPath string) (*Volume, error) {
+ runtime := c.Runtime()
+ cleanedContainerPath := filepath.Clean(containerPath)
+ for _, vol := range c.Config().NamedVolumes {
+ if cleanedContainerPath == filepath.Clean(vol.Dest) {
+ return runtime.GetVolume(vol.Name)
+ }
+ }
+ return nil, nil
+}
+
+// isPathOnVolume returns true if the specified containerPath is a subdir of any
+// Volume's destination.
+func isPathOnVolume(c *Container, containerPath string) bool {
+ cleanedContainerPath := filepath.Clean(containerPath)
+ for _, vol := range c.Config().NamedVolumes {
+ if cleanedContainerPath == filepath.Clean(vol.Dest) {
+ return true
+ }
+ for dest := vol.Dest; dest != "/"; dest = filepath.Dir(dest) {
+ if cleanedContainerPath == dest {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// findBindMounts checks if the specified containerPath matches the destination
+// path of a Mount. Returns a matching Mount or nil.
+func findBindMount(c *Container, containerPath string) *specs.Mount {
+ cleanedPath := filepath.Clean(containerPath)
+ for _, m := range c.Config().Spec.Mounts {
+ if m.Type != "bind" {
+ continue
+ }
+ if cleanedPath == filepath.Clean(m.Destination) {
+ mount := m
+ return &mount
+ }
+ }
+ return nil
+}
+
+/// isPathOnBindMount returns true if the specified containerPath is a subdir of any
+// Mount's destination.
+func isPathOnBindMount(c *Container, containerPath string) bool {
+ cleanedContainerPath := filepath.Clean(containerPath)
+ for _, m := range c.Config().Spec.Mounts {
+ if cleanedContainerPath == filepath.Clean(m.Destination) {
+ return true
+ }
+ for dest := m.Destination; dest != "/"; dest = filepath.Dir(dest) {
+ if cleanedContainerPath == dest {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go
index 14f7c2f83..1ae3693c9 100644
--- a/libpod/image/manifests.go
+++ b/libpod/image/manifests.go
@@ -46,6 +46,15 @@ func (i *Image) InspectManifest() (*manifest.Schema2List, error) {
return list.Docker(), nil
}
+// ExistsManifest checks if a manifest list exists
+func (i *Image) ExistsManifest() (bool, error) {
+ _, err := i.getManifestList()
+ if err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
// RemoveManifest removes the given digest from the manifest list.
func (i *Image) RemoveManifest(d digest.Digest) (string, error) {
list, err := i.getManifestList()
diff --git a/libpod/image/search.go b/libpod/image/search.go
index 6020fbca9..c5799219a 100644
--- a/libpod/image/search.go
+++ b/libpod/image/search.go
@@ -102,8 +102,8 @@ func SearchImages(term string, options SearchOptions) ([]SearchResult, error) {
searchImageInRegistryHelper := func(index int, registry string) {
defer sem.Release(1)
defer wg.Done()
- searchOutput := searchImageInRegistry(term, registry, options)
- data[index] = searchOutputData{data: searchOutput}
+ searchOutput, err := searchImageInRegistry(term, registry, options)
+ data[index] = searchOutputData{data: searchOutput, err: err}
}
ctx := context.Background()
@@ -116,13 +116,21 @@ func SearchImages(term string, options SearchOptions) ([]SearchResult, error) {
wg.Wait()
results := []SearchResult{}
+ var lastError error
for _, d := range data {
if d.err != nil {
- return nil, d.err
+ if lastError != nil {
+ logrus.Errorf("%v", lastError)
+ }
+ lastError = d.err
+ continue
}
results = append(results, d.data...)
}
- return results, nil
+ if len(results) > 0 {
+ return results, nil
+ }
+ return results, lastError
}
// getRegistries returns the list of registries to search, depending on an optional registry specification
@@ -140,7 +148,7 @@ func getRegistries(registry string) ([]string, error) {
return registries, nil
}
-func searchImageInRegistry(term string, registry string, options SearchOptions) []SearchResult {
+func searchImageInRegistry(term string, registry string, options SearchOptions) ([]SearchResult, error) {
// Max number of queries by default is 25
limit := maxQueries
if options.Limit > 0 {
@@ -156,16 +164,14 @@ func searchImageInRegistry(term string, registry string, options SearchOptions)
if options.ListTags {
results, err := searchRepositoryTags(registry, term, sc, options)
if err != nil {
- logrus.Errorf("error listing registry tags %q: %v", registry, err)
- return []SearchResult{}
+ return []SearchResult{}, err
}
- return results
+ return results, nil
}
results, err := docker.SearchRegistry(context.TODO(), sc, registry, term, limit)
if err != nil {
- logrus.Errorf("error searching registry %q: %v", registry, err)
- return []SearchResult{}
+ return []SearchResult{}, err
}
index := registry
arr := strings.Split(registry, ".")
@@ -219,7 +225,7 @@ func searchImageInRegistry(term string, registry string, options SearchOptions)
}
paramsArr = append(paramsArr, params)
}
- return paramsArr
+ return paramsArr, nil
}
func searchRepositoryTags(registry, term string, sc *types.SystemContext, options SearchOptions) ([]SearchResult, error) {
diff --git a/libpod/network/create.go b/libpod/network/create.go
index e7f65358b..a8f985af9 100644
--- a/libpod/network/create.go
+++ b/libpod/network/create.go
@@ -14,6 +14,7 @@ import (
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/util"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// Create the CNI network
@@ -226,8 +227,12 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon
// if we find the dnsname plugin or are rootless, we add configuration for it
// the rootless-cni-infra container has the dnsname plugin always installed
if (HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) || rootless.IsRootless()) && !options.DisableDNS {
- // Note: in the future we might like to allow for dynamic domain names
- plugins = append(plugins, NewDNSNamePlugin(DefaultPodmanDomainName))
+ if options.Internal {
+ logrus.Warnf("dnsname and --internal networks are incompatible. dnsname plugin not configured for network %s", name)
+ } else {
+ // Note: in the future we might like to allow for dynamic domain names
+ plugins = append(plugins, NewDNSNamePlugin(DefaultPodmanDomainName))
+ }
}
ncList["plugins"] = plugins
b, err := json.MarshalIndent(ncList, "", " ")
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index addf1814c..ef2f034ab 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -35,6 +35,15 @@ import (
"golang.org/x/sys/unix"
)
+const (
+ // slirp4netnsIP is the IP used by slirp4netns to configure the tap device
+ // inside the network namespace.
+ slirp4netnsIP = "10.0.2.100"
+
+ // slirp4netnsDNS is the IP for the built-in DNS server in the slirp network
+ slirp4netnsDNS = "10.0.2.3"
+)
+
// Get an OCICNI network config
func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP, staticMAC net.HardwareAddr, netDescriptions ContainerNetworkDescriptions) ocicni.PodNetwork {
var networkKey string
@@ -541,12 +550,25 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath strin
}
}
+ childIP := slirp4netnsIP
+outer:
+ for _, r := range ctr.state.NetworkStatus {
+ for _, i := range r.IPs {
+ ipv4 := i.Address.IP.To4()
+ if ipv4 != nil {
+ childIP = ipv4.String()
+ break outer
+ }
+ }
+ }
+
cfg := rootlessport.Config{
Mappings: ctr.config.PortMappings,
NetNSPath: netnsPath,
ExitFD: 3,
ReadyFD: 4,
TmpDir: ctr.runtime.config.Engine.TmpDir,
+ ChildIP: childIP,
}
cfgJSON, err := json.Marshal(cfg)
if err != nil {
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 34c737a67..0dc220b52 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -180,6 +180,13 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R
}
}
+ if err := shutdown.Register("libpod", func(sig os.Signal) error {
+ os.Exit(1)
+ return nil
+ }); err != nil && errors.Cause(err) != shutdown.ErrHandlerExists {
+ logrus.Errorf("Error registering shutdown handler for libpod: %v", err)
+ }
+
if err := shutdown.Start(); err != nil {
return nil, errors.Wrapf(err, "error starting shutdown signal handler")
}
@@ -188,13 +195,6 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R
return nil, err
}
- if err := shutdown.Register("libpod", func(sig os.Signal) error {
- os.Exit(1)
- return nil
- }); err != nil && errors.Cause(err) != shutdown.ErrHandlerExists {
- logrus.Errorf("Error registering shutdown handler for libpod: %v", err)
- }
-
return runtime, nil
}
diff --git a/libpod/shutdown/handler.go b/libpod/shutdown/handler.go
index f0f228b19..ac1d33910 100644
--- a/libpod/shutdown/handler.go
+++ b/libpod/shutdown/handler.go
@@ -18,6 +18,8 @@ var (
stopped bool
sigChan chan os.Signal
cancelChan chan bool
+ // Syncronize accesses to the map
+ handlerLock sync.Mutex
// Definitions of all on-shutdown handlers
handlers map[string]func(os.Signal) error
// Ordering that on-shutdown handlers will be invoked.
@@ -50,6 +52,7 @@ func Start() error {
case sig := <-sigChan:
logrus.Infof("Received shutdown signal %v, terminating!", sig)
shutdownInhibit.Lock()
+ handlerLock.Lock()
for _, name := range handlerOrder {
handler, ok := handlers[name]
if !ok {
@@ -61,6 +64,7 @@ func Start() error {
logrus.Errorf("Error running shutdown handler %s: %v", name, err)
}
}
+ handlerLock.Unlock()
shutdownInhibit.Unlock()
return
}
@@ -97,6 +101,9 @@ func Uninhibit() {
// by a signal. Handlers are invoked LIFO - the last handler registered is the
// first run.
func Register(name string, handler func(os.Signal) error) error {
+ handlerLock.Lock()
+ defer handlerLock.Unlock()
+
if handlers == nil {
handlers = make(map[string]func(os.Signal) error)
}
@@ -113,6 +120,9 @@ func Register(name string, handler func(os.Signal) error) error {
// Unregister un-registers a given shutdown handler.
func Unregister(name string) error {
+ handlerLock.Lock()
+ defer handlerLock.Unlock()
+
if handlers == nil {
return nil
}