github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/mount_test.go (about)

     1  // Copyright (c) 2017 Intel Corporation
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  
     6  package virtcontainers
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"strconv"
    17  	"strings"
    18  	"syscall"
    19  	"testing"
    20  
    21  	ktu "github.com/kata-containers/runtime/pkg/katatestutils"
    22  	"github.com/kata-containers/runtime/virtcontainers/types"
    23  	"github.com/stretchr/testify/assert"
    24  )
    25  
    26  const (
    27  	testDirMode = os.FileMode(0750)
    28  )
    29  
    30  var tc ktu.TestConstraint
    31  
    32  func init() {
    33  	tc = ktu.NewTestConstraint(false)
    34  }
    35  
    36  func TestIsSystemMount(t *testing.T) {
    37  	assert := assert.New(t)
    38  	tests := []struct {
    39  		mnt      string
    40  		expected bool
    41  	}{
    42  		{"/sys", true},
    43  		{"/sys/", true},
    44  		{"/sys//", true},
    45  		{"/sys/fs", true},
    46  		{"/sys/fs/", true},
    47  		{"/sys/fs/cgroup", true},
    48  		{"/sysfoo", false},
    49  		{"/home", false},
    50  		{"/dev/block/", false},
    51  		{"/mnt/dev/foo", false},
    52  	}
    53  
    54  	for _, test := range tests {
    55  		result := isSystemMount(test.mnt)
    56  		assert.Exactly(result, test.expected)
    57  	}
    58  }
    59  
    60  func TestIsHostDevice(t *testing.T) {
    61  	assert := assert.New(t)
    62  	tests := []struct {
    63  		mnt      string
    64  		expected bool
    65  	}{
    66  		{"/dev", true},
    67  		{"/dev/zero", true},
    68  		{"/dev/block", true},
    69  		{"/mnt/dev/block", false},
    70  	}
    71  
    72  	for _, test := range tests {
    73  		result := isHostDevice(test.mnt)
    74  		assert.Equal(result, test.expected)
    75  	}
    76  }
    77  
    78  func TestIsHostDeviceCreateFile(t *testing.T) {
    79  	assert := assert.New(t)
    80  	if tc.NotValid(ktu.NeedRoot()) {
    81  		t.Skip(ktu.TestDisabledNeedRoot)
    82  	}
    83  	// Create regular file in /dev
    84  
    85  	path := "/dev/foobar"
    86  	f, err := os.Create(path)
    87  	assert.NoError(err)
    88  	f.Close()
    89  
    90  	assert.False(isHostDevice(path))
    91  	assert.NoError(os.Remove(path))
    92  }
    93  
    94  func TestMajorMinorNumber(t *testing.T) {
    95  	assert := assert.New(t)
    96  	devices := []string{"/dev/zero", "/dev/net/tun"}
    97  
    98  	for _, device := range devices {
    99  		cmdStr := fmt.Sprintf("ls -l %s | awk '{print $5$6}'", device)
   100  		cmd := exec.Command("sh", "-c", cmdStr)
   101  		output, err := cmd.Output()
   102  		assert.NoError(err)
   103  
   104  		data := bytes.Split(output, []byte(","))
   105  		assert.False(len(data) < 2)
   106  
   107  		majorStr := strings.TrimSpace(string(data[0]))
   108  		minorStr := strings.TrimSpace(string(data[1]))
   109  
   110  		majorNo, err := strconv.Atoi(majorStr)
   111  		assert.NoError(err)
   112  
   113  		minorNo, err := strconv.Atoi(minorStr)
   114  		assert.NoError(err)
   115  
   116  		stat := syscall.Stat_t{}
   117  		err = syscall.Stat(device, &stat)
   118  		assert.NoError(err)
   119  
   120  		// Get major and minor numbers for the device itself. Note the use of stat.Rdev instead of Dev.
   121  		major := major(stat.Rdev)
   122  		minor := minor(stat.Rdev)
   123  
   124  		assert.Equal(minor, minorNo)
   125  		assert.Equal(major, majorNo)
   126  	}
   127  }
   128  
   129  func TestGetDeviceForPathRoot(t *testing.T) {
   130  	assert := assert.New(t)
   131  	dev, err := getDeviceForPath("/")
   132  	assert.NoError(err)
   133  
   134  	expected := "/"
   135  
   136  	assert.Equal(dev.mountPoint, expected)
   137  }
   138  
   139  func TestGetDeviceForPathValidMount(t *testing.T) {
   140  	assert := assert.New(t)
   141  	dev, err := getDeviceForPath("/proc")
   142  	assert.NoError(err)
   143  
   144  	expected := "/proc"
   145  
   146  	assert.Equal(dev.mountPoint, expected)
   147  }
   148  
   149  func TestGetDeviceForPathEmptyPath(t *testing.T) {
   150  	assert := assert.New(t)
   151  	_, err := getDeviceForPath("")
   152  	assert.Error(err)
   153  }
   154  
   155  func TestGetDeviceForPath(t *testing.T) {
   156  	assert := assert.New(t)
   157  
   158  	dev, err := getDeviceForPath("///")
   159  	assert.NoError(err)
   160  
   161  	assert.Equal(dev.mountPoint, "/")
   162  
   163  	_, err = getDeviceForPath("/../../.././././../.")
   164  	assert.NoError(err)
   165  
   166  	_, err = getDeviceForPath("/root/file with spaces")
   167  	assert.Error(err)
   168  }
   169  
   170  func TestGetDeviceForPathBindMount(t *testing.T) {
   171  	assert := assert.New(t)
   172  
   173  	if tc.NotValid(ktu.NeedRoot()) {
   174  		t.Skip(ktu.TestDisabledNeedRoot)
   175  	}
   176  
   177  	source := filepath.Join(testDir, "testDeviceDirSrc")
   178  	dest := filepath.Join(testDir, "testDeviceDirDest")
   179  	syscall.Unmount(dest, 0)
   180  	os.Remove(source)
   181  	os.Remove(dest)
   182  
   183  	err := os.MkdirAll(source, mountPerm)
   184  	assert.NoError(err)
   185  
   186  	defer os.Remove(source)
   187  
   188  	err = os.MkdirAll(dest, mountPerm)
   189  	assert.NoError(err)
   190  
   191  	defer os.Remove(dest)
   192  
   193  	err = bindMount(context.Background(), source, dest, false, "private")
   194  	assert.NoError(err)
   195  
   196  	defer syscall.Unmount(dest, syscall.MNT_DETACH)
   197  
   198  	destFile := filepath.Join(dest, "test")
   199  	_, err = os.Create(destFile)
   200  	assert.NoError(err)
   201  
   202  	defer os.Remove(destFile)
   203  
   204  	sourceDev, _ := getDeviceForPath(source)
   205  	destDev, _ := getDeviceForPath(destFile)
   206  
   207  	assert.Equal(sourceDev, destDev)
   208  }
   209  
   210  func TestIsDeviceMapper(t *testing.T) {
   211  	assert := assert.New(t)
   212  
   213  	// known major, minor for /dev/tty
   214  	major := 5
   215  	minor := 0
   216  
   217  	isDM, err := isDeviceMapper(major, minor)
   218  	assert.NoError(err)
   219  	assert.False(isDM)
   220  
   221  	// fake the block device format
   222  	blockFormatTemplate = "/sys/dev/char/%d:%d"
   223  	isDM, err = isDeviceMapper(major, minor)
   224  	assert.NoError(err)
   225  	assert.True(isDM)
   226  }
   227  
   228  func TestIsDockerVolume(t *testing.T) {
   229  	assert := assert.New(t)
   230  	path := "/var/lib/docker/volumes/00da1347c7cf4f15db35f/_data"
   231  	isDockerVolume := IsDockerVolume(path)
   232  	assert.True(isDockerVolume)
   233  
   234  	path = "/var/lib/testdir"
   235  	isDockerVolume = IsDockerVolume(path)
   236  	assert.False(isDockerVolume)
   237  }
   238  
   239  func TestIsEphemeralStorage(t *testing.T) {
   240  	assert := assert.New(t)
   241  	if tc.NotValid(ktu.NeedRoot()) {
   242  		t.Skip(ktu.TestDisabledNeedRoot)
   243  	}
   244  
   245  	dir, err := ioutil.TempDir(testDir, "foo")
   246  	assert.NoError(err)
   247  	defer os.RemoveAll(dir)
   248  
   249  	sampleEphePath := filepath.Join(dir, K8sEmptyDir, "tmp-volume")
   250  	err = os.MkdirAll(sampleEphePath, testDirMode)
   251  	assert.Nil(err)
   252  
   253  	err = syscall.Mount("tmpfs", sampleEphePath, "tmpfs", 0, "")
   254  	assert.NoError(err)
   255  	defer syscall.Unmount(sampleEphePath, 0)
   256  
   257  	isEphe := IsEphemeralStorage(sampleEphePath)
   258  	assert.True(isEphe)
   259  
   260  	isHostEmptyDir := Isk8sHostEmptyDir(sampleEphePath)
   261  	assert.False(isHostEmptyDir)
   262  
   263  	sampleEphePath = "/var/lib/kubelet/pods/366c3a75-4869-11e8-b479-507b9ddd5ce4/volumes/cache-volume"
   264  	isEphe = IsEphemeralStorage(sampleEphePath)
   265  	assert.False(isEphe)
   266  
   267  	isHostEmptyDir = Isk8sHostEmptyDir(sampleEphePath)
   268  	assert.False(isHostEmptyDir)
   269  }
   270  
   271  func TestBindMountInvalidSourceSymlink(t *testing.T) {
   272  	source := filepath.Join(testDir, "fooFile")
   273  	os.Remove(source)
   274  
   275  	err := bindMount(context.Background(), source, "", false, "private")
   276  	assert.Error(t, err)
   277  }
   278  
   279  func TestBindMountFailingMount(t *testing.T) {
   280  	source := filepath.Join(testDir, "fooLink")
   281  	fakeSource := filepath.Join(testDir, "fooFile")
   282  	os.Remove(source)
   283  	os.Remove(fakeSource)
   284  	assert := assert.New(t)
   285  
   286  	_, err := os.OpenFile(fakeSource, os.O_CREATE, mountPerm)
   287  	assert.NoError(err)
   288  
   289  	err = os.Symlink(fakeSource, source)
   290  	assert.NoError(err)
   291  
   292  	err = bindMount(context.Background(), source, "", false, "private")
   293  	assert.Error(err)
   294  }
   295  
   296  func TestBindMountSuccessful(t *testing.T) {
   297  	assert := assert.New(t)
   298  	if tc.NotValid(ktu.NeedRoot()) {
   299  		t.Skip(testDisabledAsNonRoot)
   300  	}
   301  
   302  	source := filepath.Join(testDir, "fooDirSrc")
   303  	dest := filepath.Join(testDir, "fooDirDest")
   304  	syscall.Unmount(dest, 0)
   305  	os.Remove(source)
   306  	os.Remove(dest)
   307  
   308  	err := os.MkdirAll(source, mountPerm)
   309  	assert.NoError(err)
   310  
   311  	err = os.MkdirAll(dest, mountPerm)
   312  	assert.NoError(err)
   313  
   314  	err = bindMount(context.Background(), source, dest, false, "private")
   315  	assert.NoError(err)
   316  
   317  	syscall.Unmount(dest, 0)
   318  }
   319  
   320  func TestBindMountReadonlySuccessful(t *testing.T) {
   321  	assert := assert.New(t)
   322  	if tc.NotValid(ktu.NeedRoot()) {
   323  		t.Skip(testDisabledAsNonRoot)
   324  	}
   325  
   326  	source := filepath.Join(testDir, "fooDirSrc")
   327  	dest := filepath.Join(testDir, "fooDirDest")
   328  	syscall.Unmount(dest, 0)
   329  	os.Remove(source)
   330  	os.Remove(dest)
   331  
   332  	err := os.MkdirAll(source, mountPerm)
   333  	assert.NoError(err)
   334  
   335  	err = os.MkdirAll(dest, mountPerm)
   336  	assert.NoError(err)
   337  
   338  	err = bindMount(context.Background(), source, dest, true, "private")
   339  	assert.NoError(err)
   340  
   341  	defer syscall.Unmount(dest, 0)
   342  
   343  	// should not be able to create file in read-only mount
   344  	destFile := filepath.Join(dest, "foo")
   345  	_, err = os.OpenFile(destFile, os.O_CREATE, mountPerm)
   346  	assert.Error(err)
   347  }
   348  
   349  func TestBindMountInvalidPgtypes(t *testing.T) {
   350  	assert := assert.New(t)
   351  	if tc.NotValid(ktu.NeedRoot()) {
   352  		t.Skip(testDisabledAsNonRoot)
   353  	}
   354  
   355  	source := filepath.Join(testDir, "fooDirSrc")
   356  	dest := filepath.Join(testDir, "fooDirDest")
   357  	syscall.Unmount(dest, 0)
   358  	os.Remove(source)
   359  	os.Remove(dest)
   360  
   361  	err := os.MkdirAll(source, mountPerm)
   362  	assert.NoError(err)
   363  
   364  	err = os.MkdirAll(dest, mountPerm)
   365  	assert.NoError(err)
   366  
   367  	err = bindMount(context.Background(), source, dest, false, "foo")
   368  	expectedErr := fmt.Sprintf("Wrong propagation type %s", "foo")
   369  	assert.EqualError(err, expectedErr)
   370  }
   371  
   372  // TestBindUnmountContainerRootfsENOENTNotError tests that if a file
   373  // or directory attempting to be unmounted doesn't exist, then it
   374  // is not considered an error
   375  func TestBindUnmountContainerRootfsENOENTNotError(t *testing.T) {
   376  	if os.Getuid() != 0 {
   377  		t.Skip("Test disabled as requires root user")
   378  	}
   379  	testMnt := "/tmp/test_mount"
   380  	sID := "sandIDTest"
   381  	cID := "contIDTest"
   382  	container := &Container{
   383  		id: cID,
   384  	}
   385  
   386  	assert := assert.New(t)
   387  
   388  	// check to make sure the file doesn't exist
   389  	testPath := filepath.Join(testMnt, sID, cID, rootfsDir)
   390  	if _, err := os.Stat(testPath); !os.IsNotExist(err) {
   391  		assert.NoError(os.Remove(testPath))
   392  	}
   393  
   394  	err := bindUnmountContainerRootfs(context.Background(), filepath.Join(testMnt, sID), container)
   395  	assert.NoError(err)
   396  }
   397  
   398  func TestBindUnmountContainerRootfsRemoveRootfsDest(t *testing.T) {
   399  	assert := assert.New(t)
   400  	if tc.NotValid(ktu.NeedRoot()) {
   401  		t.Skip(ktu.TestDisabledNeedRoot)
   402  	}
   403  
   404  	sID := "sandIDTestRemoveRootfsDest"
   405  	cID := "contIDTestRemoveRootfsDest"
   406  	container := &Container{
   407  		id: cID,
   408  	}
   409  
   410  	testPath := filepath.Join(testDir, sID, cID, rootfsDir)
   411  	syscall.Unmount(testPath, 0)
   412  	os.Remove(testPath)
   413  
   414  	err := os.MkdirAll(testPath, mountPerm)
   415  	assert.NoError(err)
   416  	defer os.RemoveAll(filepath.Join(testDir, sID))
   417  
   418  	bindUnmountContainerRootfs(context.Background(), filepath.Join(testDir, sID), container)
   419  
   420  	if _, err := os.Stat(testPath); err == nil {
   421  		t.Fatal("empty rootfs dest should be removed")
   422  	} else if !os.IsNotExist(err) {
   423  		t.Fatal(err)
   424  	}
   425  }
   426  
   427  func TestBindUnmountContainerRootfsDevicemapper(t *testing.T) {
   428  	assert := assert.New(t)
   429  	if tc.NotValid(ktu.NeedRoot()) {
   430  		t.Skip(ktu.TestDisabledNeedRoot)
   431  	}
   432  
   433  	sID := "sandIDDevicemapper"
   434  
   435  	cID1 := "contIDDevicemapper1"
   436  	container := &Container{
   437  		id: cID1,
   438  		state: types.ContainerState{
   439  			Fstype:        "xfs",
   440  			BlockDeviceID: "4b073a87f3c9242a",
   441  		},
   442  	}
   443  
   444  	testPath1 := filepath.Join(testDir, sID, cID1, rootfsDir)
   445  	err := os.MkdirAll(testPath1, mountPerm)
   446  	assert.NoError(err)
   447  
   448  	bindUnmountContainerRootfs(context.Background(), filepath.Join(testDir, sID), container)
   449  
   450  	// expect testPath1 exist, if not exist, it means this case failed.
   451  	if _, err := os.Stat(testPath1); err != nil && !os.IsExist(err) {
   452  		t.Fatal("testPath should not be removed")
   453  	}
   454  
   455  	cID2 := "contIDDevicemapper2"
   456  	container.state.Fstype = ""
   457  	container.id = cID2
   458  	testPath2 := filepath.Join(testDir, sID, cID2, rootfsDir)
   459  	err = os.MkdirAll(testPath2, mountPerm)
   460  	assert.NoError(err)
   461  
   462  	defer os.RemoveAll(filepath.Join(testDir, sID))
   463  
   464  	// expect testPath2 not exist, if exist, it means this case failed.
   465  	bindUnmountContainerRootfs(context.Background(), filepath.Join(testDir, sID), container)
   466  	if _, err := os.Stat(testPath2); err == nil || os.IsExist(err) {
   467  		t.Fatal("testPath should be removed")
   468  	}
   469  }