mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Merge pull request #74393 from codenrhoden/refactor-subpath
Refactor subpath out of pkg/util/mount
This commit is contained in:
		@@ -108,6 +108,7 @@ go_library(
 | 
			
		||||
        "//pkg/volume/scaleio:go_default_library",
 | 
			
		||||
        "//pkg/volume/secret:go_default_library",
 | 
			
		||||
        "//pkg/volume/storageos:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//pkg/volume/vsphere_volume:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -95,6 +95,7 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/rlimit"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/version"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/version/verflag"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
	nodeapiclientset "k8s.io/node-api/pkg/client/clientset/versioned"
 | 
			
		||||
	"k8s.io/utils/exec"
 | 
			
		||||
	"k8s.io/utils/nsenter"
 | 
			
		||||
@@ -364,6 +365,7 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mounter := mount.New(s.ExperimentalMounterPath)
 | 
			
		||||
	subpather := subpath.New(mounter)
 | 
			
		||||
	var pluginRunner = exec.New()
 | 
			
		||||
	if s.Containerized {
 | 
			
		||||
		klog.V(2).Info("Running kubelet in containerized mode")
 | 
			
		||||
@@ -372,6 +374,8 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		mounter = mount.NewNsenterMounter(s.RootDirectory, ne)
 | 
			
		||||
		// NSenter only valid on Linux
 | 
			
		||||
		subpather = subpath.NewNSEnter(mounter, ne, s.RootDirectory)
 | 
			
		||||
		// an exec interface which can use nsenter for flex plugin calls
 | 
			
		||||
		pluginRunner, err = nsenter.NewNsenter(nsenter.DefaultHostRootFsPath, exec.New())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -399,6 +403,7 @@ func UnsecuredDependencies(s *options.KubeletServer) (*kubelet.Dependencies, err
 | 
			
		||||
		CSIClient:           nil,
 | 
			
		||||
		EventClient:         nil,
 | 
			
		||||
		Mounter:             mounter,
 | 
			
		||||
		Subpather:           subpather,
 | 
			
		||||
		OOMAdjuster:         oom.NewOOMAdjuster(),
 | 
			
		||||
		OSInterface:         kubecontainer.RealOS{},
 | 
			
		||||
		VolumePlugins:       ProbeVolumePlugins(),
 | 
			
		||||
 
 | 
			
		||||
@@ -167,6 +167,7 @@
 | 
			
		||||
      "AllowedPrefixes": [
 | 
			
		||||
        "k8s.io/kubernetes/pkg/api/legacyscheme",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/api/v1/endpoints",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/api/v1/node",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/api/v1/pod",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/apis/apps/v1",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/apis/autoscaling",
 | 
			
		||||
@@ -241,6 +242,7 @@
 | 
			
		||||
        "k8s.io/kubernetes/pkg/volume/util",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/volume/util/operationexecutor",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/volume/util/recyclerclient",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/volume/util/subpath",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/volume/util/types",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/volume/util/volumepathhandler",
 | 
			
		||||
        "k8s.io/kubernetes/pkg/api/service",
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ go_library(
 | 
			
		||||
        "//pkg/volume:go_default_library",
 | 
			
		||||
        "//pkg/volume/util:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/operationexecutor:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/volumepathhandler:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/authentication/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume"
 | 
			
		||||
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -768,3 +769,8 @@ func (adc *attachDetachController) GetEventRecorder() record.EventRecorder {
 | 
			
		||||
func (adc *attachDetachController) GetCSIClient() csiclient.Interface {
 | 
			
		||||
	return adc.csiClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (adc *attachDetachController) GetSubpather() subpath.Interface {
 | 
			
		||||
	// Subpaths not needed in attachdetach controller
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ go_library(
 | 
			
		||||
        "//pkg/volume:go_default_library",
 | 
			
		||||
        "//pkg/volume/util:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/operationexecutor:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/volumepathhandler:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/authentication/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -339,3 +340,8 @@ func (expc *expandController) GetCSIClient() csiclientset.Interface {
 | 
			
		||||
	// No volume plugin in expand controller needs csi.storage.k8s.io
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (expc *expandController) GetSubpather() subpath.Interface {
 | 
			
		||||
	// not needed for expand controller
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ go_library(
 | 
			
		||||
        "//pkg/volume:go_default_library",
 | 
			
		||||
        "//pkg/volume/util:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/recyclerclient:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/authentication/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/storage/v1:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ import (
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	vol "k8s.io/kubernetes/pkg/volume"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// VolumeHost interface implementation for PersistentVolumeController.
 | 
			
		||||
@@ -136,3 +137,8 @@ func (ctrl *PersistentVolumeController) GetCSIClient() csiclientset.Interface {
 | 
			
		||||
	// No volume plugin needs csi.storage.k8s.io client in PV controller.
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctrl *PersistentVolumeController) GetSubpather() subpath.Interface {
 | 
			
		||||
	// No volume plugin needs Subpaths in PV controller.
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -112,6 +112,7 @@ go_library(
 | 
			
		||||
        "//pkg/volume:go_default_library",
 | 
			
		||||
        "//pkg/volume/csi:go_default_library",
 | 
			
		||||
        "//pkg/volume/util:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/types:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/volumepathhandler:go_default_library",
 | 
			
		||||
        "//pkg/volume/validation:go_default_library",
 | 
			
		||||
@@ -220,6 +221,7 @@ go_test(
 | 
			
		||||
        "//pkg/volume/host_path:go_default_library",
 | 
			
		||||
        "//pkg/volume/testing:go_default_library",
 | 
			
		||||
        "//pkg/volume/util:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -113,6 +113,7 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/oom"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/csi"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
	nodeapiclientset "k8s.io/node-api/pkg/client/clientset/versioned"
 | 
			
		||||
	utilexec "k8s.io/utils/exec"
 | 
			
		||||
	"k8s.io/utils/integer"
 | 
			
		||||
@@ -255,6 +256,7 @@ type Dependencies struct {
 | 
			
		||||
	OSInterface             kubecontainer.OSInterface
 | 
			
		||||
	PodConfig               *config.PodConfig
 | 
			
		||||
	Recorder                record.EventRecorder
 | 
			
		||||
	Subpather               subpath.Interface
 | 
			
		||||
	VolumePlugins           []volume.VolumePlugin
 | 
			
		||||
	DynamicPluginProber     volume.DynamicPluginProber
 | 
			
		||||
	TLSOptions              *server.TLSOptions
 | 
			
		||||
@@ -519,6 +521,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
 | 
			
		||||
		cgroupsPerQOS:                           kubeCfg.CgroupsPerQOS,
 | 
			
		||||
		cgroupRoot:                              kubeCfg.CgroupRoot,
 | 
			
		||||
		mounter:                                 kubeDeps.Mounter,
 | 
			
		||||
		subpather:                               kubeDeps.Subpather,
 | 
			
		||||
		maxPods:                                 int(kubeCfg.MaxPods),
 | 
			
		||||
		podsPerCore:                             int(kubeCfg.PodsPerCore),
 | 
			
		||||
		syncLoopMonitor:                         atomic.Value{},
 | 
			
		||||
@@ -1099,6 +1102,9 @@ type Kubelet struct {
 | 
			
		||||
	// Mounter to use for volumes.
 | 
			
		||||
	mounter mount.Interface
 | 
			
		||||
 | 
			
		||||
	// subpather to execute subpath actions
 | 
			
		||||
	subpather subpath.Interface
 | 
			
		||||
 | 
			
		||||
	// Manager of non-Runtime containers.
 | 
			
		||||
	containerManager cm.ContainerManager
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,7 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/util/format"
 | 
			
		||||
	mountutil "k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
 | 
			
		||||
	volumevalidation "k8s.io/kubernetes/pkg/volume/validation"
 | 
			
		||||
	"k8s.io/kubernetes/third_party/forked/golang/expansion"
 | 
			
		||||
@@ -127,7 +128,7 @@ func (kl *Kubelet) makeBlockVolumes(pod *v1.Pod, container *v1.Container, podVol
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// makeMounts determines the mount points for the given container.
 | 
			
		||||
func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, hostDomain, podIP string, podVolumes kubecontainer.VolumeMap, mounter mountutil.Interface, expandEnvs []kubecontainer.EnvVar) ([]kubecontainer.Mount, func(), error) {
 | 
			
		||||
func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, hostDomain, podIP string, podVolumes kubecontainer.VolumeMap, mounter mountutil.Interface, subpather subpath.Interface, expandEnvs []kubecontainer.EnvVar) ([]kubecontainer.Mount, func(), error) {
 | 
			
		||||
	// Kubernetes only mounts on /etc/hosts if:
 | 
			
		||||
	// - container is not an infrastructure (pause) container
 | 
			
		||||
	// - container is not already mounting on /etc/hosts
 | 
			
		||||
@@ -206,13 +207,13 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, cleanupAction, err
 | 
			
		||||
				}
 | 
			
		||||
				if err := mounter.SafeMakeDir(subPath, volumePath, perm); err != nil {
 | 
			
		||||
				if err := subpather.SafeMakeDir(subPath, volumePath, perm); err != nil {
 | 
			
		||||
					// Don't pass detailed error back to the user because it could give information about host filesystem
 | 
			
		||||
					klog.Errorf("failed to create subPath directory for volumeMount %q of container %q: %v", mount.Name, container.Name, err)
 | 
			
		||||
					return nil, cleanupAction, fmt.Errorf("failed to create subPath directory for volumeMount %q of container %q", mount.Name, container.Name)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			hostPath, cleanupAction, err = mounter.PrepareSafeSubpath(mountutil.Subpath{
 | 
			
		||||
			hostPath, cleanupAction, err = subpather.PrepareSafeSubpath(subpath.Subpath{
 | 
			
		||||
				VolumeMountIndex: i,
 | 
			
		||||
				Path:             hostPath,
 | 
			
		||||
				VolumeName:       vol.InnerVolumeSpecName,
 | 
			
		||||
@@ -464,7 +465,7 @@ func (kl *Kubelet) GenerateRunContainerOptions(pod *v1.Pod, container *v1.Contai
 | 
			
		||||
	}
 | 
			
		||||
	opts.Envs = append(opts.Envs, envs...)
 | 
			
		||||
 | 
			
		||||
	mounts, cleanupAction, err := makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes, kl.mounter, opts.Envs)
 | 
			
		||||
	mounts, cleanupAction, err := makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, volumes, kl.mounter, kl.subpather, opts.Envs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, cleanupAction, err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ import (
 | 
			
		||||
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	volumetest "k8s.io/kubernetes/pkg/volume/testing"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMakeMounts(t *testing.T) {
 | 
			
		||||
@@ -241,13 +242,14 @@ func TestMakeMounts(t *testing.T) {
 | 
			
		||||
	for name, tc := range testCases {
 | 
			
		||||
		t.Run(name, func(t *testing.T) {
 | 
			
		||||
			fm := &mount.FakeMounter{}
 | 
			
		||||
			fsp := &subpath.FakeSubpath{}
 | 
			
		||||
			pod := v1.Pod{
 | 
			
		||||
				Spec: v1.PodSpec{
 | 
			
		||||
					HostNetwork: true,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", "", tc.podVolumes, fm, nil)
 | 
			
		||||
			mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", "", tc.podVolumes, fm, fsp, nil)
 | 
			
		||||
 | 
			
		||||
			// validate only the error if we expect an error
 | 
			
		||||
			if tc.expectErr {
 | 
			
		||||
 
 | 
			
		||||
@@ -49,10 +49,12 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/server/portforward"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDisabledSubpath(t *testing.T) {
 | 
			
		||||
	fm := &mount.FakeMounter{}
 | 
			
		||||
	fsp := &subpath.FakeSubpath{}
 | 
			
		||||
	pod := v1.Pod{
 | 
			
		||||
		Spec: v1.PodSpec{
 | 
			
		||||
			HostNetwork: true,
 | 
			
		||||
@@ -95,7 +97,7 @@ func TestDisabledSubpath(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpath, false)()
 | 
			
		||||
	for name, test := range cases {
 | 
			
		||||
		_, _, err := makeMounts(&pod, "/pod", &test.container, "fakepodname", "", "", podVolumes, fm, nil)
 | 
			
		||||
		_, _, err := makeMounts(&pod, "/pod", &test.container, "fakepodname", "", "", podVolumes, fm, fsp, nil)
 | 
			
		||||
		if err != nil && !test.expectError {
 | 
			
		||||
			t.Errorf("test %v failed: %v", name, err)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import (
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMakeMountsWindows(t *testing.T) {
 | 
			
		||||
@@ -82,7 +83,8 @@ func TestMakeMountsWindows(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fm := &mount.FakeMounter{}
 | 
			
		||||
	mounts, _, _ := makeMounts(&pod, "/pod", &container, "fakepodname", "", "", podVolumes, fm, nil)
 | 
			
		||||
	fsp := &subpath.FakeSubpath{}
 | 
			
		||||
	mounts, _, _ := makeMounts(&pod, "/pod", &container, "fakepodname", "", "", podVolumes, fm, fsp, nil)
 | 
			
		||||
 | 
			
		||||
	expectedMounts := []kubecontainer.Mount{
 | 
			
		||||
		{
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,7 @@ import (
 | 
			
		||||
	_ "k8s.io/kubernetes/pkg/volume/host_path"
 | 
			
		||||
	volumetest "k8s.io/kubernetes/pkg/volume/testing"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
@@ -176,6 +177,7 @@ func newTestKubeletWithImageList(
 | 
			
		||||
	kubelet.heartbeatClient = fakeKubeClient
 | 
			
		||||
	kubelet.os = &containertest.FakeOS{}
 | 
			
		||||
	kubelet.mounter = &mount.FakeMounter{}
 | 
			
		||||
	kubelet.subpather = &subpath.FakeSubpath{}
 | 
			
		||||
 | 
			
		||||
	kubelet.hostname = testKubeletHostname
 | 
			
		||||
	kubelet.nodeName = types.NodeName(testKubeletHostname)
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewInitializedVolumePluginMgr returns a new instance of
 | 
			
		||||
@@ -126,6 +127,10 @@ func (kvh *kubeletVolumeHost) GetCSIClient() csiclientset.Interface {
 | 
			
		||||
	return kvh.kubelet.csiClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (kvh *kubeletVolumeHost) GetSubpather() subpath.Interface {
 | 
			
		||||
	return kvh.kubelet.subpather
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (kvh *kubeletVolumeHost) NewWrapperMounter(
 | 
			
		||||
	volName string,
 | 
			
		||||
	spec volume.Spec,
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ go_library(
 | 
			
		||||
        "//pkg/volume/emptydir:go_default_library",
 | 
			
		||||
        "//pkg/volume/projected:go_default_library",
 | 
			
		||||
        "//pkg/volume/secret:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/emptydir"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/projected"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/secret"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
	"k8s.io/kubernetes/test/utils"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
@@ -78,6 +79,7 @@ func NewHollowKubelet(
 | 
			
		||||
		TLSOptions:         nil,
 | 
			
		||||
		OOMAdjuster:        oom.NewFakeOOMAdjuster(),
 | 
			
		||||
		Mounter:            mount.New("" /* default mount path */),
 | 
			
		||||
		Subpather:          &subpath.FakeSubpath{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &HollowKubelet{
 | 
			
		||||
 
 | 
			
		||||
@@ -80,8 +80,6 @@ go_test(
 | 
			
		||||
        "//vendor/k8s.io/utils/exec/testing:go_default_library",
 | 
			
		||||
    ] + select({
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:linux": [
 | 
			
		||||
            "//vendor/golang.org/x/sys/unix:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/klog:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/exec:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -144,18 +144,6 @@ func (m *execMounter) EvalHostSymlinks(pathname string) (string, error) {
 | 
			
		||||
	return m.wrappedMounter.EvalHostSymlinks(pathname)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *execMounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	return m.wrappedMounter.PrepareSafeSubpath(subPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *execMounter) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return m.wrappedMounter.CleanSubPaths(podDir, volumeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *execMounter) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	return m.wrappedMounter.SafeMakeDir(pathname, base, perm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *execMounter) GetMountRefs(pathname string) ([]string, error) {
 | 
			
		||||
	return m.wrappedMounter.GetMountRefs(pathname)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -91,18 +91,6 @@ func (m *execMounter) EvalHostSymlinks(pathname string) (string, error) {
 | 
			
		||||
	return "", errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *execMounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	return subPath.Path, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *execMounter) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *execMounter) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *execMounter) GetMountRefs(pathname string) ([]string, error) {
 | 
			
		||||
	return nil, errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -220,17 +220,6 @@ func (f *FakeMounter) EvalHostSymlinks(pathname string) (string, error) {
 | 
			
		||||
	return pathname, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *FakeMounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	return subPath.Path, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *FakeMounter) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (mounter *FakeMounter) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) {
 | 
			
		||||
	realpath, err := filepath.EvalSymlinks(pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -84,35 +84,12 @@ type Interface interface {
 | 
			
		||||
	// MakeDir creates a new directory.
 | 
			
		||||
	// Will operate in the host mount namespace if kubelet is running in a container
 | 
			
		||||
	MakeDir(pathname string) error
 | 
			
		||||
	// SafeMakeDir creates subdir within given base. It makes sure that the
 | 
			
		||||
	// created directory does not escape given base directory mis-using
 | 
			
		||||
	// symlinks. Note that the function makes sure that it creates the directory
 | 
			
		||||
	// somewhere under the base, nothing else. E.g. if the directory already
 | 
			
		||||
	// exists, it may exist outside of the base due to symlinks.
 | 
			
		||||
	// This method should be used if the directory to create is inside volume
 | 
			
		||||
	// that's under user control. User must not be able to use symlinks to
 | 
			
		||||
	// escape the volume to create directories somewhere else.
 | 
			
		||||
	SafeMakeDir(subdir string, base string, perm os.FileMode) error
 | 
			
		||||
	// Will operate in the host mount namespace if kubelet is running in a container.
 | 
			
		||||
	// Error is returned on any other error than "file not found".
 | 
			
		||||
	ExistsPath(pathname string) (bool, error)
 | 
			
		||||
	// EvalHostSymlinks returns the path name after evaluating symlinks.
 | 
			
		||||
	// Will operate in the host mount namespace if kubelet is running in a container.
 | 
			
		||||
	EvalHostSymlinks(pathname string) (string, error)
 | 
			
		||||
	// CleanSubPaths removes any bind-mounts created by PrepareSafeSubpath in given
 | 
			
		||||
	// pod volume directory.
 | 
			
		||||
	CleanSubPaths(podDir string, volumeName string) error
 | 
			
		||||
	// PrepareSafeSubpath does everything that's necessary to prepare a subPath
 | 
			
		||||
	// that's 1) inside given volumePath and 2) immutable after this call.
 | 
			
		||||
	//
 | 
			
		||||
	// newHostPath - location of prepared subPath. It should be used instead of
 | 
			
		||||
	// hostName when running the container.
 | 
			
		||||
	// cleanupAction - action to run when the container is running or it failed to start.
 | 
			
		||||
	//
 | 
			
		||||
	// CleanupAction must be called immediately after the container with given
 | 
			
		||||
	// subpath starts. On the other hand, Interface.CleanSubPaths must be called
 | 
			
		||||
	// when the pod finishes.
 | 
			
		||||
	PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error)
 | 
			
		||||
	// GetMountRefs finds all mount references to the path, returns a
 | 
			
		||||
	// list of paths. Path could be a mountpoint path, device or a normal
 | 
			
		||||
	// directory (for bind mount).
 | 
			
		||||
@@ -355,15 +332,15 @@ func PathWithinBase(fullPath, basePath string) bool {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if startsWithBackstep(rel) {
 | 
			
		||||
	if StartsWithBackstep(rel) {
 | 
			
		||||
		// Needed to escape the base path
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// startsWithBackstep checks if the given path starts with a backstep segment
 | 
			
		||||
func startsWithBackstep(rel string) bool {
 | 
			
		||||
// StartsWithBackstep checks if the given path starts with a backstep segment
 | 
			
		||||
func StartsWithBackstep(rel string) bool {
 | 
			
		||||
	// normalize to / and check for ../
 | 
			
		||||
	return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ package mount
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
@@ -53,14 +52,6 @@ const (
 | 
			
		||||
	fsckErrorsCorrected = 1
 | 
			
		||||
	// 'fsck' found errors but exited without correcting them
 | 
			
		||||
	fsckErrorsUncorrected = 4
 | 
			
		||||
 | 
			
		||||
	// place for subpath mounts
 | 
			
		||||
	// TODO: pass in directory using kubelet_getters instead
 | 
			
		||||
	containerSubPathDirectoryName = "volume-subpaths"
 | 
			
		||||
	// syscall.Openat flags used to traverse directories not following symlinks
 | 
			
		||||
	nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
 | 
			
		||||
	// flags for getting file descriptor without following the symlink
 | 
			
		||||
	openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Mounter provides the default implementation of mount.Interface
 | 
			
		||||
@@ -726,282 +717,6 @@ func getSELinuxSupport(path string, mountInfoFilename string) (bool, error) {
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *Mounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	newHostPath, err = doBindSubPath(mounter, subPath)
 | 
			
		||||
 | 
			
		||||
	// There is no action when the container starts. Bind-mount will be cleaned
 | 
			
		||||
	// when container stops by CleanSubPaths.
 | 
			
		||||
	cleanupAction = nil
 | 
			
		||||
	return newHostPath, cleanupAction, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This implementation is shared between Linux and NsEnterMounter
 | 
			
		||||
func safeOpenSubPath(mounter Interface, subpath Subpath) (int, error) {
 | 
			
		||||
	if !PathWithinBase(subpath.Path, subpath.VolumePath) {
 | 
			
		||||
		return -1, fmt.Errorf("subpath %q not within volume path %q", subpath.Path, subpath.VolumePath)
 | 
			
		||||
	}
 | 
			
		||||
	fd, err := doSafeOpen(subpath.Path, subpath.VolumePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, fmt.Errorf("error opening subpath %v: %v", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
	return fd, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// prepareSubpathTarget creates target for bind-mount of subpath. It returns
 | 
			
		||||
// "true" when the target already exists and something is mounted there.
 | 
			
		||||
// Given Subpath must have all paths with already resolved symlinks and with
 | 
			
		||||
// paths relevant to kubelet (when it runs in a container).
 | 
			
		||||
// This function is called also by NsEnterMounter. It works because
 | 
			
		||||
// /var/lib/kubelet is mounted from the host into the container with Kubelet as
 | 
			
		||||
// /var/lib/kubelet too.
 | 
			
		||||
func prepareSubpathTarget(mounter Interface, subpath Subpath) (bool, string, error) {
 | 
			
		||||
	// Early check for already bind-mounted subpath.
 | 
			
		||||
	bindPathTarget := getSubpathBindTarget(subpath)
 | 
			
		||||
	notMount, err := mounter.IsNotMountPoint(bindPathTarget)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return false, "", fmt.Errorf("error checking path %s for mount: %s", bindPathTarget, err)
 | 
			
		||||
		}
 | 
			
		||||
		// Ignore ErrorNotExist: the file/directory will be created below if it does not exist yet.
 | 
			
		||||
		notMount = true
 | 
			
		||||
	}
 | 
			
		||||
	if !notMount {
 | 
			
		||||
		// It's already mounted
 | 
			
		||||
		klog.V(5).Infof("Skipping bind-mounting subpath %s: already mounted", bindPathTarget)
 | 
			
		||||
		return true, bindPathTarget, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// bindPathTarget is in /var/lib/kubelet and thus reachable without any
 | 
			
		||||
	// translation even to containerized kubelet.
 | 
			
		||||
	bindParent := filepath.Dir(bindPathTarget)
 | 
			
		||||
	err = os.MkdirAll(bindParent, 0750)
 | 
			
		||||
	if err != nil && !os.IsExist(err) {
 | 
			
		||||
		return false, "", fmt.Errorf("error creating directory %s: %s", bindParent, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := os.Lstat(subpath.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, "", fmt.Errorf("lstat %s failed: %s", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if t.Mode()&os.ModeDir > 0 {
 | 
			
		||||
		if err = os.Mkdir(bindPathTarget, 0750); err != nil && !os.IsExist(err) {
 | 
			
		||||
			return false, "", fmt.Errorf("error creating directory %s: %s", bindPathTarget, err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// "/bin/touch <bindPathTarget>".
 | 
			
		||||
		// A file is enough for all possible targets (symlink, device, pipe,
 | 
			
		||||
		// socket, ...), bind-mounting them into a file correctly changes type
 | 
			
		||||
		// of the target file.
 | 
			
		||||
		if err = ioutil.WriteFile(bindPathTarget, []byte{}, 0640); err != nil {
 | 
			
		||||
			return false, "", fmt.Errorf("error creating file %s: %s", bindPathTarget, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, bindPathTarget, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSubpathBindTarget(subpath Subpath) string {
 | 
			
		||||
	// containerName is DNS label, i.e. safe as a directory name.
 | 
			
		||||
	return filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName, strconv.Itoa(subpath.VolumeMountIndex))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doBindSubPath(mounter Interface, subpath Subpath) (hostPath string, err error) {
 | 
			
		||||
	// Linux, kubelet runs on the host:
 | 
			
		||||
	// - safely open the subpath
 | 
			
		||||
	// - bind-mount /proc/<pid of kubelet>/fd/<fd> to subpath target
 | 
			
		||||
	// User can't change /proc/<pid of kubelet>/fd/<fd> to point to a bad place.
 | 
			
		||||
 | 
			
		||||
	// Evaluate all symlinks here once for all subsequent functions.
 | 
			
		||||
	newVolumePath, err := filepath.EvalSymlinks(subpath.VolumePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.VolumePath, err)
 | 
			
		||||
	}
 | 
			
		||||
	newPath, err := filepath.EvalSymlinks(subpath.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
	klog.V(5).Infof("doBindSubPath %q (%q) for volumepath %q", subpath.Path, newPath, subpath.VolumePath)
 | 
			
		||||
	subpath.VolumePath = newVolumePath
 | 
			
		||||
	subpath.Path = newPath
 | 
			
		||||
 | 
			
		||||
	fd, err := safeOpenSubPath(mounter, subpath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer syscall.Close(fd)
 | 
			
		||||
 | 
			
		||||
	alreadyMounted, bindPathTarget, err := prepareSubpathTarget(mounter, subpath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if alreadyMounted {
 | 
			
		||||
		return bindPathTarget, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	success := false
 | 
			
		||||
	defer func() {
 | 
			
		||||
		// Cleanup subpath on error
 | 
			
		||||
		if !success {
 | 
			
		||||
			klog.V(4).Infof("doBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
 | 
			
		||||
			if cleanErr := cleanSubPath(mounter, subpath); cleanErr != nil {
 | 
			
		||||
				klog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	kubeletPid := os.Getpid()
 | 
			
		||||
	mountSource := fmt.Sprintf("/proc/%d/fd/%v", kubeletPid, fd)
 | 
			
		||||
 | 
			
		||||
	// Do the bind mount
 | 
			
		||||
	options := []string{"bind"}
 | 
			
		||||
	klog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget)
 | 
			
		||||
	if err = mounter.Mount(mountSource, bindPathTarget, "" /*fstype*/, options); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
	success = true
 | 
			
		||||
 | 
			
		||||
	klog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
 | 
			
		||||
	return bindPathTarget, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *Mounter) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return doCleanSubPaths(mounter, podDir, volumeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This implementation is shared between Linux and NsEnterMounter
 | 
			
		||||
func doCleanSubPaths(mounter Interface, podDir string, volumeName string) error {
 | 
			
		||||
	// scan /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/*
 | 
			
		||||
	subPathDir := filepath.Join(podDir, containerSubPathDirectoryName, volumeName)
 | 
			
		||||
	klog.V(4).Infof("Cleaning up subpath mounts for %s", subPathDir)
 | 
			
		||||
 | 
			
		||||
	containerDirs, err := ioutil.ReadDir(subPathDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Errorf("error reading %s: %s", subPathDir, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, containerDir := range containerDirs {
 | 
			
		||||
		if !containerDir.IsDir() {
 | 
			
		||||
			klog.V(4).Infof("Container file is not a directory: %s", containerDir.Name())
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		klog.V(4).Infof("Cleaning up subpath mounts for container %s", containerDir.Name())
 | 
			
		||||
 | 
			
		||||
		// scan /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/<container name>/*
 | 
			
		||||
		fullContainerDirPath := filepath.Join(subPathDir, containerDir.Name())
 | 
			
		||||
		err = filepath.Walk(fullContainerDirPath, func(path string, info os.FileInfo, err error) error {
 | 
			
		||||
			if path == fullContainerDirPath {
 | 
			
		||||
				// Skip top level directory
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// pass through errors and let doCleanSubPath handle them
 | 
			
		||||
			if err = doCleanSubPath(mounter, fullContainerDirPath, filepath.Base(path)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("error processing %s: %s", fullContainerDirPath, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Whole container has been processed, remove its directory.
 | 
			
		||||
		if err := os.Remove(fullContainerDirPath); err != nil {
 | 
			
		||||
			return fmt.Errorf("error deleting %s: %s", fullContainerDirPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		klog.V(5).Infof("Removed %s", fullContainerDirPath)
 | 
			
		||||
	}
 | 
			
		||||
	// Whole pod volume subpaths have been cleaned up, remove its subpath directory.
 | 
			
		||||
	if err := os.Remove(subPathDir); err != nil {
 | 
			
		||||
		return fmt.Errorf("error deleting %s: %s", subPathDir, err)
 | 
			
		||||
	}
 | 
			
		||||
	klog.V(5).Infof("Removed %s", subPathDir)
 | 
			
		||||
 | 
			
		||||
	// Remove entire subpath directory if it's the last one
 | 
			
		||||
	podSubPathDir := filepath.Join(podDir, containerSubPathDirectoryName)
 | 
			
		||||
	if err := os.Remove(podSubPathDir); err != nil && !os.IsExist(err) {
 | 
			
		||||
		return fmt.Errorf("error deleting %s: %s", podSubPathDir, err)
 | 
			
		||||
	}
 | 
			
		||||
	klog.V(5).Infof("Removed %s", podSubPathDir)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// doCleanSubPath tears down the single subpath bind mount
 | 
			
		||||
func doCleanSubPath(mounter Interface, fullContainerDirPath, subPathIndex string) error {
 | 
			
		||||
	// process /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/<container name>/<subPathName>
 | 
			
		||||
	klog.V(4).Infof("Cleaning up subpath mounts for subpath %v", subPathIndex)
 | 
			
		||||
	fullSubPath := filepath.Join(fullContainerDirPath, subPathIndex)
 | 
			
		||||
 | 
			
		||||
	if err := CleanupMountPoint(fullSubPath, mounter, true); err != nil {
 | 
			
		||||
		return fmt.Errorf("error cleaning subpath mount %s: %s", fullSubPath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	klog.V(4).Infof("Successfully cleaned subpath directory %s", fullSubPath)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cleanSubPath will teardown the subpath bind mount and any remove any directories if empty
 | 
			
		||||
func cleanSubPath(mounter Interface, subpath Subpath) error {
 | 
			
		||||
	containerDir := filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName)
 | 
			
		||||
 | 
			
		||||
	// Clean subdir bindmount
 | 
			
		||||
	if err := doCleanSubPath(mounter, containerDir, strconv.Itoa(subpath.VolumeMountIndex)); err != nil && !os.IsNotExist(err) {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Recusively remove directories if empty
 | 
			
		||||
	if err := removeEmptyDirs(subpath.PodDir, containerDir); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// removeEmptyDirs works backwards from endDir to baseDir and removes each directory
 | 
			
		||||
// if it is empty.  It stops once it encounters a directory that has content
 | 
			
		||||
func removeEmptyDirs(baseDir, endDir string) error {
 | 
			
		||||
	if !PathWithinBase(endDir, baseDir) {
 | 
			
		||||
		return fmt.Errorf("endDir %q is not within baseDir %q", endDir, baseDir)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for curDir := endDir; curDir != baseDir; curDir = filepath.Dir(curDir) {
 | 
			
		||||
		s, err := os.Stat(curDir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				klog.V(5).Infof("curDir %q doesn't exist, skipping", curDir)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("error stat %q: %v", curDir, err)
 | 
			
		||||
		}
 | 
			
		||||
		if !s.IsDir() {
 | 
			
		||||
			return fmt.Errorf("path %q not a directory", curDir)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = os.Remove(curDir)
 | 
			
		||||
		if os.IsExist(err) {
 | 
			
		||||
			klog.V(5).Infof("Directory %q not empty, not removing", curDir)
 | 
			
		||||
			break
 | 
			
		||||
		} else if err != nil {
 | 
			
		||||
			return fmt.Errorf("error removing directory %q: %v", curDir, err)
 | 
			
		||||
		}
 | 
			
		||||
		klog.V(5).Infof("Removed directory %q", curDir)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *Mounter) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
 | 
			
		||||
	realBase, err := filepath.EvalSymlinks(base)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	realFullPath := filepath.Join(realBase, subdir)
 | 
			
		||||
 | 
			
		||||
	return doSafeMakeDir(realFullPath, realBase, perm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
 | 
			
		||||
	if _, err := os.Stat(pathname); os.IsNotExist(err) {
 | 
			
		||||
		return []string{}, nil
 | 
			
		||||
@@ -1049,237 +764,6 @@ func getMode(pathname string) (os.FileMode, error) {
 | 
			
		||||
	return info.Mode(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This implementation is shared between Linux and NsEnterMounter. Both pathname
 | 
			
		||||
// and base must be either already resolved symlinks or thet will be resolved in
 | 
			
		||||
// kubelet's mount namespace (in case it runs containerized).
 | 
			
		||||
func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
 | 
			
		||||
 | 
			
		||||
	if !PathWithinBase(pathname, base) {
 | 
			
		||||
		return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Quick check if the directory already exists
 | 
			
		||||
	s, err := os.Stat(pathname)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// Path exists
 | 
			
		||||
		if s.IsDir() {
 | 
			
		||||
			// The directory already exists. It can be outside of the parent,
 | 
			
		||||
			// but there is no race-proof check.
 | 
			
		||||
			klog.V(4).Infof("Directory %s already exists", pathname)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Find all existing directories
 | 
			
		||||
	existingPath, toCreate, err := findExistingPrefix(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error opening directory %s: %s", pathname, err)
 | 
			
		||||
	}
 | 
			
		||||
	// Ensure the existing directory is inside allowed base
 | 
			
		||||
	fullExistingPath, err := filepath.EvalSymlinks(existingPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error opening directory %s: %s", existingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	if !PathWithinBase(fullExistingPath, base) {
 | 
			
		||||
		return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
 | 
			
		||||
	parentFD, err := doSafeOpen(fullExistingPath, base)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("cannot open directory %s: %s", existingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	childFD := -1
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if parentFD != -1 {
 | 
			
		||||
			if err = syscall.Close(parentFD); err != nil {
 | 
			
		||||
				klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if childFD != -1 {
 | 
			
		||||
			if err = syscall.Close(childFD); err != nil {
 | 
			
		||||
				klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", childFD, pathname, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	currentPath := fullExistingPath
 | 
			
		||||
	// create the directories one by one, making sure nobody can change
 | 
			
		||||
	// created directory into symlink.
 | 
			
		||||
	for _, dir := range toCreate {
 | 
			
		||||
		currentPath = filepath.Join(currentPath, dir)
 | 
			
		||||
		klog.V(4).Infof("Creating %s", dir)
 | 
			
		||||
		err = syscall.Mkdirat(parentFD, currentPath, uint32(perm))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		// Dive into the created directory
 | 
			
		||||
		childFD, err := syscall.Openat(parentFD, dir, nofollowFlags, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("cannot open %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		// We can be sure that childFD is safe to use. It could be changed
 | 
			
		||||
		// by user after Mkdirat() and before Openat(), however:
 | 
			
		||||
		// - it could not be changed to symlink - we use nofollowFlags
 | 
			
		||||
		// - it could be changed to a file (or device, pipe, socket, ...)
 | 
			
		||||
		//   but either subsequent Mkdirat() fails or we mount this file
 | 
			
		||||
		//   to user's container. Security is no violated in both cases
 | 
			
		||||
		//   and user either gets error or the file that it can already access.
 | 
			
		||||
 | 
			
		||||
		if err = syscall.Close(parentFD); err != nil {
 | 
			
		||||
			klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
 | 
			
		||||
		}
 | 
			
		||||
		parentFD = childFD
 | 
			
		||||
		childFD = -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Everything was created. mkdirat(..., perm) above was affected by current
 | 
			
		||||
	// umask and we must apply the right permissions to the last directory
 | 
			
		||||
	// (that's the one that will be available to the container as subpath)
 | 
			
		||||
	// so user can read/write it. This is the behavior of previous code.
 | 
			
		||||
	// TODO: chmod all created directories, not just the last one.
 | 
			
		||||
	// parentFD is the last created directory.
 | 
			
		||||
 | 
			
		||||
	// Translate perm (os.FileMode) to uint32 that fchmod() expects
 | 
			
		||||
	kernelPerm := uint32(perm & os.ModePerm)
 | 
			
		||||
	if perm&os.ModeSetgid > 0 {
 | 
			
		||||
		kernelPerm |= syscall.S_ISGID
 | 
			
		||||
	}
 | 
			
		||||
	if perm&os.ModeSetuid > 0 {
 | 
			
		||||
		kernelPerm |= syscall.S_ISUID
 | 
			
		||||
	}
 | 
			
		||||
	if perm&os.ModeSticky > 0 {
 | 
			
		||||
		kernelPerm |= syscall.S_ISVTX
 | 
			
		||||
	}
 | 
			
		||||
	if err = syscall.Fchmod(parentFD, kernelPerm); err != nil {
 | 
			
		||||
		return fmt.Errorf("chmod %q failed: %s", currentPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// findExistingPrefix finds prefix of pathname that exists. In addition, it
 | 
			
		||||
// returns list of remaining directories that don't exist yet.
 | 
			
		||||
func findExistingPrefix(base, pathname string) (string, []string, error) {
 | 
			
		||||
	rel, err := filepath.Rel(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return base, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	dirs := strings.Split(rel, string(filepath.Separator))
 | 
			
		||||
 | 
			
		||||
	// Do OpenAt in a loop to find the first non-existing dir. Resolve symlinks.
 | 
			
		||||
	// This should be faster than looping through all dirs and calling os.Stat()
 | 
			
		||||
	// on each of them, as the symlinks are resolved only once with OpenAt().
 | 
			
		||||
	currentPath := base
 | 
			
		||||
	fd, err := syscall.Open(currentPath, syscall.O_RDONLY, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return pathname, nil, fmt.Errorf("error opening %s: %s", currentPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err = syscall.Close(fd); err != nil {
 | 
			
		||||
			klog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	for i, dir := range dirs {
 | 
			
		||||
		// Using O_PATH here will prevent hangs in case user replaces directory with
 | 
			
		||||
		// fifo
 | 
			
		||||
		childFD, err := syscall.Openat(fd, dir, unix.O_PATH, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				return currentPath, dirs[i:], nil
 | 
			
		||||
			}
 | 
			
		||||
			return base, nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if err = syscall.Close(fd); err != nil {
 | 
			
		||||
			klog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
 | 
			
		||||
		}
 | 
			
		||||
		fd = childFD
 | 
			
		||||
		currentPath = filepath.Join(currentPath, dir)
 | 
			
		||||
	}
 | 
			
		||||
	return pathname, []string{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This implementation is shared between Linux and NsEnterMounter
 | 
			
		||||
// Open path and return its fd.
 | 
			
		||||
// Symlinks are disallowed (pathname must already resolve symlinks),
 | 
			
		||||
// and the path must be within the base directory.
 | 
			
		||||
func doSafeOpen(pathname string, base string) (int, error) {
 | 
			
		||||
	pathname = filepath.Clean(pathname)
 | 
			
		||||
	base = filepath.Clean(base)
 | 
			
		||||
 | 
			
		||||
	// Calculate segments to follow
 | 
			
		||||
	subpath, err := filepath.Rel(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
	segments := strings.Split(subpath, string(filepath.Separator))
 | 
			
		||||
 | 
			
		||||
	// Assumption: base is the only directory that we have under control.
 | 
			
		||||
	// Base dir is not allowed to be a symlink.
 | 
			
		||||
	parentFD, err := syscall.Open(base, nofollowFlags, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, fmt.Errorf("cannot open directory %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if parentFD != -1 {
 | 
			
		||||
			if err = syscall.Close(parentFD); err != nil {
 | 
			
		||||
				klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	childFD := -1
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if childFD != -1 {
 | 
			
		||||
			if err = syscall.Close(childFD); err != nil {
 | 
			
		||||
				klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	currentPath := base
 | 
			
		||||
 | 
			
		||||
	// Follow the segments one by one using openat() to make
 | 
			
		||||
	// sure the user cannot change already existing directories into symlinks.
 | 
			
		||||
	for _, seg := range segments {
 | 
			
		||||
		currentPath = filepath.Join(currentPath, seg)
 | 
			
		||||
		if !PathWithinBase(currentPath, base) {
 | 
			
		||||
			return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		klog.V(5).Infof("Opening path %s", currentPath)
 | 
			
		||||
		childFD, err = syscall.Openat(parentFD, seg, openFDFlags, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return -1, fmt.Errorf("cannot open %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var deviceStat unix.Stat_t
 | 
			
		||||
		err := unix.Fstat(childFD, &deviceStat)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return -1, fmt.Errorf("Error running fstat on %s with %v", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		fileFmt := deviceStat.Mode & syscall.S_IFMT
 | 
			
		||||
		if fileFmt == syscall.S_IFLNK {
 | 
			
		||||
			return -1, fmt.Errorf("Unexpected symlink found %s", currentPath)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Close parentFD
 | 
			
		||||
		if err = syscall.Close(parentFD); err != nil {
 | 
			
		||||
			return -1, fmt.Errorf("closing fd for %q failed: %v", filepath.Dir(currentPath), err)
 | 
			
		||||
		}
 | 
			
		||||
		// Set child to new parent
 | 
			
		||||
		parentFD = childFD
 | 
			
		||||
		childFD = -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We made it to the end, return this fd, don't close it
 | 
			
		||||
	finalFD := parentFD
 | 
			
		||||
	parentFD = -1
 | 
			
		||||
 | 
			
		||||
	return finalFD, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// searchMountPoints finds all mount references to the source, returns a list of
 | 
			
		||||
// mountpoints.
 | 
			
		||||
// This function assumes source cannot be device.
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -110,18 +110,6 @@ func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) {
 | 
			
		||||
	return "", unsupportedErr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *Mounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	return subPath.Path, nil, unsupportedErr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *Mounter) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return unsupportedErr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *Mounter) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	return unsupportedErr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
 | 
			
		||||
	return nil, errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,6 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
 | 
			
		||||
@@ -279,123 +278,6 @@ func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) {
 | 
			
		||||
	return filepath.EvalSymlinks(pathname)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// check whether hostPath is within volume path
 | 
			
		||||
// this func will lock all intermediate subpath directories, need to close handle outside of this func after container started
 | 
			
		||||
func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
 | 
			
		||||
	if len(volumePath) == 0 || len(hostPath) == 0 {
 | 
			
		||||
		return []uintptr{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	finalSubPath, err := filepath.EvalSymlinks(hostPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []uintptr{}, fmt.Errorf("cannot read link %s: %s", hostPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	finalVolumePath, err := filepath.EvalSymlinks(volumePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []uintptr{}, fmt.Errorf("cannot read link %s: %s", volumePath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return lockAndCheckSubPathWithoutSymlink(finalVolumePath, finalSubPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// lock all intermediate subPath directories and check they are all within volumePath
 | 
			
		||||
// volumePath & subPath should not contain any symlink, otherwise it will return error
 | 
			
		||||
func lockAndCheckSubPathWithoutSymlink(volumePath, subPath string) ([]uintptr, error) {
 | 
			
		||||
	if len(volumePath) == 0 || len(subPath) == 0 {
 | 
			
		||||
		return []uintptr{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get relative path to volumePath
 | 
			
		||||
	relSubPath, err := filepath.Rel(volumePath, subPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []uintptr{}, fmt.Errorf("Rel(%s, %s) error: %v", volumePath, subPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	if startsWithBackstep(relSubPath) {
 | 
			
		||||
		return []uintptr{}, fmt.Errorf("SubPath %q not within volume path %q", subPath, volumePath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if relSubPath == "." {
 | 
			
		||||
		// volumePath and subPath are equal
 | 
			
		||||
		return []uintptr{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fileHandles := []uintptr{}
 | 
			
		||||
	var errorResult error
 | 
			
		||||
 | 
			
		||||
	currentFullPath := volumePath
 | 
			
		||||
	dirs := strings.Split(relSubPath, string(os.PathSeparator))
 | 
			
		||||
	for _, dir := range dirs {
 | 
			
		||||
		// lock intermediate subPath directory first
 | 
			
		||||
		currentFullPath = filepath.Join(currentFullPath, dir)
 | 
			
		||||
		handle, err := lockPath(currentFullPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorResult = fmt.Errorf("cannot lock path %s: %s", currentFullPath, err)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fileHandles = append(fileHandles, handle)
 | 
			
		||||
 | 
			
		||||
		// make sure intermediate subPath directory does not contain symlink any more
 | 
			
		||||
		stat, err := os.Lstat(currentFullPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorResult = fmt.Errorf("Lstat(%q) error: %v", currentFullPath, err)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if stat.Mode()&os.ModeSymlink != 0 {
 | 
			
		||||
			errorResult = fmt.Errorf("subpath %q is an unexpected symlink after EvalSymlinks", currentFullPath)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !PathWithinBase(currentFullPath, volumePath) {
 | 
			
		||||
			errorResult = fmt.Errorf("SubPath %q not within volume path %q", currentFullPath, volumePath)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fileHandles, errorResult
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// unlockPath unlock directories
 | 
			
		||||
func unlockPath(fileHandles []uintptr) {
 | 
			
		||||
	if fileHandles != nil {
 | 
			
		||||
		for _, handle := range fileHandles {
 | 
			
		||||
			syscall.CloseHandle(syscall.Handle(handle))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// lockPath locks a directory or symlink, return handle, exec "syscall.CloseHandle(handle)" to unlock the path
 | 
			
		||||
func lockPath(path string) (uintptr, error) {
 | 
			
		||||
	if len(path) == 0 {
 | 
			
		||||
		return uintptr(syscall.InvalidHandle), syscall.ERROR_FILE_NOT_FOUND
 | 
			
		||||
	}
 | 
			
		||||
	pathp, err := syscall.UTF16PtrFromString(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return uintptr(syscall.InvalidHandle), err
 | 
			
		||||
	}
 | 
			
		||||
	access := uint32(syscall.GENERIC_READ)
 | 
			
		||||
	sharemode := uint32(syscall.FILE_SHARE_READ)
 | 
			
		||||
	createmode := uint32(syscall.OPEN_EXISTING)
 | 
			
		||||
	flags := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
 | 
			
		||||
	fd, err := syscall.CreateFile(pathp, access, sharemode, nil, createmode, flags, 0)
 | 
			
		||||
	return uintptr(fd), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Lock all directories in subPath and check they're not symlinks.
 | 
			
		||||
func (mounter *Mounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	handles, err := lockAndCheckSubPath(subPath.VolumePath, subPath.Path)
 | 
			
		||||
 | 
			
		||||
	// Unlock the directories when the container starts
 | 
			
		||||
	cleanupAction = func() {
 | 
			
		||||
		unlockPath(handles)
 | 
			
		||||
	}
 | 
			
		||||
	return subPath.Path, cleanupAction, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// No bind-mounts for subpaths are necessary on Windows
 | 
			
		||||
func (mounter *Mounter) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
 | 
			
		||||
	// Try to mount the disk
 | 
			
		||||
	klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, source, target)
 | 
			
		||||
@@ -522,126 +404,3 @@ func (mounter *Mounter) GetMode(pathname string) (os.FileMode, error) {
 | 
			
		||||
	}
 | 
			
		||||
	return info.Mode(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SafeMakeDir makes sure that the created directory does not escape given base directory mis-using symlinks.
 | 
			
		||||
func (mounter *Mounter) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
 | 
			
		||||
	realBase, err := filepath.EvalSymlinks(base)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	realFullPath := filepath.Join(realBase, subdir)
 | 
			
		||||
	return doSafeMakeDir(realFullPath, realBase, perm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
 | 
			
		||||
 | 
			
		||||
	if !PathWithinBase(pathname, base) {
 | 
			
		||||
		return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Quick check if the directory already exists
 | 
			
		||||
	s, err := os.Stat(pathname)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// Path exists
 | 
			
		||||
		if s.IsDir() {
 | 
			
		||||
			// The directory already exists. It can be outside of the parent,
 | 
			
		||||
			// but there is no race-proof check.
 | 
			
		||||
			klog.V(4).Infof("Directory %s already exists", pathname)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Find all existing directories
 | 
			
		||||
	existingPath, toCreate, err := findExistingPrefix(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error opening directory %s: %s", pathname, err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(toCreate) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure the existing directory is inside allowed base
 | 
			
		||||
	fullExistingPath, err := filepath.EvalSymlinks(existingPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error opening existing directory %s: %s", existingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	fullBasePath, err := filepath.EvalSymlinks(base)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("cannot read link %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
	if !PathWithinBase(fullExistingPath, fullBasePath) {
 | 
			
		||||
		return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// lock all intermediate directories from fullBasePath to fullExistingPath (top to bottom)
 | 
			
		||||
	fileHandles, err := lockAndCheckSubPathWithoutSymlink(fullBasePath, fullExistingPath)
 | 
			
		||||
	defer unlockPath(fileHandles)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
 | 
			
		||||
	currentPath := fullExistingPath
 | 
			
		||||
	// create the directories one by one, making sure nobody can change
 | 
			
		||||
	// created directory into symlink by lock that directory immediately
 | 
			
		||||
	for _, dir := range toCreate {
 | 
			
		||||
		currentPath = filepath.Join(currentPath, dir)
 | 
			
		||||
		klog.V(4).Infof("Creating %s", dir)
 | 
			
		||||
		if err := os.Mkdir(currentPath, perm); err != nil {
 | 
			
		||||
			return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		handle, err := lockPath(currentPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("cannot lock path %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		defer syscall.CloseHandle(syscall.Handle(handle))
 | 
			
		||||
		// make sure newly created directory does not contain symlink after lock
 | 
			
		||||
		stat, err := os.Lstat(currentPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Lstat(%q) error: %v", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		if stat.Mode()&os.ModeSymlink != 0 {
 | 
			
		||||
			return fmt.Errorf("subpath %q is an unexpected symlink after Mkdir", currentPath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// findExistingPrefix finds prefix of pathname that exists. In addition, it
 | 
			
		||||
// returns list of remaining directories that don't exist yet.
 | 
			
		||||
func findExistingPrefix(base, pathname string) (string, []string, error) {
 | 
			
		||||
	rel, err := filepath.Rel(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return base, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if startsWithBackstep(rel) {
 | 
			
		||||
		return base, nil, fmt.Errorf("pathname(%s) is not within base(%s)", pathname, base)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if rel == "." {
 | 
			
		||||
		// base and pathname are equal
 | 
			
		||||
		return pathname, []string{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dirs := strings.Split(rel, string(filepath.Separator))
 | 
			
		||||
 | 
			
		||||
	parent := base
 | 
			
		||||
	currentPath := base
 | 
			
		||||
	for i, dir := range dirs {
 | 
			
		||||
		parent = currentPath
 | 
			
		||||
		currentPath = filepath.Join(parent, dir)
 | 
			
		||||
		if _, err := os.Lstat(currentPath); err != nil {
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				return parent, dirs[i:], nil
 | 
			
		||||
			}
 | 
			
		||||
			return base, nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pathname, []string{}, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -134,409 +134,6 @@ func TestGetMountRefs(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDoSafeMakeDir(t *testing.T) {
 | 
			
		||||
	base, err := ioutil.TempDir("", "TestDoSafeMakeDir")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(base)
 | 
			
		||||
 | 
			
		||||
	testingVolumePath := filepath.Join(base, "testingVolumePath")
 | 
			
		||||
	os.MkdirAll(testingVolumePath, 0755)
 | 
			
		||||
	defer os.RemoveAll(testingVolumePath)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		volumePath    string
 | 
			
		||||
		subPath       string
 | 
			
		||||
		expectError   bool
 | 
			
		||||
		symlinkTarget string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       ``,
 | 
			
		||||
			expectError:   true,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `x`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `a\b\c\d`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `symlink`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `symlink\c\d`),
 | 
			
		||||
			expectError:   true,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `symlink\y926`),
 | 
			
		||||
			expectError:   true,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `a\b\symlink`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `a\x\symlink`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: filepath.Join(testingVolumePath, `a`),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if len(test.volumePath) > 0 && len(test.subPath) > 0 && len(test.symlinkTarget) > 0 {
 | 
			
		||||
			// make all parent sub directories
 | 
			
		||||
			if parent := filepath.Dir(test.subPath); parent != "." {
 | 
			
		||||
				os.MkdirAll(parent, 0755)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// make last element as symlink
 | 
			
		||||
			linkPath := test.subPath
 | 
			
		||||
			if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
 | 
			
		||||
				if err := makeLink(linkPath, test.symlinkTarget); err != nil {
 | 
			
		||||
					t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err := doSafeMakeDir(test.subPath, test.volumePath, os.FileMode(0755))
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			assert.NotNil(t, err, "Expect error during doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		assert.Nil(t, err, "Expect no error during doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
 | 
			
		||||
		if _, err := os.Stat(test.subPath); os.IsNotExist(err) {
 | 
			
		||||
			t.Errorf("subPath should exists after doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLockAndCheckSubPath(t *testing.T) {
 | 
			
		||||
	base, err := ioutil.TempDir("", "TestLockAndCheckSubPath")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(base)
 | 
			
		||||
 | 
			
		||||
	testingVolumePath := filepath.Join(base, "testingVolumePath")
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		volumePath          string
 | 
			
		||||
		subPath             string
 | 
			
		||||
		expectedHandleCount int
 | 
			
		||||
		expectError         bool
 | 
			
		||||
		symlinkTarget       string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          `c:\`,
 | 
			
		||||
			subPath:             ``,
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          ``,
 | 
			
		||||
			subPath:             `a`,
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a`),
 | 
			
		||||
			expectedHandleCount: 1,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\d`),
 | 
			
		||||
			expectedHandleCount: 4,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `symlink`),
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\symlink`),
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\d\symlink`),
 | 
			
		||||
			expectedHandleCount: 2,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if len(test.volumePath) > 0 && len(test.subPath) > 0 {
 | 
			
		||||
			os.MkdirAll(test.volumePath, 0755)
 | 
			
		||||
			if len(test.symlinkTarget) == 0 {
 | 
			
		||||
				// make all intermediate sub directories
 | 
			
		||||
				os.MkdirAll(test.subPath, 0755)
 | 
			
		||||
			} else {
 | 
			
		||||
				// make all parent sub directories
 | 
			
		||||
				if parent := filepath.Dir(test.subPath); parent != "." {
 | 
			
		||||
					os.MkdirAll(parent, 0755)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// make last element as symlink
 | 
			
		||||
				linkPath := test.subPath
 | 
			
		||||
				if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
 | 
			
		||||
					if err := makeLink(linkPath, test.symlinkTarget); err != nil {
 | 
			
		||||
						t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fileHandles, err := lockAndCheckSubPath(test.volumePath, test.subPath)
 | 
			
		||||
		unlockPath(fileHandles)
 | 
			
		||||
		assert.Equal(t, test.expectedHandleCount, len(fileHandles))
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			assert.NotNil(t, err, "Expect error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		assert.Nil(t, err, "Expect no error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove dir will happen after closing all file handles
 | 
			
		||||
	assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLockAndCheckSubPathWithoutSymlink(t *testing.T) {
 | 
			
		||||
	base, err := ioutil.TempDir("", "TestLockAndCheckSubPathWithoutSymlink")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(base)
 | 
			
		||||
 | 
			
		||||
	testingVolumePath := filepath.Join(base, "testingVolumePath")
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		volumePath          string
 | 
			
		||||
		subPath             string
 | 
			
		||||
		expectedHandleCount int
 | 
			
		||||
		expectError         bool
 | 
			
		||||
		symlinkTarget       string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          `c:\`,
 | 
			
		||||
			subPath:             ``,
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          ``,
 | 
			
		||||
			subPath:             `a`,
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a`),
 | 
			
		||||
			expectedHandleCount: 1,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\d`),
 | 
			
		||||
			expectedHandleCount: 4,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `symlink`),
 | 
			
		||||
			expectedHandleCount: 1,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\symlink`),
 | 
			
		||||
			expectedHandleCount: 4,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\d\symlink`),
 | 
			
		||||
			expectedHandleCount: 5,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if len(test.volumePath) > 0 && len(test.subPath) > 0 {
 | 
			
		||||
			os.MkdirAll(test.volumePath, 0755)
 | 
			
		||||
			if len(test.symlinkTarget) == 0 {
 | 
			
		||||
				// make all intermediate sub directories
 | 
			
		||||
				os.MkdirAll(test.subPath, 0755)
 | 
			
		||||
			} else {
 | 
			
		||||
				// make all parent sub directories
 | 
			
		||||
				if parent := filepath.Dir(test.subPath); parent != "." {
 | 
			
		||||
					os.MkdirAll(parent, 0755)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// make last element as symlink
 | 
			
		||||
				linkPath := test.subPath
 | 
			
		||||
				if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
 | 
			
		||||
					if err := makeLink(linkPath, test.symlinkTarget); err != nil {
 | 
			
		||||
						t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fileHandles, err := lockAndCheckSubPathWithoutSymlink(test.volumePath, test.subPath)
 | 
			
		||||
		unlockPath(fileHandles)
 | 
			
		||||
		assert.Equal(t, test.expectedHandleCount, len(fileHandles))
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			assert.NotNil(t, err, "Expect error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		assert.Nil(t, err, "Expect no error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove dir will happen after closing all file handles
 | 
			
		||||
	assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFindExistingPrefix(t *testing.T) {
 | 
			
		||||
	base, err := ioutil.TempDir("", "TestFindExistingPrefix")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(base)
 | 
			
		||||
 | 
			
		||||
	testingVolumePath := filepath.Join(base, "testingVolumePath")
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		base                    string
 | 
			
		||||
		pathname                string
 | 
			
		||||
		expectError             bool
 | 
			
		||||
		expectedExistingPath    string
 | 
			
		||||
		expectedToCreateDirs    []string
 | 
			
		||||
		createSubPathBeforeTest bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			base:                    `c:\tmp\a`,
 | 
			
		||||
			pathname:                `c:\tmp\b`,
 | 
			
		||||
			expectError:             true,
 | 
			
		||||
			expectedExistingPath:    "",
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    ``,
 | 
			
		||||
			pathname:                `c:\tmp\b`,
 | 
			
		||||
			expectError:             true,
 | 
			
		||||
			expectedExistingPath:    "",
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    `c:\tmp\a`,
 | 
			
		||||
			pathname:                `d:\tmp\b`,
 | 
			
		||||
			expectError:             true,
 | 
			
		||||
			expectedExistingPath:    "",
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    testingVolumePath,
 | 
			
		||||
			pathname:                testingVolumePath,
 | 
			
		||||
			expectError:             false,
 | 
			
		||||
			expectedExistingPath:    testingVolumePath,
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    testingVolumePath,
 | 
			
		||||
			pathname:                filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
			expectError:             false,
 | 
			
		||||
			expectedExistingPath:    filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    testingVolumePath,
 | 
			
		||||
			pathname:                filepath.Join(testingVolumePath, `a\b\c\`),
 | 
			
		||||
			expectError:             false,
 | 
			
		||||
			expectedExistingPath:    filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
			expectedToCreateDirs:    []string{`c`},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    testingVolumePath,
 | 
			
		||||
			pathname:                filepath.Join(testingVolumePath, `a\b\c\d`),
 | 
			
		||||
			expectError:             false,
 | 
			
		||||
			expectedExistingPath:    filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
			expectedToCreateDirs:    []string{`c`, `d`},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if test.createSubPathBeforeTest {
 | 
			
		||||
			os.MkdirAll(test.pathname, 0755)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		existingPath, toCreate, err := findExistingPrefix(test.base, test.pathname)
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			assert.NotNil(t, err, "Expect error during findExistingPrefix(%s, %s)", test.base, test.pathname)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		assert.Nil(t, err, "Expect no error during findExistingPrefix(%s, %s)", test.base, test.pathname)
 | 
			
		||||
 | 
			
		||||
		assert.Equal(t, test.expectedExistingPath, existingPath, "Expect result not equal with findExistingPrefix(%s, %s) return: %q, expected: %q",
 | 
			
		||||
			test.base, test.pathname, existingPath, test.expectedExistingPath)
 | 
			
		||||
 | 
			
		||||
		assert.Equal(t, test.expectedToCreateDirs, toCreate, "Expect result not equal with findExistingPrefix(%s, %s) return: %q, expected: %q",
 | 
			
		||||
			test.base, test.pathname, toCreate, test.expectedToCreateDirs)
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	// remove dir will happen after closing all file handles
 | 
			
		||||
	assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPathWithinBase(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		fullPath       string
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,7 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
	"k8s.io/utils/nsenter"
 | 
			
		||||
	utilpath "k8s.io/utils/path"
 | 
			
		||||
@@ -298,51 +296,6 @@ func (mounter *NsenterMounter) EvalHostSymlinks(pathname string) (string, error)
 | 
			
		||||
	return mounter.ne.EvalSymlinks(pathname, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *NsenterMounter) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return doCleanSubPaths(mounter, podDir, volumeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *NsenterMounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	// Bind-mount the subpath to avoid using symlinks in subpaths.
 | 
			
		||||
	newHostPath, err = doNsEnterBindSubPath(mounter, subPath)
 | 
			
		||||
 | 
			
		||||
	// There is no action when the container starts. Bind-mount will be cleaned
 | 
			
		||||
	// when container stops by CleanSubPaths.
 | 
			
		||||
	cleanupAction = nil
 | 
			
		||||
	return newHostPath, cleanupAction, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *NsenterMounter) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
 | 
			
		||||
	fullSubdirPath := filepath.Join(base, subdir)
 | 
			
		||||
	evaluatedSubdirPath, err := mounter.ne.EvalSymlinks(fullSubdirPath, false /* mustExist */)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error resolving symlinks in %s: %s", fullSubdirPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	evaluatedSubdirPath = filepath.Clean(evaluatedSubdirPath)
 | 
			
		||||
 | 
			
		||||
	evaluatedBase, err := mounter.ne.EvalSymlinks(base, true /* mustExist */)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
	evaluatedBase = filepath.Clean(evaluatedBase)
 | 
			
		||||
 | 
			
		||||
	rootDir := filepath.Clean(mounter.rootDir)
 | 
			
		||||
	if PathWithinBase(evaluatedBase, rootDir) {
 | 
			
		||||
		// Base is in /var/lib/kubelet. This directory is shared between the
 | 
			
		||||
		// container with kubelet and the host. We don't need to add '/rootfs'.
 | 
			
		||||
		// This is useful when /rootfs is mounted as read-only - we can still
 | 
			
		||||
		// create subpaths for paths in /var/lib/kubelet.
 | 
			
		||||
		return doSafeMakeDir(evaluatedSubdirPath, evaluatedBase, perm)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Base is somewhere on the host's filesystem. Add /rootfs and try to make
 | 
			
		||||
	// the directory there.
 | 
			
		||||
	// This requires /rootfs to be writable.
 | 
			
		||||
	kubeletSubdirPath := mounter.ne.KubeletPath(evaluatedSubdirPath)
 | 
			
		||||
	kubeletBase := mounter.ne.KubeletPath(evaluatedBase)
 | 
			
		||||
	return doSafeMakeDir(kubeletSubdirPath, kubeletBase, perm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *NsenterMounter) GetMountRefs(pathname string) ([]string, error) {
 | 
			
		||||
	exists, err := mounter.ExistsPath(pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -358,95 +311,6 @@ func (mounter *NsenterMounter) GetMountRefs(pathname string) ([]string, error) {
 | 
			
		||||
	return searchMountPoints(hostpath, hostProcMountinfoPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doNsEnterBindSubPath(mounter *NsenterMounter, subpath Subpath) (hostPath string, err error) {
 | 
			
		||||
	// Linux, kubelet runs in a container:
 | 
			
		||||
	// - safely open the subpath
 | 
			
		||||
	// - bind-mount the subpath to target (this can be unsafe)
 | 
			
		||||
	// - check that we mounted the right thing by comparing device ID and inode
 | 
			
		||||
	//   of the subpath (via safely opened fd) and the target (that's under our
 | 
			
		||||
	//   control)
 | 
			
		||||
 | 
			
		||||
	// Evaluate all symlinks here once for all subsequent functions.
 | 
			
		||||
	evaluatedHostVolumePath, err := mounter.ne.EvalSymlinks(subpath.VolumePath, true /*mustExist*/)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.VolumePath, err)
 | 
			
		||||
	}
 | 
			
		||||
	evaluatedHostSubpath, err := mounter.ne.EvalSymlinks(subpath.Path, true /*mustExist*/)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
	klog.V(5).Infof("doBindSubPath %q (%q) for volumepath %q", subpath.Path, evaluatedHostSubpath, subpath.VolumePath)
 | 
			
		||||
	subpath.VolumePath = mounter.ne.KubeletPath(evaluatedHostVolumePath)
 | 
			
		||||
	subpath.Path = mounter.ne.KubeletPath(evaluatedHostSubpath)
 | 
			
		||||
 | 
			
		||||
	// Check the subpath is correct and open it
 | 
			
		||||
	fd, err := safeOpenSubPath(mounter, subpath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer syscall.Close(fd)
 | 
			
		||||
 | 
			
		||||
	alreadyMounted, bindPathTarget, err := prepareSubpathTarget(mounter, subpath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if alreadyMounted {
 | 
			
		||||
		return bindPathTarget, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	success := false
 | 
			
		||||
	defer func() {
 | 
			
		||||
		// Cleanup subpath on error
 | 
			
		||||
		if !success {
 | 
			
		||||
			klog.V(4).Infof("doNsEnterBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
 | 
			
		||||
			if cleanErr := cleanSubPath(mounter, subpath); cleanErr != nil {
 | 
			
		||||
				klog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// Leap of faith: optimistically expect that nobody has modified previously
 | 
			
		||||
	// expanded evalSubPath with evil symlinks and bind-mount it.
 | 
			
		||||
	// Mount is done on the host! don't use kubelet path!
 | 
			
		||||
	klog.V(5).Infof("bind mounting %q at %q", evaluatedHostSubpath, bindPathTarget)
 | 
			
		||||
	if err = mounter.Mount(evaluatedHostSubpath, bindPathTarget, "" /*fstype*/, []string{"bind"}); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error mounting %s: %s", evaluatedHostSubpath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check that the bind-mount target is the same inode and device as the
 | 
			
		||||
	// source that we keept open, i.e. we mounted the right thing.
 | 
			
		||||
	err = checkDeviceInode(fd, bindPathTarget)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error checking bind mount for subpath %s: %s", subpath.VolumePath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	success = true
 | 
			
		||||
	klog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
 | 
			
		||||
	return bindPathTarget, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkDeviceInode checks that opened file and path represent the same file.
 | 
			
		||||
func checkDeviceInode(fd int, path string) error {
 | 
			
		||||
	var srcStat, dstStat unix.Stat_t
 | 
			
		||||
	err := unix.Fstat(fd, &srcStat)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error running fstat on subpath FD: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = unix.Stat(path, &dstStat)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error running fstat on %s: %v", path, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if srcStat.Dev != dstStat.Dev {
 | 
			
		||||
		return fmt.Errorf("different device number")
 | 
			
		||||
	}
 | 
			
		||||
	if srcStat.Ino != dstStat.Ino {
 | 
			
		||||
		return fmt.Errorf("different inode")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mounter *NsenterMounter) GetFSGroup(pathname string) (int64, error) {
 | 
			
		||||
	hostPath, err := mounter.ne.EvalSymlinks(pathname, true /* mustExist */)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,8 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
	"k8s.io/utils/nsenter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -76,80 +74,6 @@ func TestParseFindMnt(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCheckDeviceInode(t *testing.T) {
 | 
			
		||||
	testDir, err := ioutil.TempDir("", "nsenter-mounter-device-")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Cannot create temporary directory: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.RemoveAll(testDir)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		srcPath     string
 | 
			
		||||
		dstPath     string
 | 
			
		||||
		expectError string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:        "the same file",
 | 
			
		||||
			srcPath:     filepath.Join(testDir, "1"),
 | 
			
		||||
			dstPath:     filepath.Join(testDir, "1"),
 | 
			
		||||
			expectError: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:        "different file on the same FS",
 | 
			
		||||
			srcPath:     filepath.Join(testDir, "2.1"),
 | 
			
		||||
			dstPath:     filepath.Join(testDir, "2.2"),
 | 
			
		||||
			expectError: "different inode",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:    "different file on different device",
 | 
			
		||||
			srcPath: filepath.Join(testDir, "3"),
 | 
			
		||||
			// /proc is always on a different "device" than /tmp (or $TEMP)
 | 
			
		||||
			dstPath:     "/proc/self/status",
 | 
			
		||||
			expectError: "different device",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if err := ioutil.WriteFile(test.srcPath, []byte{}, 0644); err != nil {
 | 
			
		||||
			t.Errorf("Test %q: cannot create srcPath %s: %s", test.name, test.srcPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Don't create dst if it exists
 | 
			
		||||
		if _, err := os.Stat(test.dstPath); os.IsNotExist(err) {
 | 
			
		||||
			if err := ioutil.WriteFile(test.dstPath, []byte{}, 0644); err != nil {
 | 
			
		||||
				t.Errorf("Test %q: cannot create dstPath %s: %s", test.name, test.dstPath, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		} else if err != nil {
 | 
			
		||||
			t.Errorf("Test %q: cannot check existence of dstPath %s: %s", test.name, test.dstPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fd, err := unix.Open(test.srcPath, unix.O_CREAT, 0644)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Test %q: cannot open srcPath %s: %s", test.name, test.srcPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = checkDeviceInode(fd, test.dstPath)
 | 
			
		||||
 | 
			
		||||
		if test.expectError == "" && err != nil {
 | 
			
		||||
			t.Errorf("Test %q: expected no error, got %s", test.name, err)
 | 
			
		||||
		}
 | 
			
		||||
		if test.expectError != "" {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Errorf("Test %q: expected error, got none", test.name)
 | 
			
		||||
			} else {
 | 
			
		||||
				if !strings.Contains(err.Error(), test.expectError) {
 | 
			
		||||
					t.Errorf("Test %q: expected error %q, got %q", test.name, test.expectError, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newFakeNsenterMounter(tmpdir string, t *testing.T) (mounter *NsenterMounter, rootfsPath string, varlibPath string, err error) {
 | 
			
		||||
	rootfsPath = filepath.Join(tmpdir, "rootfs")
 | 
			
		||||
	if err := os.Mkdir(rootfsPath, 0755); err != nil {
 | 
			
		||||
@@ -504,221 +428,3 @@ func writeRootfsFile(rootfs, path string, mode os.FileMode) (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
	return fullPath, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNsenterSafeMakeDir(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		prepare     func(base, rootfs, varlib string) (expectedDir string, err error)
 | 
			
		||||
		subdir      string
 | 
			
		||||
		expectError bool
 | 
			
		||||
		// If true, "base" directory for SafeMakeDir will be /var/lib/kubelet
 | 
			
		||||
		baseIsVarLib bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "simple directory",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// expected to be created in /roots/
 | 
			
		||||
				expectedDir = filepath.Join(rootfs, base, "some/subdirectory/structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "simple existing directory",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: directory exists
 | 
			
		||||
				hostPath := filepath.Join(base, "some/subdirectory/structure")
 | 
			
		||||
				if err := os.MkdirAll(hostPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				// In rootfs: directory exists
 | 
			
		||||
				kubeletPath := filepath.Join(rootfs, hostPath)
 | 
			
		||||
				if err := os.MkdirAll(kubeletPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				// expected to be created in /roots/
 | 
			
		||||
				expectedDir = kubeletPath
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "absolute symlink into safe place",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /base/other/subdirectory exists, /base/some is link to /base/other
 | 
			
		||||
				hostPath := filepath.Join(base, "other/subdirectory")
 | 
			
		||||
				if err := os.MkdirAll(hostPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				somePath := filepath.Join(base, "some")
 | 
			
		||||
				otherPath := filepath.Join(base, "other")
 | 
			
		||||
				if err := os.Symlink(otherPath, somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// In rootfs: /base/other/subdirectory exists
 | 
			
		||||
				kubeletPath := filepath.Join(rootfs, hostPath)
 | 
			
		||||
				if err := os.MkdirAll(kubeletPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				// expected 'structure' to be created
 | 
			
		||||
				expectedDir = filepath.Join(rootfs, hostPath, "structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "relative symlink into safe place",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /base/other/subdirectory exists, /base/some is link to other
 | 
			
		||||
				hostPath := filepath.Join(base, "other/subdirectory")
 | 
			
		||||
				if err := os.MkdirAll(hostPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				somePath := filepath.Join(base, "some")
 | 
			
		||||
				if err := os.Symlink("other", somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// In rootfs: /base/other/subdirectory exists
 | 
			
		||||
				kubeletPath := filepath.Join(rootfs, hostPath)
 | 
			
		||||
				if err := os.MkdirAll(kubeletPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				// expected 'structure' to be created
 | 
			
		||||
				expectedDir = filepath.Join(rootfs, hostPath, "structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "symlink into unsafe place",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /base/some is link to /bin/other
 | 
			
		||||
				somePath := filepath.Join(base, "some")
 | 
			
		||||
				if err := os.Symlink("/bin", somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				return "", nil
 | 
			
		||||
			},
 | 
			
		||||
			expectError: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "simple directory in /var/lib/kubelet",
 | 
			
		||||
			// evaluated in varlib
 | 
			
		||||
			subdir:       "some/subdirectory/structure",
 | 
			
		||||
			baseIsVarLib: true,
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// expected to be created in /base/var/lib/kubelet, not in /rootfs!
 | 
			
		||||
				expectedDir = filepath.Join(varlib, "some/subdirectory/structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "safe symlink in /var/lib/kubelet",
 | 
			
		||||
			// evaluated in varlib
 | 
			
		||||
			subdir:       "some/subdirectory/structure",
 | 
			
		||||
			baseIsVarLib: true,
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /varlib/kubelet/other/subdirectory exists, /varlib/some is link to other
 | 
			
		||||
				hostPath := filepath.Join(varlib, "other/subdirectory")
 | 
			
		||||
				if err := os.MkdirAll(hostPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				somePath := filepath.Join(varlib, "some")
 | 
			
		||||
				if err := os.Symlink("other", somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// expected to be created in /base/var/lib/kubelet, not in /rootfs!
 | 
			
		||||
				expectedDir = filepath.Join(varlib, "other/subdirectory/structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "unsafe symlink in /var/lib/kubelet",
 | 
			
		||||
			// evaluated in varlib
 | 
			
		||||
			subdir:       "some/subdirectory/structure",
 | 
			
		||||
			baseIsVarLib: true,
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /varlib/some is link to /bin
 | 
			
		||||
				somePath := filepath.Join(varlib, "some")
 | 
			
		||||
				if err := os.Symlink("/bin", somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return "", nil
 | 
			
		||||
			},
 | 
			
		||||
			expectError: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		tmpdir, err := ioutil.TempDir("", "nsenter-get-mode-")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		defer os.RemoveAll(tmpdir)
 | 
			
		||||
 | 
			
		||||
		mounter, rootfs, varlib, err := newFakeNsenterMounter(tmpdir, t)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// Prepare base directory for the test
 | 
			
		||||
		testBase := filepath.Join(tmpdir, "base")
 | 
			
		||||
		if err := os.Mkdir(testBase, 0755); err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// Prepare base directory also in /rootfs
 | 
			
		||||
		rootfsBase := filepath.Join(rootfs, testBase)
 | 
			
		||||
		if err := os.MkdirAll(rootfsBase, 0755); err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		expectedDir := ""
 | 
			
		||||
		if test.prepare != nil {
 | 
			
		||||
			expectedDir, err = test.prepare(testBase, rootfs, varlib)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Error(err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if test.baseIsVarLib {
 | 
			
		||||
			// use /var/lib/kubelet as the test base so we can test creating
 | 
			
		||||
			// subdirs there directly in /var/lib/kubenet and not in
 | 
			
		||||
			// /rootfs/var/lib/kubelet
 | 
			
		||||
			testBase = varlib
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = mounter.SafeMakeDir(test.subdir, testBase, 0755)
 | 
			
		||||
		if err != nil && !test.expectError {
 | 
			
		||||
			t.Errorf("Test %q: unexpected error: %s", test.name, err)
 | 
			
		||||
		}
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Errorf("Test %q: expected error, got none", test.name)
 | 
			
		||||
			} else {
 | 
			
		||||
				if !strings.Contains(err.Error(), "is outside of allowed base") {
 | 
			
		||||
					t.Errorf("Test %q: expected error to contain \"is outside of allowed base\", got this one instead: %s", test.name, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if expectedDir != "" {
 | 
			
		||||
			_, err := os.Stat(expectedDir)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Errorf("Test %q: expected %q to exist, got error: %s", test.name, expectedDir, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -93,18 +93,6 @@ func (*NsenterMounter) EvalHostSymlinks(pathname string) (string, error) {
 | 
			
		||||
	return "", errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*NsenterMounter) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*NsenterMounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	return subPath.Path, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*NsenterMounter) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*NsenterMounter) GetMountRefs(pathname string) ([]string, error) {
 | 
			
		||||
	return nil, errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ go_library(
 | 
			
		||||
        "//pkg/util/mount:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/fs:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/recyclerclient:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/authentication/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ import (
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/recyclerclient"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ProbeOperation uint32
 | 
			
		||||
@@ -367,6 +368,9 @@ type VolumeHost interface {
 | 
			
		||||
 | 
			
		||||
	// Returns the event recorder of kubelet.
 | 
			
		||||
	GetEventRecorder() record.EventRecorder
 | 
			
		||||
 | 
			
		||||
	// Returns an interface that should be used to execute subpath operations
 | 
			
		||||
	GetSubpather() subpath.Interface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VolumePluginMgr tracks registered plugins.
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ go_library(
 | 
			
		||||
        "//pkg/volume:go_default_library",
 | 
			
		||||
        "//pkg/volume/util:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/recyclerclient:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/subpath:go_default_library",
 | 
			
		||||
        "//pkg/volume/util/volumepathhandler:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/authentication/v1:go_default_library",
 | 
			
		||||
        "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,7 @@ import (
 | 
			
		||||
	. "k8s.io/kubernetes/pkg/volume"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/recyclerclient"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/subpath"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
 | 
			
		||||
	utilstrings "k8s.io/utils/strings"
 | 
			
		||||
)
 | 
			
		||||
@@ -71,6 +72,7 @@ type fakeVolumeHost struct {
 | 
			
		||||
	exec       mount.Exec
 | 
			
		||||
	nodeLabels map[string]string
 | 
			
		||||
	nodeName   string
 | 
			
		||||
	subpather  subpath.Interface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin) *fakeVolumeHost {
 | 
			
		||||
@@ -101,6 +103,7 @@ func newFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins [
 | 
			
		||||
	}
 | 
			
		||||
	host.exec = mount.NewFakeExec(nil)
 | 
			
		||||
	host.pluginMgr.InitPlugins(plugins, nil /* prober */, host)
 | 
			
		||||
	host.subpather = &subpath.FakeSubpath{}
 | 
			
		||||
	return host
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -149,6 +152,10 @@ func (f *fakeVolumeHost) GetMounter(pluginName string) mount.Interface {
 | 
			
		||||
	return f.mounter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fakeVolumeHost) GetSubpather() subpath.Interface {
 | 
			
		||||
	return f.subpather
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fakeVolumeHost) NewWrapperMounter(volName string, spec Spec, pod *v1.Pod, opts VolumeOptions) (Mounter, error) {
 | 
			
		||||
	// The name of wrapper volume is set to "wrapped_{wrapped_volume_name}"
 | 
			
		||||
	wrapperVolumeName := "wrapped_" + volName
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,7 @@ filegroup(
 | 
			
		||||
        "//pkg/volume/util/nestedpendingoperations:all-srcs",
 | 
			
		||||
        "//pkg/volume/util/operationexecutor:all-srcs",
 | 
			
		||||
        "//pkg/volume/util/recyclerclient:all-srcs",
 | 
			
		||||
        "//pkg/volume/util/subpath:all-srcs",
 | 
			
		||||
        "//pkg/volume/util/types:all-srcs",
 | 
			
		||||
        "//pkg/volume/util/volumepathhandler:all-srcs",
 | 
			
		||||
    ],
 | 
			
		||||
 
 | 
			
		||||
@@ -750,11 +750,11 @@ func (og *operationGenerator) GenerateUnmountVolumeFunc(
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unmountVolumeFunc := func() (error, error) {
 | 
			
		||||
		mounter := og.volumePluginMgr.Host.GetMounter(volumeToUnmount.PluginName)
 | 
			
		||||
		subpather := og.volumePluginMgr.Host.GetSubpather()
 | 
			
		||||
 | 
			
		||||
		// Remove all bind-mounts for subPaths
 | 
			
		||||
		podDir := path.Join(podsDir, string(volumeToUnmount.PodUID))
 | 
			
		||||
		if err := mounter.CleanSubPaths(podDir, volumeToUnmount.InnerVolumeSpecName); err != nil {
 | 
			
		||||
		if err := subpather.CleanSubPaths(podDir, volumeToUnmount.InnerVolumeSpecName); err != nil {
 | 
			
		||||
			return volumeToUnmount.GenerateError("error cleaning subPath mounts", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								pkg/volume/util/subpath/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								pkg/volume/util/subpath/BUILD
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 | 
			
		||||
 | 
			
		||||
go_library(
 | 
			
		||||
    name = "go_default_library",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "subpath.go",
 | 
			
		||||
        "subpath_linux.go",
 | 
			
		||||
        "subpath_nsenter.go",
 | 
			
		||||
        "subpath_unsupported.go",
 | 
			
		||||
        "subpath_windows.go",
 | 
			
		||||
    ],
 | 
			
		||||
    importpath = "k8s.io/kubernetes/pkg/volume/util/subpath",
 | 
			
		||||
    visibility = ["//visibility:public"],
 | 
			
		||||
    deps = select({
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:android": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:darwin": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:dragonfly": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:freebsd": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:linux": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/golang.org/x/sys/unix:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/klog:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:nacl": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:netbsd": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:openbsd": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:plan9": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:solaris": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:windows": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/klog:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "//conditions:default": [],
 | 
			
		||||
    }),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
go_test(
 | 
			
		||||
    name = "go_default_test",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "subpath_linux_test.go",
 | 
			
		||||
        "subpath_nsenter_test.go",
 | 
			
		||||
        "subpath_windows_test.go",
 | 
			
		||||
    ],
 | 
			
		||||
    embed = [":go_default_library"],
 | 
			
		||||
    deps = select({
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:linux": [
 | 
			
		||||
            "//pkg/util/mount:go_default_library",
 | 
			
		||||
            "//vendor/golang.org/x/sys/unix:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/klog:go_default_library",
 | 
			
		||||
            "//vendor/k8s.io/utils/nsenter:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "@io_bazel_rules_go//go/platform:windows": [
 | 
			
		||||
            "//vendor/github.com/stretchr/testify/assert:go_default_library",
 | 
			
		||||
        ],
 | 
			
		||||
        "//conditions:default": [],
 | 
			
		||||
    }),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
filegroup(
 | 
			
		||||
    name = "package-srcs",
 | 
			
		||||
    srcs = glob(["**"]),
 | 
			
		||||
    tags = ["automanaged"],
 | 
			
		||||
    visibility = ["//visibility:private"],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
filegroup(
 | 
			
		||||
    name = "all-srcs",
 | 
			
		||||
    srcs = [":package-srcs"],
 | 
			
		||||
    tags = ["automanaged"],
 | 
			
		||||
    visibility = ["//visibility:public"],
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										13
									
								
								pkg/volume/util/subpath/OWNERS
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								pkg/volume/util/subpath/OWNERS
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
# See the OWNERS docs at https://go.k8s.io/owners
 | 
			
		||||
 | 
			
		||||
reviewers:
 | 
			
		||||
  - jingxu97
 | 
			
		||||
  - saad-ali
 | 
			
		||||
  - jsafrane
 | 
			
		||||
  - msau42
 | 
			
		||||
  - andyzhangx
 | 
			
		||||
approvers:
 | 
			
		||||
  - jingxu97
 | 
			
		||||
  - saad-ali
 | 
			
		||||
  - jsafrane
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										92
									
								
								pkg/volume/util/subpath/subpath.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								pkg/volume/util/subpath/subpath.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2019 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package subpath
 | 
			
		||||
 | 
			
		||||
import "os"
 | 
			
		||||
 | 
			
		||||
// Interface defines the set of methods all subpathers must implement
 | 
			
		||||
type Interface interface {
 | 
			
		||||
	// CleanSubPaths removes any bind-mounts created by PrepareSafeSubpath in given
 | 
			
		||||
	// pod volume directory.
 | 
			
		||||
	CleanSubPaths(poodDir string, volumeName string) error
 | 
			
		||||
 | 
			
		||||
	// PrepareSafeSubpath does everything that's necessary to prepare a subPath
 | 
			
		||||
	// that's 1) inside given volumePath and 2) immutable after this call.
 | 
			
		||||
	//
 | 
			
		||||
	// newHostPath - location of prepared subPath. It should be used instead of
 | 
			
		||||
	// hostName when running the container.
 | 
			
		||||
	// cleanupAction - action to run when the container is running or it failed to start.
 | 
			
		||||
	//
 | 
			
		||||
	// CleanupAction must be called immediately after the container with given
 | 
			
		||||
	// subpath starts. On the other hand, Interface.CleanSubPaths must be called
 | 
			
		||||
	// when the pod finishes.
 | 
			
		||||
	PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error)
 | 
			
		||||
 | 
			
		||||
	// SafeMakeDir creates subdir within given base. It makes sure that the
 | 
			
		||||
	// created directory does not escape given base directory mis-using
 | 
			
		||||
	// symlinks. Note that the function makes sure that it creates the directory
 | 
			
		||||
	// somewhere under the base, nothing else. E.g. if the directory already
 | 
			
		||||
	// exists, it may exist outside of the base due to symlinks.
 | 
			
		||||
	// This method should be used if the directory to create is inside volume
 | 
			
		||||
	// that's under user control. User must not be able to use symlinks to
 | 
			
		||||
	// escape the volume to create directories somewhere else.
 | 
			
		||||
	SafeMakeDir(subdir string, base string, perm os.FileMode) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Subpath defines the attributes of a subpath
 | 
			
		||||
type Subpath struct {
 | 
			
		||||
	// index of the VolumeMount for this container
 | 
			
		||||
	VolumeMountIndex int
 | 
			
		||||
 | 
			
		||||
	// Full path to the subpath directory on the host
 | 
			
		||||
	Path string
 | 
			
		||||
 | 
			
		||||
	// name of the volume that is a valid directory name.
 | 
			
		||||
	VolumeName string
 | 
			
		||||
 | 
			
		||||
	// Full path to the volume path
 | 
			
		||||
	VolumePath string
 | 
			
		||||
 | 
			
		||||
	// Path to the pod's directory, including pod UID
 | 
			
		||||
	PodDir string
 | 
			
		||||
 | 
			
		||||
	// Name of the container
 | 
			
		||||
	ContainerName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Compile time-check for all implementers of subpath interface
 | 
			
		||||
var _ Interface = &subpath{}
 | 
			
		||||
var _ Interface = &FakeSubpath{}
 | 
			
		||||
 | 
			
		||||
// FakeSubpath is a subpather implementation for testing
 | 
			
		||||
type FakeSubpath struct{}
 | 
			
		||||
 | 
			
		||||
// PrepareSafeSubpath is a fake implementation of PrepareSafeSubpath. Always returns
 | 
			
		||||
// newHostPath == subPath.Path
 | 
			
		||||
func (fs *FakeSubpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	return subPath.Path, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CleanSubPaths is a fake implementation of CleanSubPaths. It is a noop
 | 
			
		||||
func (fs *FakeSubpath) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SafeMakeDir is a fake implementation of SafeMakeDir. It is a noop
 | 
			
		||||
func (fs *FakeSubpath) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										563
									
								
								pkg/volume/util/subpath/subpath_linux.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										563
									
								
								pkg/volume/util/subpath/subpath_linux.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,563 @@
 | 
			
		||||
// +build linux
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2014 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package subpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// place for subpath mounts
 | 
			
		||||
	// TODO: pass in directory using kubelet_getters instead
 | 
			
		||||
	containerSubPathDirectoryName = "volume-subpaths"
 | 
			
		||||
	// syscall.Openat flags used to traverse directories not following symlinks
 | 
			
		||||
	nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
 | 
			
		||||
	// flags for getting file descriptor without following the symlink
 | 
			
		||||
	openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type subpath struct {
 | 
			
		||||
	mounter mount.Interface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New returns a subpath.Interface for the current system
 | 
			
		||||
func New(mounter mount.Interface) Interface {
 | 
			
		||||
	return &subpath{
 | 
			
		||||
		mounter: mounter,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return doCleanSubPaths(sp.mounter, podDir, volumeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
 | 
			
		||||
	realBase, err := filepath.EvalSymlinks(base)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	realFullPath := filepath.Join(realBase, subdir)
 | 
			
		||||
 | 
			
		||||
	return doSafeMakeDir(realFullPath, realBase, perm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	newHostPath, err = doBindSubPath(sp.mounter, subPath)
 | 
			
		||||
 | 
			
		||||
	// There is no action when the container starts. Bind-mount will be cleaned
 | 
			
		||||
	// when container stops by CleanSubPaths.
 | 
			
		||||
	cleanupAction = nil
 | 
			
		||||
	return newHostPath, cleanupAction, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This implementation is shared between Linux and NsEnter
 | 
			
		||||
func safeOpenSubPath(mounter mount.Interface, subpath Subpath) (int, error) {
 | 
			
		||||
	if !mount.PathWithinBase(subpath.Path, subpath.VolumePath) {
 | 
			
		||||
		return -1, fmt.Errorf("subpath %q not within volume path %q", subpath.Path, subpath.VolumePath)
 | 
			
		||||
	}
 | 
			
		||||
	fd, err := doSafeOpen(subpath.Path, subpath.VolumePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, fmt.Errorf("error opening subpath %v: %v", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
	return fd, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// prepareSubpathTarget creates target for bind-mount of subpath. It returns
 | 
			
		||||
// "true" when the target already exists and something is mounted there.
 | 
			
		||||
// Given Subpath must have all paths with already resolved symlinks and with
 | 
			
		||||
// paths relevant to kubelet (when it runs in a container).
 | 
			
		||||
// This function is called also by NsEnterMounter. It works because
 | 
			
		||||
// /var/lib/kubelet is mounted from the host into the container with Kubelet as
 | 
			
		||||
// /var/lib/kubelet too.
 | 
			
		||||
func prepareSubpathTarget(mounter mount.Interface, subpath Subpath) (bool, string, error) {
 | 
			
		||||
	// Early check for already bind-mounted subpath.
 | 
			
		||||
	bindPathTarget := getSubpathBindTarget(subpath)
 | 
			
		||||
	notMount, err := mounter.IsNotMountPoint(bindPathTarget)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return false, "", fmt.Errorf("error checking path %s for mount: %s", bindPathTarget, err)
 | 
			
		||||
		}
 | 
			
		||||
		// Ignore ErrorNotExist: the file/directory will be created below if it does not exist yet.
 | 
			
		||||
		notMount = true
 | 
			
		||||
	}
 | 
			
		||||
	if !notMount {
 | 
			
		||||
		// It's already mounted
 | 
			
		||||
		klog.V(5).Infof("Skipping bind-mounting subpath %s: already mounted", bindPathTarget)
 | 
			
		||||
		return true, bindPathTarget, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// bindPathTarget is in /var/lib/kubelet and thus reachable without any
 | 
			
		||||
	// translation even to containerized kubelet.
 | 
			
		||||
	bindParent := filepath.Dir(bindPathTarget)
 | 
			
		||||
	err = os.MkdirAll(bindParent, 0750)
 | 
			
		||||
	if err != nil && !os.IsExist(err) {
 | 
			
		||||
		return false, "", fmt.Errorf("error creating directory %s: %s", bindParent, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := os.Lstat(subpath.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, "", fmt.Errorf("lstat %s failed: %s", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if t.Mode()&os.ModeDir > 0 {
 | 
			
		||||
		if err = os.Mkdir(bindPathTarget, 0750); err != nil && !os.IsExist(err) {
 | 
			
		||||
			return false, "", fmt.Errorf("error creating directory %s: %s", bindPathTarget, err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// "/bin/touch <bindPathTarget>".
 | 
			
		||||
		// A file is enough for all possible targets (symlink, device, pipe,
 | 
			
		||||
		// socket, ...), bind-mounting them into a file correctly changes type
 | 
			
		||||
		// of the target file.
 | 
			
		||||
		if err = ioutil.WriteFile(bindPathTarget, []byte{}, 0640); err != nil {
 | 
			
		||||
			return false, "", fmt.Errorf("error creating file %s: %s", bindPathTarget, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, bindPathTarget, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSubpathBindTarget(subpath Subpath) string {
 | 
			
		||||
	// containerName is DNS label, i.e. safe as a directory name.
 | 
			
		||||
	return filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName, strconv.Itoa(subpath.VolumeMountIndex))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doBindSubPath(mounter mount.Interface, subpath Subpath) (hostPath string, err error) {
 | 
			
		||||
	// Linux, kubelet runs on the host:
 | 
			
		||||
	// - safely open the subpath
 | 
			
		||||
	// - bind-mount /proc/<pid of kubelet>/fd/<fd> to subpath target
 | 
			
		||||
	// User can't change /proc/<pid of kubelet>/fd/<fd> to point to a bad place.
 | 
			
		||||
 | 
			
		||||
	// Evaluate all symlinks here once for all subsequent functions.
 | 
			
		||||
	newVolumePath, err := filepath.EvalSymlinks(subpath.VolumePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.VolumePath, err)
 | 
			
		||||
	}
 | 
			
		||||
	newPath, err := filepath.EvalSymlinks(subpath.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
	klog.V(5).Infof("doBindSubPath %q (%q) for volumepath %q", subpath.Path, newPath, subpath.VolumePath)
 | 
			
		||||
	subpath.VolumePath = newVolumePath
 | 
			
		||||
	subpath.Path = newPath
 | 
			
		||||
 | 
			
		||||
	fd, err := safeOpenSubPath(mounter, subpath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer syscall.Close(fd)
 | 
			
		||||
 | 
			
		||||
	alreadyMounted, bindPathTarget, err := prepareSubpathTarget(mounter, subpath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if alreadyMounted {
 | 
			
		||||
		return bindPathTarget, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	success := false
 | 
			
		||||
	defer func() {
 | 
			
		||||
		// Cleanup subpath on error
 | 
			
		||||
		if !success {
 | 
			
		||||
			klog.V(4).Infof("doBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
 | 
			
		||||
			if cleanErr := cleanSubPath(mounter, subpath); cleanErr != nil {
 | 
			
		||||
				klog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	kubeletPid := os.Getpid()
 | 
			
		||||
	mountSource := fmt.Sprintf("/proc/%d/fd/%v", kubeletPid, fd)
 | 
			
		||||
 | 
			
		||||
	// Do the bind mount
 | 
			
		||||
	options := []string{"bind"}
 | 
			
		||||
	klog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget)
 | 
			
		||||
	if err = mounter.Mount(mountSource, bindPathTarget, "" /*fstype*/, options); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
	success = true
 | 
			
		||||
 | 
			
		||||
	klog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
 | 
			
		||||
	return bindPathTarget, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This implementation is shared between Linux and NsEnter
 | 
			
		||||
func doCleanSubPaths(mounter mount.Interface, podDir string, volumeName string) error {
 | 
			
		||||
	// scan /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/*
 | 
			
		||||
	subPathDir := filepath.Join(podDir, containerSubPathDirectoryName, volumeName)
 | 
			
		||||
	klog.V(4).Infof("Cleaning up subpath mounts for %s", subPathDir)
 | 
			
		||||
 | 
			
		||||
	containerDirs, err := ioutil.ReadDir(subPathDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Errorf("error reading %s: %s", subPathDir, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, containerDir := range containerDirs {
 | 
			
		||||
		if !containerDir.IsDir() {
 | 
			
		||||
			klog.V(4).Infof("Container file is not a directory: %s", containerDir.Name())
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		klog.V(4).Infof("Cleaning up subpath mounts for container %s", containerDir.Name())
 | 
			
		||||
 | 
			
		||||
		// scan /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/<container name>/*
 | 
			
		||||
		fullContainerDirPath := filepath.Join(subPathDir, containerDir.Name())
 | 
			
		||||
		err = filepath.Walk(fullContainerDirPath, func(path string, info os.FileInfo, err error) error {
 | 
			
		||||
			if path == fullContainerDirPath {
 | 
			
		||||
				// Skip top level directory
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// pass through errors and let doCleanSubPath handle them
 | 
			
		||||
			if err = doCleanSubPath(mounter, fullContainerDirPath, filepath.Base(path)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("error processing %s: %s", fullContainerDirPath, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Whole container has been processed, remove its directory.
 | 
			
		||||
		if err := os.Remove(fullContainerDirPath); err != nil {
 | 
			
		||||
			return fmt.Errorf("error deleting %s: %s", fullContainerDirPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		klog.V(5).Infof("Removed %s", fullContainerDirPath)
 | 
			
		||||
	}
 | 
			
		||||
	// Whole pod volume subpaths have been cleaned up, remove its subpath directory.
 | 
			
		||||
	if err := os.Remove(subPathDir); err != nil {
 | 
			
		||||
		return fmt.Errorf("error deleting %s: %s", subPathDir, err)
 | 
			
		||||
	}
 | 
			
		||||
	klog.V(5).Infof("Removed %s", subPathDir)
 | 
			
		||||
 | 
			
		||||
	// Remove entire subpath directory if it's the last one
 | 
			
		||||
	podSubPathDir := filepath.Join(podDir, containerSubPathDirectoryName)
 | 
			
		||||
	if err := os.Remove(podSubPathDir); err != nil && !os.IsExist(err) {
 | 
			
		||||
		return fmt.Errorf("error deleting %s: %s", podSubPathDir, err)
 | 
			
		||||
	}
 | 
			
		||||
	klog.V(5).Infof("Removed %s", podSubPathDir)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// doCleanSubPath tears down the single subpath bind mount
 | 
			
		||||
func doCleanSubPath(mounter mount.Interface, fullContainerDirPath, subPathIndex string) error {
 | 
			
		||||
	// process /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/<container name>/<subPathName>
 | 
			
		||||
	klog.V(4).Infof("Cleaning up subpath mounts for subpath %v", subPathIndex)
 | 
			
		||||
	fullSubPath := filepath.Join(fullContainerDirPath, subPathIndex)
 | 
			
		||||
 | 
			
		||||
	if err := mount.CleanupMountPoint(fullSubPath, mounter, true); err != nil {
 | 
			
		||||
		return fmt.Errorf("error cleaning subpath mount %s: %s", fullSubPath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	klog.V(4).Infof("Successfully cleaned subpath directory %s", fullSubPath)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// cleanSubPath will teardown the subpath bind mount and any remove any directories if empty
 | 
			
		||||
func cleanSubPath(mounter mount.Interface, subpath Subpath) error {
 | 
			
		||||
	containerDir := filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName)
 | 
			
		||||
 | 
			
		||||
	// Clean subdir bindmount
 | 
			
		||||
	if err := doCleanSubPath(mounter, containerDir, strconv.Itoa(subpath.VolumeMountIndex)); err != nil && !os.IsNotExist(err) {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Recusively remove directories if empty
 | 
			
		||||
	if err := removeEmptyDirs(subpath.PodDir, containerDir); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// removeEmptyDirs works backwards from endDir to baseDir and removes each directory
 | 
			
		||||
// if it is empty.  It stops once it encounters a directory that has content
 | 
			
		||||
func removeEmptyDirs(baseDir, endDir string) error {
 | 
			
		||||
	if !mount.PathWithinBase(endDir, baseDir) {
 | 
			
		||||
		return fmt.Errorf("endDir %q is not within baseDir %q", endDir, baseDir)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for curDir := endDir; curDir != baseDir; curDir = filepath.Dir(curDir) {
 | 
			
		||||
		s, err := os.Stat(curDir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				klog.V(5).Infof("curDir %q doesn't exist, skipping", curDir)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("error stat %q: %v", curDir, err)
 | 
			
		||||
		}
 | 
			
		||||
		if !s.IsDir() {
 | 
			
		||||
			return fmt.Errorf("path %q not a directory", curDir)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = os.Remove(curDir)
 | 
			
		||||
		if os.IsExist(err) {
 | 
			
		||||
			klog.V(5).Infof("Directory %q not empty, not removing", curDir)
 | 
			
		||||
			break
 | 
			
		||||
		} else if err != nil {
 | 
			
		||||
			return fmt.Errorf("error removing directory %q: %v", curDir, err)
 | 
			
		||||
		}
 | 
			
		||||
		klog.V(5).Infof("Removed directory %q", curDir)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This implementation is shared between Linux and NsEnterMounter. Both pathname
 | 
			
		||||
// and base must be either already resolved symlinks or thet will be resolved in
 | 
			
		||||
// kubelet's mount namespace (in case it runs containerized).
 | 
			
		||||
func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
 | 
			
		||||
 | 
			
		||||
	if !mount.PathWithinBase(pathname, base) {
 | 
			
		||||
		return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Quick check if the directory already exists
 | 
			
		||||
	s, err := os.Stat(pathname)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// Path exists
 | 
			
		||||
		if s.IsDir() {
 | 
			
		||||
			// The directory already exists. It can be outside of the parent,
 | 
			
		||||
			// but there is no race-proof check.
 | 
			
		||||
			klog.V(4).Infof("Directory %s already exists", pathname)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Find all existing directories
 | 
			
		||||
	existingPath, toCreate, err := findExistingPrefix(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error opening directory %s: %s", pathname, err)
 | 
			
		||||
	}
 | 
			
		||||
	// Ensure the existing directory is inside allowed base
 | 
			
		||||
	fullExistingPath, err := filepath.EvalSymlinks(existingPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error opening directory %s: %s", existingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	if !mount.PathWithinBase(fullExistingPath, base) {
 | 
			
		||||
		return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
 | 
			
		||||
	parentFD, err := doSafeOpen(fullExistingPath, base)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("cannot open directory %s: %s", existingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	childFD := -1
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if parentFD != -1 {
 | 
			
		||||
			if err = syscall.Close(parentFD); err != nil {
 | 
			
		||||
				klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if childFD != -1 {
 | 
			
		||||
			if err = syscall.Close(childFD); err != nil {
 | 
			
		||||
				klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", childFD, pathname, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	currentPath := fullExistingPath
 | 
			
		||||
	// create the directories one by one, making sure nobody can change
 | 
			
		||||
	// created directory into symlink.
 | 
			
		||||
	for _, dir := range toCreate {
 | 
			
		||||
		currentPath = filepath.Join(currentPath, dir)
 | 
			
		||||
		klog.V(4).Infof("Creating %s", dir)
 | 
			
		||||
		err = syscall.Mkdirat(parentFD, currentPath, uint32(perm))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		// Dive into the created directory
 | 
			
		||||
		childFD, err := syscall.Openat(parentFD, dir, nofollowFlags, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("cannot open %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		// We can be sure that childFD is safe to use. It could be changed
 | 
			
		||||
		// by user after Mkdirat() and before Openat(), however:
 | 
			
		||||
		// - it could not be changed to symlink - we use nofollowFlags
 | 
			
		||||
		// - it could be changed to a file (or device, pipe, socket, ...)
 | 
			
		||||
		//   but either subsequent Mkdirat() fails or we mount this file
 | 
			
		||||
		//   to user's container. Security is no violated in both cases
 | 
			
		||||
		//   and user either gets error or the file that it can already access.
 | 
			
		||||
 | 
			
		||||
		if err = syscall.Close(parentFD); err != nil {
 | 
			
		||||
			klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
 | 
			
		||||
		}
 | 
			
		||||
		parentFD = childFD
 | 
			
		||||
		childFD = -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Everything was created. mkdirat(..., perm) above was affected by current
 | 
			
		||||
	// umask and we must apply the right permissions to the last directory
 | 
			
		||||
	// (that's the one that will be available to the container as subpath)
 | 
			
		||||
	// so user can read/write it. This is the behavior of previous code.
 | 
			
		||||
	// TODO: chmod all created directories, not just the last one.
 | 
			
		||||
	// parentFD is the last created directory.
 | 
			
		||||
 | 
			
		||||
	// Translate perm (os.FileMode) to uint32 that fchmod() expects
 | 
			
		||||
	kernelPerm := uint32(perm & os.ModePerm)
 | 
			
		||||
	if perm&os.ModeSetgid > 0 {
 | 
			
		||||
		kernelPerm |= syscall.S_ISGID
 | 
			
		||||
	}
 | 
			
		||||
	if perm&os.ModeSetuid > 0 {
 | 
			
		||||
		kernelPerm |= syscall.S_ISUID
 | 
			
		||||
	}
 | 
			
		||||
	if perm&os.ModeSticky > 0 {
 | 
			
		||||
		kernelPerm |= syscall.S_ISVTX
 | 
			
		||||
	}
 | 
			
		||||
	if err = syscall.Fchmod(parentFD, kernelPerm); err != nil {
 | 
			
		||||
		return fmt.Errorf("chmod %q failed: %s", currentPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// findExistingPrefix finds prefix of pathname that exists. In addition, it
 | 
			
		||||
// returns list of remaining directories that don't exist yet.
 | 
			
		||||
func findExistingPrefix(base, pathname string) (string, []string, error) {
 | 
			
		||||
	rel, err := filepath.Rel(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return base, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	dirs := strings.Split(rel, string(filepath.Separator))
 | 
			
		||||
 | 
			
		||||
	// Do OpenAt in a loop to find the first non-existing dir. Resolve symlinks.
 | 
			
		||||
	// This should be faster than looping through all dirs and calling os.Stat()
 | 
			
		||||
	// on each of them, as the symlinks are resolved only once with OpenAt().
 | 
			
		||||
	currentPath := base
 | 
			
		||||
	fd, err := syscall.Open(currentPath, syscall.O_RDONLY, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return pathname, nil, fmt.Errorf("error opening %s: %s", currentPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err = syscall.Close(fd); err != nil {
 | 
			
		||||
			klog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	for i, dir := range dirs {
 | 
			
		||||
		// Using O_PATH here will prevent hangs in case user replaces directory with
 | 
			
		||||
		// fifo
 | 
			
		||||
		childFD, err := syscall.Openat(fd, dir, unix.O_PATH, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				return currentPath, dirs[i:], nil
 | 
			
		||||
			}
 | 
			
		||||
			return base, nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if err = syscall.Close(fd); err != nil {
 | 
			
		||||
			klog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
 | 
			
		||||
		}
 | 
			
		||||
		fd = childFD
 | 
			
		||||
		currentPath = filepath.Join(currentPath, dir)
 | 
			
		||||
	}
 | 
			
		||||
	return pathname, []string{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This implementation is shared between Linux and NsEnterMounter
 | 
			
		||||
// Open path and return its fd.
 | 
			
		||||
// Symlinks are disallowed (pathname must already resolve symlinks),
 | 
			
		||||
// and the path must be within the base directory.
 | 
			
		||||
func doSafeOpen(pathname string, base string) (int, error) {
 | 
			
		||||
	pathname = filepath.Clean(pathname)
 | 
			
		||||
	base = filepath.Clean(base)
 | 
			
		||||
 | 
			
		||||
	// Calculate segments to follow
 | 
			
		||||
	subpath, err := filepath.Rel(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
	segments := strings.Split(subpath, string(filepath.Separator))
 | 
			
		||||
 | 
			
		||||
	// Assumption: base is the only directory that we have under control.
 | 
			
		||||
	// Base dir is not allowed to be a symlink.
 | 
			
		||||
	parentFD, err := syscall.Open(base, nofollowFlags, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, fmt.Errorf("cannot open directory %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if parentFD != -1 {
 | 
			
		||||
			if err = syscall.Close(parentFD); err != nil {
 | 
			
		||||
				klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	childFD := -1
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if childFD != -1 {
 | 
			
		||||
			if err = syscall.Close(childFD); err != nil {
 | 
			
		||||
				klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	currentPath := base
 | 
			
		||||
 | 
			
		||||
	// Follow the segments one by one using openat() to make
 | 
			
		||||
	// sure the user cannot change already existing directories into symlinks.
 | 
			
		||||
	for _, seg := range segments {
 | 
			
		||||
		currentPath = filepath.Join(currentPath, seg)
 | 
			
		||||
		if !mount.PathWithinBase(currentPath, base) {
 | 
			
		||||
			return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		klog.V(5).Infof("Opening path %s", currentPath)
 | 
			
		||||
		childFD, err = syscall.Openat(parentFD, seg, openFDFlags, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return -1, fmt.Errorf("cannot open %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var deviceStat unix.Stat_t
 | 
			
		||||
		err := unix.Fstat(childFD, &deviceStat)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return -1, fmt.Errorf("Error running fstat on %s with %v", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		fileFmt := deviceStat.Mode & syscall.S_IFMT
 | 
			
		||||
		if fileFmt == syscall.S_IFLNK {
 | 
			
		||||
			return -1, fmt.Errorf("Unexpected symlink found %s", currentPath)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Close parentFD
 | 
			
		||||
		if err = syscall.Close(parentFD); err != nil {
 | 
			
		||||
			return -1, fmt.Errorf("closing fd for %q failed: %v", filepath.Dir(currentPath), err)
 | 
			
		||||
		}
 | 
			
		||||
		// Set child to new parent
 | 
			
		||||
		parentFD = childFD
 | 
			
		||||
		childFD = -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We made it to the end, return this fd, don't close it
 | 
			
		||||
	finalFD := parentFD
 | 
			
		||||
	parentFD = -1
 | 
			
		||||
 | 
			
		||||
	return finalFD, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1224
									
								
								pkg/volume/util/subpath/subpath_linux_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1224
									
								
								pkg/volume/util/subpath/subpath_linux_test.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										186
									
								
								pkg/volume/util/subpath/subpath_nsenter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								pkg/volume/util/subpath/subpath_nsenter.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
			
		||||
// +build linux
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2014 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package subpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
	"k8s.io/utils/nsenter"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type subpathNSE struct {
 | 
			
		||||
	mounter mount.Interface
 | 
			
		||||
	ne      *nsenter.Nsenter
 | 
			
		||||
	rootDir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Compile time-check for all implementers of subpath interface
 | 
			
		||||
var _ Interface = &subpathNSE{}
 | 
			
		||||
 | 
			
		||||
// NewNSEnter returns a subpath.Interface that is to be used with the NsenterMounter
 | 
			
		||||
// It is only valid on Linux systems
 | 
			
		||||
func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) Interface {
 | 
			
		||||
	return &subpathNSE{
 | 
			
		||||
		mounter: mounter,
 | 
			
		||||
		ne:      ne,
 | 
			
		||||
		rootDir: rootDir,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpathNSE) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return doCleanSubPaths(sp.mounter, podDir, volumeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpathNSE) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	// Bind-mount the subpath to avoid using symlinks in subpaths.
 | 
			
		||||
	newHostPath, err = sp.doNsEnterBindSubPath(subPath)
 | 
			
		||||
 | 
			
		||||
	// There is no action when the container starts. Bind-mount will be cleaned
 | 
			
		||||
	// when container stops by CleanSubPaths.
 | 
			
		||||
	cleanupAction = nil
 | 
			
		||||
	return newHostPath, cleanupAction, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpathNSE) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
 | 
			
		||||
	fullSubdirPath := filepath.Join(base, subdir)
 | 
			
		||||
	evaluatedSubdirPath, err := sp.ne.EvalSymlinks(fullSubdirPath, false /* mustExist */)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error resolving symlinks in %s: %s", fullSubdirPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	evaluatedSubdirPath = filepath.Clean(evaluatedSubdirPath)
 | 
			
		||||
 | 
			
		||||
	evaluatedBase, err := sp.ne.EvalSymlinks(base, true /* mustExist */)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
	evaluatedBase = filepath.Clean(evaluatedBase)
 | 
			
		||||
 | 
			
		||||
	rootDir := filepath.Clean(sp.rootDir)
 | 
			
		||||
	if mount.PathWithinBase(evaluatedBase, rootDir) {
 | 
			
		||||
		// Base is in /var/lib/kubelet. This directory is shared between the
 | 
			
		||||
		// container with kubelet and the host. We don't need to add '/rootfs'.
 | 
			
		||||
		// This is useful when /rootfs is mounted as read-only - we can still
 | 
			
		||||
		// create subpaths for paths in /var/lib/kubelet.
 | 
			
		||||
		return doSafeMakeDir(evaluatedSubdirPath, evaluatedBase, perm)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Base is somewhere on the host's filesystem. Add /rootfs and try to make
 | 
			
		||||
	// the directory there.
 | 
			
		||||
	// This requires /rootfs to be writable.
 | 
			
		||||
	kubeletSubdirPath := sp.ne.KubeletPath(evaluatedSubdirPath)
 | 
			
		||||
	kubeletBase := sp.ne.KubeletPath(evaluatedBase)
 | 
			
		||||
	return doSafeMakeDir(kubeletSubdirPath, kubeletBase, perm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpathNSE) doNsEnterBindSubPath(subpath Subpath) (hostPath string, err error) {
 | 
			
		||||
	// Linux, kubelet runs in a container:
 | 
			
		||||
	// - safely open the subpath
 | 
			
		||||
	// - bind-mount the subpath to target (this can be unsafe)
 | 
			
		||||
	// - check that we mounted the right thing by comparing device ID and inode
 | 
			
		||||
	//   of the subpath (via safely opened fd) and the target (that's under our
 | 
			
		||||
	//   control)
 | 
			
		||||
 | 
			
		||||
	// Evaluate all symlinks here once for all subsequent functions.
 | 
			
		||||
	evaluatedHostVolumePath, err := sp.ne.EvalSymlinks(subpath.VolumePath, true /*mustExist*/)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.VolumePath, err)
 | 
			
		||||
	}
 | 
			
		||||
	evaluatedHostSubpath, err := sp.ne.EvalSymlinks(subpath.Path, true /*mustExist*/)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.Path, err)
 | 
			
		||||
	}
 | 
			
		||||
	klog.V(5).Infof("doBindSubPath %q (%q) for volumepath %q", subpath.Path, evaluatedHostSubpath, subpath.VolumePath)
 | 
			
		||||
	subpath.VolumePath = sp.ne.KubeletPath(evaluatedHostVolumePath)
 | 
			
		||||
	subpath.Path = sp.ne.KubeletPath(evaluatedHostSubpath)
 | 
			
		||||
 | 
			
		||||
	// Check the subpath is correct and open it
 | 
			
		||||
	fd, err := safeOpenSubPath(sp.mounter, subpath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer syscall.Close(fd)
 | 
			
		||||
 | 
			
		||||
	alreadyMounted, bindPathTarget, err := prepareSubpathTarget(sp.mounter, subpath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if alreadyMounted {
 | 
			
		||||
		return bindPathTarget, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	success := false
 | 
			
		||||
	defer func() {
 | 
			
		||||
		// Cleanup subpath on error
 | 
			
		||||
		if !success {
 | 
			
		||||
			klog.V(4).Infof("doNsEnterBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
 | 
			
		||||
			if cleanErr := cleanSubPath(sp.mounter, subpath); cleanErr != nil {
 | 
			
		||||
				klog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// Leap of faith: optimistically expect that nobody has modified previously
 | 
			
		||||
	// expanded evalSubPath with evil symlinks and bind-mount it.
 | 
			
		||||
	// Mount is done on the host! don't use kubelet path!
 | 
			
		||||
	klog.V(5).Infof("bind mounting %q at %q", evaluatedHostSubpath, bindPathTarget)
 | 
			
		||||
	if err = sp.mounter.Mount(evaluatedHostSubpath, bindPathTarget, "" /*fstype*/, []string{"bind"}); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error mounting %s: %s", evaluatedHostSubpath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check that the bind-mount target is the same inode and device as the
 | 
			
		||||
	// source that we keept open, i.e. we mounted the right thing.
 | 
			
		||||
	err = checkDeviceInode(fd, bindPathTarget)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error checking bind mount for subpath %s: %s", subpath.VolumePath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	success = true
 | 
			
		||||
	klog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
 | 
			
		||||
	return bindPathTarget, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkDeviceInode checks that opened file and path represent the same file.
 | 
			
		||||
func checkDeviceInode(fd int, path string) error {
 | 
			
		||||
	var srcStat, dstStat unix.Stat_t
 | 
			
		||||
	err := unix.Fstat(fd, &srcStat)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error running fstat on subpath FD: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = unix.Stat(path, &dstStat)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error running fstat on %s: %v", path, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if srcStat.Dev != dstStat.Dev {
 | 
			
		||||
		return fmt.Errorf("different device number")
 | 
			
		||||
	}
 | 
			
		||||
	if srcStat.Ino != dstStat.Ino {
 | 
			
		||||
		return fmt.Errorf("different inode")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										346
									
								
								pkg/volume/util/subpath/subpath_nsenter_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										346
									
								
								pkg/volume/util/subpath/subpath_nsenter_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,346 @@
 | 
			
		||||
// +build linux
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2017 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package subpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/utils/nsenter"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCheckDeviceInode(t *testing.T) {
 | 
			
		||||
	testDir, err := ioutil.TempDir("", "nsenter-mounter-device-")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Cannot create temporary directory: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.RemoveAll(testDir)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		srcPath     string
 | 
			
		||||
		dstPath     string
 | 
			
		||||
		expectError string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:        "the same file",
 | 
			
		||||
			srcPath:     filepath.Join(testDir, "1"),
 | 
			
		||||
			dstPath:     filepath.Join(testDir, "1"),
 | 
			
		||||
			expectError: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:        "different file on the same FS",
 | 
			
		||||
			srcPath:     filepath.Join(testDir, "2.1"),
 | 
			
		||||
			dstPath:     filepath.Join(testDir, "2.2"),
 | 
			
		||||
			expectError: "different inode",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:    "different file on different device",
 | 
			
		||||
			srcPath: filepath.Join(testDir, "3"),
 | 
			
		||||
			// /proc is always on a different "device" than /tmp (or $TEMP)
 | 
			
		||||
			dstPath:     "/proc/self/status",
 | 
			
		||||
			expectError: "different device",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if err := ioutil.WriteFile(test.srcPath, []byte{}, 0644); err != nil {
 | 
			
		||||
			t.Errorf("Test %q: cannot create srcPath %s: %s", test.name, test.srcPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Don't create dst if it exists
 | 
			
		||||
		if _, err := os.Stat(test.dstPath); os.IsNotExist(err) {
 | 
			
		||||
			if err := ioutil.WriteFile(test.dstPath, []byte{}, 0644); err != nil {
 | 
			
		||||
				t.Errorf("Test %q: cannot create dstPath %s: %s", test.name, test.dstPath, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		} else if err != nil {
 | 
			
		||||
			t.Errorf("Test %q: cannot check existence of dstPath %s: %s", test.name, test.dstPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fd, err := unix.Open(test.srcPath, unix.O_CREAT, 0644)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Test %q: cannot open srcPath %s: %s", test.name, test.srcPath, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = checkDeviceInode(fd, test.dstPath)
 | 
			
		||||
 | 
			
		||||
		if test.expectError == "" && err != nil {
 | 
			
		||||
			t.Errorf("Test %q: expected no error, got %s", test.name, err)
 | 
			
		||||
		}
 | 
			
		||||
		if test.expectError != "" {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Errorf("Test %q: expected error, got none", test.name)
 | 
			
		||||
			} else {
 | 
			
		||||
				if !strings.Contains(err.Error(), test.expectError) {
 | 
			
		||||
					t.Errorf("Test %q: expected error %q, got %q", test.name, test.expectError, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newFakeNsenterMounter(tmpdir string, t *testing.T) (*mount.NsenterMounter, string, string, *nsenter.Nsenter, error) {
 | 
			
		||||
	rootfsPath := filepath.Join(tmpdir, "rootfs")
 | 
			
		||||
	if err := os.Mkdir(rootfsPath, 0755); err != nil {
 | 
			
		||||
		return nil, "", "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
	ne, err := nsenter.NewFakeNsenter(rootfsPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, "", "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	varlibPath := filepath.Join(tmpdir, "/var/lib/kubelet")
 | 
			
		||||
	if err := os.MkdirAll(varlibPath, 0755); err != nil {
 | 
			
		||||
		return nil, "", "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return mount.NewNsenterMounter(varlibPath, ne), rootfsPath, varlibPath, ne, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNsenterSafeMakeDir(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		prepare     func(base, rootfs, varlib string) (expectedDir string, err error)
 | 
			
		||||
		subdir      string
 | 
			
		||||
		expectError bool
 | 
			
		||||
		// If true, "base" directory for SafeMakeDir will be /var/lib/kubelet
 | 
			
		||||
		baseIsVarLib bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "simple directory",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// expected to be created in /roots/
 | 
			
		||||
				expectedDir = filepath.Join(rootfs, base, "some/subdirectory/structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "simple existing directory",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: directory exists
 | 
			
		||||
				hostPath := filepath.Join(base, "some/subdirectory/structure")
 | 
			
		||||
				if err := os.MkdirAll(hostPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				// In rootfs: directory exists
 | 
			
		||||
				kubeletPath := filepath.Join(rootfs, hostPath)
 | 
			
		||||
				if err := os.MkdirAll(kubeletPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				// expected to be created in /roots/
 | 
			
		||||
				expectedDir = kubeletPath
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "absolute symlink into safe place",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /base/other/subdirectory exists, /base/some is link to /base/other
 | 
			
		||||
				hostPath := filepath.Join(base, "other/subdirectory")
 | 
			
		||||
				if err := os.MkdirAll(hostPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				somePath := filepath.Join(base, "some")
 | 
			
		||||
				otherPath := filepath.Join(base, "other")
 | 
			
		||||
				if err := os.Symlink(otherPath, somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// In rootfs: /base/other/subdirectory exists
 | 
			
		||||
				kubeletPath := filepath.Join(rootfs, hostPath)
 | 
			
		||||
				if err := os.MkdirAll(kubeletPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				// expected 'structure' to be created
 | 
			
		||||
				expectedDir = filepath.Join(rootfs, hostPath, "structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "relative symlink into safe place",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /base/other/subdirectory exists, /base/some is link to other
 | 
			
		||||
				hostPath := filepath.Join(base, "other/subdirectory")
 | 
			
		||||
				if err := os.MkdirAll(hostPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				somePath := filepath.Join(base, "some")
 | 
			
		||||
				if err := os.Symlink("other", somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// In rootfs: /base/other/subdirectory exists
 | 
			
		||||
				kubeletPath := filepath.Join(rootfs, hostPath)
 | 
			
		||||
				if err := os.MkdirAll(kubeletPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				// expected 'structure' to be created
 | 
			
		||||
				expectedDir = filepath.Join(rootfs, hostPath, "structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "symlink into unsafe place",
 | 
			
		||||
			// evaluated in base
 | 
			
		||||
			subdir: "some/subdirectory/structure",
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /base/some is link to /bin/other
 | 
			
		||||
				somePath := filepath.Join(base, "some")
 | 
			
		||||
				if err := os.Symlink("/bin", somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				return "", nil
 | 
			
		||||
			},
 | 
			
		||||
			expectError: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "simple directory in /var/lib/kubelet",
 | 
			
		||||
			// evaluated in varlib
 | 
			
		||||
			subdir:       "some/subdirectory/structure",
 | 
			
		||||
			baseIsVarLib: true,
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// expected to be created in /base/var/lib/kubelet, not in /rootfs!
 | 
			
		||||
				expectedDir = filepath.Join(varlib, "some/subdirectory/structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "safe symlink in /var/lib/kubelet",
 | 
			
		||||
			// evaluated in varlib
 | 
			
		||||
			subdir:       "some/subdirectory/structure",
 | 
			
		||||
			baseIsVarLib: true,
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /varlib/kubelet/other/subdirectory exists, /varlib/some is link to other
 | 
			
		||||
				hostPath := filepath.Join(varlib, "other/subdirectory")
 | 
			
		||||
				if err := os.MkdirAll(hostPath, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
				somePath := filepath.Join(varlib, "some")
 | 
			
		||||
				if err := os.Symlink("other", somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// expected to be created in /base/var/lib/kubelet, not in /rootfs!
 | 
			
		||||
				expectedDir = filepath.Join(varlib, "other/subdirectory/structure")
 | 
			
		||||
				return expectedDir, nil
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "unsafe symlink in /var/lib/kubelet",
 | 
			
		||||
			// evaluated in varlib
 | 
			
		||||
			subdir:       "some/subdirectory/structure",
 | 
			
		||||
			baseIsVarLib: true,
 | 
			
		||||
			prepare: func(base, rootfs, varlib string) (expectedDir string, err error) {
 | 
			
		||||
				// On the host: /varlib/some is link to /bin
 | 
			
		||||
				somePath := filepath.Join(varlib, "some")
 | 
			
		||||
				if err := os.Symlink("/bin", somePath); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return "", nil
 | 
			
		||||
			},
 | 
			
		||||
			expectError: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		tmpdir, err := ioutil.TempDir("", "nsenter-get-safedir-")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		defer os.RemoveAll(tmpdir)
 | 
			
		||||
 | 
			
		||||
		mounter, rootfs, varlib, ne, err := newFakeNsenterMounter(tmpdir, t)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fsp := NewNSEnter(mounter, ne, varlib)
 | 
			
		||||
 | 
			
		||||
		// Prepare base directory for the test
 | 
			
		||||
		testBase := filepath.Join(tmpdir, "base")
 | 
			
		||||
		if err := os.Mkdir(testBase, 0755); err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// Prepare base directory also in /rootfs
 | 
			
		||||
		rootfsBase := filepath.Join(rootfs, testBase)
 | 
			
		||||
		if err := os.MkdirAll(rootfsBase, 0755); err != nil {
 | 
			
		||||
			t.Error(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		expectedDir := ""
 | 
			
		||||
		if test.prepare != nil {
 | 
			
		||||
			expectedDir, err = test.prepare(testBase, rootfs, varlib)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Error(err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if test.baseIsVarLib {
 | 
			
		||||
			// use /var/lib/kubelet as the test base so we can test creating
 | 
			
		||||
			// subdirs there directly in /var/lib/kubenet and not in
 | 
			
		||||
			// /rootfs/var/lib/kubelet
 | 
			
		||||
			testBase = varlib
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = fsp.SafeMakeDir(test.subdir, testBase, 0755)
 | 
			
		||||
		if err != nil && !test.expectError {
 | 
			
		||||
			t.Errorf("Test %q: unexpected error: %s", test.name, err)
 | 
			
		||||
		}
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Errorf("Test %q: expected error, got none", test.name)
 | 
			
		||||
			} else {
 | 
			
		||||
				if !strings.Contains(err.Error(), "is outside of allowed base") {
 | 
			
		||||
					t.Errorf("Test %q: expected error to contain \"is outside of allowed base\", got this one instead: %s", test.name, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if expectedDir != "" {
 | 
			
		||||
			_, err := os.Stat(expectedDir)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Errorf("Test %q: expected %q to exist, got error: %s", test.name, expectedDir, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								pkg/volume/util/subpath/subpath_unsupported.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								pkg/volume/util/subpath/subpath_unsupported.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
// +build !linux,!windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2014 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package subpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	"k8s.io/utils/nsenter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type subpath struct{}
 | 
			
		||||
 | 
			
		||||
var errUnsupported = errors.New("util/subpath on this platform is not supported")
 | 
			
		||||
 | 
			
		||||
// New returns a subpath.Interface for the current system.
 | 
			
		||||
func New(mount.Interface) Interface {
 | 
			
		||||
	return &subpath{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewNSEnter is to satisfy the compiler for having NewSubpathNSEnter exist for all
 | 
			
		||||
// OS choices. however, NSEnter is only valid on Linux
 | 
			
		||||
func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) Interface {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	return subPath.Path, nil, errUnsupported
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return errUnsupported
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sp *subpath) SafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	return errUnsupported
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										284
									
								
								pkg/volume/util/subpath/subpath_windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								pkg/volume/util/subpath/subpath_windows.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,284 @@
 | 
			
		||||
// +build windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2017 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package subpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/mount"
 | 
			
		||||
	"k8s.io/utils/nsenter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type subpath struct{}
 | 
			
		||||
 | 
			
		||||
// New returns a subpath.Interface for the current system
 | 
			
		||||
func New(mount.Interface) Interface {
 | 
			
		||||
	return &subpath{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewNSEnter is to satisfy the compiler for having NewSubpathNSEnter exist for all
 | 
			
		||||
// OS choices. however, NSEnter is only valid on Linux
 | 
			
		||||
func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) Interface {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// check whether hostPath is within volume path
 | 
			
		||||
// this func will lock all intermediate subpath directories, need to close handle outside of this func after container started
 | 
			
		||||
func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
 | 
			
		||||
	if len(volumePath) == 0 || len(hostPath) == 0 {
 | 
			
		||||
		return []uintptr{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	finalSubPath, err := filepath.EvalSymlinks(hostPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []uintptr{}, fmt.Errorf("cannot read link %s: %s", hostPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	finalVolumePath, err := filepath.EvalSymlinks(volumePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []uintptr{}, fmt.Errorf("cannot read link %s: %s", volumePath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return lockAndCheckSubPathWithoutSymlink(finalVolumePath, finalSubPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// lock all intermediate subPath directories and check they are all within volumePath
 | 
			
		||||
// volumePath & subPath should not contain any symlink, otherwise it will return error
 | 
			
		||||
func lockAndCheckSubPathWithoutSymlink(volumePath, subPath string) ([]uintptr, error) {
 | 
			
		||||
	if len(volumePath) == 0 || len(subPath) == 0 {
 | 
			
		||||
		return []uintptr{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get relative path to volumePath
 | 
			
		||||
	relSubPath, err := filepath.Rel(volumePath, subPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []uintptr{}, fmt.Errorf("Rel(%s, %s) error: %v", volumePath, subPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	if mount.StartsWithBackstep(relSubPath) {
 | 
			
		||||
		return []uintptr{}, fmt.Errorf("SubPath %q not within volume path %q", subPath, volumePath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if relSubPath == "." {
 | 
			
		||||
		// volumePath and subPath are equal
 | 
			
		||||
		return []uintptr{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fileHandles := []uintptr{}
 | 
			
		||||
	var errorResult error
 | 
			
		||||
 | 
			
		||||
	currentFullPath := volumePath
 | 
			
		||||
	dirs := strings.Split(relSubPath, string(os.PathSeparator))
 | 
			
		||||
	for _, dir := range dirs {
 | 
			
		||||
		// lock intermediate subPath directory first
 | 
			
		||||
		currentFullPath = filepath.Join(currentFullPath, dir)
 | 
			
		||||
		handle, err := lockPath(currentFullPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorResult = fmt.Errorf("cannot lock path %s: %s", currentFullPath, err)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		fileHandles = append(fileHandles, handle)
 | 
			
		||||
 | 
			
		||||
		// make sure intermediate subPath directory does not contain symlink any more
 | 
			
		||||
		stat, err := os.Lstat(currentFullPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorResult = fmt.Errorf("Lstat(%q) error: %v", currentFullPath, err)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if stat.Mode()&os.ModeSymlink != 0 {
 | 
			
		||||
			errorResult = fmt.Errorf("subpath %q is an unexpected symlink after EvalSymlinks", currentFullPath)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !mount.PathWithinBase(currentFullPath, volumePath) {
 | 
			
		||||
			errorResult = fmt.Errorf("SubPath %q not within volume path %q", currentFullPath, volumePath)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fileHandles, errorResult
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// unlockPath unlock directories
 | 
			
		||||
func unlockPath(fileHandles []uintptr) {
 | 
			
		||||
	if fileHandles != nil {
 | 
			
		||||
		for _, handle := range fileHandles {
 | 
			
		||||
			syscall.CloseHandle(syscall.Handle(handle))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// lockPath locks a directory or symlink, return handle, exec "syscall.CloseHandle(handle)" to unlock the path
 | 
			
		||||
func lockPath(path string) (uintptr, error) {
 | 
			
		||||
	if len(path) == 0 {
 | 
			
		||||
		return uintptr(syscall.InvalidHandle), syscall.ERROR_FILE_NOT_FOUND
 | 
			
		||||
	}
 | 
			
		||||
	pathp, err := syscall.UTF16PtrFromString(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return uintptr(syscall.InvalidHandle), err
 | 
			
		||||
	}
 | 
			
		||||
	access := uint32(syscall.GENERIC_READ)
 | 
			
		||||
	sharemode := uint32(syscall.FILE_SHARE_READ)
 | 
			
		||||
	createmode := uint32(syscall.OPEN_EXISTING)
 | 
			
		||||
	flags := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
 | 
			
		||||
	fd, err := syscall.CreateFile(pathp, access, sharemode, nil, createmode, flags, 0)
 | 
			
		||||
	return uintptr(fd), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Lock all directories in subPath and check they're not symlinks.
 | 
			
		||||
func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
 | 
			
		||||
	handles, err := lockAndCheckSubPath(subPath.VolumePath, subPath.Path)
 | 
			
		||||
 | 
			
		||||
	// Unlock the directories when the container starts
 | 
			
		||||
	cleanupAction = func() {
 | 
			
		||||
		unlockPath(handles)
 | 
			
		||||
	}
 | 
			
		||||
	return subPath.Path, cleanupAction, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// No bind-mounts for subpaths are necessary on Windows
 | 
			
		||||
func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SafeMakeDir makes sure that the created directory does not escape given base directory mis-using symlinks.
 | 
			
		||||
func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
 | 
			
		||||
	realBase, err := filepath.EvalSymlinks(base)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	realFullPath := filepath.Join(realBase, subdir)
 | 
			
		||||
	return doSafeMakeDir(realFullPath, realBase, perm)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
 | 
			
		||||
	klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
 | 
			
		||||
 | 
			
		||||
	if !mount.PathWithinBase(pathname, base) {
 | 
			
		||||
		return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Quick check if the directory already exists
 | 
			
		||||
	s, err := os.Stat(pathname)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// Path exists
 | 
			
		||||
		if s.IsDir() {
 | 
			
		||||
			// The directory already exists. It can be outside of the parent,
 | 
			
		||||
			// but there is no race-proof check.
 | 
			
		||||
			klog.V(4).Infof("Directory %s already exists", pathname)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Find all existing directories
 | 
			
		||||
	existingPath, toCreate, err := findExistingPrefix(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error opening directory %s: %s", pathname, err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(toCreate) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure the existing directory is inside allowed base
 | 
			
		||||
	fullExistingPath, err := filepath.EvalSymlinks(existingPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error opening existing directory %s: %s", existingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	fullBasePath, err := filepath.EvalSymlinks(base)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("cannot read link %s: %s", base, err)
 | 
			
		||||
	}
 | 
			
		||||
	if !mount.PathWithinBase(fullExistingPath, fullBasePath) {
 | 
			
		||||
		return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// lock all intermediate directories from fullBasePath to fullExistingPath (top to bottom)
 | 
			
		||||
	fileHandles, err := lockAndCheckSubPathWithoutSymlink(fullBasePath, fullExistingPath)
 | 
			
		||||
	defer unlockPath(fileHandles)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
 | 
			
		||||
	currentPath := fullExistingPath
 | 
			
		||||
	// create the directories one by one, making sure nobody can change
 | 
			
		||||
	// created directory into symlink by lock that directory immediately
 | 
			
		||||
	for _, dir := range toCreate {
 | 
			
		||||
		currentPath = filepath.Join(currentPath, dir)
 | 
			
		||||
		klog.V(4).Infof("Creating %s", dir)
 | 
			
		||||
		if err := os.Mkdir(currentPath, perm); err != nil {
 | 
			
		||||
			return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		handle, err := lockPath(currentPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("cannot lock path %s: %s", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		defer syscall.CloseHandle(syscall.Handle(handle))
 | 
			
		||||
		// make sure newly created directory does not contain symlink after lock
 | 
			
		||||
		stat, err := os.Lstat(currentPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Lstat(%q) error: %v", currentPath, err)
 | 
			
		||||
		}
 | 
			
		||||
		if stat.Mode()&os.ModeSymlink != 0 {
 | 
			
		||||
			return fmt.Errorf("subpath %q is an unexpected symlink after Mkdir", currentPath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// findExistingPrefix finds prefix of pathname that exists. In addition, it
 | 
			
		||||
// returns list of remaining directories that don't exist yet.
 | 
			
		||||
func findExistingPrefix(base, pathname string) (string, []string, error) {
 | 
			
		||||
	rel, err := filepath.Rel(base, pathname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return base, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mount.StartsWithBackstep(rel) {
 | 
			
		||||
		return base, nil, fmt.Errorf("pathname(%s) is not within base(%s)", pathname, base)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if rel == "." {
 | 
			
		||||
		// base and pathname are equal
 | 
			
		||||
		return pathname, []string{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dirs := strings.Split(rel, string(filepath.Separator))
 | 
			
		||||
 | 
			
		||||
	parent := base
 | 
			
		||||
	currentPath := base
 | 
			
		||||
	for i, dir := range dirs {
 | 
			
		||||
		parent = currentPath
 | 
			
		||||
		currentPath = filepath.Join(parent, dir)
 | 
			
		||||
		if _, err := os.Lstat(currentPath); err != nil {
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				return parent, dirs[i:], nil
 | 
			
		||||
			}
 | 
			
		||||
			return base, nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pathname, []string{}, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										440
									
								
								pkg/volume/util/subpath/subpath_windows_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										440
									
								
								pkg/volume/util/subpath/subpath_windows_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,440 @@
 | 
			
		||||
// +build windows
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2017 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package subpath
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func makeLink(link, target string) error {
 | 
			
		||||
	if output, err := exec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput(); err != nil {
 | 
			
		||||
		return fmt.Errorf("mklink failed: %v, link(%q) target(%q) output: %q", err, link, target, string(output))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDoSafeMakeDir(t *testing.T) {
 | 
			
		||||
	base, err := ioutil.TempDir("", "TestDoSafeMakeDir")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(base)
 | 
			
		||||
 | 
			
		||||
	testingVolumePath := filepath.Join(base, "testingVolumePath")
 | 
			
		||||
	os.MkdirAll(testingVolumePath, 0755)
 | 
			
		||||
	defer os.RemoveAll(testingVolumePath)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		volumePath    string
 | 
			
		||||
		subPath       string
 | 
			
		||||
		expectError   bool
 | 
			
		||||
		symlinkTarget string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       ``,
 | 
			
		||||
			expectError:   true,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `x`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `a\b\c\d`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `symlink`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `symlink\c\d`),
 | 
			
		||||
			expectError:   true,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `symlink\y926`),
 | 
			
		||||
			expectError:   true,
 | 
			
		||||
			symlinkTarget: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `a\b\symlink`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:    testingVolumePath,
 | 
			
		||||
			subPath:       filepath.Join(testingVolumePath, `a\x\symlink`),
 | 
			
		||||
			expectError:   false,
 | 
			
		||||
			symlinkTarget: filepath.Join(testingVolumePath, `a`),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if len(test.volumePath) > 0 && len(test.subPath) > 0 && len(test.symlinkTarget) > 0 {
 | 
			
		||||
			// make all parent sub directories
 | 
			
		||||
			if parent := filepath.Dir(test.subPath); parent != "." {
 | 
			
		||||
				os.MkdirAll(parent, 0755)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// make last element as symlink
 | 
			
		||||
			linkPath := test.subPath
 | 
			
		||||
			if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
 | 
			
		||||
				if err := makeLink(linkPath, test.symlinkTarget); err != nil {
 | 
			
		||||
					t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err := doSafeMakeDir(test.subPath, test.volumePath, os.FileMode(0755))
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			assert.NotNil(t, err, "Expect error during doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		assert.Nil(t, err, "Expect no error during doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
 | 
			
		||||
		if _, err := os.Stat(test.subPath); os.IsNotExist(err) {
 | 
			
		||||
			t.Errorf("subPath should exists after doSafeMakeDir(%s, %s)", test.subPath, test.volumePath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLockAndCheckSubPath(t *testing.T) {
 | 
			
		||||
	base, err := ioutil.TempDir("", "TestLockAndCheckSubPath")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(base)
 | 
			
		||||
 | 
			
		||||
	testingVolumePath := filepath.Join(base, "testingVolumePath")
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		volumePath          string
 | 
			
		||||
		subPath             string
 | 
			
		||||
		expectedHandleCount int
 | 
			
		||||
		expectError         bool
 | 
			
		||||
		symlinkTarget       string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          `c:\`,
 | 
			
		||||
			subPath:             ``,
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          ``,
 | 
			
		||||
			subPath:             `a`,
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a`),
 | 
			
		||||
			expectedHandleCount: 1,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\d`),
 | 
			
		||||
			expectedHandleCount: 4,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `symlink`),
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\symlink`),
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\d\symlink`),
 | 
			
		||||
			expectedHandleCount: 2,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if len(test.volumePath) > 0 && len(test.subPath) > 0 {
 | 
			
		||||
			os.MkdirAll(test.volumePath, 0755)
 | 
			
		||||
			if len(test.symlinkTarget) == 0 {
 | 
			
		||||
				// make all intermediate sub directories
 | 
			
		||||
				os.MkdirAll(test.subPath, 0755)
 | 
			
		||||
			} else {
 | 
			
		||||
				// make all parent sub directories
 | 
			
		||||
				if parent := filepath.Dir(test.subPath); parent != "." {
 | 
			
		||||
					os.MkdirAll(parent, 0755)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// make last element as symlink
 | 
			
		||||
				linkPath := test.subPath
 | 
			
		||||
				if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
 | 
			
		||||
					if err := makeLink(linkPath, test.symlinkTarget); err != nil {
 | 
			
		||||
						t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fileHandles, err := lockAndCheckSubPath(test.volumePath, test.subPath)
 | 
			
		||||
		unlockPath(fileHandles)
 | 
			
		||||
		assert.Equal(t, test.expectedHandleCount, len(fileHandles))
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			assert.NotNil(t, err, "Expect error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		assert.Nil(t, err, "Expect no error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove dir will happen after closing all file handles
 | 
			
		||||
	assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLockAndCheckSubPathWithoutSymlink(t *testing.T) {
 | 
			
		||||
	base, err := ioutil.TempDir("", "TestLockAndCheckSubPathWithoutSymlink")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(base)
 | 
			
		||||
 | 
			
		||||
	testingVolumePath := filepath.Join(base, "testingVolumePath")
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		volumePath          string
 | 
			
		||||
		subPath             string
 | 
			
		||||
		expectedHandleCount int
 | 
			
		||||
		expectError         bool
 | 
			
		||||
		symlinkTarget       string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          `c:\`,
 | 
			
		||||
			subPath:             ``,
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          ``,
 | 
			
		||||
			subPath:             `a`,
 | 
			
		||||
			expectedHandleCount: 0,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a`),
 | 
			
		||||
			expectedHandleCount: 1,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\d`),
 | 
			
		||||
			expectedHandleCount: 4,
 | 
			
		||||
			expectError:         false,
 | 
			
		||||
			symlinkTarget:       "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `symlink`),
 | 
			
		||||
			expectedHandleCount: 1,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\symlink`),
 | 
			
		||||
			expectedHandleCount: 4,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       base,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			volumePath:          testingVolumePath,
 | 
			
		||||
			subPath:             filepath.Join(testingVolumePath, `a\b\c\d\symlink`),
 | 
			
		||||
			expectedHandleCount: 5,
 | 
			
		||||
			expectError:         true,
 | 
			
		||||
			symlinkTarget:       filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if len(test.volumePath) > 0 && len(test.subPath) > 0 {
 | 
			
		||||
			os.MkdirAll(test.volumePath, 0755)
 | 
			
		||||
			if len(test.symlinkTarget) == 0 {
 | 
			
		||||
				// make all intermediate sub directories
 | 
			
		||||
				os.MkdirAll(test.subPath, 0755)
 | 
			
		||||
			} else {
 | 
			
		||||
				// make all parent sub directories
 | 
			
		||||
				if parent := filepath.Dir(test.subPath); parent != "." {
 | 
			
		||||
					os.MkdirAll(parent, 0755)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// make last element as symlink
 | 
			
		||||
				linkPath := test.subPath
 | 
			
		||||
				if _, err := os.Stat(linkPath); err != nil && os.IsNotExist(err) {
 | 
			
		||||
					if err := makeLink(linkPath, test.symlinkTarget); err != nil {
 | 
			
		||||
						t.Fatalf("unexpected error: %v", fmt.Errorf("mklink link(%q) target(%q) error: %q", linkPath, test.symlinkTarget, err))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fileHandles, err := lockAndCheckSubPathWithoutSymlink(test.volumePath, test.subPath)
 | 
			
		||||
		unlockPath(fileHandles)
 | 
			
		||||
		assert.Equal(t, test.expectedHandleCount, len(fileHandles))
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			assert.NotNil(t, err, "Expect error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		assert.Nil(t, err, "Expect no error during LockAndCheckSubPath(%s, %s)", test.volumePath, test.subPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove dir will happen after closing all file handles
 | 
			
		||||
	assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFindExistingPrefix(t *testing.T) {
 | 
			
		||||
	base, err := ioutil.TempDir("", "TestFindExistingPrefix")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(base)
 | 
			
		||||
 | 
			
		||||
	testingVolumePath := filepath.Join(base, "testingVolumePath")
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		base                    string
 | 
			
		||||
		pathname                string
 | 
			
		||||
		expectError             bool
 | 
			
		||||
		expectedExistingPath    string
 | 
			
		||||
		expectedToCreateDirs    []string
 | 
			
		||||
		createSubPathBeforeTest bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			base:                    `c:\tmp\a`,
 | 
			
		||||
			pathname:                `c:\tmp\b`,
 | 
			
		||||
			expectError:             true,
 | 
			
		||||
			expectedExistingPath:    "",
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    ``,
 | 
			
		||||
			pathname:                `c:\tmp\b`,
 | 
			
		||||
			expectError:             true,
 | 
			
		||||
			expectedExistingPath:    "",
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    `c:\tmp\a`,
 | 
			
		||||
			pathname:                `d:\tmp\b`,
 | 
			
		||||
			expectError:             true,
 | 
			
		||||
			expectedExistingPath:    "",
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    testingVolumePath,
 | 
			
		||||
			pathname:                testingVolumePath,
 | 
			
		||||
			expectError:             false,
 | 
			
		||||
			expectedExistingPath:    testingVolumePath,
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    testingVolumePath,
 | 
			
		||||
			pathname:                filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
			expectError:             false,
 | 
			
		||||
			expectedExistingPath:    filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
			expectedToCreateDirs:    []string{},
 | 
			
		||||
			createSubPathBeforeTest: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    testingVolumePath,
 | 
			
		||||
			pathname:                filepath.Join(testingVolumePath, `a\b\c\`),
 | 
			
		||||
			expectError:             false,
 | 
			
		||||
			expectedExistingPath:    filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
			expectedToCreateDirs:    []string{`c`},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			base:                    testingVolumePath,
 | 
			
		||||
			pathname:                filepath.Join(testingVolumePath, `a\b\c\d`),
 | 
			
		||||
			expectError:             false,
 | 
			
		||||
			expectedExistingPath:    filepath.Join(testingVolumePath, `a\b`),
 | 
			
		||||
			expectedToCreateDirs:    []string{`c`, `d`},
 | 
			
		||||
			createSubPathBeforeTest: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if test.createSubPathBeforeTest {
 | 
			
		||||
			os.MkdirAll(test.pathname, 0755)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		existingPath, toCreate, err := findExistingPrefix(test.base, test.pathname)
 | 
			
		||||
		if test.expectError {
 | 
			
		||||
			assert.NotNil(t, err, "Expect error during findExistingPrefix(%s, %s)", test.base, test.pathname)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		assert.Nil(t, err, "Expect no error during findExistingPrefix(%s, %s)", test.base, test.pathname)
 | 
			
		||||
 | 
			
		||||
		assert.Equal(t, test.expectedExistingPath, existingPath, "Expect result not equal with findExistingPrefix(%s, %s) return: %q, expected: %q",
 | 
			
		||||
			test.base, test.pathname, existingPath, test.expectedExistingPath)
 | 
			
		||||
 | 
			
		||||
		assert.Equal(t, test.expectedToCreateDirs, toCreate, "Expect result not equal with findExistingPrefix(%s, %s) return: %q, expected: %q",
 | 
			
		||||
			test.base, test.pathname, toCreate, test.expectedToCreateDirs)
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	// remove dir will happen after closing all file handles
 | 
			
		||||
	assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user