diff --git a/pkg/util/mount/BUILD b/pkg/util/mount/BUILD index 3b2a78a5651..9040cd93b26 100644 --- a/pkg/util/mount/BUILD +++ b/pkg/util/mount/BUILD @@ -102,6 +102,7 @@ go_test( ] + select({ "@io_bazel_rules_go//go/platform:linux": [ "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/utils/exec:go_default_library", ], "@io_bazel_rules_go//go/platform:windows": [ "//vendor/github.com/stretchr/testify/assert:go_default_library", diff --git a/pkg/util/mount/mount.go b/pkg/util/mount/mount.go index f30db7692e8..cbc083dac0c 100644 --- a/pkg/util/mount/mount.go +++ b/pkg/util/mount/mount.go @@ -19,6 +19,7 @@ limitations under the License. package mount import ( + "fmt" "os" "path/filepath" "strings" @@ -337,3 +338,37 @@ func startsWithBackstep(rel string) bool { // normalize to / and check for ../ return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../") } + +// getFileType checks for file/directory/socket and block/character devices +func getFileType(pathname string) (FileType, error) { + var pathType FileType + info, err := os.Stat(pathname) + if os.IsNotExist(err) { + return pathType, fmt.Errorf("path %q does not exist", pathname) + } + // err in call to os.Stat + if err != nil { + return pathType, err + } + + // checks whether the mode is the target mode + isSpecificMode := func(mode, targetMode os.FileMode) bool { + return mode&targetMode == targetMode + } + + mode := info.Mode() + if mode.IsDir() { + return FileTypeDirectory, nil + } else if mode.IsRegular() { + return FileTypeFile, nil + } else if isSpecificMode(mode, os.ModeSocket) { + return FileTypeSocket, nil + } else if isSpecificMode(mode, os.ModeDevice) { + if isSpecificMode(mode, os.ModeCharDevice) { + return FileTypeCharDev, nil + } + return FileTypeBlockDev, nil + } + + return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device") +} diff --git a/pkg/util/mount/mount_linux.go b/pkg/util/mount/mount_linux.go index 012b32673fc..2eac05a7ccd 100644 --- a/pkg/util/mount/mount_linux.go +++ b/pkg/util/mount/mount_linux.go @@ -423,31 +423,7 @@ func (mounter *Mounter) MakeRShared(path string) error { } func (mounter *Mounter) GetFileType(pathname string) (FileType, error) { - var pathType FileType - finfo, err := os.Stat(pathname) - if os.IsNotExist(err) { - return pathType, fmt.Errorf("path %q does not exist", pathname) - } - // err in call to os.Stat - if err != nil { - return pathType, err - } - - mode := finfo.Sys().(*syscall.Stat_t).Mode - switch mode & syscall.S_IFMT { - case syscall.S_IFSOCK: - return FileTypeSocket, nil - case syscall.S_IFBLK: - return FileTypeBlockDev, nil - case syscall.S_IFCHR: - return FileTypeCharDev, nil - case syscall.S_IFDIR: - return FileTypeDirectory, nil - case syscall.S_IFREG: - return FileTypeFile, nil - } - - return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device") + return getFileType(pathname) } func (mounter *Mounter) MakeDir(pathname string) error { diff --git a/pkg/util/mount/mount_linux_test.go b/pkg/util/mount/mount_linux_test.go index fe7b7028d5e..d6b3c497c20 100644 --- a/pkg/util/mount/mount_linux_test.go +++ b/pkg/util/mount/mount_linux_test.go @@ -25,10 +25,12 @@ import ( "os" "path/filepath" "reflect" + "strconv" + "strings" "syscall" "testing" - "strconv" + "k8s.io/utils/exec" "github.com/golang/glog" ) @@ -1680,3 +1682,110 @@ func TestFindExistingPrefix(t *testing.T) { os.RemoveAll(base) } } + +func TestGetFileType(t *testing.T) { + mounter := Mounter{"fake/path", false} + + testCase := []struct { + name string + expectedType FileType + setUp func() (string, string, error) + }{ + { + "Directory Test", + FileTypeDirectory, + func() (string, string, error) { + tempDir, err := ioutil.TempDir("", "test-get-filetype-") + return tempDir, tempDir, err + }, + }, + { + "File Test", + FileTypeFile, + func() (string, string, error) { + tempFile, err := ioutil.TempFile("", "test-get-filetype") + if err != nil { + return "", "", err + } + tempFile.Close() + return tempFile.Name(), tempFile.Name(), nil + }, + }, + { + "Socket Test", + FileTypeSocket, + func() (string, string, error) { + tempDir, err := ioutil.TempDir("", "test-get-filetype-") + if err != nil { + return "", "", err + } + tempSocketFile, err := createSocketFile(tempDir) + return tempSocketFile, tempDir, err + }, + }, + { + "Block Device Test", + FileTypeBlockDev, + func() (string, string, error) { + tempDir, err := ioutil.TempDir("", "test-get-filetype-") + if err != nil { + return "", "", err + } + + tempBlockFile := filepath.Join(tempDir, "test_blk_dev") + outputBytes, err := exec.New().Command("mknod", tempBlockFile, "b", "89", "1").CombinedOutput() + if err != nil { + err = fmt.Errorf("%v: %s ", err, outputBytes) + } + return tempBlockFile, tempDir, err + }, + }, + { + "Character Device Test", + FileTypeCharDev, + func() (string, string, error) { + tempDir, err := ioutil.TempDir("", "test-get-filetype-") + if err != nil { + return "", "", err + } + + tempCharFile := filepath.Join(tempDir, "test_char_dev") + outputBytes, err := exec.New().Command("mknod", tempCharFile, "c", "89", "1").CombinedOutput() + if err != nil { + err = fmt.Errorf("%v: %s ", err, outputBytes) + } + return tempCharFile, tempDir, err + }, + }, + } + + for idx, tc := range testCase { + path, cleanUpPath, err := tc.setUp() + if err != nil { + // Locally passed, but upstream CI is not friendly to create such device files + // Leave "Operation not permitted" out, which can be covered in an e2e test + if isOperationNotPermittedError(err) { + continue + } + t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err) + } + if len(cleanUpPath) > 0 { + defer os.RemoveAll(cleanUpPath) + } + + fileType, err := mounter.GetFileType(path) + if err != nil { + t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err) + } + if fileType != tc.expectedType { + t.Fatalf("[%d-%s] expected %s, but got %s", idx, tc.name, tc.expectedType, fileType) + } + } +} + +func isOperationNotPermittedError(err error) bool { + if strings.Contains(err.Error(), "Operation not permitted") { + return true + } + return false +} diff --git a/pkg/util/mount/mount_windows.go b/pkg/util/mount/mount_windows.go index 12e1bca118a..0384ba179f1 100644 --- a/pkg/util/mount/mount_windows.go +++ b/pkg/util/mount/mount_windows.go @@ -201,31 +201,7 @@ func (mounter *Mounter) MakeRShared(path string) error { // GetFileType checks for sockets/block/character devices func (mounter *Mounter) GetFileType(pathname string) (FileType, error) { - var pathType FileType - info, err := os.Stat(pathname) - if os.IsNotExist(err) { - return pathType, fmt.Errorf("path %q does not exist", pathname) - } - // err in call to os.Stat - if err != nil { - return pathType, err - } - - mode := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes - switch mode & syscall.S_IFMT { - case syscall.S_IFSOCK: - return FileTypeSocket, nil - case syscall.S_IFBLK: - return FileTypeBlockDev, nil - case syscall.S_IFCHR: - return FileTypeCharDev, nil - case syscall.S_IFDIR: - return FileTypeDirectory, nil - case syscall.S_IFREG: - return FileTypeFile, nil - } - - return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device") + return getFileType(pathname) } // MakeFile creates a new directory diff --git a/pkg/util/mount/mount_windows_test.go b/pkg/util/mount/mount_windows_test.go index e31217e975d..12fa5152f51 100644 --- a/pkg/util/mount/mount_windows_test.go +++ b/pkg/util/mount/mount_windows_test.go @@ -20,6 +20,7 @@ package mount import ( "fmt" + "io/ioutil" "os" "os/exec" "path/filepath" @@ -551,3 +552,52 @@ func TestPathWithinBase(t *testing.T) { test.fullPath, test.basePath, result, test.expectedResult) } } + +func TestGetFileType(t *testing.T) { + mounter := New("fake/path") + + testCase := []struct { + name string + expectedType FileType + setUp func() (string, string, error) + }{ + { + "Directory Test", + FileTypeDirectory, + func() (string, string, error) { + tempDir, err := ioutil.TempDir("", "test-get-filetype-") + return tempDir, tempDir, err + }, + }, + { + "File Test", + FileTypeFile, + func() (string, string, error) { + tempFile, err := ioutil.TempFile("", "test-get-filetype") + if err != nil { + return "", "", err + } + tempFile.Close() + return tempFile.Name(), tempFile.Name(), nil + }, + }, + } + + for idx, tc := range testCase { + path, cleanUpPath, err := tc.setUp() + if err != nil { + t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err) + } + if len(cleanUpPath) > 0 { + defer os.RemoveAll(cleanUpPath) + } + + fileType, err := mounter.GetFileType(path) + if err != nil { + t.Fatalf("[%d-%s] unexpected error : %v", idx, tc.name, err) + } + if fileType != tc.expectedType { + t.Fatalf("[%d-%s] expected %s, but got %s", idx, tc.name, tc.expectedType, fileType) + } + } +}