Merge pull request #109217 from manugupt1/improve-mount-detection

Improved mount detection using openat2 for kernel 5.10+
This commit is contained in:
Kubernetes Prow Robot 2022-07-27 11:34:28 -07:00 committed by GitHub
commit c27e82604e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 133 additions and 49 deletions

View File

@ -370,7 +370,7 @@ func TestMountUnmount(t *testing.T) {
} }
if runtime.GOOS != "windows" { 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 _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
t.Errorf("SetUp() failed, volume path not created: %s", path) t.Errorf("SetUp() failed, volume path not created: %s", path)

View File

@ -70,6 +70,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect

View File

@ -282,6 +282,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/sys/mountinfo v0.6.0 h1:gUDhXQx58YNrpHlK4nSL+7y2pxFZkUcXqzFDKWdC0Oo=
github.com/moby/sys/mountinfo v0.6.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

View File

@ -222,6 +222,14 @@ func (f *FakeMounter) canSafelySkipMountPointCheck() bool {
return f.skipMountPointCheck 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 // GetMountRefs finds all mount references to the path, returns a
// list of paths. // list of paths.
func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) { func (f *FakeMounter) GetMountRefs(pathname string) ([]string, error) {

View File

@ -5,6 +5,7 @@ module k8s.io/mount-utils
go 1.18 go 1.18
require ( require (
github.com/moby/sys/mountinfo v0.6.0
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
k8s.io/klog/v2 v2.70.1 k8s.io/klog/v2 v2.70.1
k8s.io/utils v0.0.0-20220725171434-9bab9ef40391 k8s.io/utils v0.0.0-20220725171434-9bab9ef40391
@ -16,6 +17,7 @@ require (
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pmezard/go-difflib v1.0.0 // 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/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View File

@ -10,6 +10,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/moby/sys/mountinfo v0.6.0 h1:gUDhXQx58YNrpHlK4nSL+7y2pxFZkUcXqzFDKWdC0Oo=
github.com/moby/sys/mountinfo v0.6.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -19,6 +21,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=

View File

@ -21,7 +21,6 @@ package mount
import ( import (
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -70,6 +69,14 @@ type Interface interface {
// operations for targets that are not mount points. If this returns true, no such // operations for targets that are not mount points. If this returns true, no such
// errors will be returned. // errors will be returned.
canSafelySkipMountPointCheck() bool 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)
// GetMountRefs finds all mount references to pathname, returning a slice of // GetMountRefs finds all mount references to pathname, returning a slice of
// paths. Pathname can be a mountpoint path or a normal directory // paths. Pathname can be a mountpoint path or a normal directory
// (for bind mount). On Linux, pathname is excluded from the slice. // (for bind mount). On Linux, pathname is excluded from the slice.
@ -191,6 +198,24 @@ func getMountRefsByDev(mounter Interface, mountPath string) ([]string, error) {
return refs, nil 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 // GetDeviceNameFromMount given a mnt point, find the device from /proc/mounts
// returns the device name, reference count, and error code. // returns the device name, reference count, and error code.
func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, error) { func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, error) {
@ -224,52 +249,6 @@ func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, e
return device, refCount, nil 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
}
}
return notMnt, nil
}
// MakeBindOpts detects whether a bind mount is being requested and makes the remount options to // 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. // use in case of bind mount, due to the fact that bind mount doesn't respect mount options.
// The list equals: // The list equals:

View File

@ -55,12 +55,16 @@ func TestDoCleanupMountPoint(t *testing.T) {
} }
return MountPoint{Device: "/dev/sdb", Path: path}, nil, nil return MountPoint{Device: "/dev/sdb", Path: path}, nil, nil
}, },
corruptedMnt: false,
expectErr: false,
}, },
"path-not-exist": { "path-not-exist": {
prepareMnt: func(base string) (MountPoint, error, error) { prepareMnt: func(base string) (MountPoint, error, error) {
path := filepath.Join(base, testMount) path := filepath.Join(base, testMount)
return MountPoint{Device: "/dev/sdb", Path: path}, nil, nil return MountPoint{Device: "/dev/sdb", Path: path}, nil, nil
}, },
corruptedMnt: false,
expectErr: false,
}, },
"mount-corrupted": { "mount-corrupted": {
prepareMnt: func(base string) (MountPoint, error, error) { prepareMnt: func(base string) (MountPoint, error, error) {
@ -71,6 +75,7 @@ func TestDoCleanupMountPoint(t *testing.T) {
return MountPoint{Device: "/dev/sdb", Path: path}, os.NewSyscallError("fake", syscall.ESTALE), nil return MountPoint{Device: "/dev/sdb", Path: path}, os.NewSyscallError("fake", syscall.ESTALE), nil
}, },
corruptedMnt: true, corruptedMnt: true,
expectErr: false,
}, },
"mount-err-not-corrupted": { "mount-err-not-corrupted": {
prepareMnt: func(base string) (MountPoint, error, error) { prepareMnt: func(base string) (MountPoint, error, error) {
@ -80,7 +85,8 @@ func TestDoCleanupMountPoint(t *testing.T) {
} }
return MountPoint{Device: "/dev/sdb", Path: path}, os.NewSyscallError("fake", syscall.ETIMEDOUT), nil return MountPoint{Device: "/dev/sdb", Path: path}, os.NewSyscallError("fake", syscall.ETIMEDOUT), nil
}, },
expectErr: true, corruptedMnt: false,
expectErr: true,
}, },
"skip-mount-point-check": { "skip-mount-point-check": {
prepareMnt: func(base string) (MountPoint, error, error) { prepareMnt: func(base string) (MountPoint, error, error) {

View File

@ -21,7 +21,10 @@ package mount
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/moby/sys/mountinfo"
"io/fs"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
@ -692,6 +695,63 @@ func SearchMountPoints(hostSource, mountInfoPath string) ([]string, error) {
return refs, nil 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)
if sure && isMntErr == nil {
return isMnt, nil
}
if isMntErr != nil {
if errors.Is(isMntErr, fs.ErrNotExist) {
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
}
}
// 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 {
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
}
// tryUnmount calls plain "umount" and waits for unmountTimeout for it to finish. // tryUnmount calls plain "umount" and waits for unmountTimeout for it to finish.
func tryUnmount(path string, unmountTimeout time.Duration) error { func tryUnmount(path string, unmountTimeout time.Duration) error {
klog.V(4).Infof("Unmounting %s", path) klog.V(4).Infof("Unmounting %s", path)

View File

@ -79,6 +79,12 @@ func (mounter *Mounter) canSafelySkipMountPointCheck() bool {
return false 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 // GetMountRefs always returns an error on unsupported platforms
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
return nil, errUnsupported return nil, errUnsupported
@ -91,3 +97,9 @@ func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target
func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) { func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) {
return true, errUnsupported 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
}

View File

@ -249,6 +249,15 @@ func (mounter *Mounter) canSafelySkipMountPointCheck() bool {
return false 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 // GetMountRefs : empty implementation here since there is no place to query all mount points on Windows
func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
windowsPath := NormalizeWindowsPath(pathname) windowsPath := NormalizeWindowsPath(pathname)