// +build linux package mount // import "github.com/docker/docker/pkg/mount" import ( "fmt" "io/ioutil" "os" "strings" "testing" ) func TestMount(t *testing.T) { if os.Getuid() != 0 { t.Skip("root required") } 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 expectedVFS 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", ""}, // Remount tests to change per filesystem options {"", "", "remount,size=128k", "rw", "", "rw,size=128k"}, {"", "", "remount,ro,size=128k", "ro", "", "ro,size=128k"}, } 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 strings.Contains(tc.options, "remount") { // create a new mount to remount first if err := Mount("tmpfs", target, "tmpfs", ""); 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, tc.expectedVFS) }) } } // 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, vfs string) { info, err := GetMounts(nil) 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{}{} } } wantedVFS := make(map[string]struct{}) if vfs != "" { for _, opt := range strings.Split(vfs, ",") { wantedVFS[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 field %q, expected %q", field, optional) } delete(wantedOptional, field) } } for field := range wantedOptional { t.Errorf("missing optional field %q, found %q", field, mi.Optional) } // Validate VFS if set if vfs != "" { if mi.VfsOpts != "" { for _, opt := range strings.Split(mi.VfsOpts, ",") { opt = clean(opt) if !has(wantedVFS, opt) && opt != "seclabel" { // can be added by selinux t.Errorf("unexpected vfs option %q, expected %q", opt, vfs) } delete(wantedVFS, opt) } } for opt := range wantedVFS { t.Errorf("missing vfs option %q, found %q", opt, mi.VfsOpts) } } 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 }