Skip to content

Commit

Permalink
Merge pull request #15856 from dfr/freebsd-copy
Browse files Browse the repository at this point in the history
Add support for 'podman cp' on FreeBSD
  • Loading branch information
openshift-merge-robot authored Sep 21, 2022
2 parents ffa73c5 + bb160be commit 7a189a6
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 368 deletions.
213 changes: 213 additions & 0 deletions libpod/container_copy_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
//go:build linux || freebsd
// +build linux freebsd

package libpod

import (
"errors"
"io"
"path/filepath"
"strings"

buildahCopiah "github.com/containers/buildah/copier"
"github.com/containers/buildah/pkg/chrootuser"
"github.com/containers/buildah/util"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)

func (c *Container) copyFromArchive(path string, chown, noOverwriteDirNonDir bool, rename map[string]string, reader io.Reader) (func() error, error) {
var (
mountPoint string
resolvedRoot string
resolvedPath string
unmount func()
err error
)

// Make sure that "/" copies the *contents* of the mount point and not
// the directory.
if path == "/" {
path = "/."
}

// Optimization: only mount if the container is not already.
if c.state.Mounted {
mountPoint = c.state.Mountpoint
unmount = func() {}
} else {
// NOTE: make sure to unmount in error paths.
mountPoint, err = c.mount()
if err != nil {
return nil, err
}
unmount = func() {
if err := c.unmount(false); err != nil {
logrus.Errorf("Failed to unmount container: %v", err)
}
}
}

resolvedRoot, resolvedPath, err = c.resolveCopyTarget(mountPoint, path)
if err != nil {
unmount()
return nil, err
}

var idPair *idtools.IDPair
if chown {
// Make sure we chown the files to the container's main user and group ID.
user, err := getContainerUser(c, mountPoint)
if err != nil {
unmount()
return nil, err
}
idPair = &idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
}

decompressed, err := archive.DecompressStream(reader)
if err != nil {
unmount()
return nil, err
}

logrus.Debugf("Container copy *to* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID())

return func() error {
defer unmount()
defer decompressed.Close()
putOptions := buildahCopiah.PutOptions{
UIDMap: c.config.IDMappings.UIDMap,
GIDMap: c.config.IDMappings.GIDMap,
ChownDirs: idPair,
ChownFiles: idPair,
NoOverwriteDirNonDir: noOverwriteDirNonDir,
NoOverwriteNonDirDir: noOverwriteDirNonDir,
Rename: rename,
}

return c.joinMountAndExec(
func() error {
return buildahCopiah.Put(resolvedRoot, resolvedPath, putOptions, decompressed)
},
)
}, nil
}

func (c *Container) copyToArchive(path string, writer io.Writer) (func() error, error) {
var (
mountPoint string
unmount func()
err error
)

// Optimization: only mount if the container is not already.
if c.state.Mounted {
mountPoint = c.state.Mountpoint
unmount = func() {}
} else {
// NOTE: make sure to unmount in error paths.
mountPoint, err = c.mount()
if err != nil {
return nil, err
}
unmount = func() {
if err := c.unmount(false); err != nil {
logrus.Errorf("Failed to unmount container: %v", err)
}
}
}

statInfo, resolvedRoot, resolvedPath, err := c.stat(mountPoint, path)
if err != nil {
unmount()
return nil, err
}

// We optimistically chown to the host user. In case of a hypothetical
// container-to-container copy, the reading side will chown back to the
// container user.
user, err := getContainerUser(c, mountPoint)
if err != nil {
unmount()
return nil, err
}
hostUID, hostGID, err := util.GetHostIDs(
idtoolsToRuntimeSpec(c.config.IDMappings.UIDMap),
idtoolsToRuntimeSpec(c.config.IDMappings.GIDMap),
user.UID,
user.GID,
)
if err != nil {
unmount()
return nil, err
}
idPair := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}

logrus.Debugf("Container copy *from* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID())

return func() error {
defer unmount()
getOptions := buildahCopiah.GetOptions{
// Unless the specified points to ".", we want to copy the base directory.
KeepDirectoryNames: statInfo.IsDir && filepath.Base(path) != ".",
UIDMap: c.config.IDMappings.UIDMap,
GIDMap: c.config.IDMappings.GIDMap,
ChownDirs: &idPair,
ChownFiles: &idPair,
Excludes: []string{"dev", "proc", "sys"},
// Ignore EPERMs when copying from rootless containers
// since we cannot read TTY devices. Those are owned
// by the host's root and hence "nobody" inside the
// container's user namespace.
IgnoreUnreadable: rootless.IsRootless() && c.state.State == define.ContainerStateRunning,
}
return c.joinMountAndExec(
func() error {
return buildahCopiah.Get(resolvedRoot, "", getOptions, []string{resolvedPath}, writer)
},
)
}, nil
}

// getContainerUser returns the specs.User and ID mappings of the container.
func getContainerUser(container *Container, mountPoint string) (specs.User, error) {
userspec := container.config.User

uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec)
u := specs.User{
UID: uid,
GID: gid,
Username: userspec,
}

if !strings.Contains(userspec, ":") {
groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
if err2 != nil {
if !errors.Is(err2, chrootuser.ErrNoSuchUser) && err == nil {
err = err2
}
} else {
u.AdditionalGids = groups
}
}

return u, err
}

// idtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec.
func idtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) {
for _, idmap := range idMaps {
tempIDMap := specs.LinuxIDMapping{
ContainerID: uint32(idmap.ContainerID),
HostID: uint32(idmap.HostID),
Size: uint32(idmap.Size),
}
convertedIDMap = append(convertedIDMap, tempIDMap)
}
return convertedIDMap
}
13 changes: 13 additions & 0 deletions libpod/container_copy_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package libpod

// On FreeBSD, the container's mounts are in the global mount
// namespace so we can just execute the function directly.
func (c *Container) joinMountAndExec(f func() error) error {
return f()
}

// Similarly, we can just use resolvePath for both running and stopped
// containers.
func (c *Container) resolveCopyTarget(mountPoint string, containerPath string) (string, string, error) {
return c.resolvePath(mountPoint, containerPath)
}
Loading

0 comments on commit 7a189a6

Please sign in to comment.