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 DeletePath method that can
delete files, directories, and junctions. The
method should tolerate when concurrent
processes delete the file.

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

See bazelbuild#5513

Closes bazelbuild#5520.

Change-Id: I21ea36dd64960b294e2b51600273bf4290ad7c0f
PiperOrigin-RevId: 203448581
  • Loading branch information
laszlocsomor authored and George Gensure committed Aug 2, 2018
1 parent 7314cec commit 611b709
Show file tree
Hide file tree
Showing 4 changed files with 263 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;
}
103 changes: 103 additions & 0 deletions src/main/native/windows/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
17 changes: 17 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,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

Expand Down
127 changes: 127 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,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

0 comments on commit 611b709

Please sign in to comment.