mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-29 04:04:45 +00:00
virtcontainers: split out Linux parts from mount
Mount handling is often unique in Linux. Let's ensure that the common parts remain in mount.go, while Linux speific parts are within a linux file. Fixes: #6049 Signed-off-by: Eric Ernst <eric_ernst@apple.com>
This commit is contained in:
parent
60ff230d80
commit
923cd3fda1
@ -6,29 +6,22 @@
|
|||||||
package virtcontainers
|
package virtcontainers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
merr "github.com/hashicorp/go-multierror"
|
|
||||||
volume "github.com/kata-containers/kata-containers/src/runtime/pkg/direct-volume"
|
volume "github.com/kata-containers/kata-containers/src/runtime/pkg/direct-volume"
|
||||||
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils/katatrace"
|
|
||||||
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
|
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
otelLabel "go.opentelemetry.io/otel/attribute"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultShmSize is the default shm size to be used in case host
|
// DefaultShmSize is the default shm size to be used in case host
|
||||||
// IPC is used.
|
// IPC is used.
|
||||||
const DefaultShmSize = 65536 * 1024
|
const DefaultShmSize = 65536 * 1024
|
||||||
|
|
||||||
// Sadly golang/sys doesn't have UmountNoFollow although it's there since Linux 2.6.34
|
|
||||||
const UmountNoFollow = 0x8
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
rootfsDir = "rootfs"
|
rootfsDir = "rootfs"
|
||||||
lowerDir = "lowerdir"
|
lowerDir = "lowerdir"
|
||||||
@ -50,13 +43,6 @@ func mountLogger() *logrus.Entry {
|
|||||||
return virtLog.WithField("subsystem", "mount")
|
return virtLog.WithField("subsystem", "mount")
|
||||||
}
|
}
|
||||||
|
|
||||||
var propagationTypes = map[string]uintptr{
|
|
||||||
"shared": syscall.MS_SHARED,
|
|
||||||
"private": syscall.MS_PRIVATE,
|
|
||||||
"slave": syscall.MS_SLAVE,
|
|
||||||
"ubind": syscall.MS_UNBINDABLE,
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSystemMount(m string) bool {
|
func isSystemMount(m string) bool {
|
||||||
for _, p := range systemMountPrefixes {
|
for _, p := range systemMountPrefixes {
|
||||||
if m == p || strings.HasPrefix(m, p+"/") {
|
if m == p || strings.HasPrefix(m, p+"/") {
|
||||||
@ -248,83 +234,6 @@ func evalMountPath(source, destination string) (string, string, error) {
|
|||||||
return absSource, destination, nil
|
return absSource, destination, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindMount bind mounts a source in to a destination. This will
|
|
||||||
// do some bookkeeping:
|
|
||||||
// * evaluate all symlinks
|
|
||||||
// * ensure the source exists
|
|
||||||
// * recursively create the destination
|
|
||||||
// pgtypes stands for propagation types, which are shared, private, slave, and ubind.
|
|
||||||
func bindMount(ctx context.Context, source, destination string, readonly bool, pgtypes string) error {
|
|
||||||
span, _ := katatrace.Trace(ctx, nil, "bindMount", mountTracingTags)
|
|
||||||
defer span.End()
|
|
||||||
span.SetAttributes(otelLabel.String("source", source), otelLabel.String("destination", destination))
|
|
||||||
|
|
||||||
absSource, destination, err := evalMountPath(source, destination)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
span.SetAttributes(otelLabel.String("source_after_eval", absSource))
|
|
||||||
|
|
||||||
if err := syscall.Mount(absSource, destination, "bind", syscall.MS_BIND, ""); err != nil {
|
|
||||||
return fmt.Errorf("Could not bind mount %v to %v: %v", absSource, destination, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pgtype, exist := propagationTypes[pgtypes]; exist {
|
|
||||||
if err := syscall.Mount("none", destination, "", pgtype, ""); err != nil {
|
|
||||||
return fmt.Errorf("Could not make mount point %v %s: %v", destination, pgtypes, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Wrong propagation type %s", pgtypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For readonly bind mounts, we need to remount with the readonly flag.
|
|
||||||
// This is needed as only very recent versions of libmount/util-linux support "bind,ro"
|
|
||||||
if readonly {
|
|
||||||
return syscall.Mount(absSource, destination, "bind", uintptr(syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// An existing mount may be remounted by specifying `MS_REMOUNT` in
|
|
||||||
// mountflags.
|
|
||||||
// This allows you to change the mountflags of an existing mount.
|
|
||||||
// The mountflags should match the values used in the original mount() call,
|
|
||||||
// except for those parameters that you are trying to change.
|
|
||||||
func remount(ctx context.Context, mountflags uintptr, src string) error {
|
|
||||||
span, _ := katatrace.Trace(ctx, nil, "remount", mountTracingTags)
|
|
||||||
defer span.End()
|
|
||||||
span.SetAttributes(otelLabel.String("source", src))
|
|
||||||
|
|
||||||
absSrc, err := filepath.EvalSymlinks(src)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Could not resolve symlink for %s", src)
|
|
||||||
}
|
|
||||||
span.SetAttributes(otelLabel.String("source_after_eval", absSrc))
|
|
||||||
|
|
||||||
if err := syscall.Mount(absSrc, absSrc, "", syscall.MS_REMOUNT|mountflags, ""); err != nil {
|
|
||||||
return fmt.Errorf("remount %s failed: %v", absSrc, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// remount a mount point as readonly
|
|
||||||
func remountRo(ctx context.Context, src string) error {
|
|
||||||
return remount(ctx, syscall.MS_BIND|syscall.MS_RDONLY, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bindMountContainerRootfs bind mounts a container rootfs into a 9pfs shared
|
|
||||||
// directory between the guest and the host.
|
|
||||||
func bindMountContainerRootfs(ctx context.Context, shareDir, cid, cRootFs string, readonly bool) error {
|
|
||||||
span, _ := katatrace.Trace(ctx, nil, "bindMountContainerRootfs", mountTracingTags)
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
rootfsDest := filepath.Join(shareDir, cid, rootfsDir)
|
|
||||||
|
|
||||||
return bindMount(ctx, cRootFs, rootfsDest, readonly, "private")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount describes a container mount.
|
// Mount describes a container mount.
|
||||||
// nolint: govet
|
// nolint: govet
|
||||||
type Mount struct {
|
type Mount struct {
|
||||||
@ -372,96 +281,6 @@ func isSymlink(path string) bool {
|
|||||||
return stat.Mode()&os.ModeSymlink != 0
|
return stat.Mode()&os.ModeSymlink != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindUnmountContainerShareDir(ctx context.Context, sharedDir, cID, target string) error {
|
|
||||||
destDir := filepath.Join(sharedDir, cID, target)
|
|
||||||
if isSymlink(filepath.Join(sharedDir, cID)) || isSymlink(destDir) {
|
|
||||||
mountLogger().WithField("container", cID).Warnf("container dir is a symlink, malicious guest?")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := syscall.Unmount(destDir, syscall.MNT_DETACH|UmountNoFollow)
|
|
||||||
if err == syscall.ENOENT {
|
|
||||||
mountLogger().WithError(err).WithField("share-dir", destDir).Warn()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := syscall.Rmdir(destDir); err != nil {
|
|
||||||
mountLogger().WithError(err).WithField("share-dir", destDir).Warn("Could not remove container share dir")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindUnmountContainerRootfs(ctx context.Context, sharedDir, cID string) error {
|
|
||||||
span, _ := katatrace.Trace(ctx, nil, "bindUnmountContainerRootfs", mountTracingTags)
|
|
||||||
defer span.End()
|
|
||||||
span.SetAttributes(otelLabel.String("shared-dir", sharedDir), otelLabel.String("container-id", cID))
|
|
||||||
return bindUnmountContainerShareDir(ctx, sharedDir, cID, rootfsDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindUnmountContainerSnapshotDir(ctx context.Context, sharedDir, cID string) error {
|
|
||||||
span, _ := katatrace.Trace(ctx, nil, "bindUnmountContainerSnapshotDir", mountTracingTags)
|
|
||||||
defer span.End()
|
|
||||||
span.SetAttributes(otelLabel.String("shared-dir", sharedDir), otelLabel.String("container-id", cID))
|
|
||||||
return bindUnmountContainerShareDir(ctx, sharedDir, cID, snapshotDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVirtiofsDaemonForNydus(sandbox *Sandbox) (VirtiofsDaemon, error) {
|
|
||||||
var virtiofsDaemon VirtiofsDaemon
|
|
||||||
switch sandbox.GetHypervisorType() {
|
|
||||||
case string(QemuHypervisor):
|
|
||||||
virtiofsDaemon = sandbox.hypervisor.(*qemu).virtiofsDaemon
|
|
||||||
case string(ClhHypervisor):
|
|
||||||
virtiofsDaemon = sandbox.hypervisor.(*cloudHypervisor).virtiofsDaemon
|
|
||||||
default:
|
|
||||||
return nil, errNydusdNotSupport
|
|
||||||
}
|
|
||||||
return virtiofsDaemon, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func nydusContainerCleanup(ctx context.Context, sharedDir string, c *Container) error {
|
|
||||||
sandbox := c.sandbox
|
|
||||||
virtiofsDaemon, err := getVirtiofsDaemonForNydus(sandbox)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := virtiofsDaemon.Umount(rafsMountPath(c.id)); err != nil {
|
|
||||||
return errors.Wrap(err, "umount rafs failed")
|
|
||||||
}
|
|
||||||
if err := bindUnmountContainerSnapshotDir(ctx, sharedDir, c.id); err != nil {
|
|
||||||
return errors.Wrap(err, "umount snapshotdir err")
|
|
||||||
}
|
|
||||||
destDir := filepath.Join(sharedDir, c.id, c.rootfsSuffix)
|
|
||||||
if err := syscall.Rmdir(destDir); err != nil {
|
|
||||||
return errors.Wrap(err, "remove container rootfs err")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindUnmountAllRootfs(ctx context.Context, sharedDir string, sandbox *Sandbox) error {
|
|
||||||
span, ctx := katatrace.Trace(ctx, nil, "bindUnmountAllRootfs", mountTracingTags)
|
|
||||||
defer span.End()
|
|
||||||
span.SetAttributes(otelLabel.String("shared-dir", sharedDir), otelLabel.String("sandbox-id", sandbox.id))
|
|
||||||
|
|
||||||
var errors *merr.Error
|
|
||||||
for _, c := range sandbox.containers {
|
|
||||||
if isSymlink(filepath.Join(sharedDir, c.id)) {
|
|
||||||
mountLogger().WithField("container", c.id).Warnf("container dir is a symlink, malicious guest?")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.unmountHostMounts(ctx)
|
|
||||||
if c.state.Fstype == "" {
|
|
||||||
// even if error found, don't break out of loop until all mounts attempted
|
|
||||||
// to be unmounted, and collect all errors
|
|
||||||
if c.rootFs.Type == NydusRootFSType {
|
|
||||||
errors = merr.Append(errors, nydusContainerCleanup(ctx, sharedDir, c))
|
|
||||||
} else {
|
|
||||||
errors = merr.Append(errors, bindUnmountContainerRootfs(ctx, sharedDir, c.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.ErrorOrNil()
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dockerVolumePrefix = "/var/lib/docker/volumes"
|
dockerVolumePrefix = "/var/lib/docker/volumes"
|
||||||
dockerVolumeSuffix = "_data"
|
dockerVolumeSuffix = "_data"
|
||||||
|
12
src/runtime/virtcontainers/mount_darwin.go
Normal file
12
src/runtime/virtcontainers/mount_darwin.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) 2023 Apple Inc.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
package virtcontainers
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
func nydusContainerCleanup(ctx context.Context, sharedDir string, c *Container) error {
|
||||||
|
return nil
|
||||||
|
}
|
195
src/runtime/virtcontainers/mount_linux.go
Normal file
195
src/runtime/virtcontainers/mount_linux.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
// Copyright (c) 2017 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
package virtcontainers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
merr "github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils/katatrace"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
otelLabel "go.opentelemetry.io/otel/attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sadly golang/sys doesn't have UmountNoFollow although it's there since Linux 2.6.34
|
||||||
|
const UmountNoFollow = 0x8
|
||||||
|
|
||||||
|
var propagationTypes = map[string]uintptr{
|
||||||
|
"shared": syscall.MS_SHARED,
|
||||||
|
"private": syscall.MS_PRIVATE,
|
||||||
|
"slave": syscall.MS_SLAVE,
|
||||||
|
"ubind": syscall.MS_UNBINDABLE,
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindMount bind mounts a source in to a destination. This will
|
||||||
|
// do some bookkeeping:
|
||||||
|
// * evaluate all symlinks
|
||||||
|
// * ensure the source exists
|
||||||
|
// * recursively create the destination
|
||||||
|
// pgtypes stands for propagation types, which are shared, private, slave, and ubind.
|
||||||
|
func bindMount(ctx context.Context, source, destination string, readonly bool, pgtypes string) error {
|
||||||
|
span, _ := katatrace.Trace(ctx, nil, "bindMount", mountTracingTags)
|
||||||
|
defer span.End()
|
||||||
|
span.SetAttributes(otelLabel.String("source", source), otelLabel.String("destination", destination))
|
||||||
|
|
||||||
|
absSource, destination, err := evalMountPath(source, destination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
span.SetAttributes(otelLabel.String("source_after_eval", absSource))
|
||||||
|
|
||||||
|
if err := syscall.Mount(absSource, destination, "bind", syscall.MS_BIND, ""); err != nil {
|
||||||
|
return fmt.Errorf("Could not bind mount %v to %v: %v", absSource, destination, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pgtype, exist := propagationTypes[pgtypes]; exist {
|
||||||
|
if err := syscall.Mount("none", destination, "", pgtype, ""); err != nil {
|
||||||
|
return fmt.Errorf("Could not make mount point %v %s: %v", destination, pgtypes, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Wrong propagation type %s", pgtypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For readonly bind mounts, we need to remount with the readonly flag.
|
||||||
|
// This is needed as only very recent versions of libmount/util-linux support "bind,ro"
|
||||||
|
if readonly {
|
||||||
|
return syscall.Mount(absSource, destination, "bind", uintptr(syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// An existing mount may be remounted by specifying `MS_REMOUNT` in
|
||||||
|
// mountflags.
|
||||||
|
// This allows you to change the mountflags of an existing mount.
|
||||||
|
// The mountflags should match the values used in the original mount() call,
|
||||||
|
// except for those parameters that you are trying to change.
|
||||||
|
func remount(ctx context.Context, mountflags uintptr, src string) error {
|
||||||
|
span, _ := katatrace.Trace(ctx, nil, "remount", mountTracingTags)
|
||||||
|
defer span.End()
|
||||||
|
span.SetAttributes(otelLabel.String("source", src))
|
||||||
|
|
||||||
|
absSrc, err := filepath.EvalSymlinks(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not resolve symlink for %s", src)
|
||||||
|
}
|
||||||
|
span.SetAttributes(otelLabel.String("source_after_eval", absSrc))
|
||||||
|
|
||||||
|
if err := syscall.Mount(absSrc, absSrc, "", syscall.MS_REMOUNT|mountflags, ""); err != nil {
|
||||||
|
return fmt.Errorf("remount %s failed: %v", absSrc, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// remount a mount point as readonly
|
||||||
|
func remountRo(ctx context.Context, src string) error {
|
||||||
|
return remount(ctx, syscall.MS_BIND|syscall.MS_RDONLY, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindMountContainerRootfs bind mounts a container rootfs into a 9pfs shared
|
||||||
|
// directory between the guest and the host.
|
||||||
|
func bindMountContainerRootfs(ctx context.Context, shareDir, cid, cRootFs string, readonly bool) error {
|
||||||
|
span, _ := katatrace.Trace(ctx, nil, "bindMountContainerRootfs", mountTracingTags)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
rootfsDest := filepath.Join(shareDir, cid, rootfsDir)
|
||||||
|
|
||||||
|
return bindMount(ctx, cRootFs, rootfsDest, readonly, "private")
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindUnmountContainerShareDir(ctx context.Context, sharedDir, cID, target string) error {
|
||||||
|
destDir := filepath.Join(sharedDir, cID, target)
|
||||||
|
if isSymlink(filepath.Join(sharedDir, cID)) || isSymlink(destDir) {
|
||||||
|
mountLogger().WithField("container", cID).Warnf("container dir is a symlink, malicious guest?")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := syscall.Unmount(destDir, syscall.MNT_DETACH|UmountNoFollow)
|
||||||
|
if err == syscall.ENOENT {
|
||||||
|
mountLogger().WithError(err).WithField("share-dir", destDir).Warn()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := syscall.Rmdir(destDir); err != nil {
|
||||||
|
mountLogger().WithError(err).WithField("share-dir", destDir).Warn("Could not remove container share dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindUnmountContainerRootfs(ctx context.Context, sharedDir, cID string) error {
|
||||||
|
span, _ := katatrace.Trace(ctx, nil, "bindUnmountContainerRootfs", mountTracingTags)
|
||||||
|
defer span.End()
|
||||||
|
span.SetAttributes(otelLabel.String("shared-dir", sharedDir), otelLabel.String("container-id", cID))
|
||||||
|
return bindUnmountContainerShareDir(ctx, sharedDir, cID, rootfsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindUnmountContainerSnapshotDir(ctx context.Context, sharedDir, cID string) error {
|
||||||
|
span, _ := katatrace.Trace(ctx, nil, "bindUnmountContainerSnapshotDir", mountTracingTags)
|
||||||
|
defer span.End()
|
||||||
|
span.SetAttributes(otelLabel.String("shared-dir", sharedDir), otelLabel.String("container-id", cID))
|
||||||
|
return bindUnmountContainerShareDir(ctx, sharedDir, cID, snapshotDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVirtiofsDaemonForNydus(sandbox *Sandbox) (VirtiofsDaemon, error) {
|
||||||
|
var virtiofsDaemon VirtiofsDaemon
|
||||||
|
switch sandbox.GetHypervisorType() {
|
||||||
|
case string(QemuHypervisor):
|
||||||
|
virtiofsDaemon = sandbox.hypervisor.(*qemu).virtiofsDaemon
|
||||||
|
case string(ClhHypervisor):
|
||||||
|
virtiofsDaemon = sandbox.hypervisor.(*cloudHypervisor).virtiofsDaemon
|
||||||
|
default:
|
||||||
|
return nil, errNydusdNotSupport
|
||||||
|
}
|
||||||
|
return virtiofsDaemon, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func nydusContainerCleanup(ctx context.Context, sharedDir string, c *Container) error {
|
||||||
|
sandbox := c.sandbox
|
||||||
|
virtiofsDaemon, err := getVirtiofsDaemonForNydus(sandbox)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := virtiofsDaemon.Umount(rafsMountPath(c.id)); err != nil {
|
||||||
|
return errors.Wrap(err, "umount rafs failed")
|
||||||
|
}
|
||||||
|
if err := bindUnmountContainerSnapshotDir(ctx, sharedDir, c.id); err != nil {
|
||||||
|
return errors.Wrap(err, "umount snapshotdir err")
|
||||||
|
}
|
||||||
|
destDir := filepath.Join(sharedDir, c.id, c.rootfsSuffix)
|
||||||
|
if err := syscall.Rmdir(destDir); err != nil {
|
||||||
|
return errors.Wrap(err, "remove container rootfs err")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindUnmountAllRootfs(ctx context.Context, sharedDir string, sandbox *Sandbox) error {
|
||||||
|
span, ctx := katatrace.Trace(ctx, nil, "bindUnmountAllRootfs", mountTracingTags)
|
||||||
|
defer span.End()
|
||||||
|
span.SetAttributes(otelLabel.String("shared-dir", sharedDir), otelLabel.String("sandbox-id", sandbox.id))
|
||||||
|
|
||||||
|
var errors *merr.Error
|
||||||
|
for _, c := range sandbox.containers {
|
||||||
|
if isSymlink(filepath.Join(sharedDir, c.id)) {
|
||||||
|
mountLogger().WithField("container", c.id).Warnf("container dir is a symlink, malicious guest?")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.unmountHostMounts(ctx)
|
||||||
|
if c.state.Fstype == "" {
|
||||||
|
// even if error found, don't break out of loop until all mounts attempted
|
||||||
|
// to be unmounted, and collect all errors
|
||||||
|
if c.rootFs.Type == NydusRootFSType {
|
||||||
|
errors = merr.Append(errors, nydusContainerCleanup(ctx, sharedDir, c))
|
||||||
|
} else {
|
||||||
|
errors = merr.Append(errors, bindUnmountContainerRootfs(ctx, sharedDir, c.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.ErrorOrNil()
|
||||||
|
}
|
321
src/runtime/virtcontainers/mount_linux_test.go
Normal file
321
src/runtime/virtcontainers/mount_linux_test.go
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
// Copyright (c) 2017 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
package virtcontainers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsEphemeralStorage(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
if tc.NotValid(ktu.NeedRoot()) {
|
||||||
|
t.Skip(ktu.TestDisabledNeedRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err := os.MkdirTemp(testDir, "foo")
|
||||||
|
assert.NoError(err)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
sampleEphePath := filepath.Join(dir, K8sEmptyDir, "tmp-volume")
|
||||||
|
err = os.MkdirAll(sampleEphePath, testDirMode)
|
||||||
|
assert.Nil(err)
|
||||||
|
|
||||||
|
err = syscall.Mount("tmpfs", sampleEphePath, "tmpfs", 0, "")
|
||||||
|
assert.NoError(err)
|
||||||
|
defer syscall.Unmount(sampleEphePath, 0)
|
||||||
|
|
||||||
|
isEphe := IsEphemeralStorage(sampleEphePath)
|
||||||
|
assert.True(isEphe)
|
||||||
|
|
||||||
|
isHostEmptyDir := Isk8sHostEmptyDir(sampleEphePath)
|
||||||
|
assert.False(isHostEmptyDir)
|
||||||
|
|
||||||
|
sampleEphePath = "/var/lib/kubelet/pods/366c3a75-4869-11e8-b479-507b9ddd5ce4/volumes/cache-volume"
|
||||||
|
isEphe = IsEphemeralStorage(sampleEphePath)
|
||||||
|
assert.False(isEphe)
|
||||||
|
|
||||||
|
isHostEmptyDir = Isk8sHostEmptyDir(sampleEphePath)
|
||||||
|
assert.False(isHostEmptyDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDeviceForPathBindMount(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
if tc.NotValid(ktu.NeedRoot()) {
|
||||||
|
t.Skip(ktu.TestDisabledNeedRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
source := filepath.Join(testDir, "testDeviceDirSrc")
|
||||||
|
dest := filepath.Join(testDir, "testDeviceDirDest")
|
||||||
|
syscall.Unmount(dest, 0)
|
||||||
|
os.Remove(source)
|
||||||
|
os.Remove(dest)
|
||||||
|
|
||||||
|
err := os.MkdirAll(source, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
defer os.Remove(source)
|
||||||
|
|
||||||
|
err = os.MkdirAll(dest, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
defer os.Remove(dest)
|
||||||
|
|
||||||
|
err = bindMount(context.Background(), source, dest, false, "private")
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
defer syscall.Unmount(dest, syscall.MNT_DETACH)
|
||||||
|
|
||||||
|
destFile := filepath.Join(dest, "test")
|
||||||
|
_, err = os.Create(destFile)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
defer os.Remove(destFile)
|
||||||
|
|
||||||
|
sourceDev, _ := getDeviceForPath(source)
|
||||||
|
destDev, _ := getDeviceForPath(destFile)
|
||||||
|
|
||||||
|
assert.Equal(sourceDev, destDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindMountInvalidSourceSymlink(t *testing.T) {
|
||||||
|
source := filepath.Join(testDir, "fooFile")
|
||||||
|
os.Remove(source)
|
||||||
|
|
||||||
|
err := bindMount(context.Background(), source, "", false, "private")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindMountFailingMount(t *testing.T) {
|
||||||
|
source := filepath.Join(testDir, "fooLink")
|
||||||
|
fakeSource := filepath.Join(testDir, "fooFile")
|
||||||
|
os.Remove(source)
|
||||||
|
os.Remove(fakeSource)
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
_, err := os.OpenFile(fakeSource, os.O_CREATE, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = os.Symlink(fakeSource, source)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = bindMount(context.Background(), source, "", false, "private")
|
||||||
|
assert.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupFooMount() {
|
||||||
|
dest := filepath.Join(testDir, "fooDirDest")
|
||||||
|
|
||||||
|
syscall.Unmount(dest, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindMountSuccessful(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
if tc.NotValid(ktu.NeedRoot()) {
|
||||||
|
t.Skip(ktu.TestDisabledNeedRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
source := filepath.Join(testDir, "fooDirSrc")
|
||||||
|
dest := filepath.Join(testDir, "fooDirDest")
|
||||||
|
t.Cleanup(cleanupFooMount)
|
||||||
|
|
||||||
|
err := os.MkdirAll(source, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = os.MkdirAll(dest, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = bindMount(context.Background(), source, dest, false, "private")
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindMountReadonlySuccessful(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
if tc.NotValid(ktu.NeedRoot()) {
|
||||||
|
t.Skip(ktu.TestDisabledNeedRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
source := filepath.Join(testDir, "fooDirSrc")
|
||||||
|
dest := filepath.Join(testDir, "fooDirDest")
|
||||||
|
t.Cleanup(cleanupFooMount)
|
||||||
|
|
||||||
|
err := os.MkdirAll(source, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = os.MkdirAll(dest, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = bindMount(context.Background(), source, dest, true, "private")
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
// should not be able to create file in read-only mount
|
||||||
|
destFile := filepath.Join(dest, "foo")
|
||||||
|
_, err = os.OpenFile(destFile, os.O_CREATE, mountPerm)
|
||||||
|
assert.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindMountInvalidPgtypes(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
if tc.NotValid(ktu.NeedRoot()) {
|
||||||
|
t.Skip(ktu.TestDisabledNeedRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
source := filepath.Join(testDir, "fooDirSrc")
|
||||||
|
dest := filepath.Join(testDir, "fooDirDest")
|
||||||
|
t.Cleanup(cleanupFooMount)
|
||||||
|
|
||||||
|
err := os.MkdirAll(source, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = os.MkdirAll(dest, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
err = bindMount(context.Background(), source, dest, false, "foo")
|
||||||
|
expectedErr := fmt.Sprintf("Wrong propagation type %s", "foo")
|
||||||
|
assert.EqualError(err, expectedErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBindUnmountContainerRootfsENOENTNotError tests that if a file
|
||||||
|
// or directory attempting to be unmounted doesn't exist, then it
|
||||||
|
// is not considered an error
|
||||||
|
func TestBindUnmountContainerRootfsENOENTNotError(t *testing.T) {
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
t.Skip("Test disabled as requires root user")
|
||||||
|
}
|
||||||
|
testMnt := "/tmp/test_mount"
|
||||||
|
sID := "sandIDTest"
|
||||||
|
cID := "contIDTest"
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// Check to make sure the file doesn't exist
|
||||||
|
testPath := filepath.Join(testMnt, sID, cID, rootfsDir)
|
||||||
|
if _, err := os.Stat(testPath); !os.IsNotExist(err) {
|
||||||
|
assert.NoError(os.Remove(testPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := bindUnmountContainerRootfs(context.Background(), filepath.Join(testMnt, sID), cID)
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindUnmountContainerRootfsRemoveRootfsDest(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
if tc.NotValid(ktu.NeedRoot()) {
|
||||||
|
t.Skip(ktu.TestDisabledNeedRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
sID := "sandIDTestRemoveRootfsDest"
|
||||||
|
cID := "contIDTestRemoveRootfsDest"
|
||||||
|
|
||||||
|
testPath := filepath.Join(testDir, sID, cID, rootfsDir)
|
||||||
|
syscall.Unmount(testPath, 0)
|
||||||
|
os.Remove(testPath)
|
||||||
|
|
||||||
|
err := os.MkdirAll(testPath, mountPerm)
|
||||||
|
assert.NoError(err)
|
||||||
|
defer os.RemoveAll(filepath.Join(testDir, sID))
|
||||||
|
|
||||||
|
bindUnmountContainerRootfs(context.Background(), filepath.Join(testDir, sID), cID)
|
||||||
|
|
||||||
|
if _, err := os.Stat(testPath); err == nil {
|
||||||
|
t.Fatal("empty rootfs dest should be removed")
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsHostDevice(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
tests := []struct {
|
||||||
|
mnt string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"/dev", true},
|
||||||
|
{"/dev/zero", true},
|
||||||
|
{"/dev/block", true},
|
||||||
|
{"/mnt/dev/block", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
result := isHostDevice(test.mnt)
|
||||||
|
assert.Equal(result, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMajorMinorNumber(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
devices := []string{"/dev/zero", "/dev/net/tun"}
|
||||||
|
|
||||||
|
for _, device := range devices {
|
||||||
|
cmdStr := fmt.Sprintf("ls -l %s | awk '{print $5$6}'", device)
|
||||||
|
cmd := exec.Command("sh", "-c", cmdStr)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
data := bytes.Split(output, []byte(","))
|
||||||
|
assert.False(len(data) < 2)
|
||||||
|
|
||||||
|
majorStr := strings.TrimSpace(string(data[0]))
|
||||||
|
minorStr := strings.TrimSpace(string(data[1]))
|
||||||
|
|
||||||
|
majorNo, err := strconv.Atoi(majorStr)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
minorNo, err := strconv.Atoi(minorStr)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
stat := syscall.Stat_t{}
|
||||||
|
err = syscall.Stat(device, &stat)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
// Get major and minor numbers for the device itself. Note the use of stat.Rdev instead of Dev.
|
||||||
|
major := major(uint64(stat.Rdev))
|
||||||
|
minor := minor(uint64(stat.Rdev))
|
||||||
|
|
||||||
|
assert.Equal(minor, minorNo)
|
||||||
|
assert.Equal(major, majorNo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDeviceForPathValidMount(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
dev, err := getDeviceForPath("/proc")
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
expected := "/proc"
|
||||||
|
|
||||||
|
assert.Equal(dev.mountPoint, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsDeviceMapper(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
// known major, minor for /dev/tty
|
||||||
|
major := 5
|
||||||
|
minor := 0
|
||||||
|
|
||||||
|
isDM, err := isDeviceMapper(major, minor)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.False(isDM)
|
||||||
|
|
||||||
|
// fake the block device format
|
||||||
|
blockFormatTemplate = "/sys/dev/char/%d:%d"
|
||||||
|
isDM, err = isDeviceMapper(major, minor)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.True(isDM)
|
||||||
|
}
|
@ -6,15 +6,9 @@
|
|||||||
package virtcontainers
|
package virtcontainers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils"
|
ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils"
|
||||||
@ -55,24 +49,6 @@ func TestIsSystemMount(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsHostDevice(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
tests := []struct {
|
|
||||||
mnt string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{"/dev", true},
|
|
||||||
{"/dev/zero", true},
|
|
||||||
{"/dev/block", true},
|
|
||||||
{"/mnt/dev/block", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
result := isHostDevice(test.mnt)
|
|
||||||
assert.Equal(result, test.expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsHostDeviceCreateFile(t *testing.T) {
|
func TestIsHostDeviceCreateFile(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
if tc.NotValid(ktu.NeedRoot()) {
|
if tc.NotValid(ktu.NeedRoot()) {
|
||||||
@ -89,41 +65,6 @@ func TestIsHostDeviceCreateFile(t *testing.T) {
|
|||||||
assert.NoError(os.Remove(path))
|
assert.NoError(os.Remove(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMajorMinorNumber(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
devices := []string{"/dev/zero", "/dev/net/tun"}
|
|
||||||
|
|
||||||
for _, device := range devices {
|
|
||||||
cmdStr := fmt.Sprintf("ls -l %s | awk '{print $5$6}'", device)
|
|
||||||
cmd := exec.Command("sh", "-c", cmdStr)
|
|
||||||
output, err := cmd.Output()
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
data := bytes.Split(output, []byte(","))
|
|
||||||
assert.False(len(data) < 2)
|
|
||||||
|
|
||||||
majorStr := strings.TrimSpace(string(data[0]))
|
|
||||||
minorStr := strings.TrimSpace(string(data[1]))
|
|
||||||
|
|
||||||
majorNo, err := strconv.Atoi(majorStr)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
minorNo, err := strconv.Atoi(minorStr)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
stat := syscall.Stat_t{}
|
|
||||||
err = syscall.Stat(device, &stat)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
// Get major and minor numbers for the device itself. Note the use of stat.Rdev instead of Dev.
|
|
||||||
major := major(stat.Rdev)
|
|
||||||
minor := minor(stat.Rdev)
|
|
||||||
|
|
||||||
assert.Equal(minor, minorNo)
|
|
||||||
assert.Equal(major, majorNo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDeviceForPathRoot(t *testing.T) {
|
func TestGetDeviceForPathRoot(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
dev, err := getDeviceForPath("/")
|
dev, err := getDeviceForPath("/")
|
||||||
@ -134,16 +75,6 @@ func TestGetDeviceForPathRoot(t *testing.T) {
|
|||||||
assert.Equal(dev.mountPoint, expected)
|
assert.Equal(dev.mountPoint, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDeviceForPathValidMount(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
dev, err := getDeviceForPath("/proc")
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
expected := "/proc"
|
|
||||||
|
|
||||||
assert.Equal(dev.mountPoint, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDeviceForPathEmptyPath(t *testing.T) {
|
func TestGetDeviceForPathEmptyPath(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
_, err := getDeviceForPath("")
|
_, err := getDeviceForPath("")
|
||||||
@ -165,64 +96,6 @@ func TestGetDeviceForPath(t *testing.T) {
|
|||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDeviceForPathBindMount(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
if tc.NotValid(ktu.NeedRoot()) {
|
|
||||||
t.Skip(ktu.TestDisabledNeedRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
source := filepath.Join(testDir, "testDeviceDirSrc")
|
|
||||||
dest := filepath.Join(testDir, "testDeviceDirDest")
|
|
||||||
syscall.Unmount(dest, 0)
|
|
||||||
os.Remove(source)
|
|
||||||
os.Remove(dest)
|
|
||||||
|
|
||||||
err := os.MkdirAll(source, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
defer os.Remove(source)
|
|
||||||
|
|
||||||
err = os.MkdirAll(dest, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
defer os.Remove(dest)
|
|
||||||
|
|
||||||
err = bindMount(context.Background(), source, dest, false, "private")
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
defer syscall.Unmount(dest, syscall.MNT_DETACH)
|
|
||||||
|
|
||||||
destFile := filepath.Join(dest, "test")
|
|
||||||
_, err = os.Create(destFile)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
defer os.Remove(destFile)
|
|
||||||
|
|
||||||
sourceDev, _ := getDeviceForPath(source)
|
|
||||||
destDev, _ := getDeviceForPath(destFile)
|
|
||||||
|
|
||||||
assert.Equal(sourceDev, destDev)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsDeviceMapper(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
// known major, minor for /dev/tty
|
|
||||||
major := 5
|
|
||||||
minor := 0
|
|
||||||
|
|
||||||
isDM, err := isDeviceMapper(major, minor)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.False(isDM)
|
|
||||||
|
|
||||||
// fake the block device format
|
|
||||||
blockFormatTemplate = "/sys/dev/char/%d:%d"
|
|
||||||
isDM, err = isDeviceMapper(major, minor)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.True(isDM)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsDockerVolume(t *testing.T) {
|
func TestIsDockerVolume(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
path := "/var/lib/docker/volumes/00da1347c7cf4f15db35f/_data"
|
path := "/var/lib/docker/volumes/00da1347c7cf4f15db35f/_data"
|
||||||
@ -234,38 +107,6 @@ func TestIsDockerVolume(t *testing.T) {
|
|||||||
assert.False(isDockerVolume)
|
assert.False(isDockerVolume)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsEphemeralStorage(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
if tc.NotValid(ktu.NeedRoot()) {
|
|
||||||
t.Skip(ktu.TestDisabledNeedRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := os.MkdirTemp(testDir, "foo")
|
|
||||||
assert.NoError(err)
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
sampleEphePath := filepath.Join(dir, K8sEmptyDir, "tmp-volume")
|
|
||||||
err = os.MkdirAll(sampleEphePath, testDirMode)
|
|
||||||
assert.Nil(err)
|
|
||||||
|
|
||||||
err = syscall.Mount("tmpfs", sampleEphePath, "tmpfs", 0, "")
|
|
||||||
assert.NoError(err)
|
|
||||||
defer syscall.Unmount(sampleEphePath, 0)
|
|
||||||
|
|
||||||
isEphe := IsEphemeralStorage(sampleEphePath)
|
|
||||||
assert.True(isEphe)
|
|
||||||
|
|
||||||
isHostEmptyDir := Isk8sHostEmptyDir(sampleEphePath)
|
|
||||||
assert.False(isHostEmptyDir)
|
|
||||||
|
|
||||||
sampleEphePath = "/var/lib/kubelet/pods/366c3a75-4869-11e8-b479-507b9ddd5ce4/volumes/cache-volume"
|
|
||||||
isEphe = IsEphemeralStorage(sampleEphePath)
|
|
||||||
assert.False(isEphe)
|
|
||||||
|
|
||||||
isHostEmptyDir = Isk8sHostEmptyDir(sampleEphePath)
|
|
||||||
assert.False(isHostEmptyDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsEmtpyDir(t *testing.T) {
|
func TestIsEmtpyDir(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
path := "/var/lib/kubelet/pods/5f0861a0-a987-4a3a-bb0f-1058ddb9678f/volumes/kubernetes.io~empty-dir/foobar"
|
path := "/var/lib/kubelet/pods/5f0861a0-a987-4a3a-bb0f-1058ddb9678f/volumes/kubernetes.io~empty-dir/foobar"
|
||||||
@ -355,148 +196,3 @@ func TestIsWatchable(t *testing.T) {
|
|||||||
result = isWatchableMount(configs)
|
result = isWatchableMount(configs)
|
||||||
assert.False(result)
|
assert.False(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindMountInvalidSourceSymlink(t *testing.T) {
|
|
||||||
source := filepath.Join(testDir, "fooFile")
|
|
||||||
os.Remove(source)
|
|
||||||
|
|
||||||
err := bindMount(context.Background(), source, "", false, "private")
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindMountFailingMount(t *testing.T) {
|
|
||||||
source := filepath.Join(testDir, "fooLink")
|
|
||||||
fakeSource := filepath.Join(testDir, "fooFile")
|
|
||||||
os.Remove(source)
|
|
||||||
os.Remove(fakeSource)
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
_, err := os.OpenFile(fakeSource, os.O_CREATE, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = os.Symlink(fakeSource, source)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = bindMount(context.Background(), source, "", false, "private")
|
|
||||||
assert.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupFooMount() {
|
|
||||||
dest := filepath.Join(testDir, "fooDirDest")
|
|
||||||
|
|
||||||
syscall.Unmount(dest, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindMountSuccessful(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
if tc.NotValid(ktu.NeedRoot()) {
|
|
||||||
t.Skip(testDisabledAsNonRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
source := filepath.Join(testDir, "fooDirSrc")
|
|
||||||
dest := filepath.Join(testDir, "fooDirDest")
|
|
||||||
t.Cleanup(cleanupFooMount)
|
|
||||||
|
|
||||||
err := os.MkdirAll(source, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = os.MkdirAll(dest, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = bindMount(context.Background(), source, dest, false, "private")
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindMountReadonlySuccessful(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
if tc.NotValid(ktu.NeedRoot()) {
|
|
||||||
t.Skip(testDisabledAsNonRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
source := filepath.Join(testDir, "fooDirSrc")
|
|
||||||
dest := filepath.Join(testDir, "fooDirDest")
|
|
||||||
t.Cleanup(cleanupFooMount)
|
|
||||||
|
|
||||||
err := os.MkdirAll(source, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = os.MkdirAll(dest, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = bindMount(context.Background(), source, dest, true, "private")
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
// should not be able to create file in read-only mount
|
|
||||||
destFile := filepath.Join(dest, "foo")
|
|
||||||
_, err = os.OpenFile(destFile, os.O_CREATE, mountPerm)
|
|
||||||
assert.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindMountInvalidPgtypes(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
if tc.NotValid(ktu.NeedRoot()) {
|
|
||||||
t.Skip(testDisabledAsNonRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
source := filepath.Join(testDir, "fooDirSrc")
|
|
||||||
dest := filepath.Join(testDir, "fooDirDest")
|
|
||||||
t.Cleanup(cleanupFooMount)
|
|
||||||
|
|
||||||
err := os.MkdirAll(source, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = os.MkdirAll(dest, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
err = bindMount(context.Background(), source, dest, false, "foo")
|
|
||||||
expectedErr := fmt.Sprintf("Wrong propagation type %s", "foo")
|
|
||||||
assert.EqualError(err, expectedErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestBindUnmountContainerRootfsENOENTNotError tests that if a file
|
|
||||||
// or directory attempting to be unmounted doesn't exist, then it
|
|
||||||
// is not considered an error
|
|
||||||
func TestBindUnmountContainerRootfsENOENTNotError(t *testing.T) {
|
|
||||||
if os.Getuid() != 0 {
|
|
||||||
t.Skip("Test disabled as requires root user")
|
|
||||||
}
|
|
||||||
testMnt := "/tmp/test_mount"
|
|
||||||
sID := "sandIDTest"
|
|
||||||
cID := "contIDTest"
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
// Check to make sure the file doesn't exist
|
|
||||||
testPath := filepath.Join(testMnt, sID, cID, rootfsDir)
|
|
||||||
if _, err := os.Stat(testPath); !os.IsNotExist(err) {
|
|
||||||
assert.NoError(os.Remove(testPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := bindUnmountContainerRootfs(context.Background(), filepath.Join(testMnt, sID), cID)
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindUnmountContainerRootfsRemoveRootfsDest(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
if tc.NotValid(ktu.NeedRoot()) {
|
|
||||||
t.Skip(ktu.TestDisabledNeedRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
sID := "sandIDTestRemoveRootfsDest"
|
|
||||||
cID := "contIDTestRemoveRootfsDest"
|
|
||||||
|
|
||||||
testPath := filepath.Join(testDir, sID, cID, rootfsDir)
|
|
||||||
syscall.Unmount(testPath, 0)
|
|
||||||
os.Remove(testPath)
|
|
||||||
|
|
||||||
err := os.MkdirAll(testPath, mountPerm)
|
|
||||||
assert.NoError(err)
|
|
||||||
defer os.RemoveAll(filepath.Join(testDir, sID))
|
|
||||||
|
|
||||||
bindUnmountContainerRootfs(context.Background(), filepath.Join(testDir, sID), cID)
|
|
||||||
|
|
||||||
if _, err := os.Stat(testPath); err == nil {
|
|
||||||
t.Fatal("empty rootfs dest should be removed")
|
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user