diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/native/windows/file-jni.cc | 16 | ||||
-rw-r--r-- | src/main/native/windows/file.cc | 103 | ||||
-rw-r--r-- | src/main/native/windows/file.h | 17 | ||||
-rw-r--r-- | src/test/native/windows/file_test.cc | 127 |
4 files changed, 263 insertions, 0 deletions
diff --git a/src/main/native/windows/file-jni.cc b/src/main/native/windows/file-jni.cc index b2c809efeb..e7c775daee 100644 --- a/src/main/native/windows/file-jni.cc +++ b/src/main/native/windows/file-jni.cc @@ -90,3 +90,19 @@ Java_com_google_devtools_build_lib_windows_jni_WindowsFileOperations_nativeCreat } return JNI_TRUE; } + +extern "C" JNIEXPORT jint JNICALL +Java_com_google_devtools_build_lib_windows_jni_WindowsFileOperations_nativeDeletePath( + JNIEnv* env, jclass clazz, jstring path, jobjectArray error_msg_holder) { + 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() && + CanReportError(env, error_msg_holder)) { + ReportLastError( + bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__, + L"nativeDeletePath", wpath, error), + env, error_msg_holder); + } + return result; +} diff --git a/src/main/native/windows/file.cc b/src/main/native/windows/file.cc index e9d7c18f5b..d8ae54710a 100644 --- a/src/main/native/windows/file.cc +++ b/src/main/native/windows/file.cc @@ -188,5 +188,108 @@ wstring CreateJunction(const wstring& junction_name, return L""; } +int DeletePath(const wstring& path, wstring* error) { + const wchar_t* wpath = path.c_str(); + if (!DeleteFileW(wpath)) { + DWORD err = GetLastError(); + if (err == ERROR_SHARING_VIOLATION) { + // The file or directory is in use by some process. + return DELETE_PATH_ACCESS_DENIED; + } 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; + } 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; + } + + // DeleteFileW failed with access denied, because the file is read-only or + // it is a directory. + DWORD attr = GetFileAttributesW(wpath); + if (attr == INVALID_FILE_ATTRIBUTES) { + err = GetLastError(); + 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; + } + + // Some unknown error occurred. + if (error) { + *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, + L"GetFileAttributesW", path, err); + } + return DELETE_PATH_ERROR; + } + + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + // It's a directory or a junction. + if (!RemoveDirectoryW(wpath)) { + // Failed to delete the directory. + err = GetLastError(); + if (err == ERROR_SHARING_VIOLATION) { + // The junction or directory is in use by another process. + return DELETE_PATH_ACCESS_DENIED; + } else if (err == ERROR_DIR_NOT_EMPTY) { + // The directory is not empty. + return DELETE_PATH_DIRECTORY_NOT_EMPTY; + } 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; + } + + // Some unknown error occurred. + if (error) { + *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, + L"DeleteDirectoryW", path, err); + } + return DELETE_PATH_ERROR; + } + } else { + // It's a file and it's probably read-only. + // Make it writable then try deleting it again. + attr &= ~FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributesW(wpath, attr)) { + err = GetLastError(); + 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; + } + // Some unknown error occurred. + if (error) { + *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, + L"SetFileAttributesW", path, err); + } + return DELETE_PATH_ERROR; + } + + if (!DeleteFileW(wpath)) { + // Failed to delete the file again. + err = GetLastError(); + 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; + } + + // Some unknown error occurred. + if (error) { + *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeleteFileW", + path, err); + } + return DELETE_PATH_ERROR; + } + } + } + return DELETE_PATH_SUCCESS; +} + } // namespace windows } // namespace bazel diff --git a/src/main/native/windows/file.h b/src/main/native/windows/file.h index 5a915fe9a4..f19ac2d92f 100644 --- a/src/main/native/windows/file.h +++ b/src/main/native/windows/file.h @@ -41,6 +41,15 @@ enum { IS_JUNCTION_ERROR = 2, }; +// 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, +}; + // Determines whether `path` is a junction (or directory symlink). // // `path` should be an absolute, normalized, Windows-style path, with "\\?\" @@ -85,6 +94,14 @@ HANDLE OpenDirectory(const WCHAR* path, bool read_write); wstring CreateJunction(const wstring& junction_name, const wstring& junction_target); +// Deletes the file, junction, or empty directory at `path`. +// Returns DELETE_PATH_SUCCESS if it successfully deleted the path, otherwise +// returns one of the other DELETE_PATH_* constants (e.g. when the directory is +// not empty or the file is in use by another process). +// Returns DELETE_PATH_ERROR for unexpected errors. If `error` is not null, the +// function writes an error message into it. +int DeletePath(const wstring& path, wstring* error); + } // namespace windows } // namespace bazel diff --git a/src/test/native/windows/file_test.cc b/src/test/native/windows/file_test.cc index 9c4e23388c..cfbb5d2f4a 100644 --- a/src/test/native/windows/file_test.cc +++ b/src/test/native/windows/file_test.cc @@ -30,6 +30,11 @@ namespace bazel { namespace windows { +#define TOSTRING(x) #x +#define TOWSTRING1(x) L##x +#define TOWSTRING(x) TOWSTRING1(x) +#define WLINE TOWSTRING(TOSTRING(__LINE__)) + using blaze_util::DeleteAllUnder; using blaze_util::GetTestTmpDirW; using std::unique_ptr; @@ -108,5 +113,127 @@ TEST_F(WindowsFileOperationsTest, TestCreateJunction) { ::GetFileAttributesW((name + L"4\\bar").c_str())); } +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); +} + +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); +} + +TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingJunction) { + wstring tmp(kUncPrefix + GetTestTmpDirW()); + wstring name = tmp + L"\\junc" WLINE; + wstring target = tmp + L"\\target" WLINE; + EXPECT_TRUE(CreateDirectoryW(target.c_str(), NULL)); + EXPECT_EQ(L"", CreateJunction(name, target)); + ASSERT_EQ(DeletePath(name.c_str(), nullptr), DELETE_PATH_SUCCESS); +} + +TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingJunctionWithoutTarget) { + wstring tmp(kUncPrefix + GetTestTmpDirW()); + wstring name = tmp + L"\\junc" WLINE; + wstring target = tmp + L"\\target" WLINE; + EXPECT_TRUE(CreateDirectoryW(target.c_str(), NULL)); + EXPECT_EQ(L"", CreateJunction(name, target)); + EXPECT_TRUE(RemoveDirectoryW(target.c_str())); + // The junction still exists, its target does not. + 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); +} + +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); +} + +TEST_F(WindowsFileOperationsTest, TestCannotDeletePathWhereParentIsFile) { + wstring tmp(kUncPrefix + GetTestTmpDirW()); + 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); +} + +TEST_F(WindowsFileOperationsTest, TestCannotDeleteNonEmptyDirectory) { + wstring tmp(kUncPrefix + GetTestTmpDirW()); + wstring parent = tmp + L"\\dir" WLINE; + wstring child = parent + L"\\file" WLINE; + 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); +} + +TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyFile) { + wstring tmp(kUncPrefix + GetTestTmpDirW()); + wstring path = tmp + L"\\file" WLINE; + EXPECT_TRUE(blaze_util::CreateDummyFile(path)); + HANDLE h = CreateFileW(path.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + EXPECT_NE(h, INVALID_HANDLE_VALUE); + int actual = DeletePath(path.c_str(), nullptr); + CloseHandle(h); + ASSERT_EQ(actual, DELETE_PATH_ACCESS_DENIED); +} + +TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyDirectory) { + wstring tmp(kUncPrefix + GetTestTmpDirW()); + wstring path = tmp + L"\\dir" WLINE; + EXPECT_TRUE(CreateDirectoryW(path.c_str(), NULL)); + HANDLE h = CreateFileW(path.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + EXPECT_NE(h, INVALID_HANDLE_VALUE); + int actual = DeletePath(path.c_str(), nullptr); + CloseHandle(h); + ASSERT_EQ(actual, DELETE_PATH_ACCESS_DENIED); +} + +TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyJunction) { + wstring tmp(kUncPrefix + GetTestTmpDirW()); + wstring name = tmp + L"\\junc" WLINE; + wstring target = tmp + L"\\target" WLINE; + EXPECT_TRUE(CreateDirectoryW(target.c_str(), NULL)); + EXPECT_EQ(L"", CreateJunction(name, target)); + // Open the junction itself (do not follow symlinks). + HANDLE h = CreateFileW( + name.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); + EXPECT_NE(h, INVALID_HANDLE_VALUE); + int actual = DeletePath(name.c_str(), nullptr); + CloseHandle(h); + ASSERT_EQ(actual, DELETE_PATH_ACCESS_DENIED); +} + +TEST_F(WindowsFileOperationsTest, TestCanDeleteJunctionWhoseTargetIsBusy) { + wstring tmp(kUncPrefix + GetTestTmpDirW()); + wstring name = tmp + L"\\junc" WLINE; + wstring target = tmp + L"\\target" WLINE; + EXPECT_TRUE(CreateDirectoryW(target.c_str(), NULL)); + EXPECT_EQ(L"", CreateJunction(name, target)); + // Open the junction's target (follow symlinks). + HANDLE h = CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + EXPECT_NE(h, INVALID_HANDLE_VALUE); + int actual = DeletePath(name.c_str(), nullptr); + CloseHandle(h); + ASSERT_EQ(actual, DELETE_PATH_SUCCESS); +} + +#undef TOSTRING +#undef TOWSTRING1 +#undef TOWSTRING +#undef WLINE + } // namespace windows } // namespace bazel |