aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/native/windows/file-jni.cc16
-rw-r--r--src/main/native/windows/file.cc103
-rw-r--r--src/main/native/windows/file.h17
-rw-r--r--src/test/native/windows/file_test.cc127
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