github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_run_mount_linux_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/containerd/containerd/mount"
    27  	"github.com/containerd/nerdctl/pkg/rootlessutil"
    28  	"github.com/containerd/nerdctl/pkg/testutil"
    29  	mobymount "github.com/moby/sys/mount"
    30  	"gotest.tools/v3/assert"
    31  )
    32  
    33  func TestRunVolume(t *testing.T) {
    34  	t.Parallel()
    35  	base := testutil.NewBase(t)
    36  	tID := testutil.Identifier(t)
    37  	rwDir, err := os.MkdirTemp(t.TempDir(), "rw")
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	roDir, err := os.MkdirTemp(t.TempDir(), "ro")
    42  	if err != nil {
    43  		t.Fatal(err)
    44  	}
    45  	rwVolName := tID + "-rw"
    46  	roVolName := tID + "-ro"
    47  	for _, v := range []string{rwVolName, roVolName} {
    48  		defer base.Cmd("volume", "rm", "-f", v).Run()
    49  		base.Cmd("volume", "create", v).AssertOK()
    50  	}
    51  
    52  	containerName := tID
    53  	defer base.Cmd("rm", "-f", containerName).AssertOK()
    54  	base.Cmd("run",
    55  		"-d",
    56  		"--name", containerName,
    57  		"-v", fmt.Sprintf("%s:/mnt1", rwDir),
    58  		"-v", fmt.Sprintf("%s:/mnt2:ro", roDir),
    59  		"-v", fmt.Sprintf("%s:/mnt3", rwVolName),
    60  		"-v", fmt.Sprintf("%s:/mnt4:ro", roVolName),
    61  		testutil.AlpineImage,
    62  		"top",
    63  	).AssertOK()
    64  	base.Cmd("exec", containerName, "sh", "-exc", "echo -n str1 > /mnt1/file1").AssertOK()
    65  	base.Cmd("exec", containerName, "sh", "-exc", "echo -n str2 > /mnt2/file2").AssertFail()
    66  	base.Cmd("exec", containerName, "sh", "-exc", "echo -n str3 > /mnt3/file3").AssertOK()
    67  	base.Cmd("exec", containerName, "sh", "-exc", "echo -n str4 > /mnt4/file4").AssertFail()
    68  	base.Cmd("rm", "-f", containerName).AssertOK()
    69  	base.Cmd("run",
    70  		"--rm",
    71  		"-v", fmt.Sprintf("%s:/mnt1", rwDir),
    72  		"-v", fmt.Sprintf("%s:/mnt3", rwVolName),
    73  		testutil.AlpineImage,
    74  		"cat", "/mnt1/file1", "/mnt3/file3",
    75  	).AssertOutExactly("str1str3")
    76  	base.Cmd("run",
    77  		"--rm",
    78  		"-v", fmt.Sprintf("%s:/mnt3/mnt1", rwDir),
    79  		"-v", fmt.Sprintf("%s:/mnt3", rwVolName),
    80  		testutil.AlpineImage,
    81  		"cat", "/mnt3/mnt1/file1", "/mnt3/file3",
    82  	).AssertOutExactly("str1str3")
    83  }
    84  
    85  func TestRunAnonymousVolume(t *testing.T) {
    86  	t.Parallel()
    87  	base := testutil.NewBase(t)
    88  	base.Cmd("run", "--rm", "-v", "/foo", testutil.AlpineImage).AssertOK()
    89  	base.Cmd("run", "--rm", "-v", "TestVolume2:/foo", testutil.AlpineImage).AssertOK()
    90  	base.Cmd("run", "--rm", "-v", "TestVolume", testutil.AlpineImage).AssertOK()
    91  
    92  	// Destination must be an absolute path not named volume
    93  	base.Cmd("run", "--rm", "-v", "TestVolume2:TestVolumes", testutil.AlpineImage).AssertFail()
    94  }
    95  
    96  func TestRunVolumeRelativePath(t *testing.T) {
    97  	t.Parallel()
    98  	base := testutil.NewBase(t)
    99  	base.Cmd("run", "--rm", "-v", "./foo:/mnt/foo", testutil.AlpineImage).AssertOK()
   100  	base.Cmd("run", "--rm", "-v", "./foo", testutil.AlpineImage).AssertOK()
   101  
   102  	// Destination must be an absolute path not a relative path
   103  	base.Cmd("run", "--rm", "-v", "./foo:./foo", testutil.AlpineImage).AssertFail()
   104  }
   105  
   106  func TestRunAnonymousVolumeWithTypeMountFlag(t *testing.T) {
   107  	t.Parallel()
   108  	base := testutil.NewBase(t)
   109  	base.Cmd("run", "--rm", "--mount", "type=volume,dst=/foo", testutil.AlpineImage,
   110  		"mountpoint", "-q", "/foo").AssertOK()
   111  }
   112  
   113  func TestRunAnonymousVolumeWithBuild(t *testing.T) {
   114  	t.Parallel()
   115  	testutil.RequiresBuild(t)
   116  	base := testutil.NewBase(t)
   117  	defer base.Cmd("builder", "prune").Run()
   118  	imageName := testutil.Identifier(t)
   119  	defer base.Cmd("rmi", imageName).Run()
   120  
   121  	dockerfile := fmt.Sprintf(`FROM %s
   122  VOLUME /foo
   123          `, testutil.AlpineImage)
   124  
   125  	buildCtx, err := createBuildContext(dockerfile)
   126  	assert.NilError(t, err)
   127  	defer os.RemoveAll(buildCtx)
   128  
   129  	base.Cmd("build", "-t", imageName, buildCtx).AssertOK()
   130  	base.Cmd("run", "--rm", "-v", "/foo", testutil.AlpineImage,
   131  		"mountpoint", "-q", "/foo").AssertOK()
   132  }
   133  
   134  func TestRunCopyingUpInitialContentsOnVolume(t *testing.T) {
   135  	t.Parallel()
   136  	testutil.RequiresBuild(t)
   137  	base := testutil.NewBase(t)
   138  	defer base.Cmd("builder", "prune").Run()
   139  	imageName := testutil.Identifier(t)
   140  	defer base.Cmd("rmi", imageName).Run()
   141  	volName := testutil.Identifier(t) + "-vol"
   142  	defer base.Cmd("volume", "rm", volName).Run()
   143  
   144  	dockerfile := fmt.Sprintf(`FROM %s
   145  RUN mkdir -p /mnt && echo hi > /mnt/initial_file
   146  CMD ["cat", "/mnt/initial_file"]
   147          `, testutil.AlpineImage)
   148  
   149  	buildCtx, err := createBuildContext(dockerfile)
   150  	assert.NilError(t, err)
   151  	defer os.RemoveAll(buildCtx)
   152  
   153  	base.Cmd("build", "-t", imageName, buildCtx).AssertOK()
   154  
   155  	//AnonymousVolume
   156  	base.Cmd("run", "--rm", imageName).AssertOutExactly("hi\n")
   157  	base.Cmd("run", "-v", "/mnt", "--rm", imageName).AssertOutExactly("hi\n")
   158  
   159  	//NamedVolume should be automatically created
   160  	base.Cmd("run", "-v", volName+":/mnt", "--rm", imageName).AssertOutExactly("hi\n")
   161  }
   162  
   163  func TestRunCopyingUpInitialContentsOnDockerfileVolume(t *testing.T) {
   164  	t.Parallel()
   165  	testutil.RequiresBuild(t)
   166  	base := testutil.NewBase(t)
   167  	defer base.Cmd("builder", "prune").Run()
   168  	imageName := testutil.Identifier(t)
   169  	defer base.Cmd("rmi", imageName).Run()
   170  	volName := testutil.Identifier(t) + "-vol"
   171  	defer base.Cmd("volume", "rm", volName).Run()
   172  
   173  	dockerfile := fmt.Sprintf(`FROM %s
   174  RUN mkdir -p /mnt && echo hi > /mnt/initial_file
   175  VOLUME /mnt
   176  CMD ["cat", "/mnt/initial_file"]
   177          `, testutil.AlpineImage)
   178  
   179  	buildCtx, err := createBuildContext(dockerfile)
   180  	assert.NilError(t, err)
   181  	defer os.RemoveAll(buildCtx)
   182  
   183  	base.Cmd("build", "-t", imageName, buildCtx).AssertOK()
   184  	//AnonymousVolume
   185  	base.Cmd("run", "--rm", imageName).AssertOutExactly("hi\n")
   186  	base.Cmd("run", "-v", "/mnt", "--rm", imageName).AssertOutExactly("hi\n")
   187  
   188  	//NamedVolume
   189  	base.Cmd("volume", "create", volName).AssertOK()
   190  	base.Cmd("run", "-v", volName+":/mnt", "--rm", imageName).AssertOutExactly("hi\n")
   191  
   192  	//mount bind
   193  	tmpDir, err := os.MkdirTemp(t.TempDir(), "hostDir")
   194  	assert.NilError(t, err)
   195  
   196  	base.Cmd("run", "-v", fmt.Sprintf("%s:/mnt", tmpDir), "--rm", imageName).AssertFail()
   197  }
   198  
   199  func TestRunCopyingUpInitialContentsOnVolumeShouldRetainSymlink(t *testing.T) {
   200  	t.Parallel()
   201  	testutil.RequiresBuild(t)
   202  	base := testutil.NewBase(t)
   203  	defer base.Cmd("builder", "prune").Run()
   204  	imageName := testutil.Identifier(t)
   205  	defer base.Cmd("rmi", imageName).Run()
   206  
   207  	dockerfile := fmt.Sprintf(`FROM %s
   208  RUN ln -s ../../../../../../../../../../../../../../../../../../etc/passwd /mnt/passwd
   209  VOLUME /mnt
   210  CMD ["readlink", "/mnt/passwd"]
   211          `, testutil.AlpineImage)
   212  	const expected = "../../../../../../../../../../../../../../../../../../etc/passwd\n"
   213  
   214  	buildCtx, err := createBuildContext(dockerfile)
   215  	assert.NilError(t, err)
   216  	defer os.RemoveAll(buildCtx)
   217  
   218  	base.Cmd("build", "-t", imageName, buildCtx).AssertOK()
   219  
   220  	base.Cmd("run", "--rm", imageName).AssertOutExactly(expected)
   221  	base.Cmd("run", "-v", "/mnt", "--rm", imageName).AssertOutExactly(expected)
   222  }
   223  
   224  func TestRunCopyingUpInitialContentsShouldNotResetTheCopiedContents(t *testing.T) {
   225  	t.Parallel()
   226  	testutil.RequiresBuild(t)
   227  	base := testutil.NewBase(t)
   228  	defer base.Cmd("builder", "prune").Run()
   229  	tID := testutil.Identifier(t)
   230  	imageName := tID + "-img"
   231  	volumeName := tID + "-vol"
   232  	containerName := tID
   233  	defer func() {
   234  		base.Cmd("rm", "-f", containerName).Run()
   235  		base.Cmd("volume", "rm", volumeName).Run()
   236  		base.Cmd("rmi", imageName).Run()
   237  	}()
   238  
   239  	dockerfile := fmt.Sprintf(`FROM %s
   240  RUN echo -n "rev0" > /mnt/file
   241  `, testutil.AlpineImage)
   242  
   243  	buildCtx, err := createBuildContext(dockerfile)
   244  	assert.NilError(t, err)
   245  	defer os.RemoveAll(buildCtx)
   246  
   247  	base.Cmd("build", "-t", imageName, buildCtx).AssertOK()
   248  
   249  	base.Cmd("volume", "create", volumeName)
   250  	runContainer := func() {
   251  		base.Cmd("run", "-d", "--name", containerName, "-v", volumeName+":/mnt", imageName, "sleep", "infinity").AssertOK()
   252  	}
   253  	runContainer()
   254  	base.EnsureContainerStarted(containerName)
   255  	base.Cmd("exec", containerName, "cat", "/mnt/file").AssertOutExactly("rev0")
   256  	base.Cmd("exec", containerName, "sh", "-euc", "echo -n \"rev1\" >/mnt/file").AssertOK()
   257  	base.Cmd("rm", "-f", containerName).AssertOK()
   258  	runContainer()
   259  	base.EnsureContainerStarted(containerName)
   260  	base.Cmd("exec", containerName, "cat", "/mnt/file").AssertOutExactly("rev1")
   261  }
   262  
   263  func TestRunTmpfs(t *testing.T) {
   264  	t.Parallel()
   265  	base := testutil.NewBase(t)
   266  	f := func(allow, deny []string) func(stdout string) error {
   267  		return func(stdout string) error {
   268  			lines := strings.Split(strings.TrimSpace(stdout), "\n")
   269  			if len(lines) != 1 {
   270  				return fmt.Errorf("expected 1 lines, got %q", stdout)
   271  			}
   272  			for _, s := range allow {
   273  				if !strings.Contains(stdout, s) {
   274  					return fmt.Errorf("expected stdout to contain %q, got %q", s, stdout)
   275  				}
   276  			}
   277  			for _, s := range deny {
   278  				if strings.Contains(stdout, s) {
   279  					return fmt.Errorf("expected stdout not to contain %q, got %q", s, stdout)
   280  				}
   281  			}
   282  			return nil
   283  		}
   284  	}
   285  	base.Cmd("run", "--rm", "--tmpfs", "/tmp", testutil.AlpineImage, "grep", "/tmp", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "noexec"}, nil))
   286  	base.Cmd("run", "--rm", "--tmpfs", "/tmp:size=64m,exec", testutil.AlpineImage, "grep", "/tmp", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "size=65536k"}, []string{"noexec"}))
   287  	// for https://github.com/containerd/nerdctl/issues/594
   288  	base.Cmd("run", "--rm", "--tmpfs", "/dev/shm:rw,exec,size=1g", testutil.AlpineImage, "grep", "/dev/shm", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "size=1048576k"}, []string{"noexec"}))
   289  }
   290  
   291  func TestRunBindMountTmpfs(t *testing.T) {
   292  	t.Parallel()
   293  	base := testutil.NewBase(t)
   294  	f := func(allow []string) func(stdout string) error {
   295  		return func(stdout string) error {
   296  			lines := strings.Split(strings.TrimSpace(stdout), "\n")
   297  			if len(lines) != 1 {
   298  				return fmt.Errorf("expected 1 lines, got %q", stdout)
   299  			}
   300  			for _, s := range allow {
   301  				if !strings.Contains(stdout, s) {
   302  					return fmt.Errorf("expected stdout to contain %q, got %q", s, stdout)
   303  				}
   304  			}
   305  			return nil
   306  		}
   307  	}
   308  	base.Cmd("run", "--rm", "--mount", "type=tmpfs,target=/tmp", testutil.AlpineImage, "grep", "/tmp", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "noexec"}))
   309  	base.Cmd("run", "--rm", "--mount", "type=tmpfs,target=/tmp,tmpfs-size=64m", testutil.AlpineImage, "grep", "/tmp", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "size=65536k"}))
   310  }
   311  
   312  func TestRunBindMountBind(t *testing.T) {
   313  	t.Parallel()
   314  	base := testutil.NewBase(t)
   315  	tID := testutil.Identifier(t)
   316  	rwDir, err := os.MkdirTemp(t.TempDir(), "rw")
   317  	if err != nil {
   318  		t.Fatal(err)
   319  	}
   320  	roDir, err := os.MkdirTemp(t.TempDir(), "ro")
   321  	if err != nil {
   322  		t.Fatal(err)
   323  	}
   324  
   325  	containerName := tID
   326  	defer base.Cmd("rm", "-f", containerName).AssertOK()
   327  	base.Cmd("run",
   328  		"-d",
   329  		"--name", containerName,
   330  		"--mount", fmt.Sprintf("type=bind,src=%s,target=/mnt1", rwDir),
   331  		"--mount", fmt.Sprintf("type=bind,src=%s,target=/mnt2,ro", roDir),
   332  		testutil.AlpineImage,
   333  		"top",
   334  	).AssertOK()
   335  	base.Cmd("exec", containerName, "sh", "-exc", "echo -n str1 > /mnt1/file1").AssertOK()
   336  	base.Cmd("exec", containerName, "sh", "-exc", "echo -n str2 > /mnt2/file2").AssertFail()
   337  
   338  	base.Cmd("run",
   339  		"--rm",
   340  		"--mount", fmt.Sprintf("type=bind,src=%s,target=/mnt1", rwDir),
   341  		testutil.AlpineImage,
   342  		"cat", "/mnt1/file1",
   343  	).AssertOutExactly("str1")
   344  
   345  	// check `bind-propagation`
   346  	f := func(allow string) func(stdout string) error {
   347  		return func(stdout string) error {
   348  			lines := strings.Split(strings.TrimSpace(stdout), "\n")
   349  			if len(lines) != 1 {
   350  				return fmt.Errorf("expected 1 lines, got %q", stdout)
   351  			}
   352  			fields := strings.Split(lines[0], " ")
   353  			if len(fields) < 4 {
   354  				return fmt.Errorf("invalid /proc/mounts format %q", stdout)
   355  			}
   356  
   357  			options := strings.Split(fields[3], ",")
   358  
   359  			found := false
   360  			for _, s := range options {
   361  				if allow == s {
   362  					found = true
   363  					break
   364  				}
   365  			}
   366  			if !found {
   367  				return fmt.Errorf("expected stdout to contain %q, got %+v", allow, options)
   368  			}
   369  			return nil
   370  		}
   371  	}
   372  	base.Cmd("exec", containerName, "grep", "/mnt1", "/proc/mounts").AssertOutWithFunc(f("rw"))
   373  	base.Cmd("exec", containerName, "grep", "/mnt2", "/proc/mounts").AssertOutWithFunc(f("ro"))
   374  }
   375  
   376  func TestRunMountBindMode(t *testing.T) {
   377  	if rootlessutil.IsRootless() {
   378  		t.Skip("must be superuser to use mount")
   379  	}
   380  	t.Parallel()
   381  	base := testutil.NewBase(t)
   382  
   383  	tmpDir1, err := os.MkdirTemp(t.TempDir(), "rw")
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  	defer os.RemoveAll(tmpDir1)
   388  	tmpDir1Mnt := filepath.Join(tmpDir1, "mnt")
   389  	if err := os.MkdirAll(tmpDir1Mnt, 0700); err != nil {
   390  		t.Fatal(err)
   391  	}
   392  
   393  	tmpDir2, err := os.MkdirTemp(t.TempDir(), "ro")
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  	defer os.RemoveAll(tmpDir2)
   398  
   399  	if err := mobymount.Mount(tmpDir2, tmpDir1Mnt, "none", "bind,ro"); err != nil {
   400  		t.Fatal(err)
   401  	}
   402  	defer func() {
   403  		if err := mobymount.Unmount(tmpDir1Mnt); err != nil {
   404  			t.Fatal(err)
   405  		}
   406  	}()
   407  
   408  	base.Cmd("run",
   409  		"--rm",
   410  		"--mount", fmt.Sprintf("type=bind,bind-nonrecursive,src=%s,target=/mnt1", tmpDir1),
   411  		testutil.AlpineImage,
   412  		"sh", "-euxc", "apk add findmnt -q && findmnt -nR /mnt1",
   413  	).AssertOutWithFunc(func(stdout string) error {
   414  		lines := strings.Split(strings.TrimSpace(stdout), "\n")
   415  		if len(lines) != 1 {
   416  			return fmt.Errorf("expected 1 line, got %q", stdout)
   417  		}
   418  		if !strings.HasPrefix(lines[0], "/mnt1") {
   419  			return fmt.Errorf("expected mount /mnt1, got %q", lines[0])
   420  		}
   421  		return nil
   422  	})
   423  
   424  	base.Cmd("run",
   425  		"--rm",
   426  		"--mount", fmt.Sprintf("type=bind,bind-nonrecursive=false,src=%s,target=/mnt1", tmpDir1),
   427  		testutil.AlpineImage,
   428  		"sh", "-euxc", "apk add findmnt -q && findmnt -nR /mnt1",
   429  	).AssertOutWithFunc(func(stdout string) error {
   430  		lines := strings.Split(strings.TrimSpace(stdout), "\n")
   431  		if len(lines) != 2 {
   432  			return fmt.Errorf("expected 2 line, got %q", stdout)
   433  		}
   434  		if !strings.HasPrefix(lines[0], "/mnt1") {
   435  			return fmt.Errorf("expected mount /mnt1, got %q", lines[0])
   436  		}
   437  		return nil
   438  	})
   439  }
   440  
   441  func TestRunVolumeBindMode(t *testing.T) {
   442  	if rootlessutil.IsRootless() {
   443  		t.Skip("must be superuser to use mount")
   444  	}
   445  	testutil.DockerIncompatible(t)
   446  	t.Parallel()
   447  	base := testutil.NewBase(t)
   448  
   449  	tmpDir1, err := os.MkdirTemp(t.TempDir(), "rw")
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  	defer os.RemoveAll(tmpDir1)
   454  	tmpDir1Mnt := filepath.Join(tmpDir1, "mnt")
   455  	if err := os.MkdirAll(tmpDir1Mnt, 0700); err != nil {
   456  		t.Fatal(err)
   457  	}
   458  
   459  	tmpDir2, err := os.MkdirTemp(t.TempDir(), "ro")
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  	defer os.RemoveAll(tmpDir2)
   464  
   465  	if err := mobymount.Mount(tmpDir2, tmpDir1Mnt, "none", "bind,ro"); err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	defer func() {
   469  		if err := mobymount.Unmount(tmpDir1Mnt); err != nil {
   470  			t.Fatal(err)
   471  		}
   472  	}()
   473  
   474  	base.Cmd("run",
   475  		"--rm",
   476  		"-v", fmt.Sprintf("%s:/mnt1:bind", tmpDir1),
   477  		testutil.AlpineImage,
   478  		"sh", "-euxc", "apk add findmnt -q && findmnt -nR /mnt1",
   479  	).AssertOutWithFunc(func(stdout string) error {
   480  		lines := strings.Split(strings.TrimSpace(stdout), "\n")
   481  		if len(lines) != 1 {
   482  			return fmt.Errorf("expected 1 line, got %q", stdout)
   483  		}
   484  		if !strings.HasPrefix(lines[0], "/mnt1") {
   485  			return fmt.Errorf("expected mount /mnt1, got %q", lines[0])
   486  		}
   487  		return nil
   488  	})
   489  
   490  	base.Cmd("run",
   491  		"--rm",
   492  		"-v", fmt.Sprintf("%s:/mnt1:rbind", tmpDir1),
   493  		testutil.AlpineImage,
   494  		"sh", "-euxc", "apk add findmnt -q && findmnt -nR /mnt1",
   495  	).AssertOutWithFunc(func(stdout string) error {
   496  		lines := strings.Split(strings.TrimSpace(stdout), "\n")
   497  		if len(lines) != 2 {
   498  			return fmt.Errorf("expected 2 line, got %q", stdout)
   499  		}
   500  		if !strings.HasPrefix(lines[0], "/mnt1") {
   501  			return fmt.Errorf("expected mount /mnt1, got %q", lines[0])
   502  		}
   503  		return nil
   504  	})
   505  }
   506  
   507  func TestRunBindMountPropagation(t *testing.T) {
   508  	tID := testutil.Identifier(t)
   509  
   510  	if !isRootfsShareableMount() {
   511  		t.Skipf("rootfs doesn't support shared mount, skip test %s", tID)
   512  	}
   513  
   514  	t.Parallel()
   515  	base := testutil.NewBase(t)
   516  
   517  	testCases := []struct {
   518  		propagation string
   519  		assertFunc  func(containerName, containerNameReplica string)
   520  	}{
   521  		{
   522  			propagation: "rshared",
   523  			assertFunc: func(containerName, containerNameReplica string) {
   524  				// replica can get sub-mounts from original
   525  				base.Cmd("exec", containerNameReplica, "cat", "/mnt1/replica/foo.txt").AssertOutExactly("toreplica")
   526  
   527  				// and sub-mounts from replica will be propagated to the original too
   528  				base.Cmd("exec", containerName, "cat", "/mnt1/bar/bar.txt").AssertOutExactly("fromreplica")
   529  			},
   530  		},
   531  		{
   532  			propagation: "rslave",
   533  			assertFunc: func(containerName, containerNameReplica string) {
   534  				// replica can get sub-mounts from original
   535  				base.Cmd("exec", containerNameReplica, "cat", "/mnt1/replica/foo.txt").AssertOutExactly("toreplica")
   536  
   537  				// but sub-mounts from replica will not be propagated to the original
   538  				base.Cmd("exec", containerName, "cat", "/mnt1/bar/bar.txt").AssertFail()
   539  			},
   540  		},
   541  		{
   542  			propagation: "rprivate",
   543  			assertFunc: func(containerName, containerNameReplica string) {
   544  				// replica can't get sub-mounts from original
   545  				base.Cmd("exec", containerNameReplica, "cat", "/mnt1/replica/foo.txt").AssertFail()
   546  				// and sub-mounts from replica will not be propagated to the original too
   547  				base.Cmd("exec", containerName, "cat", "/mnt1/bar/bar.txt").AssertFail()
   548  			},
   549  		},
   550  		{
   551  			propagation: "",
   552  			assertFunc: func(containerName, containerNameReplica string) {
   553  				// replica can't get sub-mounts from original
   554  				base.Cmd("exec", containerNameReplica, "cat", "/mnt1/replica/foo.txt").AssertFail()
   555  				// and sub-mounts from replica will not be propagated to the original too
   556  				base.Cmd("exec", containerName, "cat", "/mnt1/bar/bar.txt").AssertFail()
   557  			},
   558  		},
   559  	}
   560  
   561  	for _, tc := range testCases {
   562  		propagationName := tc.propagation
   563  		if propagationName == "" {
   564  			propagationName = "default"
   565  		}
   566  
   567  		t.Logf("Running test propagation case %s", propagationName)
   568  
   569  		rwDir, err := os.MkdirTemp(t.TempDir(), "rw")
   570  		if err != nil {
   571  			t.Fatal(err)
   572  		}
   573  
   574  		containerName := tID + "-" + propagationName
   575  		containerNameReplica := containerName + "-replica"
   576  
   577  		mountOption := fmt.Sprintf("type=bind,src=%s,target=/mnt1,bind-propagation=%s", rwDir, tc.propagation)
   578  		if tc.propagation == "" {
   579  			mountOption = fmt.Sprintf("type=bind,src=%s,target=/mnt1", rwDir)
   580  		}
   581  
   582  		containers := []struct {
   583  			name        string
   584  			mountOption string
   585  		}{
   586  			{
   587  				name:        containerName,
   588  				mountOption: fmt.Sprintf("type=bind,src=%s,target=/mnt1,bind-propagation=rshared", rwDir),
   589  			},
   590  			{
   591  				name:        containerNameReplica,
   592  				mountOption: mountOption,
   593  			},
   594  		}
   595  		for _, c := range containers {
   596  			base.Cmd("run", "-d",
   597  				"--privileged",
   598  				"--name", c.name,
   599  				"--mount", c.mountOption,
   600  				testutil.AlpineImage,
   601  				"top").AssertOK()
   602  			defer base.Cmd("rm", "-f", c.name).Run()
   603  		}
   604  
   605  		// mount in the first container
   606  		base.Cmd("exec", containerName, "sh", "-exc", "mkdir /app && mkdir /mnt1/replica && mount --bind /app /mnt1/replica && echo -n toreplica > /app/foo.txt").AssertOK()
   607  		base.Cmd("exec", containerName, "cat", "/mnt1/replica/foo.txt").AssertOutExactly("toreplica")
   608  
   609  		// mount in the second container
   610  		base.Cmd("exec", containerNameReplica, "sh", "-exc", "mkdir /bar && mkdir /mnt1/bar").AssertOK()
   611  		base.Cmd("exec", containerNameReplica, "sh", "-exc", "mount --bind /bar /mnt1/bar").AssertOK()
   612  
   613  		base.Cmd("exec", containerNameReplica, "sh", "-exc", "echo -n fromreplica > /bar/bar.txt").AssertOK()
   614  		base.Cmd("exec", containerNameReplica, "cat", "/mnt1/bar/bar.txt").AssertOutExactly("fromreplica")
   615  
   616  		// call case specific assert function
   617  		tc.assertFunc(containerName, containerNameReplica)
   618  
   619  		// umount mount point in the first privileged container
   620  		base.Cmd("exec", containerNameReplica, "sh", "-exc", "umount /mnt1/bar").AssertOK()
   621  		base.Cmd("exec", containerName, "sh", "-exc", "umount /mnt1/replica").AssertOK()
   622  	}
   623  }
   624  
   625  // isRootfsShareableMount will check if /tmp or / support shareable mount
   626  func isRootfsShareableMount() bool {
   627  	existFunc := func(mi mount.Info) bool {
   628  		for _, opt := range strings.Split(mi.Optional, " ") {
   629  			if strings.HasPrefix(opt, "shared:") {
   630  				return true
   631  			}
   632  		}
   633  		return false
   634  	}
   635  
   636  	mi, err := mount.Lookup("/tmp")
   637  	if err == nil {
   638  		return existFunc(mi)
   639  	}
   640  
   641  	mi, err = mount.Lookup("/")
   642  	if err == nil {
   643  		return existFunc(mi)
   644  	}
   645  
   646  	return false
   647  }
   648  
   649  func TestRunVolumesFrom(t *testing.T) {
   650  	t.Parallel()
   651  	base := testutil.NewBase(t)
   652  	tID := testutil.Identifier(t)
   653  	rwDir, err := os.MkdirTemp(t.TempDir(), "rw")
   654  	if err != nil {
   655  		t.Fatal(err)
   656  	}
   657  	roDir, err := os.MkdirTemp(t.TempDir(), "ro")
   658  	if err != nil {
   659  		t.Fatal(err)
   660  	}
   661  	rwVolName := tID + "-rw"
   662  	roVolName := tID + "-ro"
   663  	for _, v := range []string{rwVolName, roVolName} {
   664  		defer base.Cmd("volume", "rm", "-f", v).Run()
   665  		base.Cmd("volume", "create", v).AssertOK()
   666  	}
   667  
   668  	fromContainerName := tID + "-from"
   669  	toContainerName := tID + "-to"
   670  	defer base.Cmd("rm", "-f", fromContainerName).AssertOK()
   671  	defer base.Cmd("rm", "-f", toContainerName).AssertOK()
   672  	base.Cmd("run",
   673  		"-d",
   674  		"--name", fromContainerName,
   675  		"-v", fmt.Sprintf("%s:/mnt1", rwDir),
   676  		"-v", fmt.Sprintf("%s:/mnt2:ro", roDir),
   677  		"-v", fmt.Sprintf("%s:/mnt3", rwVolName),
   678  		"-v", fmt.Sprintf("%s:/mnt4:ro", roVolName),
   679  		testutil.AlpineImage,
   680  		"top",
   681  	).AssertOK()
   682  	base.Cmd("run",
   683  		"-d",
   684  		"--name", toContainerName,
   685  		"--volumes-from", fromContainerName,
   686  		testutil.AlpineImage,
   687  		"top",
   688  	).AssertOK()
   689  	base.Cmd("exec", toContainerName, "sh", "-exc", "echo -n str1 > /mnt1/file1").AssertOK()
   690  	base.Cmd("exec", toContainerName, "sh", "-exc", "echo -n str2 > /mnt2/file2").AssertFail()
   691  	base.Cmd("exec", toContainerName, "sh", "-exc", "echo -n str3 > /mnt3/file3").AssertOK()
   692  	base.Cmd("exec", toContainerName, "sh", "-exc", "echo -n str4 > /mnt4/file4").AssertFail()
   693  	base.Cmd("rm", "-f", toContainerName).AssertOK()
   694  	base.Cmd("run",
   695  		"--rm",
   696  		"--volumes-from", fromContainerName,
   697  		testutil.AlpineImage,
   698  		"cat", "/mnt1/file1", "/mnt3/file3",
   699  	).AssertOutExactly("str1str3")
   700  }
   701  
   702  func TestBindMountWhenHostFolderDoesNotExist(t *testing.T) {
   703  	t.Parallel()
   704  	base := testutil.NewBase(t)
   705  	containerName := testutil.Identifier(t) + "-host-dir-not-found"
   706  	hostDir, err := os.MkdirTemp(t.TempDir(), "rw")
   707  	if err != nil {
   708  		t.Fatal(err)
   709  	}
   710  	defer os.RemoveAll(hostDir)
   711  	hp := filepath.Join(hostDir, "does-not-exist")
   712  	base.Cmd("run", "--name", containerName, "-d", "-v", fmt.Sprintf("%s:/tmp",
   713  		hp), testutil.AlpineImage).AssertOK()
   714  	base.Cmd("rm", "-f", containerName).AssertOK()
   715  
   716  	// Host directory should get created
   717  	_, err = os.Stat(hp)
   718  	assert.NilError(t, err)
   719  
   720  	// Test for --mount
   721  	os.RemoveAll(hp)
   722  	base.Cmd("run", "--name", containerName, "-d", "--mount", fmt.Sprintf("type=bind, source=%s, target=/tmp",
   723  		hp), testutil.AlpineImage).AssertFail()
   724  	_, err = os.Stat(hp)
   725  	assert.ErrorIs(t, err, os.ErrNotExist)
   726  }