diff --git a/cmd/posix-errors.go b/cmd/posix-errors.go new file mode 100644 index 000000000..0fc152822 --- /dev/null +++ b/cmd/posix-errors.go @@ -0,0 +1,89 @@ +/* + * Minio Cloud Storage, (C) 2016 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "os" + "runtime" + "syscall" +) + +// Check if the given error corresponds to ENOTDIR (is not a directory) +func isSysErrNotDir(err error) bool { + if pathErr, ok := err.(*os.PathError); ok { + switch pathErr.Err { + case syscall.ENOTDIR: + return true + } + } + return false +} + +// Check if the given error corresponds to EISDIR (is a directory) +func isSysErrIsDir(err error) bool { + if pathErr, ok := err.(*os.PathError); ok { + switch pathErr.Err { + case syscall.EISDIR: + return true + } + } + return false +} + +// Check if the given error corresponds to ENOTEMPTY for unix +// and ERROR_DIR_NOT_EMPTY for windows (directory not empty) +func isSysErrNotEmpty(err error) bool { + if pathErr, ok := err.(*os.PathError); ok { + if runtime.GOOS == "windows" { + if errno, _ok := pathErr.Err.(syscall.Errno); _ok && errno == 0x91 { + // ERROR_DIR_NOT_EMPTY + return true + } + } + switch pathErr.Err { + case syscall.ENOTEMPTY: + return true + } + } + return false +} + +// Check if the given error corresponds to the specific ERROR_PATH_NOT_FOUND for windows +func isSysErrPathNotFound(err error) bool { + if runtime.GOOS != "windows" { + return false + } + if pathErr, ok := err.(*os.PathError); ok { + if errno, _ok := pathErr.Err.(syscall.Errno); _ok && errno == 0x03 { + // ERROR_PATH_NOT_FOUND + return true + } + } + return false +} + +// Check if the given error corresponds to the specific ERROR_INVALID_HANDLE for windows +func isSysErrHandleInvalid(err error) bool { + if runtime.GOOS != "windows" { + return false + } + // Check if err contains ERROR_INVALID_HANDLE errno + if errno, ok := err.(syscall.Errno); ok && errno == 0x6 { + return true + } + return false +} diff --git a/cmd/posix.go b/cmd/posix.go index e0ce58f11..8bf5bec3b 100644 --- a/cmd/posix.go +++ b/cmd/posix.go @@ -339,12 +339,7 @@ func (s *posix) DeleteVol(volume string) (err error) { if err != nil { if os.IsNotExist(err) { return errVolumeNotFound - } else if strings.Contains(err.Error(), "directory is not empty") { - // On windows the string is slightly different, handle it here. - return errVolumeNotEmpty - } else if strings.Contains(err.Error(), "directory not empty") { - // Hopefully for all other operating systems, this is - // assumed to be consistent. + } else if isSysErrNotEmpty(err) { return errVolumeNotEmpty } return err @@ -439,7 +434,7 @@ func (s *posix) ReadAll(volume, path string) (buf []byte, err error) { case syscall.ENOTDIR, syscall.EISDIR: return nil, errFileNotFound default: - if strings.Contains(pathErr.Err.Error(), "The handle is invalid") { + if isSysErrHandleInvalid(pathErr.Err) { // This case is special and needs to be handled for windows. return nil, errFileNotFound } @@ -498,7 +493,7 @@ func (s *posix) ReadFile(volume string, path string, offset int64, buf []byte) ( return 0, errFileNotFound } else if os.IsPermission(err) { return 0, errFileAccessDenied - } else if strings.Contains(err.Error(), "not a directory") { + } else if isSysErrNotDir(err) { return 0, errFileAccessDenied } return 0, err @@ -575,9 +570,9 @@ func (s *posix) AppendFile(volume, path string, buf []byte) (err error) { // with mode 0777 mkdir honors system umask. if err = mkdirAll(filepath.Dir(filePath), 0777); err != nil { // File path cannot be verified since one of the parents is a file. - if strings.Contains(err.Error(), "not a directory") { + if isSysErrNotDir(err) { return errFileAccessDenied - } else if runtime.GOOS == "windows" && strings.Contains(err.Error(), "system cannot find the path specified") { + } else if isSysErrPathNotFound(err) { // Add specific case for windows. return errFileAccessDenied } @@ -589,7 +584,7 @@ func (s *posix) AppendFile(volume, path string, buf []byte) (err error) { w, err := os.OpenFile(preparePath(filePath), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) if err != nil { // File path cannot be verified since one of the parents is a file. - if strings.Contains(err.Error(), "not a directory") { + if isSysErrNotDir(err) { return errFileAccessDenied } return err @@ -645,7 +640,7 @@ func (s *posix) StatFile(volume, path string) (file FileInfo, err error) { } // File path cannot be verified since one of the parents is a file. - if strings.Contains(err.Error(), "not a directory") { + if isSysErrNotDir(err) { return FileInfo{}, errFileNotFound } @@ -804,9 +799,9 @@ func (s *posix) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) (err e // Creates all the parent directories, with mode 0777 mkdir honors system umask. if err = mkdirAll(preparePath(slashpath.Dir(dstFilePath)), 0777); err != nil { // File path cannot be verified since one of the parents is a file. - if strings.Contains(err.Error(), "not a directory") { + if isSysErrNotDir(err) { return errFileAccessDenied - } else if strings.Contains(err.Error(), "The system cannot find the path specified.") && runtime.GOOS == "windows" { + } else if isSysErrPathNotFound(err) { // This is a special case should be handled only for // windows, because windows API does not return "not a // directory" error message. Handle this specifically here. diff --git a/cmd/posix_test.go b/cmd/posix_test.go index 25ac68b43..6bccb66cb 100644 --- a/cmd/posix_test.go +++ b/cmd/posix_test.go @@ -18,7 +18,6 @@ package cmd import ( "bytes" - "errors" "io" "io/ioutil" "os" @@ -901,7 +900,7 @@ func TestReadFile(t *testing.T) { return &os.PathError{ Op: "seek", Path: preparePath(slashpath.Join(path, "success-vol", "myobject")), - Err: errors.New("An attempt was made to move the file pointer before the beginning of the file."), + Err: syscall.Errno(0x83), // ERROR_NEGATIVE_SEEK } } return &os.PathError{ @@ -953,7 +952,24 @@ func TestReadFile(t *testing.T) { if err != nil && testCase.expectedErr != nil { // Validate if the type string of the errors are an exact match. if err.Error() != testCase.expectedErr.Error() { - t.Errorf("Case: %d %#v, expected: %s, got: %s", i+1, testCase, testCase.expectedErr, err) + if runtime.GOOS != "windows" { + t.Errorf("Case: %d %#v, expected: %s, got: %s", i+1, testCase, testCase.expectedErr, err) + } else { + var resultErrno, expectErrno uintptr + if pathErr, ok := err.(*os.PathError); ok { + if errno, pok := pathErr.Err.(syscall.Errno); pok { + resultErrno = uintptr(errno) + } + } + if pathErr, ok := testCase.expectedErr.(*os.PathError); ok { + if errno, pok := pathErr.Err.(syscall.Errno); pok { + expectErrno = uintptr(errno) + } + } + if !(expectErrno != 0 && resultErrno != 0 && expectErrno == resultErrno) { + t.Errorf("Case: %d %#v, expected: %s, got: %s", i+1, testCase, testCase.expectedErr, err) + } + } } // Err unexpected EOF special case, where we verify we have provided a larger // buffer than the data itself, but the results are in-fact valid. So we validate