|
@@ -0,0 +1,195 @@
|
|
|
|
+// +build linux
|
|
|
|
+
|
|
|
|
+package mount
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "fmt"
|
|
|
|
+ "io/ioutil"
|
|
|
|
+ "os"
|
|
|
|
+ "strings"
|
|
|
|
+ "testing"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+func TestMount(t *testing.T) {
|
|
|
|
+ if os.Getuid() != 0 {
|
|
|
|
+ t.Skip("not root tests would fail")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ source, err := ioutil.TempDir("", "mount-test-source-")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ defer os.RemoveAll(source)
|
|
|
|
+
|
|
|
|
+ // Ensure we have a known start point by mounting tmpfs with given options
|
|
|
|
+ if err := Mount("tmpfs", source, "tmpfs", "private"); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ defer ensureUnmount(t, source)
|
|
|
|
+ validateMount(t, source, "", "")
|
|
|
|
+ if t.Failed() {
|
|
|
|
+ t.FailNow()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ target, err := ioutil.TempDir("", "mount-test-target-")
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ defer os.RemoveAll(target)
|
|
|
|
+
|
|
|
|
+ tests := []struct {
|
|
|
|
+ source string
|
|
|
|
+ ftype string
|
|
|
|
+ options string
|
|
|
|
+ expectedOpts string
|
|
|
|
+ expectedOptional string
|
|
|
|
+ }{
|
|
|
|
+ // No options
|
|
|
|
+ {"tmpfs", "tmpfs", "", "", ""},
|
|
|
|
+ // Default rw / ro test
|
|
|
|
+ {source, "", "bind", "", ""},
|
|
|
|
+ {source, "", "bind,private", "", ""},
|
|
|
|
+ {source, "", "bind,shared", "", "shared"},
|
|
|
|
+ {source, "", "bind,slave", "", "master"},
|
|
|
|
+ {source, "", "bind,unbindable", "", "unbindable"},
|
|
|
|
+ // Read Write tests
|
|
|
|
+ {source, "", "bind,rw", "rw", ""},
|
|
|
|
+ {source, "", "bind,rw,private", "rw", ""},
|
|
|
|
+ {source, "", "bind,rw,shared", "rw", "shared"},
|
|
|
|
+ {source, "", "bind,rw,slave", "rw", "master"},
|
|
|
|
+ {source, "", "bind,rw,unbindable", "rw", "unbindable"},
|
|
|
|
+ // Read Only tests
|
|
|
|
+ {source, "", "bind,ro", "ro", ""},
|
|
|
|
+ {source, "", "bind,ro,private", "ro", ""},
|
|
|
|
+ {source, "", "bind,ro,shared", "ro", "shared"},
|
|
|
|
+ {source, "", "bind,ro,slave", "ro", "master"},
|
|
|
|
+ {source, "", "bind,ro,unbindable", "ro", "unbindable"},
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, tc := range tests {
|
|
|
|
+ ftype, options := tc.ftype, tc.options
|
|
|
|
+ if tc.ftype == "" {
|
|
|
|
+ ftype = "none"
|
|
|
|
+ }
|
|
|
|
+ if tc.options == "" {
|
|
|
|
+ options = "none"
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ t.Run(fmt.Sprintf("%v-%v", ftype, options), func(t *testing.T) {
|
|
|
|
+ if strings.Contains(tc.options, "slave") {
|
|
|
|
+ // Slave requires a shared source
|
|
|
|
+ if err := MakeShared(source); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ defer func() {
|
|
|
|
+ if err := MakePrivate(source); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+ }
|
|
|
|
+ if err := Mount(tc.source, target, tc.ftype, tc.options); err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ defer ensureUnmount(t, target)
|
|
|
|
+ validateMount(t, target, tc.expectedOpts, tc.expectedOptional)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ensureUnmount umounts mnt checking for errors
|
|
|
|
+func ensureUnmount(t *testing.T, mnt string) {
|
|
|
|
+ if err := Unmount(mnt); err != nil {
|
|
|
|
+ t.Error(err)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// validateMount checks that mnt has the given options
|
|
|
|
+func validateMount(t *testing.T, mnt string, opts, optional string) {
|
|
|
|
+ info, err := GetMounts()
|
|
|
|
+ if err != nil {
|
|
|
|
+ t.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ wantedOpts := make(map[string]struct{})
|
|
|
|
+ if opts != "" {
|
|
|
|
+ for _, opt := range strings.Split(opts, ",") {
|
|
|
|
+ wantedOpts[opt] = struct{}{}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ wantedOptional := make(map[string]struct{})
|
|
|
|
+ if optional != "" {
|
|
|
|
+ for _, opt := range strings.Split(optional, ",") {
|
|
|
|
+ wantedOptional[opt] = struct{}{}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mnts := make(map[int]*Info, len(info))
|
|
|
|
+ for _, mi := range info {
|
|
|
|
+ mnts[mi.ID] = mi
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, mi := range info {
|
|
|
|
+ if mi.Mountpoint != mnt {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Use parent info as the defaults
|
|
|
|
+ p := mnts[mi.Parent]
|
|
|
|
+ pOpts := make(map[string]struct{})
|
|
|
|
+ if p.Opts != "" {
|
|
|
|
+ for _, opt := range strings.Split(p.Opts, ",") {
|
|
|
|
+ pOpts[clean(opt)] = struct{}{}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ pOptional := make(map[string]struct{})
|
|
|
|
+ if p.Optional != "" {
|
|
|
|
+ for _, field := range strings.Split(p.Optional, ",") {
|
|
|
|
+ pOptional[clean(field)] = struct{}{}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Validate Opts
|
|
|
|
+ if mi.Opts != "" {
|
|
|
|
+ for _, opt := range strings.Split(mi.Opts, ",") {
|
|
|
|
+ opt = clean(opt)
|
|
|
|
+ if !has(wantedOpts, opt) && !has(pOpts, opt) {
|
|
|
|
+ t.Errorf("unexpected mount option %q expected %q", opt, opts)
|
|
|
|
+ }
|
|
|
|
+ delete(wantedOpts, opt)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ for opt := range wantedOpts {
|
|
|
|
+ t.Errorf("missing mount option %q found %q", opt, mi.Opts)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Validate Optional
|
|
|
|
+ if mi.Optional != "" {
|
|
|
|
+ for _, field := range strings.Split(mi.Optional, ",") {
|
|
|
|
+ field = clean(field)
|
|
|
|
+ if !has(wantedOptional, field) && !has(pOptional, field) {
|
|
|
|
+ t.Errorf("unexpected optional failed %q expected %q", field, optional)
|
|
|
|
+ }
|
|
|
|
+ delete(wantedOptional, field)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ for field := range wantedOptional {
|
|
|
|
+ t.Errorf("missing optional field %q found %q", field, mi.Optional)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ t.Errorf("failed to find mount %q", mnt)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// clean strips off any value param after the colon
|
|
|
|
+func clean(v string) string {
|
|
|
|
+ return strings.SplitN(v, ":", 2)[0]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// has returns true if key is a member of m
|
|
|
|
+func has(m map[string]struct{}, key string) bool {
|
|
|
|
+ _, ok := m[key]
|
|
|
|
+ return ok
|
|
|
|
+}
|