Skip to content

Commit

Permalink
Windows,JNI: implement native DeletePath method
Browse files Browse the repository at this point in the history
Implement a native version of the Java
Path.delete().

The new JNI function is more robust than Java IO
file deletion function because it can also delete
readonly files.

The new JNI function can tolerate some concurrent
modification errors, i.e. when other processes
move or remove the file we are trying to delete.
We'll use this method in WindowsFileSystem.delete
to reduce the likelihood of such concurrent
modifications crashing Bazel.

See bazelbuild#5513

Change-Id: I8a03568171b6911f688587b65ca14031ed86b54b
RELNOTES: none
  • Loading branch information
laszlocsomor committed Jul 5, 2018
1 parent 55d0c1c commit f00bfee
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/main/native/windows/file-jni.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
109 changes: 109 additions & 0 deletions src/main/native/windows/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,114 @@ 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)) {
return DELETE_PATH_SUCCESS;
}

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;
} else {
// 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)) {
return DELETE_PATH_SUCCESS;
}

// 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 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;
} else {
// 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;
} else {
// Some unknown error occurred.
if (error) {
*error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
L"SetFileAttributesW", path, err);
}
return DELETE_PATH_ERROR;
}
}

if (DeleteFileW(wpath)) {
return DELETE_PATH_SUCCESS;
}

// 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;
} else {
// Some unknown error occurred.
if (error) {
*error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeleteFileW",
path, err);
}
return DELETE_PATH_ERROR;
}
}
}

} // namespace windows
} // namespace bazel
16 changes: 16 additions & 0 deletions src/main/native/windows/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 "\\?\"
Expand Down Expand Up @@ -85,6 +94,13 @@ HANDLE OpenDirectory(const WCHAR* path, bool read_write);
wstring CreateJunction(const wstring& junction_name,
const wstring& junction_target);

// Deletes the file or directory at `path`.
// Returns DELETE_PATH_SUCCESS if it successfully deleted the path, otherwise
// returns one of the other DELETE_PATH_* constants.
// 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

Expand Down
121 changes: 121 additions & 0 deletions src/test/native/windows/file_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -108,5 +113,121 @@ 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);
ASSERT_EQ(DeletePath(path.c_str(), nullptr), DELETE_PATH_ACCESS_DENIED);
CloseHandle(h);
}

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);
ASSERT_EQ(DeletePath(path.c_str(), nullptr), DELETE_PATH_ACCESS_DENIED);
CloseHandle(h);
}

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);
ASSERT_EQ(DeletePath(name.c_str(), nullptr), DELETE_PATH_ACCESS_DENIED);
CloseHandle(h);
}

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);
ASSERT_EQ(DeletePath(name.c_str(), nullptr), DELETE_PATH_SUCCESS);
CloseHandle(h);
}

#undef TOSTRING
#undef TOWSTRING1
#undef TOWSTRING
#undef WLINE

} // namespace windows
} // namespace bazel

0 comments on commit f00bfee

Please sign in to comment.