Merge pull request #8897 from vbatts/vbatts-mount_sharedsubtree
pkg/mount: sharedsubtree options and testing
This commit is contained in:
commit
2533456bd3
6 changed files with 396 additions and 1 deletions
|
@ -37,7 +37,14 @@ func parseOptions(options string) (int, string) {
|
||||||
"nodiratime": {false, NODIRATIME},
|
"nodiratime": {false, NODIRATIME},
|
||||||
"bind": {false, BIND},
|
"bind": {false, BIND},
|
||||||
"rbind": {false, RBIND},
|
"rbind": {false, RBIND},
|
||||||
|
"unbindable": {false, UNBINDABLE},
|
||||||
|
"runbindable": {false, RUNBINDABLE},
|
||||||
"private": {false, PRIVATE},
|
"private": {false, PRIVATE},
|
||||||
|
"rprivate": {false, RPRIVATE},
|
||||||
|
"shared": {false, SHARED},
|
||||||
|
"rshared": {false, RSHARED},
|
||||||
|
"slave": {false, SLAVE},
|
||||||
|
"rslave": {false, RSLAVE},
|
||||||
"relatime": {false, RELATIME},
|
"relatime": {false, RELATIME},
|
||||||
"norelatime": {true, RELATIME},
|
"norelatime": {true, RELATIME},
|
||||||
"strictatime": {false, STRICTATIME},
|
"strictatime": {false, STRICTATIME},
|
||||||
|
|
|
@ -19,7 +19,14 @@ const (
|
||||||
MANDLOCK = 0
|
MANDLOCK = 0
|
||||||
NODEV = 0
|
NODEV = 0
|
||||||
NODIRATIME = 0
|
NODIRATIME = 0
|
||||||
|
UNBINDABLE = 0
|
||||||
|
RUNBINDABLE = 0
|
||||||
PRIVATE = 0
|
PRIVATE = 0
|
||||||
|
RPRIVATE = 0
|
||||||
|
SHARED = 0
|
||||||
|
RSHARED = 0
|
||||||
|
SLAVE = 0
|
||||||
|
RSLAVE = 0
|
||||||
RBIND = 0
|
RBIND = 0
|
||||||
RELATIVE = 0
|
RELATIVE = 0
|
||||||
RELATIME = 0
|
RELATIME = 0
|
||||||
|
|
|
@ -17,7 +17,14 @@ const (
|
||||||
NODIRATIME = syscall.MS_NODIRATIME
|
NODIRATIME = syscall.MS_NODIRATIME
|
||||||
BIND = syscall.MS_BIND
|
BIND = syscall.MS_BIND
|
||||||
RBIND = syscall.MS_BIND | syscall.MS_REC
|
RBIND = syscall.MS_BIND | syscall.MS_REC
|
||||||
|
UNBINDABLE = syscall.MS_UNBINDABLE
|
||||||
|
RUNBINDABLE = syscall.MS_UNBINDABLE | syscall.MS_REC
|
||||||
PRIVATE = syscall.MS_PRIVATE
|
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
|
RELATIME = syscall.MS_RELATIME
|
||||||
STRICTATIME = syscall.MS_STRICTATIME
|
STRICTATIME = syscall.MS_STRICTATIME
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,7 +11,14 @@ const (
|
||||||
NODIRATIME = 0
|
NODIRATIME = 0
|
||||||
NOEXEC = 0
|
NOEXEC = 0
|
||||||
NOSUID = 0
|
NOSUID = 0
|
||||||
|
UNBINDABLE = 0
|
||||||
|
RUNBINDABLE = 0
|
||||||
PRIVATE = 0
|
PRIVATE = 0
|
||||||
|
RPRIVATE = 0
|
||||||
|
SHARED = 0
|
||||||
|
RSHARED = 0
|
||||||
|
SLAVE = 0
|
||||||
|
RSLAVE = 0
|
||||||
RBIND = 0
|
RBIND = 0
|
||||||
RELATIME = 0
|
RELATIME = 0
|
||||||
RELATIVE = 0
|
RELATIVE = 0
|
||||||
|
|
|
@ -2,7 +2,39 @@
|
||||||
|
|
||||||
package mount
|
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 {
|
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)
|
mounted, err := Mounted(mountPoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -13,6 +45,10 @@ func MakePrivate(mountPoint string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mounted, err = Mounted(mountPoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return ForceMount("", mountPoint, "none", "private")
|
return ForceMount("", mountPoint, "none", options)
|
||||||
}
|
}
|
||||||
|
|
331
mount/sharedsubtree_linux_test.go
Normal file
331
mount/sharedsubtree_linux_test.go
Normal file
|
@ -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()
|
||||||
|
}
|
Loading…
Reference in a new issue