Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved mount detection using openat2 for kernel 5.10+ #109217

Merged
merged 3 commits into from Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/volume/local/local_test.go
Expand Up @@ -370,7 +370,7 @@ func TestMountUnmount(t *testing.T) {
}

if runtime.GOOS != "windows" {
// skip this check in windows since the "bind mount" logic is implemented differently in mount_wiondows.go
// skip this check in windows since the "bind mount" logic is implemented differently in mount_windows.go
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
t.Errorf("SetUp() failed, volume path not created: %s", path)
Expand Down
1 change: 1 addition & 0 deletions staging/src/k8s.io/legacy-cloud-providers/go.mod
Expand Up @@ -70,6 +70,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/moby/sys/mountinfo v0.6.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
Expand Down
2 changes: 2 additions & 0 deletions staging/src/k8s.io/legacy-cloud-providers/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions staging/src/k8s.io/mount-utils/fake_mounter.go
Expand Up @@ -222,6 +222,14 @@ func (f *FakeMounter) canSafelySkipMountPointCheck() bool {
return f.skipMountPointCheck
}

func (f *FakeMounter) IsMountPoint(file string) (bool, error) {
notMnt, err := f.IsLikelyNotMountPoint(file)
if err != nil {
return false, err
}
return !notMnt, nil
}

// GetMountRefs finds all mount references to the path, returns a
// list of paths.
func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) {
Expand Down
2 changes: 2 additions & 0 deletions staging/src/k8s.io/mount-utils/go.mod
Expand Up @@ -5,6 +5,7 @@ module k8s.io/mount-utils
go 1.18

require (
github.com/moby/sys/mountinfo v0.6.0
github.com/stretchr/testify v1.7.0
k8s.io/klog/v2 v2.70.1
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
Expand All @@ -16,6 +17,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Expand Down
5 changes: 5 additions & 0 deletions staging/src/k8s.io/mount-utils/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 26 additions & 47 deletions staging/src/k8s.io/mount-utils/mount.go
Expand Up @@ -21,7 +21,6 @@ package mount

import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
Expand Down Expand Up @@ -70,6 +69,14 @@ type Interface interface {
// operations for targets that are not mount points. If this returns true, no such
// errors will be returned.
canSafelySkipMountPointCheck() bool
// IsMountPoint determines if a directory is a mountpoint.
// It should return ErrNotExist when the directory does not exist.
// IsMountPoint is more expensive than IsLikelyNotMountPoint.
// IsMountPoint detects bind mounts in linux.
// IsMountPoint may enumerate all the mountpoints using List() and
// the list of mountpoints may be large, then it uses
// isMountPointMatch to evaluate whether the directory is a mountpoint.
IsMountPoint(file string) (bool, error)
manugupt1 marked this conversation as resolved.
Show resolved Hide resolved
// GetMountRefs finds all mount references to pathname, returning a slice of
// paths. Pathname can be a mountpoint path or a normal directory
// (for bind mount). On Linux, pathname is excluded from the slice.
Expand Down Expand Up @@ -191,6 +198,24 @@ func getMountRefsByDev(mounter Interface, mountPath string) ([]string, error) {
return refs, nil
}

// IsNotMountPoint determines if a directory is a mountpoint.
// It should return ErrNotExist when the directory does not exist.
// IsNotMountPoint is more expensive than IsLikelyNotMountPoint
// and depends on IsMountPoint.
//
// If an error occurs, it returns true (assuming it is not a mountpoint)
// when ErrNotExist is returned for callers similar to IsLikelyNotMountPoint.
//
// Deprecated: This function is kept to keep changes backward compatible with
// previous library version. Callers should prefer mounter.IsMountPoint.
func IsNotMountPoint(mounter Interface, file string) (bool, error) {
isMnt, err := mounter.IsMountPoint(file)
if err != nil {
return true, err
}
return !isMnt, nil
}

// GetDeviceNameFromMount given a mnt point, find the device from /proc/mounts
// returns the device name, reference count, and error code.
func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, error) {
Expand Down Expand Up @@ -224,52 +249,6 @@ func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, e
return device, refCount, nil
}

// IsNotMountPoint determines if a directory is a mountpoint.
// It should return ErrNotExist when the directory does not exist.
// IsNotMountPoint is more expensive than IsLikelyNotMountPoint.
// IsNotMountPoint detects bind mounts in linux.
// IsNotMountPoint enumerates all the mountpoints using List() and
// the list of mountpoints may be large, then it uses
// isMountPointMatch to evaluate whether the directory is a mountpoint.
func IsNotMountPoint(mounter Interface, file string) (bool, error) {
// IsLikelyNotMountPoint provides a quick check
// to determine whether file IS A mountpoint.
notMnt, notMntErr := mounter.IsLikelyNotMountPoint(file)
if notMntErr != nil && os.IsPermission(notMntErr) {
// We were not allowed to do the simple stat() check, e.g. on NFS with
// root_squash. Fall back to /proc/mounts check below.
notMnt = true
notMntErr = nil
}
if notMntErr != nil {
return notMnt, notMntErr
}
// identified as mountpoint, so return this fact.
if notMnt == false {
return notMnt, nil
}

// Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts.
resolvedFile, err := filepath.EvalSymlinks(file)
if err != nil {
return true, err
}

// check all mountpoints since IsLikelyNotMountPoint
// is not reliable for some mountpoint types.
mountPoints, mountPointsErr := mounter.List()
if mountPointsErr != nil {
return notMnt, mountPointsErr
}
for _, mp := range mountPoints {
if isMountPointMatch(mp, resolvedFile) {
notMnt = false
break
}
}
manugupt1 marked this conversation as resolved.
Show resolved Hide resolved
return notMnt, nil
}

// MakeBindOpts detects whether a bind mount is being requested and makes the remount options to
// use in case of bind mount, due to the fact that bind mount doesn't respect mount options.
// The list equals:
Expand Down
8 changes: 7 additions & 1 deletion staging/src/k8s.io/mount-utils/mount_helper_test.go
Expand Up @@ -55,12 +55,16 @@ func TestDoCleanupMountPoint(t *testing.T) {
}
return MountPoint{Device: "/dev/sdb", Path: path}, nil, nil
},
corruptedMnt: false,
expectErr: false,
},
"path-not-exist": {
prepareMnt: func(base string) (MountPoint, error, error) {
path := filepath.Join(base, testMount)
return MountPoint{Device: "/dev/sdb", Path: path}, nil, nil
},
corruptedMnt: false,
expectErr: false,
},
"mount-corrupted": {
prepareMnt: func(base string) (MountPoint, error, error) {
Expand All @@ -71,6 +75,7 @@ func TestDoCleanupMountPoint(t *testing.T) {
return MountPoint{Device: "/dev/sdb", Path: path}, os.NewSyscallError("fake", syscall.ESTALE), nil
},
corruptedMnt: true,
expectErr: false,
},
"mount-err-not-corrupted": {
prepareMnt: func(base string) (MountPoint, error, error) {
Expand All @@ -80,7 +85,8 @@ func TestDoCleanupMountPoint(t *testing.T) {
}
return MountPoint{Device: "/dev/sdb", Path: path}, os.NewSyscallError("fake", syscall.ETIMEDOUT), nil
},
expectErr: true,
corruptedMnt: false,
expectErr: true,
},
"skip-mount-point-check": {
prepareMnt: func(base string) (MountPoint, error, error) {
Expand Down
60 changes: 60 additions & 0 deletions staging/src/k8s.io/mount-utils/mount_linux.go
Expand Up @@ -21,7 +21,10 @@ package mount

import (
"context"
"errors"
"fmt"
"github.com/moby/sys/mountinfo"
"io/fs"
"io/ioutil"
"os"
"os/exec"
Expand Down Expand Up @@ -692,6 +695,63 @@ func SearchMountPoints(hostSource, mountInfoPath string) ([]string, error) {
return refs, nil
}

// IsMountPoint determines if a file is a mountpoint.
// It first detects bind & any other mountpoints using
// MountedFast function. If the MountedFast function returns
// sure as true and err as nil, then a mountpoint is detected
// successfully. When an error is returned by MountedFast, the
// following is true:
// 1. All errors are returned with IsMountPoint as false
// except os.IsPermission.
// 2. When os.IsPermission is returned by MountedFast, List()
// is called to confirm if the given file is a mountpoint are not.
//
// os.ErrNotExist should always be returned if a file does not exist
// as callers have in past relied on this error and not fallback.
//
// When MountedFast returns sure as false and err as nil (eg: in
// case of bindmounts on kernel version 5.10- ); mounter.List()
// endpoint is called to enumerate all the mountpoints and check if
// it is mountpoint match or not.
func (mounter *Mounter) IsMountPoint(file string) (bool, error) {
isMnt, sure, isMntErr := mountinfo.MountedFast(file)
manugupt1 marked this conversation as resolved.
Show resolved Hide resolved
if sure && isMntErr == nil {
return isMnt, nil
}
if isMntErr != nil {
if errors.Is(isMntErr, fs.ErrNotExist) {
manugupt1 marked this conversation as resolved.
Show resolved Hide resolved
return false, fs.ErrNotExist
}
// We were not allowed to do the simple stat() check, e.g. on NFS with
// root_squash. Fall back to /proc/mounts check below when
// fs.ErrPermission is returned.
if !errors.Is(isMntErr, fs.ErrPermission) {
return false, isMntErr
manugupt1 marked this conversation as resolved.
Show resolved Hide resolved
}
}
// Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts.
resolvedFile, err := filepath.EvalSymlinks(file)
manugupt1 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
if errors.Is(isMntErr, fs.ErrNotExist) {
return false, fs.ErrNotExist
}
return false, err
}

// check all mountpoints since MountedFast is not sure.
// is not reliable for some mountpoint types.
mountPoints, mountPointsErr := mounter.List()
if mountPointsErr != nil {
return false, mountPointsErr
}
for _, mp := range mountPoints {
if isMountPointMatch(mp, resolvedFile) {
return true, nil
}
}
return false, nil
manugupt1 marked this conversation as resolved.
Show resolved Hide resolved
}

// tryUnmount calls plain "umount" and waits for unmountTimeout for it to finish.
func tryUnmount(path string, unmountTimeout time.Duration) error {
klog.V(4).Infof("Unmounting %s", path)
Expand Down
12 changes: 12 additions & 0 deletions staging/src/k8s.io/mount-utils/mount_unsupported.go
Expand Up @@ -79,6 +79,12 @@ func (mounter *Mounter) canSafelySkipMountPointCheck() bool {
return false
}

// IsMountPoint determines if a directory is a mountpoint.
// It always returns an error on unsupported platforms.
func (mounter *Mounter) IsMountPoint(file string) (bool, error) {
return false, errUnsupported
}

// GetMountRefs always returns an error on unsupported platforms
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
return nil, errUnsupported
Expand All @@ -91,3 +97,9 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target
func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) {
return true, errUnsupported
}

// IsMountPoint determines if a directory is a mountpoint.
// It always returns an error on unsupported platforms.
func (mounter *SafeFormatAndMount) IsMountPoint(file string) (bool, error) {
return false, errUnsupported
}
9 changes: 9 additions & 0 deletions staging/src/k8s.io/mount-utils/mount_windows.go
Expand Up @@ -249,6 +249,15 @@ func (mounter *Mounter) canSafelySkipMountPointCheck() bool {
return false
}

// IsMountPoint: determines if a directory is a mountpoint.
func (mounter *Mounter) IsMountPoint(file string) (bool, error) {
isNotMnt, err := mounter.IsLikelyNotMountPoint(file)
if err != nil {
return false, err
}
return !isNotMnt, nil
}

// GetMountRefs : empty implementation here since there is no place to query all mount points on Windows
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
windowsPath := NormalizeWindowsPath(pathname)
Expand Down