diff options
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java | 8 | ||||
-rw-r--r-- | src/main/native/windows/file-jni.cc | 2 | ||||
-rw-r--r-- | src/main/native/windows/file.cc | 89 | ||||
-rw-r--r-- | src/main/native/windows/file.h | 14 | ||||
-rw-r--r-- | src/test/native/windows/file_test.cc | 27 |
5 files changed, 89 insertions, 51 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java index 229c207e91..4b5d74f701 100644 --- a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java +++ b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java @@ -61,10 +61,10 @@ public class WindowsFileOperations { // Keep DELETE_PATH_* values in sync with src/main/native/windows/file.cc. private static final int DELETE_PATH_SUCCESS = 0; - private static final int DELETE_PATH_DOES_NOT_EXIST = 1; - private static final int DELETE_PATH_DIRECTORY_NOT_EMPTY = 2; - private static final int DELETE_PATH_ACCESS_DENIED = 3; - private static final int DELETE_PATH_ERROR = 4; + private static final int DELETE_PATH_ERROR = 1; + private static final int DELETE_PATH_DOES_NOT_EXIST = 2; + private static final int DELETE_PATH_DIRECTORY_NOT_EMPTY = 3; + private static final int DELETE_PATH_ACCESS_DENIED = 4; private static native int nativeIsJunction(String path, String[] error); diff --git a/src/main/native/windows/file-jni.cc b/src/main/native/windows/file-jni.cc index 0f3334c517..0dc4fbd7ab 100644 --- a/src/main/native/windows/file-jni.cc +++ b/src/main/native/windows/file-jni.cc @@ -98,7 +98,7 @@ Java_com_google_devtools_build_lib_windows_jni_WindowsFileOperations_nativeDelet std::wstring wpath(bazel::windows::GetJavaWstring(env, path)); std::wstring error; int result = bazel::windows::DeletePath(wpath, &error); - if (result != bazel::windows::DELETE_PATH_SUCCESS && !error.empty() && + if (result != bazel::windows::DeletePathResult::kSuccess && !error.empty() && CanReportError(env, error_msg_holder)) { ReportLastError( bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, diff --git a/src/main/native/windows/file.cc b/src/main/native/windows/file.cc index c020eebd24..c46744a2c2 100644 --- a/src/main/native/windows/file.cc +++ b/src/main/native/windows/file.cc @@ -154,22 +154,25 @@ int CreateJunction(const wstring& junction_name, const wstring& junction_target, } AutoHandle handle(CreateFileW( - name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL)); + name.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + NULL)); if (!handle.IsValid()) { DWORD err = GetLastError(); // We can't open the directory for writing: either it disappeared, or turned // into a file, or another process holds it open without write-sharing. - // Either way, don't try to create the junction, just try opening it for - // reading and check its value. + // Either way, don't try to create the junction, just try opening it without + // any read or write access (we can still read its metadata) and maximum + // sharing, and check its target. create = false; handle = CreateFileW( - name.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, + name.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); if (!handle.IsValid()) { - // We can't open the directory even for reading: either it disappeared, or - // it turned into a file, or another process holds it open without - // read-sharing. Give up. + // We can't open the directory at all: either it disappeared, or it turned + // into a file, or another process holds it open without any sharing. + // Give up. DWORD err = GetLastError(); if (err == ERROR_SHARING_VIOLATION) { // The junction is held open by another process. @@ -226,7 +229,23 @@ int CreateJunction(const wstring& junction_name, const wstring& junction_target, create = false; if (!(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { // The path is no longer a directory, and it's not a junction either. - return CreateJunctionResult::kAlreadyExistsButNotJunction; + // Though this is a case for kAlreadyExistsButNotJunction, let's instead + // print the attributes and return kError, to give more information to + // the user. + if (error) { + // Print the attributes as a 32-bit hexadecimal number. + WCHAR attr_str[9]; + DWORD attr_value = info.dwFileAttributes; + attr_str[8] = 0; + for (int i = 0; i < 8; ++i) { + attr_str[7 - i] = L"0123456789abcdef"[attr_value & 0xF]; + attr_value >>= 4; + } + *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, + L"GetFileInformationByHandle", name, + wstring(L"attrs=0x") + attr_str); + } + return CreateJunctionResult::kError; } } } @@ -334,18 +353,18 @@ int DeletePath(const wstring& path, wstring* error) { DWORD err = GetLastError(); if (err == ERROR_SHARING_VIOLATION) { // The file or directory is in use by some process. - return DELETE_PATH_ACCESS_DENIED; + return DeletePathResult::kAccessDenied; } else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { // The file or directory does not exist, or a parent directory does not // exist, or a parent directory is actually a file. - return DELETE_PATH_DOES_NOT_EXIST; + return DeletePathResult::kDoesNotExist; } else if (err != ERROR_ACCESS_DENIED) { // Some unknown error occurred. if (error) { *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeleteFileW", path, err); } - return DELETE_PATH_ERROR; + return DeletePathResult::kError; } // DeleteFileW failed with access denied, because the file is read-only or @@ -356,15 +375,31 @@ int DeletePath(const wstring& path, wstring* error) { if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { // The file disappeared, or one of its parent directories disappeared, // or one of its parent directories is no longer a directory. - return DELETE_PATH_DOES_NOT_EXIST; + return DeletePathResult::kDoesNotExist; + } else if (err != ERROR_ACCESS_DENIED) { + // Some unknown error occurred. + if (error) { + *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, + L"GetFileAttributesW", path, err); + } + return DeletePathResult::kError; } - // Some unknown error occurred. - if (error) { - *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, - L"GetFileAttributesW", path, err); + // According to a comment in .NET CoreFX [1] (which is the only relevant + // information we found as of 2018-07-13) GetFileAttributesW may fail with + // ERROR_ACCESS_DENIED if the file is marked for deletion but not yet + // actually deleted, but FindFirstFileW should succeed even then. + // + // [1] + // https://github.com/dotnet/corefx/blob/f25eb288a449010574a6e95fe298f3ad880ada1e/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs#L205-L208 + WIN32_FIND_DATAW find_data; + HANDLE find = FindFirstFileW(wpath, &find_data); + if (find == INVALID_HANDLE_VALUE) { + // The path is already deleted. + return DeletePathResult::kDoesNotExist; } - return DELETE_PATH_ERROR; + FindClose(find); + attr = find_data.dwFileAttributes; } if (attr & FILE_ATTRIBUTE_DIRECTORY) { @@ -374,14 +409,14 @@ int DeletePath(const wstring& path, wstring* error) { err = GetLastError(); if (err == ERROR_SHARING_VIOLATION) { // The junction or directory is in use by another process. - return DELETE_PATH_ACCESS_DENIED; + return DeletePathResult::kAccessDenied; } else if (err == ERROR_DIR_NOT_EMPTY) { // The directory is not empty. - return DELETE_PATH_DIRECTORY_NOT_EMPTY; + return DeletePathResult::kDirectoryNotEmpty; } else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { // The directory or one of its directories disappeared or is no longer // a directory. - return DELETE_PATH_DOES_NOT_EXIST; + return DeletePathResult::kDoesNotExist; } // Some unknown error occurred. @@ -389,7 +424,7 @@ int DeletePath(const wstring& path, wstring* error) { *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeleteDirectoryW", path, err); } - return DELETE_PATH_ERROR; + return DeletePathResult::kError; } } else { // It's a file and it's probably read-only. @@ -400,14 +435,14 @@ int DeletePath(const wstring& path, wstring* error) { if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { // The file disappeared, or one of its parent directories disappeared, // or one of its parent directories is no longer a directory. - return DELETE_PATH_DOES_NOT_EXIST; + return DeletePathResult::kDoesNotExist; } // Some unknown error occurred. if (error) { *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"SetFileAttributesW", path, err); } - return DELETE_PATH_ERROR; + return DeletePathResult::kError; } if (!DeleteFileW(wpath)) { @@ -416,7 +451,7 @@ int DeletePath(const wstring& path, wstring* error) { if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { // The file disappeared, or one of its parent directories disappeared, // or one of its parent directories is no longer a directory. - return DELETE_PATH_DOES_NOT_EXIST; + return DeletePathResult::kDoesNotExist; } // Some unknown error occurred. @@ -424,11 +459,11 @@ int DeletePath(const wstring& path, wstring* error) { *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeleteFileW", path, err); } - return DELETE_PATH_ERROR; + return DeletePathResult::kError; } } } - return DELETE_PATH_SUCCESS; + return DeletePathResult::kSuccess; } } // namespace windows diff --git a/src/main/native/windows/file.h b/src/main/native/windows/file.h index ce13908140..e16b03bd23 100644 --- a/src/main/native/windows/file.h +++ b/src/main/native/windows/file.h @@ -42,12 +42,14 @@ enum { }; // Keep in sync with j.c.g.devtools.build.lib.windows.WindowsFileOperations -enum { - DELETE_PATH_SUCCESS = 0, - DELETE_PATH_DOES_NOT_EXIST = 1, - DELETE_PATH_DIRECTORY_NOT_EMPTY = 2, - DELETE_PATH_ACCESS_DENIED = 3, - DELETE_PATH_ERROR = 4, +struct DeletePathResult { + enum { + kSuccess = 0, + kError = 1, + kDoesNotExist = 2, + kDirectoryNotEmpty = 3, + kAccessDenied = 4, + }; }; struct CreateJunctionResult { diff --git a/src/test/native/windows/file_test.cc b/src/test/native/windows/file_test.cc index 4624fec39c..4792aaae61 100644 --- a/src/test/native/windows/file_test.cc +++ b/src/test/native/windows/file_test.cc @@ -177,7 +177,7 @@ TEST_F(WindowsFileOperationsTest, TestCannotCreateJunctionFromExistingFile) { CreateJunctionResult::kAlreadyExistsButNotJunction); } -TEST_F(WindowsFileOperationsTest, TestCannotCreateJunctionIfNameIsBusy) { +TEST_F(WindowsFileOperationsTest, TestCannotCreateButCanCheckIfNameIsBusy) { wstring tmp(kUncPrefix + GetTestTmpDirW()); wstring name = tmp + L"\\junc" WLINE; wstring target = tmp + L"\\target" WLINE; @@ -188,7 +188,7 @@ TEST_F(WindowsFileOperationsTest, TestCannotCreateJunctionIfNameIsBusy) { EXPECT_NE(h, INVALID_HANDLE_VALUE); int actual = CreateJunction(name, target, nullptr); CloseHandle(h); - ASSERT_EQ(actual, CreateJunctionResult::kAccessDenied); + ASSERT_EQ(actual, CreateJunctionResult::kAlreadyExistsButNotJunction); } TEST_F(WindowsFileOperationsTest, TestCanCreateJunctionIfTargetIsBusy) { @@ -208,14 +208,14 @@ TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingFile) { wstring tmp(kUncPrefix + GetTestTmpDirW()); wstring path = tmp + L"\\file" WLINE; EXPECT_TRUE(blaze_util::CreateDummyFile(path)); - ASSERT_EQ(DeletePath(path.c_str(), nullptr), DELETE_PATH_SUCCESS); + ASSERT_EQ(DeletePath(path.c_str(), nullptr), DeletePathResult::kSuccess); } TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingDirectory) { wstring tmp(kUncPrefix + GetTestTmpDirW()); wstring path = tmp + L"\\dir" WLINE; EXPECT_TRUE(CreateDirectoryW(path.c_str(), NULL)); - ASSERT_EQ(DeletePath(path.c_str(), nullptr), DELETE_PATH_SUCCESS); + ASSERT_EQ(DeletePath(path.c_str(), nullptr), DeletePathResult::kSuccess); } TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingJunction) { @@ -225,7 +225,7 @@ TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingJunction) { EXPECT_TRUE(CreateDirectoryW(target.c_str(), NULL)); EXPECT_EQ(CreateJunction(name, target, nullptr), CreateJunctionResult::kSuccess); - ASSERT_EQ(DeletePath(name.c_str(), nullptr), DELETE_PATH_SUCCESS); + ASSERT_EQ(DeletePath(name.c_str(), nullptr), DeletePathResult::kSuccess); } TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingJunctionWithoutTarget) { @@ -240,14 +240,14 @@ TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingJunctionWithoutTarget) { EXPECT_NE(GetFileAttributesW(name.c_str()), INVALID_FILE_ATTRIBUTES); EXPECT_EQ(GetFileAttributesW(target.c_str()), INVALID_FILE_ATTRIBUTES); // We can delete the dangling junction. - ASSERT_EQ(DeletePath(name.c_str(), nullptr), DELETE_PATH_SUCCESS); + ASSERT_EQ(DeletePath(name.c_str(), nullptr), DeletePathResult::kSuccess); } TEST_F(WindowsFileOperationsTest, TestCannotDeleteNonExistentPath) { wstring tmp(kUncPrefix + GetTestTmpDirW()); wstring path = tmp + L"\\dummy" WLINE; EXPECT_EQ(GetFileAttributesW(path.c_str()), INVALID_FILE_ATTRIBUTES); - ASSERT_EQ(DeletePath(path.c_str(), nullptr), DELETE_PATH_DOES_NOT_EXIST); + ASSERT_EQ(DeletePath(path.c_str(), nullptr), DeletePathResult::kDoesNotExist); } TEST_F(WindowsFileOperationsTest, TestCannotDeletePathWhereParentIsFile) { @@ -255,7 +255,8 @@ TEST_F(WindowsFileOperationsTest, TestCannotDeletePathWhereParentIsFile) { wstring parent = tmp + L"\\file" WLINE; wstring child = parent + L"\\file" WLINE; EXPECT_TRUE(blaze_util::CreateDummyFile(parent)); - ASSERT_EQ(DeletePath(child.c_str(), nullptr), DELETE_PATH_DOES_NOT_EXIST); + ASSERT_EQ(DeletePath(child.c_str(), nullptr), + DeletePathResult::kDoesNotExist); } TEST_F(WindowsFileOperationsTest, TestCannotDeleteNonEmptyDirectory) { @@ -265,7 +266,7 @@ TEST_F(WindowsFileOperationsTest, TestCannotDeleteNonEmptyDirectory) { EXPECT_TRUE(CreateDirectoryW(parent.c_str(), NULL)); EXPECT_TRUE(blaze_util::CreateDummyFile(child)); ASSERT_EQ(DeletePath(parent.c_str(), nullptr), - DELETE_PATH_DIRECTORY_NOT_EMPTY); + DeletePathResult::kDirectoryNotEmpty); } TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyFile) { @@ -277,7 +278,7 @@ TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyFile) { EXPECT_NE(h, INVALID_HANDLE_VALUE); int actual = DeletePath(path.c_str(), nullptr); CloseHandle(h); - ASSERT_EQ(actual, DELETE_PATH_ACCESS_DENIED); + ASSERT_EQ(actual, DeletePathResult::kAccessDenied); } TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyDirectory) { @@ -289,7 +290,7 @@ TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyDirectory) { EXPECT_NE(h, INVALID_HANDLE_VALUE); int actual = DeletePath(path.c_str(), nullptr); CloseHandle(h); - ASSERT_EQ(actual, DELETE_PATH_ACCESS_DENIED); + ASSERT_EQ(actual, DeletePathResult::kAccessDenied); } TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyJunction) { @@ -306,7 +307,7 @@ TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyJunction) { EXPECT_NE(h, INVALID_HANDLE_VALUE); int actual = DeletePath(name.c_str(), nullptr); CloseHandle(h); - ASSERT_EQ(actual, DELETE_PATH_ACCESS_DENIED); + ASSERT_EQ(actual, DeletePathResult::kAccessDenied); } TEST_F(WindowsFileOperationsTest, TestCanDeleteJunctionWhoseTargetIsBusy) { @@ -322,7 +323,7 @@ TEST_F(WindowsFileOperationsTest, TestCanDeleteJunctionWhoseTargetIsBusy) { EXPECT_NE(h, INVALID_HANDLE_VALUE); int actual = DeletePath(name.c_str(), nullptr); CloseHandle(h); - ASSERT_EQ(actual, DELETE_PATH_SUCCESS); + ASSERT_EQ(actual, DeletePathResult::kSuccess); } #undef TOSTRING1 |