diff --git a/pkg/util/mount/mount.go b/pkg/util/mount/mount.go index 1c09e157f8..953b571900 100644 --- a/pkg/util/mount/mount.go +++ b/pkg/util/mount/mount.go @@ -19,12 +19,7 @@ limitations under the License. package mount import ( - "fmt" - "path" "path/filepath" - "strings" - - "github.com/golang/glog" ) type FileType string @@ -138,41 +133,6 @@ func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string, return mounter.formatAndMount(source, target, fstype, options) } -// GetMountRefs finds all other references to the device referenced -// by mountPath; returns a list of paths. -func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { - mps, err := mounter.List() - if err != nil { - return nil, err - } - // Find the device name. - deviceName := "" - // If mountPath is symlink, need get its target path. - slTarget, err := filepath.EvalSymlinks(mountPath) - if err != nil { - slTarget = mountPath - } - for i := range mps { - if mps[i].Path == slTarget { - deviceName = mps[i].Device - break - } - } - - // Find all references to the device. - var refs []string - if deviceName == "" { - glog.Warningf("could not determine device for path: %q", mountPath) - } else { - for i := range mps { - if mps[i].Device == deviceName && mps[i].Path != slTarget { - refs = append(refs, mps[i].Path) - } - } - } - return refs, nil -} - // GetMountRefsByDev finds all references to the device provided // by mountPath; returns a list of paths. func GetMountRefsByDev(mounter Interface, mountPath string) ([]string, error) { @@ -239,34 +199,6 @@ func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, e return device, refCount, nil } -// getDeviceNameFromMount find the device name from /proc/mounts in which -// the mount path reference should match the given plugin directory. In case no mount path reference -// matches, returns the volume name taken from its given mountPath -func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { - refs, err := GetMountRefs(mounter, mountPath) - if err != nil { - glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) - return "", err - } - if len(refs) == 0 { - glog.V(4).Infof("Directory %s is not mounted", mountPath) - return "", fmt.Errorf("directory %s is not mounted", mountPath) - } - basemountPath := path.Join(pluginDir, MountsInGlobalPDPath) - for _, ref := range refs { - if strings.HasPrefix(ref, basemountPath) { - volumeID, err := filepath.Rel(basemountPath, ref) - if err != nil { - glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) - return "", err - } - return volumeID, nil - } - } - - return path.Base(mountPath), nil -} - // IsNotMountPoint determines if a directory is a mountpoint. // It should return ErrNotExist when the directory does not exist. // This method uses the List() of all mountpoints diff --git a/pkg/util/mount/mount_linux.go b/pkg/util/mount/mount_linux.go index 8976bd2554..9caec6707b 100644 --- a/pkg/util/mount/mount_linux.go +++ b/pkg/util/mount/mount_linux.go @@ -22,6 +22,8 @@ import ( "fmt" "os" "os/exec" + "path" + "path/filepath" "strconv" "strings" "syscall" @@ -141,6 +143,41 @@ func (m *Mounter) doMount(mounterPath string, mountCmd string, source string, ta return err } +// GetMountRefs finds all other references to the device referenced +// by mountPath; returns a list of paths. +func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { + mps, err := mounter.List() + if err != nil { + return nil, err + } + // Find the device name. + deviceName := "" + // If mountPath is symlink, need get its target path. + slTarget, err := filepath.EvalSymlinks(mountPath) + if err != nil { + slTarget = mountPath + } + for i := range mps { + if mps[i].Path == slTarget { + deviceName = mps[i].Device + break + } + } + + // Find all references to the device. + var refs []string + if deviceName == "" { + glog.Warningf("could not determine device for path: %q", mountPath) + } else { + for i := range mps { + if mps[i].Device == deviceName && mps[i].Path != slTarget { + refs = append(refs, mps[i].Path) + } + } + } + return refs, nil +} + // detectSystemd returns true if OS runs with systemd as init. When not sure // (permission errors, ...), it returns false. // There may be different ways how to detect systemd, this one makes sure that @@ -299,6 +336,34 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str return getDeviceNameFromMount(mounter, mountPath, pluginDir) } +// getDeviceNameFromMount find the device name from /proc/mounts in which +// the mount path reference should match the given plugin directory. In case no mount path reference +// matches, returns the volume name taken from its given mountPath +func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { + refs, err := GetMountRefs(mounter, mountPath) + if err != nil { + glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err + } + if len(refs) == 0 { + glog.V(4).Infof("Directory %s is not mounted", mountPath) + return "", fmt.Errorf("directory %s is not mounted", mountPath) + } + basemountPath := path.Join(pluginDir, MountsInGlobalPDPath) + for _, ref := range refs { + if strings.HasPrefix(ref, basemountPath) { + volumeID, err := filepath.Rel(basemountPath, ref) + if err != nil { + glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) + return "", err + } + return volumeID, nil + } + } + + return path.Base(mountPath), nil +} + func listProcMounts(mountFilePath string) ([]MountPoint, error) { content, err := utilio.ConsistentRead(mountFilePath, maxListTries) if err != nil { diff --git a/pkg/util/mount/mount_unsupported.go b/pkg/util/mount/mount_unsupported.go index 865d53d09e..244c22c04c 100644 --- a/pkg/util/mount/mount_unsupported.go +++ b/pkg/util/mount/mount_unsupported.go @@ -43,6 +43,12 @@ func (mounter *Mounter) Unmount(target string) error { return nil } +// GetMountRefs finds all other references to the device referenced +// by mountPath; returns a list of paths. +func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { + return []string{}, nil +} + func (mounter *Mounter) List() ([]MountPoint, error) { return []MountPoint{}, nil } @@ -63,6 +69,10 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str return "", nil } +func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { + return "", nil +} + func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { return false, nil } diff --git a/pkg/util/mount/mount_windows.go b/pkg/util/mount/mount_windows.go index 50c95caa52..7b2b56f818 100644 --- a/pkg/util/mount/mount_windows.go +++ b/pkg/util/mount/mount_windows.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "os/exec" + "path" "path/filepath" "strconv" "strings" @@ -118,6 +119,16 @@ func (mounter *Mounter) Unmount(target string) error { return nil } +// GetMountRefs finds all other references to the device(drive) referenced +// by mountPath; returns a list of paths. +func GetMountRefs(mounter Interface, mountPath string) ([]string, error) { + refs, err := getAllParentLinks(normalizeWindowsPath(mountPath)) + if err != nil { + return nil, err + } + return refs, nil +} + // List returns a list of all mounted filesystems. todo func (mounter *Mounter) List() ([]MountPoint, error) { return []MountPoint{}, nil @@ -152,6 +163,33 @@ func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (str return getDeviceNameFromMount(mounter, mountPath, pluginDir) } +// getDeviceNameFromMount find the device(drive) name in which +// the mount path reference should match the given plugin directory. In case no mount path reference +// matches, returns the volume name taken from its given mountPath +func getDeviceNameFromMount(mounter Interface, mountPath, pluginDir string) (string, error) { + refs, err := GetMountRefs(mounter, mountPath) + if err != nil { + glog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err) + return "", err + } + if len(refs) == 0 { + return "", fmt.Errorf("directory %s is not mounted", mountPath) + } + basemountPath := normalizeWindowsPath(path.Join(pluginDir, MountsInGlobalPDPath)) + for _, ref := range refs { + if strings.Contains(ref, basemountPath) { + volumeID, err := filepath.Rel(normalizeWindowsPath(basemountPath), ref) + if err != nil { + glog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err) + return "", err + } + return volumeID, nil + } + } + + return path.Base(mountPath), nil +} + // DeviceOpened determines if the device is in use elsewhere func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { return false, nil @@ -300,3 +338,30 @@ func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) { } return string(output)[:1], nil } + +// getAllParentLinks walks all symbolic links and return all the parent targets recursively +func getAllParentLinks(path string) ([]string, error) { + const maxIter = 255 + links := []string{} + for { + links = append(links, path) + if len(links) > maxIter { + return links, fmt.Errorf("unexpected length of parent links: %v", links) + } + + fi, err := os.Lstat(path) + if err != nil { + return links, fmt.Errorf("Lstat: %v", err) + } + if fi.Mode()&os.ModeSymlink == 0 { + break + } + + path, err = os.Readlink(path) + if err != nil { + return links, fmt.Errorf("Readlink error: %v", err) + } + } + + return links, nil +} diff --git a/pkg/util/mount/mount_windows_test.go b/pkg/util/mount/mount_windows_test.go index 6be0fc43e1..24b05eb1c2 100644 --- a/pkg/util/mount/mount_windows_test.go +++ b/pkg/util/mount/mount_windows_test.go @@ -19,6 +19,8 @@ limitations under the License. package mount import ( + "fmt" + "os/exec" "testing" ) @@ -69,3 +71,70 @@ func TestValidateDiskNumber(t *testing.T) { t.Errorf("TestValidateDiskNumber test failed, disk number : %s", diskNum) } } + +func makeLink(link, target string) error { + if output, err := exec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput(); err != nil { + return fmt.Errorf("mklink failed: %v, link(%q) target(%q) output: %q", err, link, target, string(output)) + } + return nil +} + +func removeLink(link string) error { + if output, err := exec.Command("cmd", "/c", "rmdir", link).CombinedOutput(); err != nil { + return fmt.Errorf("rmdir failed: %v, output: %q", err, string(output)) + } + return nil +} + +func setEquivalent(set1, set2 []string) bool { + map1 := make(map[string]bool) + map2 := make(map[string]bool) + for _, s := range set1 { + map1[s] = true + } + for _, s := range set2 { + map2[s] = true + } + + for s := range map1 { + if !map2[s] { + return false + } + } + for s := range map2 { + if !map1[s] { + return false + } + } + return true +} + +// this func must run in admin mode, otherwise it will fail +func TestGetMountRefs(t *testing.T) { + fm := &FakeMounter{MountPoints: []MountPoint{}} + mountPath := `c:\secondmountpath` + expectedRefs := []string{`c:\`, `c:\firstmountpath`, mountPath} + + // remove symbolic links first + for i := 1; i < len(expectedRefs); i++ { + removeLink(expectedRefs[i]) + } + + // create symbolic links + for i := 1; i < len(expectedRefs); i++ { + if err := makeLink(expectedRefs[i], expectedRefs[i-1]); err != nil { + t.Errorf("makeLink failed: %v", err) + } + } + + if refs, err := GetMountRefs(fm, mountPath); err != nil || !setEquivalent(expectedRefs, refs) { + t.Errorf("getMountRefs(%q) = %v, error: %v; expected %v", mountPath, refs, err, expectedRefs) + } + + // remove symbolic links + for i := 1; i < len(expectedRefs); i++ { + if err := removeLink(expectedRefs[i]); err != nil { + t.Errorf("removeLink failed: %v", err) + } + } +}