瀏覽代碼

Merge pull request #8897 from vbatts/vbatts-mount_sharedsubtree

pkg/mount: sharedsubtree options and testing
Michael Crosby 10 年之前
父節點
當前提交
e4105e4535

+ 7 - 0
pkg/mount/flags.go

@@ -37,7 +37,14 @@ func parseOptions(options string) (int, string) {
 		"nodiratime":    {false, NODIRATIME},
 		"bind":          {false, BIND},
 		"rbind":         {false, RBIND},
+		"unbindable":    {false, UNBINDABLE},
+		"runbindable":   {false, RUNBINDABLE},
 		"private":       {false, PRIVATE},
+		"rprivate":      {false, RPRIVATE},
+		"shared":        {false, SHARED},
+		"rshared":       {false, RSHARED},
+		"slave":         {false, SLAVE},
+		"rslave":        {false, RSLAVE},
 		"relatime":      {false, RELATIME},
 		"norelatime":    {true, RELATIME},
 		"strictatime":   {false, STRICTATIME},

+ 7 - 0
pkg/mount/flags_freebsd.go

@@ -19,7 +19,14 @@ const (
 	MANDLOCK    = 0
 	NODEV       = 0
 	NODIRATIME  = 0
+	UNBINDABLE  = 0
+	RUNBINDABLE = 0
 	PRIVATE     = 0
+	RPRIVATE    = 0
+	SHARED      = 0
+	RSHARED     = 0
+	SLAVE       = 0
+	RSLAVE      = 0
 	RBIND       = 0
 	RELATIVE    = 0
 	RELATIME    = 0

+ 7 - 0
pkg/mount/flags_linux.go

@@ -17,7 +17,14 @@ const (
 	NODIRATIME  = syscall.MS_NODIRATIME
 	BIND        = syscall.MS_BIND
 	RBIND       = syscall.MS_BIND | syscall.MS_REC
+	UNBINDABLE  = syscall.MS_UNBINDABLE
+	RUNBINDABLE = syscall.MS_UNBINDABLE | syscall.MS_REC
 	PRIVATE     = syscall.MS_PRIVATE
+	RPRIVATE    = syscall.MS_PRIVATE | syscall.MS_REC
+	SLAVE       = syscall.MS_SLAVE
+	RSLAVE      = syscall.MS_SLAVE | syscall.MS_REC
+	SHARED      = syscall.MS_SHARED
+	RSHARED     = syscall.MS_SHARED | syscall.MS_REC
 	RELATIME    = syscall.MS_RELATIME
 	STRICTATIME = syscall.MS_STRICTATIME
 )

+ 7 - 0
pkg/mount/flags_unsupported.go

@@ -11,7 +11,14 @@ const (
 	NODIRATIME  = 0
 	NOEXEC      = 0
 	NOSUID      = 0
+	UNBINDABLE  = 0
+	RUNBINDABLE = 0
 	PRIVATE     = 0
+	RPRIVATE    = 0
+	SHARED      = 0
+	RSHARED     = 0
+	SLAVE       = 0
+	RSLAVE      = 0
 	RBIND       = 0
 	RELATIME    = 0
 	RELATIVE    = 0

+ 37 - 1
pkg/mount/sharedsubtree_linux.go

@@ -2,7 +2,39 @@
 
 package mount
 
+func MakeShared(mountPoint string) error {
+	return ensureMountedAs(mountPoint, "shared")
+}
+
+func MakeRShared(mountPoint string) error {
+	return ensureMountedAs(mountPoint, "rshared")
+}
+
 func MakePrivate(mountPoint string) error {
+	return ensureMountedAs(mountPoint, "private")
+}
+
+func MakeRPrivate(mountPoint string) error {
+	return ensureMountedAs(mountPoint, "rprivate")
+}
+
+func MakeSlave(mountPoint string) error {
+	return ensureMountedAs(mountPoint, "slave")
+}
+
+func MakeRSlave(mountPoint string) error {
+	return ensureMountedAs(mountPoint, "rslave")
+}
+
+func MakeUnbindable(mountPoint string) error {
+	return ensureMountedAs(mountPoint, "unbindable")
+}
+
+func MakeRUnbindable(mountPoint string) error {
+	return ensureMountedAs(mountPoint, "runbindable")
+}
+
+func ensureMountedAs(mountPoint, options string) error {
 	mounted, err := Mounted(mountPoint)
 	if err != nil {
 		return err
@@ -13,6 +45,10 @@ func MakePrivate(mountPoint string) error {
 			return err
 		}
 	}
+	mounted, err = Mounted(mountPoint)
+	if err != nil {
+		return err
+	}
 
-	return ForceMount("", mountPoint, "none", "private")
+	return ForceMount("", mountPoint, "none", options)
 }

+ 331 - 0
pkg/mount/sharedsubtree_linux_test.go

@@ -0,0 +1,331 @@
+// +build linux
+
+package mount
+
+import (
+	"os"
+	"path"
+	"syscall"
+	"testing"
+)
+
+// nothing is propogated in or out
+func TestSubtreePrivate(t *testing.T) {
+	tmp := path.Join(os.TempDir(), "mount-tests")
+	if err := os.MkdirAll(tmp, 0777); err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+
+	var (
+		sourceDir   = path.Join(tmp, "source")
+		targetDir   = path.Join(tmp, "target")
+		outside1Dir = path.Join(tmp, "outside1")
+		outside2Dir = path.Join(tmp, "outside2")
+
+		outside1Path      = path.Join(outside1Dir, "file.txt")
+		outside2Path      = path.Join(outside2Dir, "file.txt")
+		outside1CheckPath = path.Join(targetDir, "a", "file.txt")
+		outside2CheckPath = path.Join(sourceDir, "b", "file.txt")
+	)
+	if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.MkdirAll(path.Join(sourceDir, "b"), 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Mkdir(targetDir, 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Mkdir(outside1Dir, 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Mkdir(outside2Dir, 0777); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := createFile(outside1Path); err != nil {
+		t.Fatal(err)
+	}
+	if err := createFile(outside2Path); err != nil {
+		t.Fatal(err)
+	}
+
+	// mount the shared directory to a target
+	if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(targetDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// next, make the target private
+	if err := MakePrivate(targetDir); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(targetDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// mount in an outside path to a mounted path inside the _source_
+	if err := Mount(outside1Dir, path.Join(sourceDir, "a"), "none", "bind,rw"); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(path.Join(sourceDir, "a")); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// check that this file _does_not_ show in the _target_
+	if _, err := os.Stat(outside1CheckPath); err != nil && !os.IsNotExist(err) {
+		t.Fatal(err)
+	} else if err == nil {
+		t.Fatalf("%q should not be visible, but is", outside1CheckPath)
+	}
+
+	// next mount outside2Dir into the _target_
+	if err := Mount(outside2Dir, path.Join(targetDir, "b"), "none", "bind,rw"); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(path.Join(targetDir, "b")); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// check that this file _does_not_ show in the _source_
+	if _, err := os.Stat(outside2CheckPath); err != nil && !os.IsNotExist(err) {
+		t.Fatal(err)
+	} else if err == nil {
+		t.Fatalf("%q should not be visible, but is", outside2CheckPath)
+	}
+}
+
+// Testing that when a target is a shared mount,
+// then child mounts propogate to the source
+func TestSubtreeShared(t *testing.T) {
+	tmp := path.Join(os.TempDir(), "mount-tests")
+	if err := os.MkdirAll(tmp, 0777); err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+
+	var (
+		sourceDir  = path.Join(tmp, "source")
+		targetDir  = path.Join(tmp, "target")
+		outsideDir = path.Join(tmp, "outside")
+
+		outsidePath     = path.Join(outsideDir, "file.txt")
+		sourceCheckPath = path.Join(sourceDir, "a", "file.txt")
+	)
+
+	if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Mkdir(targetDir, 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Mkdir(outsideDir, 0777); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := createFile(outsidePath); err != nil {
+		t.Fatal(err)
+	}
+
+	// mount the source as shared
+	if err := MakeShared(sourceDir); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(sourceDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// mount the shared directory to a target
+	if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(targetDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// mount in an outside path to a mounted path inside the target
+	if err := Mount(outsideDir, path.Join(targetDir, "a"), "none", "bind,rw"); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(path.Join(targetDir, "a")); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// NOW, check that the file from the outside directory is avaible in the source directory
+	if _, err := os.Stat(sourceCheckPath); err != nil {
+		t.Fatal(err)
+	}
+}
+
+// testing that mounts to a shared source show up in the slave target,
+// and that mounts into a slave target do _not_ show up in the shared source
+func TestSubtreeSharedSlave(t *testing.T) {
+	tmp := path.Join(os.TempDir(), "mount-tests")
+	if err := os.MkdirAll(tmp, 0777); err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+
+	var (
+		sourceDir   = path.Join(tmp, "source")
+		targetDir   = path.Join(tmp, "target")
+		outside1Dir = path.Join(tmp, "outside1")
+		outside2Dir = path.Join(tmp, "outside2")
+
+		outside1Path      = path.Join(outside1Dir, "file.txt")
+		outside2Path      = path.Join(outside2Dir, "file.txt")
+		outside1CheckPath = path.Join(targetDir, "a", "file.txt")
+		outside2CheckPath = path.Join(sourceDir, "b", "file.txt")
+	)
+	if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.MkdirAll(path.Join(sourceDir, "b"), 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Mkdir(targetDir, 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Mkdir(outside1Dir, 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Mkdir(outside2Dir, 0777); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := createFile(outside1Path); err != nil {
+		t.Fatal(err)
+	}
+	if err := createFile(outside2Path); err != nil {
+		t.Fatal(err)
+	}
+
+	// mount the source as shared
+	if err := MakeShared(sourceDir); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(sourceDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// mount the shared directory to a target
+	if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(targetDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// next, make the target slave
+	if err := MakeSlave(targetDir); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(targetDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// mount in an outside path to a mounted path inside the _source_
+	if err := Mount(outside1Dir, path.Join(sourceDir, "a"), "none", "bind,rw"); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(path.Join(sourceDir, "a")); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// check that this file _does_ show in the _target_
+	if _, err := os.Stat(outside1CheckPath); err != nil {
+		t.Fatal(err)
+	}
+
+	// next mount outside2Dir into the _target_
+	if err := Mount(outside2Dir, path.Join(targetDir, "b"), "none", "bind,rw"); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(path.Join(targetDir, "b")); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// check that this file _does_not_ show in the _source_
+	if _, err := os.Stat(outside2CheckPath); err != nil && !os.IsNotExist(err) {
+		t.Fatal(err)
+	} else if err == nil {
+		t.Fatalf("%q should not be visible, but is", outside2CheckPath)
+	}
+}
+
+func TestSubtreeUnbindable(t *testing.T) {
+	tmp := path.Join(os.TempDir(), "mount-tests")
+	if err := os.MkdirAll(tmp, 0777); err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+
+	var (
+		sourceDir = path.Join(tmp, "source")
+		targetDir = path.Join(tmp, "target")
+	)
+	if err := os.MkdirAll(sourceDir, 0777); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.MkdirAll(targetDir, 0777); err != nil {
+		t.Fatal(err)
+	}
+
+	// next, make the source unbindable
+	if err := MakeUnbindable(sourceDir); err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := Unmount(sourceDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	// then attempt to mount it to target. It should fail
+	if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil && err != syscall.EINVAL {
+		t.Fatal(err)
+	} else if err == nil {
+		t.Fatalf("%q should not have been bindable")
+	}
+	defer func() {
+		if err := Unmount(targetDir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+}
+
+func createFile(path string) error {
+	f, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	f.WriteString("hello world!")
+	return f.Close()
+}