mirror of
https://github.com/containers/podman.git
synced 2025-06-25 20:26:51 +08:00
Merge pull request #9630 from vrothberg/cp-rootless-eperms
podman cp: ignore EPERMs in rootless mode
This commit is contained in:
@ -160,6 +160,25 @@ func copyFromContainer(container string, containerPath string, hostPath string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we copy a directory via the "." notation and the host path does
|
||||||
|
// not exist, we need to make sure that the destination on the host
|
||||||
|
// gets created; otherwise the contents of the source directory will be
|
||||||
|
// written to the destination's parent directory.
|
||||||
|
//
|
||||||
|
// While we could cut it short on the host and do create the directory
|
||||||
|
// ourselves, we would run into problems trying to that the other way
|
||||||
|
// around when copying into a container. Instead, to keep both
|
||||||
|
// implementations symmetrical, we need to massage the code a bit to
|
||||||
|
// let Buildah's copier package create the destination.
|
||||||
|
//
|
||||||
|
// Hence, whenever "." is the source and the destination does not exist,
|
||||||
|
// we copy the source's parent and let the copier package create the
|
||||||
|
// destination via the Rename option.
|
||||||
|
containerTarget := containerInfo.LinkTarget
|
||||||
|
if hostInfoErr != nil && containerInfo.IsDir && strings.HasSuffix(containerTarget, ".") {
|
||||||
|
containerTarget = filepath.Dir(containerTarget)
|
||||||
|
}
|
||||||
|
|
||||||
reader, writer := io.Pipe()
|
reader, writer := io.Pipe()
|
||||||
hostCopy := func() error {
|
hostCopy := func() error {
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
@ -193,10 +212,10 @@ func copyFromContainer(container string, containerPath string, hostPath string)
|
|||||||
ChownFiles: &idPair,
|
ChownFiles: &idPair,
|
||||||
IgnoreDevices: true,
|
IgnoreDevices: true,
|
||||||
}
|
}
|
||||||
if !containerInfo.IsDir && (!hostInfo.IsDir || hostInfoErr != nil) {
|
if (!containerInfo.IsDir && !hostInfo.IsDir) || hostInfoErr != nil {
|
||||||
// If we're having a file-to-file copy, make sure to
|
// If we're having a file-to-file copy, make sure to
|
||||||
// rename accordingly.
|
// rename accordingly.
|
||||||
putOptions.Rename = map[string]string{filepath.Base(containerInfo.LinkTarget): hostBaseName}
|
putOptions.Rename = map[string]string{filepath.Base(containerTarget): hostBaseName}
|
||||||
}
|
}
|
||||||
dir := hostInfo.LinkTarget
|
dir := hostInfo.LinkTarget
|
||||||
if !hostInfo.IsDir {
|
if !hostInfo.IsDir {
|
||||||
@ -210,7 +229,7 @@ func copyFromContainer(container string, containerPath string, hostPath string)
|
|||||||
|
|
||||||
containerCopy := func() error {
|
containerCopy := func() error {
|
||||||
defer writer.Close()
|
defer writer.Close()
|
||||||
copyFunc, err := registry.ContainerEngine().ContainerCopyToArchive(registry.GetContext(), container, containerInfo.LinkTarget, writer)
|
copyFunc, err := registry.ContainerEngine().ContainerCopyToArchive(registry.GetContext(), container, containerTarget, writer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -278,6 +297,19 @@ func copyToContainer(container string, containerPath string, hostPath string) er
|
|||||||
containerBaseName = filepath.Base(containerInfo.LinkTarget)
|
containerBaseName = filepath.Base(containerInfo.LinkTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we copy a directory via the "." notation and the container path
|
||||||
|
// does not exist, we need to make sure that the destination on the
|
||||||
|
// container gets created; otherwise the contents of the source
|
||||||
|
// directory will be written to the destination's parent directory.
|
||||||
|
//
|
||||||
|
// Hence, whenever "." is the source and the destination does not
|
||||||
|
// exist, we copy the source's parent and let the copier package create
|
||||||
|
// the destination via the Rename option.
|
||||||
|
hostTarget := hostInfo.LinkTarget
|
||||||
|
if containerInfoErr != nil && hostInfo.IsDir && strings.HasSuffix(hostTarget, ".") {
|
||||||
|
hostTarget = filepath.Dir(hostTarget)
|
||||||
|
}
|
||||||
|
|
||||||
var stdinFile string
|
var stdinFile string
|
||||||
if isStdin {
|
if isStdin {
|
||||||
if !containerInfo.IsDir {
|
if !containerInfo.IsDir {
|
||||||
@ -318,15 +350,16 @@ func copyToContainer(container string, containerPath string, hostPath string) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOptions := buildahCopiah.GetOptions{
|
getOptions := buildahCopiah.GetOptions{
|
||||||
// Unless the specified points to ".", we want to copy the base directory.
|
// Unless the specified path points to ".", we want to
|
||||||
KeepDirectoryNames: hostInfo.IsDir && filepath.Base(hostPath) != ".",
|
// copy the base directory.
|
||||||
|
KeepDirectoryNames: hostInfo.IsDir && filepath.Base(hostTarget) != ".",
|
||||||
}
|
}
|
||||||
if !hostInfo.IsDir && (!containerInfo.IsDir || containerInfoErr != nil) {
|
if (!hostInfo.IsDir && !containerInfo.IsDir) || containerInfoErr != nil {
|
||||||
// If we're having a file-to-file copy, make sure to
|
// If we're having a file-to-file copy, make sure to
|
||||||
// rename accordingly.
|
// rename accordingly.
|
||||||
getOptions.Rename = map[string]string{filepath.Base(hostInfo.LinkTarget): containerBaseName}
|
getOptions.Rename = map[string]string{filepath.Base(hostTarget): containerBaseName}
|
||||||
}
|
}
|
||||||
if err := buildahCopiah.Get("/", "", getOptions, []string{hostInfo.LinkTarget}, writer); err != nil {
|
if err := buildahCopiah.Get("/", "", getOptions, []string{hostTarget}, writer); err != nil {
|
||||||
return errors.Wrap(err, "error copying from host")
|
return errors.Wrap(err, "error copying from host")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -57,6 +57,8 @@ If you use a : in a local machine path, you must be explicit with a relative or
|
|||||||
|
|
||||||
Using `-` as the *src_path* streams the contents of STDIN as a tar archive. The command extracts the content of the tar to the *DEST_PATH* in the container. In this case, *dest_path* must specify a directory. Using `-` as the *dest_path* streams the contents of the resource (can be a directory) as a tar archive to STDOUT.
|
Using `-` as the *src_path* streams the contents of STDIN as a tar archive. The command extracts the content of the tar to the *DEST_PATH* in the container. In this case, *dest_path* must specify a directory. Using `-` as the *dest_path* streams the contents of the resource (can be a directory) as a tar archive to STDOUT.
|
||||||
|
|
||||||
|
Note that `podman cp` ignores permission errors when copying from a running rootless container. The TTY devices inside a rootless container are owned by the host's root user and hence cannot be read inside the container's user namespace.
|
||||||
|
|
||||||
## OPTIONS
|
## OPTIONS
|
||||||
|
|
||||||
## ALTERNATIVES
|
## ALTERNATIVES
|
||||||
|
2
go.mod
2
go.mod
@ -10,7 +10,7 @@ require (
|
|||||||
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
|
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
|
||||||
github.com/containernetworking/cni v0.8.1
|
github.com/containernetworking/cni v0.8.1
|
||||||
github.com/containernetworking/plugins v0.9.1
|
github.com/containernetworking/plugins v0.9.1
|
||||||
github.com/containers/buildah v1.19.7
|
github.com/containers/buildah v1.19.8
|
||||||
github.com/containers/common v0.35.0
|
github.com/containers/common v0.35.0
|
||||||
github.com/containers/conmon v2.0.20+incompatible
|
github.com/containers/conmon v2.0.20+incompatible
|
||||||
github.com/containers/image/v5 v5.10.2
|
github.com/containers/image/v5 v5.10.2
|
||||||
|
4
go.sum
4
go.sum
@ -94,8 +94,8 @@ github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ
|
|||||||
github.com/containernetworking/plugins v0.8.7/go.mod h1:R7lXeZaBzpfqapcAbHRW8/CYwm0dHzbz0XEjofx0uB0=
|
github.com/containernetworking/plugins v0.8.7/go.mod h1:R7lXeZaBzpfqapcAbHRW8/CYwm0dHzbz0XEjofx0uB0=
|
||||||
github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9r2Quag7HMLV8=
|
github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9r2Quag7HMLV8=
|
||||||
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
|
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
|
||||||
github.com/containers/buildah v1.19.7 h1:/g11GlhTo177xFex+5GHlF22hq01SyWaJuSA26UGFNU=
|
github.com/containers/buildah v1.19.8 h1:4TzmetfKPQF5hh6GgMwbAfrD50j+PAcsRiWDnx+gCI8=
|
||||||
github.com/containers/buildah v1.19.7/go.mod h1:VnyHWgNmfR1d89/zJ/F4cbwOzaQS+6sBky46W7dCo3E=
|
github.com/containers/buildah v1.19.8/go.mod h1:VnyHWgNmfR1d89/zJ/F4cbwOzaQS+6sBky46W7dCo3E=
|
||||||
github.com/containers/common v0.33.4/go.mod h1:PhgL71XuC4jJ/1BIqeP7doke3aMFkCP90YBXwDeUr9g=
|
github.com/containers/common v0.33.4/go.mod h1:PhgL71XuC4jJ/1BIqeP7doke3aMFkCP90YBXwDeUr9g=
|
||||||
github.com/containers/common v0.35.0 h1:1OLZ2v+Tj/CN9BTQkKZ5VOriOiArJedinMMqfJRUI38=
|
github.com/containers/common v0.35.0 h1:1OLZ2v+Tj/CN9BTQkKZ5VOriOiArJedinMMqfJRUI38=
|
||||||
github.com/containers/common v0.35.0/go.mod h1:gs1th7XFTOvVUl4LDPdQjOfOeNiVRDbQ7CNrZ0wS6F8=
|
github.com/containers/common v0.35.0/go.mod h1:gs1th7XFTOvVUl4LDPdQjOfOeNiVRDbQ7CNrZ0wS6F8=
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/containers/buildah/pkg/chrootuser"
|
"github.com/containers/buildah/pkg/chrootuser"
|
||||||
"github.com/containers/buildah/util"
|
"github.com/containers/buildah/util"
|
||||||
"github.com/containers/podman/v3/libpod/define"
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/podman/v3/pkg/rootless"
|
||||||
"github.com/containers/storage/pkg/idtools"
|
"github.com/containers/storage/pkg/idtools"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
@ -62,15 +62,16 @@ func (c *Container) copyFromArchive(ctx context.Context, path string, reader io.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decompressed, err := archive.DecompressStream(reader)
|
// Make sure we chown the files to the container's main user and group ID.
|
||||||
|
user, err := getContainerUser(c, mountPoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unmount()
|
unmount()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
idPair := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
|
||||||
|
|
||||||
idMappings, idPair, err := getIDMappingsAndPair(c, mountPoint)
|
decompressed, err := archive.DecompressStream(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
decompressed.Close()
|
|
||||||
unmount()
|
unmount()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -81,10 +82,10 @@ func (c *Container) copyFromArchive(ctx context.Context, path string, reader io.
|
|||||||
defer unmount()
|
defer unmount()
|
||||||
defer decompressed.Close()
|
defer decompressed.Close()
|
||||||
putOptions := buildahCopiah.PutOptions{
|
putOptions := buildahCopiah.PutOptions{
|
||||||
UIDMap: idMappings.UIDMap,
|
UIDMap: c.config.IDMappings.UIDMap,
|
||||||
GIDMap: idMappings.GIDMap,
|
GIDMap: c.config.IDMappings.GIDMap,
|
||||||
ChownDirs: idPair,
|
ChownDirs: &idPair,
|
||||||
ChownFiles: idPair,
|
ChownFiles: &idPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.joinMountAndExec(ctx,
|
return c.joinMountAndExec(ctx,
|
||||||
@ -121,11 +122,25 @@ func (c *Container) copyToArchive(ctx context.Context, path string, writer io.Wr
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
idMappings, idPair, err := getIDMappingsAndPair(c, mountPoint)
|
// 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 {
|
if err != nil {
|
||||||
unmount()
|
unmount()
|
||||||
return nil, err
|
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())
|
logrus.Debugf("Container copy *from* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID())
|
||||||
|
|
||||||
@ -134,11 +149,16 @@ func (c *Container) copyToArchive(ctx context.Context, path string, writer io.Wr
|
|||||||
getOptions := buildahCopiah.GetOptions{
|
getOptions := buildahCopiah.GetOptions{
|
||||||
// Unless the specified points to ".", we want to copy the base directory.
|
// Unless the specified points to ".", we want to copy the base directory.
|
||||||
KeepDirectoryNames: statInfo.IsDir && filepath.Base(path) != ".",
|
KeepDirectoryNames: statInfo.IsDir && filepath.Base(path) != ".",
|
||||||
UIDMap: idMappings.UIDMap,
|
UIDMap: c.config.IDMappings.UIDMap,
|
||||||
GIDMap: idMappings.GIDMap,
|
GIDMap: c.config.IDMappings.GIDMap,
|
||||||
ChownDirs: idPair,
|
ChownDirs: &idPair,
|
||||||
ChownFiles: idPair,
|
ChownFiles: &idPair,
|
||||||
Excludes: []string{"dev", "proc", "sys"},
|
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(ctx,
|
return c.joinMountAndExec(ctx,
|
||||||
func() error {
|
func() error {
|
||||||
@ -148,29 +168,7 @@ func (c *Container) copyToArchive(ctx context.Context, path string, writer io.Wr
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getIDMappingsAndPair returns the ID mappings for the container and the host
|
// getContainerUser returns the specs.User and ID mappings of the container.
|
||||||
// ID pair.
|
|
||||||
func getIDMappingsAndPair(container *Container, containerMount string) (*storage.IDMappingOptions, *idtools.IDPair, error) {
|
|
||||||
user, err := getContainerUser(container, containerMount)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
idMappingOpts, err := container.IDMappings()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostUID, hostGID, err := util.GetHostIDs(idtoolsToRuntimeSpec(idMappingOpts.UIDMap), idtoolsToRuntimeSpec(idMappingOpts.GIDMap), user.UID, user.GID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
idPair := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
|
|
||||||
return &idMappingOpts, &idPair, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getContainerUser returns the specs.User of the container.
|
|
||||||
func getContainerUser(container *Container, mountPoint string) (specs.User, error) {
|
func getContainerUser(container *Container, mountPoint string) (specs.User, error) {
|
||||||
userspec := container.Config().User
|
userspec := container.Config().User
|
||||||
|
|
||||||
|
@ -64,6 +64,13 @@ func (c *Container) stat(ctx context.Context, containerMountPoint string, contai
|
|||||||
containerPath = "/."
|
containerPath = "/."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wildcards are not allowed.
|
||||||
|
// TODO: it's now technically possible wildcards.
|
||||||
|
// We may consider enabling support in the future.
|
||||||
|
if strings.Contains(containerPath, "*") {
|
||||||
|
return nil, "", "", copy.ErrENOENT
|
||||||
|
}
|
||||||
|
|
||||||
if c.state.State == define.ContainerStateRunning {
|
if c.state.State == define.ContainerStateRunning {
|
||||||
// If the container is running, we need to join it's mount namespace
|
// If the container is running, we need to join it's mount namespace
|
||||||
// and stat there.
|
// and stat there.
|
||||||
@ -88,7 +95,8 @@ func (c *Container) stat(ctx context.Context, containerMountPoint string, contai
|
|||||||
}
|
}
|
||||||
|
|
||||||
if statInfo.IsSymlink {
|
if statInfo.IsSymlink {
|
||||||
// Evaluated symlinks are always relative to the container's mount point.
|
// Symlinks are already evaluated and always relative to the
|
||||||
|
// container's mount point.
|
||||||
absContainerPath = statInfo.ImmediateTarget
|
absContainerPath = statInfo.ImmediateTarget
|
||||||
} else if strings.HasPrefix(resolvedPath, containerMountPoint) {
|
} else if strings.HasPrefix(resolvedPath, containerMountPoint) {
|
||||||
// If the path is on the container's mount point, strip it off.
|
// If the path is on the container's mount point, strip it off.
|
||||||
@ -143,15 +151,31 @@ func secureStat(root string, path string) (*copier.StatForItem, error) {
|
|||||||
if len(globStats) != 1 {
|
if len(globStats) != 1 {
|
||||||
return nil, errors.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats))
|
return nil, errors.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats))
|
||||||
}
|
}
|
||||||
|
if len(globStats) != 1 {
|
||||||
stat, exists := globStats[0].Results[glob] // only one glob passed, so that's okay
|
return nil, errors.Errorf("internal error: secureStat: expected 1 result but got %d", len(globStats[0].Results))
|
||||||
if !exists {
|
|
||||||
return nil, copy.ErrENOENT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: the key in the map differ from `glob` when hitting symlink.
|
||||||
|
// Hence, we just take the first (and only) key/value pair.
|
||||||
|
for _, stat := range globStats[0].Results {
|
||||||
var statErr error
|
var statErr error
|
||||||
if stat.Error != "" {
|
if stat.Error != "" {
|
||||||
statErr = errors.New(stat.Error)
|
statErr = errors.New(stat.Error)
|
||||||
}
|
}
|
||||||
|
// If necessary evaluate the symlink
|
||||||
|
if stat.IsSymlink {
|
||||||
|
target, err := copier.Eval(root, path, copier.EvalOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error evaluating symlink in container")
|
||||||
|
}
|
||||||
|
// Need to make sure the symlink is relative to the root!
|
||||||
|
target = strings.TrimPrefix(target, root)
|
||||||
|
target = filepath.Join("/", target)
|
||||||
|
stat.ImmediateTarget = target
|
||||||
|
}
|
||||||
return stat, statErr
|
return stat, statErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found!
|
||||||
|
return nil, copy.ErrENOENT
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,6 @@ var _ = Describe("Podman cp", func() {
|
|||||||
|
|
||||||
// Copy the root dir "/" of a container to the host.
|
// Copy the root dir "/" of a container to the host.
|
||||||
It("podman cp the root directory from the ctr to an existing directory on the host ", func() {
|
It("podman cp the root directory from the ctr to an existing directory on the host ", func() {
|
||||||
SkipIfRootless("cannot copy tty devices in rootless mode")
|
|
||||||
container := "copyroottohost"
|
container := "copyroottohost"
|
||||||
session := podmanTest.RunTopContainer(container)
|
session := podmanTest.RunTopContainer(container)
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
|
@ -88,6 +88,7 @@ load helpers
|
|||||||
run_podman rmi -f $cpimage
|
run_podman rmi -f $cpimage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "podman cp file from host to container tmpfs mount" {
|
@test "podman cp file from host to container tmpfs mount" {
|
||||||
srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
|
srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
|
||||||
mkdir -p $srcdir
|
mkdir -p $srcdir
|
||||||
@ -113,6 +114,22 @@ load helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@test "podman cp file from host to container and check ownership" {
|
||||||
|
srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
|
||||||
|
mkdir -p $srcdir
|
||||||
|
content=cp-user-test-$(random_string 10)
|
||||||
|
echo "content" > $srcdir/hostfile
|
||||||
|
userid=$(id -u)
|
||||||
|
|
||||||
|
run_podman run --user=$userid --userns=keep-id -d --name cpcontainer $IMAGE sleep infinity
|
||||||
|
run_podman cp $srcdir/hostfile cpcontainer:/tmp/hostfile
|
||||||
|
run_podman exec cpcontainer stat -c "%u" /tmp/hostfile
|
||||||
|
is "$output" "$userid" "copied file is chowned to the container user"
|
||||||
|
run_podman kill cpcontainer
|
||||||
|
run_podman rm -f cpcontainer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "podman cp file from container to host" {
|
@test "podman cp file from container to host" {
|
||||||
srcdir=$PODMAN_TMPDIR/cp-test-file-ctr-to-host
|
srcdir=$PODMAN_TMPDIR/cp-test-file-ctr-to-host
|
||||||
mkdir -p $srcdir
|
mkdir -p $srcdir
|
||||||
@ -175,20 +192,19 @@ load helpers
|
|||||||
|
|
||||||
|
|
||||||
@test "podman cp dir from host to container" {
|
@test "podman cp dir from host to container" {
|
||||||
dirname=dir-test
|
srcdir=$PODMAN_TMPDIR
|
||||||
srcdir=$PODMAN_TMPDIR/$dirname
|
mkdir -p $srcdir/dir/sub
|
||||||
mkdir -p $srcdir
|
|
||||||
local -a randomcontent=(
|
local -a randomcontent=(
|
||||||
random-0-$(random_string 10)
|
random-0-$(random_string 10)
|
||||||
random-1-$(random_string 15)
|
random-1-$(random_string 15)
|
||||||
)
|
)
|
||||||
echo "${randomcontent[0]}" > $srcdir/hostfile0
|
echo "${randomcontent[0]}" > $srcdir/dir/sub/hostfile0
|
||||||
echo "${randomcontent[1]}" > $srcdir/hostfile1
|
echo "${randomcontent[1]}" > $srcdir/dir/sub/hostfile1
|
||||||
|
|
||||||
# "." and "dir/." will copy the contents, so make sure that a dir ending
|
# "." and "dir/." will copy the contents, so make sure that a dir ending
|
||||||
# with dot is treated correctly.
|
# with dot is treated correctly.
|
||||||
mkdir -p $srcdir.
|
mkdir -p $srcdir/dir.
|
||||||
cp $srcdir/* $srcdir./
|
cp -r $srcdir/dir/* $srcdir/dir.
|
||||||
|
|
||||||
run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
|
run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
|
||||||
run_podman exec cpcontainer mkdir /srv/subdir
|
run_podman exec cpcontainer mkdir /srv/subdir
|
||||||
@ -199,12 +215,15 @@ load helpers
|
|||||||
|
|
||||||
# format is: <source arg to cp (appended to srcdir)> | <destination arg to cp> | <full dest path> | <test name>
|
# format is: <source arg to cp (appended to srcdir)> | <destination arg to cp> | <full dest path> | <test name>
|
||||||
tests="
|
tests="
|
||||||
| / | /dir-test | copy to root
|
dir | / | /dir/sub | copy dir to root
|
||||||
. | / | /dir-test. | copy dotdir to root
|
dir. | / | /dir./sub | copy dir. to root
|
||||||
/ | /tmp | /tmp/dir-test | copy to tmp
|
dir/ | /tmp | /tmp/dir/sub | copy dir/ to tmp
|
||||||
/. | /usr/ | /usr/ | copy contents of dir to usr/
|
dir/. | /usr/ | /usr/sub | copy dir/. usr/
|
||||||
| . | /srv/dir-test | copy to workdir (rel path)
|
dir/sub | . | /srv/sub | copy dir/sub to workdir (rel path)
|
||||||
| subdir/. | /srv/subdir/dir-test | copy to workdir subdir (rel path)
|
dir/sub/. | subdir/. | /srv/subdir | copy dir/sub/. to workdir subdir (rel path)
|
||||||
|
dir | /newdir1 | /newdir1/sub | copy dir to newdir1
|
||||||
|
dir/ | /newdir2 | /newdir2/sub | copy dir/ to newdir2
|
||||||
|
dir/. | /newdir3 | /newdir3/sub | copy dir/. to newdir3
|
||||||
"
|
"
|
||||||
|
|
||||||
# RUNNING container
|
# RUNNING container
|
||||||
@ -213,12 +232,10 @@ load helpers
|
|||||||
if [[ $src == "''" ]];then
|
if [[ $src == "''" ]];then
|
||||||
unset src
|
unset src
|
||||||
fi
|
fi
|
||||||
run_podman cp $srcdir$src cpcontainer:$dest
|
run_podman cp $srcdir/$src cpcontainer:$dest
|
||||||
run_podman exec cpcontainer ls $dest_fullname
|
run_podman exec cpcontainer cat $dest_fullname/hostfile0 $dest_fullname/hostfile1
|
||||||
run_podman exec cpcontainer cat $dest_fullname/hostfile0
|
is "${lines[0]}" "${randomcontent[0]}" "$description (cp -> ctr:$dest)"
|
||||||
is "$output" "${randomcontent[0]}" "$description (cp -> ctr:$dest)"
|
is "${lines[1]}" "${randomcontent[1]}" "$description (cp -> ctr:$dest)"
|
||||||
run_podman exec cpcontainer cat $dest_fullname/hostfile1
|
|
||||||
is "$output" "${randomcontent[1]}" "$description (cp -> ctr:$dest)"
|
|
||||||
done < <(parse_table "$tests")
|
done < <(parse_table "$tests")
|
||||||
run_podman kill cpcontainer
|
run_podman kill cpcontainer
|
||||||
run_podman rm -f cpcontainer
|
run_podman rm -f cpcontainer
|
||||||
@ -230,7 +247,7 @@ load helpers
|
|||||||
unset src
|
unset src
|
||||||
fi
|
fi
|
||||||
run_podman create --name cpcontainer --workdir=/srv $cpimage sleep infinity
|
run_podman create --name cpcontainer --workdir=/srv $cpimage sleep infinity
|
||||||
run_podman cp $srcdir$src cpcontainer:$dest
|
run_podman cp $srcdir/$src cpcontainer:$dest
|
||||||
run_podman start cpcontainer
|
run_podman start cpcontainer
|
||||||
run_podman exec cpcontainer cat $dest_fullname/hostfile0 $dest_fullname/hostfile1
|
run_podman exec cpcontainer cat $dest_fullname/hostfile0 $dest_fullname/hostfile1
|
||||||
is "${lines[0]}" "${randomcontent[0]}" "$description (cp -> ctr:$dest)"
|
is "${lines[0]}" "${randomcontent[0]}" "$description (cp -> ctr:$dest)"
|
||||||
@ -263,17 +280,19 @@ load helpers
|
|||||||
run_podman commit -q cpcontainer
|
run_podman commit -q cpcontainer
|
||||||
cpimage="$output"
|
cpimage="$output"
|
||||||
|
|
||||||
# format is: <source arg to cp (appended to /srv)> | <full dest path> | <test name>
|
# format is: <source arg to cp (appended to /srv)> | <dest> | <full dest path> | <test name>
|
||||||
tests="
|
tests="
|
||||||
/srv | /srv/subdir | copy /srv
|
/srv | | /srv/subdir | copy /srv
|
||||||
/srv/ | /srv/subdir | copy /srv/
|
/srv | /newdir | /newdir/subdir | copy /srv to /newdir
|
||||||
/srv/. | /subdir | copy /srv/.
|
/srv/ | | /srv/subdir | copy /srv/
|
||||||
/srv/subdir/. | | copy /srv/subdir/.
|
/srv/. | | /subdir | copy /srv/.
|
||||||
/tmp/subdir. | /subdir. | copy /tmp/subdir.
|
/srv/. | /newdir | /newdir/subdir | copy /srv/. to /newdir
|
||||||
|
/srv/subdir/. | | | copy /srv/subdir/.
|
||||||
|
/tmp/subdir. | | /subdir. | copy /tmp/subdir.
|
||||||
"
|
"
|
||||||
|
|
||||||
# RUNNING container
|
# RUNNING container
|
||||||
while read src dest_fullname description; do
|
while read src dest dest_fullname description; do
|
||||||
if [[ $src == "''" ]];then
|
if [[ $src == "''" ]];then
|
||||||
unset src
|
unset src
|
||||||
fi
|
fi
|
||||||
@ -283,7 +302,7 @@ load helpers
|
|||||||
if [[ $dest_fullname == "''" ]];then
|
if [[ $dest_fullname == "''" ]];then
|
||||||
unset dest_fullname
|
unset dest_fullname
|
||||||
fi
|
fi
|
||||||
run_podman cp cpcontainer:$src $destdir
|
run_podman cp cpcontainer:$src $destdir$dest
|
||||||
is "$(< $destdir$dest_fullname/containerfile0)" "${randomcontent[0]}" "$description"
|
is "$(< $destdir$dest_fullname/containerfile0)" "${randomcontent[0]}" "$description"
|
||||||
is "$(< $destdir$dest_fullname/containerfile1)" "${randomcontent[1]}" "$description"
|
is "$(< $destdir$dest_fullname/containerfile1)" "${randomcontent[1]}" "$description"
|
||||||
rm -rf $destdir/*
|
rm -rf $destdir/*
|
||||||
@ -293,7 +312,7 @@ load helpers
|
|||||||
|
|
||||||
# CREATED container
|
# CREATED container
|
||||||
run_podman create --name cpcontainer --workdir=/srv $cpimage
|
run_podman create --name cpcontainer --workdir=/srv $cpimage
|
||||||
while read src dest_fullname description; do
|
while read src dest dest_fullname description; do
|
||||||
if [[ $src == "''" ]];then
|
if [[ $src == "''" ]];then
|
||||||
unset src
|
unset src
|
||||||
fi
|
fi
|
||||||
@ -303,7 +322,7 @@ load helpers
|
|||||||
if [[ $dest_fullname == "''" ]];then
|
if [[ $dest_fullname == "''" ]];then
|
||||||
unset dest_fullname
|
unset dest_fullname
|
||||||
fi
|
fi
|
||||||
run_podman cp cpcontainer:$src $destdir
|
run_podman cp cpcontainer:$src $destdir$dest
|
||||||
is "$(< $destdir$dest_fullname/containerfile0)" "${randomcontent[0]}" "$description"
|
is "$(< $destdir$dest_fullname/containerfile0)" "${randomcontent[0]}" "$description"
|
||||||
is "$(< $destdir$dest_fullname/containerfile1)" "${randomcontent[1]}" "$description"
|
is "$(< $destdir$dest_fullname/containerfile1)" "${randomcontent[1]}" "$description"
|
||||||
rm -rf $destdir/*
|
rm -rf $destdir/*
|
||||||
@ -314,6 +333,46 @@ load helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@test "podman cp symlinked directory from container" {
|
||||||
|
destdir=$PODMAN_TMPDIR/cp-weird-symlink
|
||||||
|
mkdir -p $destdir
|
||||||
|
|
||||||
|
# Create 3 files with random content in the container.
|
||||||
|
local -a randomcontent=(
|
||||||
|
random-0-$(random_string 10)
|
||||||
|
random-1-$(random_string 15)
|
||||||
|
)
|
||||||
|
|
||||||
|
run_podman run -d --name cpcontainer $IMAGE sleep infinity
|
||||||
|
run_podman exec cpcontainer sh -c "echo ${randomcontent[0]} > /tmp/containerfile0"
|
||||||
|
run_podman exec cpcontainer sh -c "echo ${randomcontent[1]} > /tmp/containerfile1"
|
||||||
|
run_podman exec cpcontainer sh -c "mkdir /tmp/sub && cd /tmp/sub && ln -s .. weirdlink"
|
||||||
|
|
||||||
|
# Commit the image for testing non-running containers
|
||||||
|
run_podman commit -q cpcontainer
|
||||||
|
cpimage="$output"
|
||||||
|
|
||||||
|
# RUNNING container
|
||||||
|
# NOTE: /dest does not exist yet but is expected to be created during copy
|
||||||
|
run_podman cp cpcontainer:/tmp/sub/weirdlink $destdir/dest
|
||||||
|
run cat $destdir/dest/containerfile0 $destdir/dest/containerfile1
|
||||||
|
is "${lines[0]}" "${randomcontent[0]}" "eval symlink - running container"
|
||||||
|
is "${lines[1]}" "${randomcontent[1]}" "eval symlink - running container"
|
||||||
|
|
||||||
|
run_podman kill cpcontainer
|
||||||
|
run_podman rm -f cpcontainer
|
||||||
|
run rm -rf $srcdir/dest
|
||||||
|
|
||||||
|
# CREATED container
|
||||||
|
run_podman create --name cpcontainer $cpimage
|
||||||
|
run_podman cp cpcontainer:/tmp/sub/weirdlink $destdir/dest
|
||||||
|
run cat $destdir/dest/containerfile0 $destdir/dest/containerfile1
|
||||||
|
is "${lines[0]}" "${randomcontent[0]}" "eval symlink - created container"
|
||||||
|
is "${lines[1]}" "${randomcontent[1]}" "eval symlink - created container"
|
||||||
|
run_podman rm -f cpcontainer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "podman cp file from host to container volume" {
|
@test "podman cp file from host to container volume" {
|
||||||
srcdir=$PODMAN_TMPDIR/cp-test-volume
|
srcdir=$PODMAN_TMPDIR/cp-test-volume
|
||||||
mkdir -p $srcdir
|
mkdir -p $srcdir
|
||||||
|
2
vendor/github.com/containers/buildah/buildah.go
generated
vendored
2
vendor/github.com/containers/buildah/buildah.go
generated
vendored
@ -28,7 +28,7 @@ const (
|
|||||||
Package = "buildah"
|
Package = "buildah"
|
||||||
// Version for the Package. Bump version in contrib/rpm/buildah.spec
|
// Version for the Package. Bump version in contrib/rpm/buildah.spec
|
||||||
// too.
|
// too.
|
||||||
Version = "1.19.7"
|
Version = "1.19.8"
|
||||||
// The value we use to identify what type of information, currently a
|
// The value we use to identify what type of information, currently a
|
||||||
// serialized Builder structure, we are using as per-container state.
|
// serialized Builder structure, we are using as per-container state.
|
||||||
// This should only be changed when we make incompatible changes to
|
// This should only be changed when we make incompatible changes to
|
||||||
|
42
vendor/github.com/containers/buildah/copier/copier.go
generated
vendored
42
vendor/github.com/containers/buildah/copier/copier.go
generated
vendored
@ -284,6 +284,7 @@ type GetOptions struct {
|
|||||||
KeepDirectoryNames bool // don't strip the top directory's basename from the paths of items in subdirectories
|
KeepDirectoryNames bool // don't strip the top directory's basename from the paths of items in subdirectories
|
||||||
Rename map[string]string // rename items with the specified names, or under the specified names
|
Rename map[string]string // rename items with the specified names, or under the specified names
|
||||||
NoDerefSymlinks bool // don't follow symlinks when globs match them
|
NoDerefSymlinks bool // don't follow symlinks when globs match them
|
||||||
|
IgnoreUnreadable bool // ignore errors reading items, instead of returning an error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get produces an archive containing items that match the specified glob
|
// Get produces an archive containing items that match the specified glob
|
||||||
@ -1035,6 +1036,14 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response {
|
|||||||
return &response{Stat: statResponse{Globs: stats}}
|
return &response{Stat: statResponse{Globs: stats}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func errorIsPermission(err error) bool {
|
||||||
|
err = errors.Cause(err)
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return os.IsPermission(err) || strings.Contains(err.Error(), "permission denied")
|
||||||
|
}
|
||||||
|
|
||||||
func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMatcher, idMappings *idtools.IDMappings) (*response, func() error, error) {
|
func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMatcher, idMappings *idtools.IDMappings) (*response, func() error, error) {
|
||||||
statRequest := req
|
statRequest := req
|
||||||
statRequest.Request = requestStat
|
statRequest.Request = requestStat
|
||||||
@ -1111,6 +1120,12 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
|
|||||||
options.ExpandArchives = false
|
options.ExpandArchives = false
|
||||||
walkfn := func(path string, info os.FileInfo, err error) error {
|
walkfn := func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if options.IgnoreUnreadable && errorIsPermission(err) {
|
||||||
|
if info != nil && info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return errors.Wrapf(err, "copier: get: error reading %q", path)
|
return errors.Wrapf(err, "copier: get: error reading %q", path)
|
||||||
}
|
}
|
||||||
// compute the path of this item
|
// compute the path of this item
|
||||||
@ -1150,7 +1165,13 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
|
|||||||
symlinkTarget = target
|
symlinkTarget = target
|
||||||
}
|
}
|
||||||
// add the item to the outgoing tar stream
|
// add the item to the outgoing tar stream
|
||||||
return copierHandlerGetOne(info, symlinkTarget, rel, path, options, tw, hardlinkChecker, idMappings)
|
if err := copierHandlerGetOne(info, symlinkTarget, rel, path, options, tw, hardlinkChecker, idMappings); err != nil {
|
||||||
|
if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
// walk the directory tree, checking/adding items individually
|
// walk the directory tree, checking/adding items individually
|
||||||
if err := filepath.Walk(item, walkfn); err != nil {
|
if err := filepath.Walk(item, walkfn); err != nil {
|
||||||
@ -1170,6 +1191,9 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
|
|||||||
// dereferenced, be sure to use the name of the
|
// dereferenced, be sure to use the name of the
|
||||||
// link.
|
// link.
|
||||||
if err := copierHandlerGetOne(info, "", filepath.Base(queue[i]), item, req.GetOptions, tw, hardlinkChecker, idMappings); err != nil {
|
if err := copierHandlerGetOne(info, "", filepath.Base(queue[i]), item, req.GetOptions, tw, hardlinkChecker, idMappings); err != nil {
|
||||||
|
if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return errors.Wrapf(err, "copier: get: %q", queue[i])
|
return errors.Wrapf(err, "copier: get: %q", queue[i])
|
||||||
}
|
}
|
||||||
itemsCopied++
|
itemsCopied++
|
||||||
@ -1250,7 +1274,7 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
|
|||||||
if options.ExpandArchives && isArchivePath(contentPath) {
|
if options.ExpandArchives && isArchivePath(contentPath) {
|
||||||
f, err := os.Open(contentPath)
|
f, err := os.Open(contentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error opening %s", contentPath)
|
return errors.Wrapf(err, "error opening file for reading archive contents")
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
rc, _, err := compression.AutoDecompress(f)
|
rc, _, err := compression.AutoDecompress(f)
|
||||||
@ -1321,17 +1345,21 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
|
|||||||
hdr.Mode = int64(*options.ChmodFiles)
|
hdr.Mode = int64(*options.ChmodFiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var f *os.File
|
||||||
|
if hdr.Typeflag == tar.TypeReg {
|
||||||
|
// open the file first so that we don't write a header for it if we can't actually read it
|
||||||
|
f, err = os.Open(contentPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error opening file for adding its contents to archive")
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
}
|
||||||
// output the header
|
// output the header
|
||||||
if err = tw.WriteHeader(hdr); err != nil {
|
if err = tw.WriteHeader(hdr); err != nil {
|
||||||
return errors.Wrapf(err, "error writing header for %s (%s)", contentPath, hdr.Name)
|
return errors.Wrapf(err, "error writing header for %s (%s)", contentPath, hdr.Name)
|
||||||
}
|
}
|
||||||
if hdr.Typeflag == tar.TypeReg {
|
if hdr.Typeflag == tar.TypeReg {
|
||||||
// output the content
|
// output the content
|
||||||
f, err := os.Open(contentPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "error opening %s", contentPath)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
n, err := io.Copy(tw, f)
|
n, err := io.Copy(tw, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error copying %s", contentPath)
|
return errors.Wrapf(err, "error copying %s", contentPath)
|
||||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -72,7 +72,7 @@ github.com/containernetworking/plugins/pkg/utils/hwaddr
|
|||||||
github.com/containernetworking/plugins/pkg/utils/sysctl
|
github.com/containernetworking/plugins/pkg/utils/sysctl
|
||||||
github.com/containernetworking/plugins/plugins/ipam/host-local/backend
|
github.com/containernetworking/plugins/plugins/ipam/host-local/backend
|
||||||
github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator
|
github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator
|
||||||
# github.com/containers/buildah v1.19.7
|
# github.com/containers/buildah v1.19.8
|
||||||
github.com/containers/buildah
|
github.com/containers/buildah
|
||||||
github.com/containers/buildah/bind
|
github.com/containers/buildah/bind
|
||||||
github.com/containers/buildah/chroot
|
github.com/containers/buildah/chroot
|
||||||
|
Reference in New Issue
Block a user