aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar Laszlo Csomor <laszlocsomor@google.com>2018-07-09 04:32:32 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-07-09 04:33:56 -0700
commitc2b70f1e28b8fbc002efbac16d555f941c7d59a3 (patch)
tree79b2ab5eb0e1a7e4d971b94ba01e6415f7e6b5cd /src
parent4304542a7438acaeb09559e71a971e56fbecc4e5 (diff)
Windows,JNI: more robust nativeCreateJunction
Rewrite the CreateJunction function in the Windows JNI library. The new implementation's improvements: - succeeds if the junction already exists with the desired target; hopefully this will fix issue https://github.com/bazelbuild/bazel/issues/5433 - tolerant to concurrent filesystem modifications, e.g. if the junction's path suddenly disappears, the function reports the error correctly Fixes https://github.com/bazelbuild/bazel/issues/5433 Change-Id: I58a2314a00f6edaa7c36c35ba54616168b44eb7d Closes #5528. Change-Id: I9f5dc9237b70a433d0d8c2578a826de3d462d110 PiperOrigin-RevId: 203744515
Diffstat (limited to 'src')
-rw-r--r--src/main/cpp/blaze_util_windows.cc5
-rw-r--r--src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java37
-rw-r--r--src/main/native/windows/file-jni.cc11
-rw-r--r--src/main/native/windows/file.cc264
-rw-r--r--src/main/native/windows/file.h30
-rw-r--r--src/test/cpp/util/file_windows_test.cc4
-rw-r--r--src/test/cpp/util/path_windows_test.cc1
-rw-r--r--src/test/native/windows/file_test.cc114
-rw-r--r--src/tools/launcher/java_launcher.cc5
9 files changed, 383 insertions, 88 deletions
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index 3bd2d81cb4..3751a1d0b1 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -64,6 +64,7 @@ static const size_t kWindowsPathBufferSize = 0x8010;
using bazel::windows::AutoAttributeList;
using bazel::windows::AutoHandle;
using bazel::windows::CreateJunction;
+using bazel::windows::CreateJunctionResult;
// TODO(bazel-team): stop using BAZEL_DIE, handle errors on the caller side.
// BAZEL_DIE calls exit(exitcode), which makes it difficult to follow the
@@ -671,8 +672,8 @@ bool SymlinkDirectories(const string &posix_target, const string &posix_name) {
<< "): AsAbsoluteWindowsPath(" << posix_name << ") failed: " << error;
return false;
}
- wstring werror(CreateJunction(name, target));
- if (!werror.empty()) {
+ wstring werror;
+ if (CreateJunction(name, target, &werror) != CreateJunctionResult::kSuccess) {
string error(blaze_util::WstringToCstring(werror.c_str()).get());
BAZEL_LOG(ERROR) << "SymlinkDirectories(" << posix_target << ", "
<< posix_name << "): CreateJunction: " << error;
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 6a16ab8aab..229c207e91 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
@@ -49,6 +49,16 @@ public class WindowsFileOperations {
private static final int IS_JUNCTION_NO = 1;
private static final int IS_JUNCTION_ERROR = 2;
+ // Keep CREATE_JUNCTION_* values in sync with src/main/native/windows/file.cc.
+ private static final int CREATE_JUNCTION_SUCCESS = 0;
+ private static final int CREATE_JUNCTION_ERROR = 1;
+ private static final int CREATE_JUNCTION_TARGET_NAME_TOO_LONG = 2;
+ private static final int CREATE_JUNCTION_PARENT_MISSING = 3;
+ private static final int CREATE_JUNCTION_ALREADY_EXISTS_WITH_DIFFERENT_TARGET = 4;
+ private static final int CREATE_JUNCTION_ALREADY_EXISTS_BUT_NOT_A_JUNCTION = 5;
+ private static final int CREATE_JUNCTION_ACCESS_DENIED = 6;
+ private static final int CREATE_JUNCTION_DISAPPEARED = 7;
+
// 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;
@@ -60,7 +70,7 @@ public class WindowsFileOperations {
private static native boolean nativeGetLongPath(String path, String[] result, String[] error);
- private static native boolean nativeCreateJunction(String name, String target, String[] error);
+ private static native int nativeCreateJunction(String name, String target, String[] error);
private static native int nativeDeletePath(String path, String[] error);
@@ -125,7 +135,30 @@ public class WindowsFileOperations {
public static void createJunction(String name, String target) throws IOException {
WindowsJniLoader.loadJni();
String[] error = new String[] {null};
- if (!nativeCreateJunction(name.replace('/', '\\'), target.replace('/', '\\'), error)) {
+ int result = nativeCreateJunction(name.replace('/', '\\'), target.replace('/', '\\'), error);
+ if (result != CREATE_JUNCTION_SUCCESS) {
+ switch (result) {
+ case CREATE_JUNCTION_TARGET_NAME_TOO_LONG:
+ error[0] = "target name is too long";
+ break;
+ case CREATE_JUNCTION_PARENT_MISSING:
+ error[0] = "a parent directory is missing";
+ break;
+ case CREATE_JUNCTION_ALREADY_EXISTS_WITH_DIFFERENT_TARGET:
+ error[0] = "junction already exists with different target";
+ break;
+ case CREATE_JUNCTION_ALREADY_EXISTS_BUT_NOT_A_JUNCTION:
+ error[0] = "a file or directory already exists at the junction's path";
+ break;
+ case CREATE_JUNCTION_ACCESS_DENIED:
+ error[0] = "access is denied";
+ break;
+ case CREATE_JUNCTION_DISAPPEARED:
+ error[0] = "the junction's path got modified unexpectedly";
+ break;
+ default:
+ break;
+ }
throw new IOException(
String.format("Cannot create junction (name=%s, target=%s): %s", name, target, error[0]));
}
diff --git a/src/main/native/windows/file-jni.cc b/src/main/native/windows/file-jni.cc
index e7c775daee..0f3334c517 100644
--- a/src/main/native/windows/file-jni.cc
+++ b/src/main/native/windows/file-jni.cc
@@ -74,21 +74,22 @@ Java_com_google_devtools_build_lib_windows_jni_WindowsFileOperations_nativeGetLo
return JNI_TRUE;
}
-extern "C" JNIEXPORT jboolean JNICALL
+extern "C" JNIEXPORT jint JNICALL
Java_com_google_devtools_build_lib_windows_jni_WindowsFileOperations_nativeCreateJunction(
JNIEnv* env, jclass clazz, jstring name, jstring target,
jobjectArray error_msg_holder) {
std::wstring wname(bazel::windows::GetJavaWstring(env, name));
std::wstring wtarget(bazel::windows::GetJavaWstring(env, target));
- std::wstring error(bazel::windows::CreateJunction(wname, wtarget));
- if (!error.empty() && CanReportError(env, error_msg_holder)) {
+ std::wstring error;
+ int result = bazel::windows::CreateJunction(wname, wtarget, &error);
+ if (result != bazel::windows::CreateJunctionResult::kSuccess &&
+ !error.empty() && CanReportError(env, error_msg_holder)) {
ReportLastError(bazel::windows::MakeErrorMessage(
WSTR(__FILE__), __LINE__, L"nativeCreateJunction",
wname + L", " + wtarget, error),
env, error_msg_holder);
- return JNI_FALSE;
}
- return JNI_TRUE;
+ return result;
}
extern "C" JNIEXPORT jint JNICALL
diff --git a/src/main/native/windows/file.cc b/src/main/native/windows/file.cc
index d8ae54710a..c020eebd24 100644
--- a/src/main/native/windows/file.cc
+++ b/src/main/native/windows/file.cc
@@ -80,16 +80,16 @@ typedef struct _JunctionDescription {
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
- } WriteDesc;
+ } Descriptor;
Header header;
- WriteDesc write;
+ Descriptor descriptor;
WCHAR PathBuffer[ANYSIZE_ARRAY];
} JunctionDescription;
#pragma pack(pop)
-wstring CreateJunction(const wstring& junction_name,
- const wstring& junction_target) {
+int CreateJunction(const wstring& junction_name, const wstring& junction_target,
+ wstring* error) {
const wstring target = HasUncPrefix(junction_target.c_str())
? junction_target.substr(4)
: junction_target;
@@ -98,7 +98,7 @@ wstring CreateJunction(const wstring& junction_name,
//
// The structure's layout is:
// [JunctionDescription::Header]
- // [JunctionDescription::WriteDesc]
+ // [JunctionDescription::Descriptor]
// ---- start of JunctionDescription::PathBuffer ----
// [4 WCHARs] : "\??\" prefix
// [target.size() WCHARs] : junction target name
@@ -109,83 +109,223 @@ wstring CreateJunction(const wstring& junction_name,
// We can rearrange this to get the limit for target.size().
static const size_t kMaxJunctionTargetLen =
((MAXIMUM_REPARSE_DATA_BUFFER_SIZE - sizeof(JunctionDescription::Header) -
- sizeof(JunctionDescription::WriteDesc) -
+ sizeof(JunctionDescription::Descriptor) -
/* one "\??\" prefix */ sizeof(WCHAR) * 4 -
/* two null terminators */ sizeof(WCHAR) * 2) /
/* two copies of the string are stored */ 2) /
sizeof(WCHAR);
if (target.size() > kMaxJunctionTargetLen) {
- return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateJunction", target,
- L"target is too long");
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateJunction",
+ target, L"target path is too long");
+ }
+ return CreateJunctionResult::kTargetNameTooLong;
}
const wstring name = HasUncPrefix(junction_name.c_str())
? junction_name
: (wstring(L"\\\\?\\") + junction_name);
- // Junctions are directories, so create one
+ // `create` is true if we attempt to create or set the junction, i.e. can open
+ // the file for writing. `create` is false if we only check that the existing
+ // file is a junction, i.e. we can only open it for reading.
+ bool create = true;
+
+ // Junctions are directories, so create a directory.
if (!::CreateDirectoryW(name.c_str(), NULL)) {
- DWORD err_code = GetLastError();
- return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateJunction", name,
- err_code);
+ DWORD err = GetLastError();
+ if (err == ERROR_PATH_NOT_FOUND) {
+ // A parent directory does not exist.
+ return CreateJunctionResult::kParentMissing;
+ }
+ if (err == ERROR_ALREADY_EXISTS) {
+ create = false;
+ } else {
+ // Some unknown error occurred.
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateDirectoryW",
+ name, err);
+ }
+ return CreateJunctionResult::kError;
+ }
+ // The directory already existed.
+ // It may be a file, an empty directory, a non-empty directory, a junction
+ // pointing to the wrong target, or ideally a junction pointing to the
+ // right target.
}
- AutoHandle handle(OpenDirectory(name.c_str(), true));
+ AutoHandle handle(CreateFileW(
+ name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL));
if (!handle.IsValid()) {
- DWORD err_code = GetLastError();
- return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"OpenDirectory", name,
- err_code);
+ 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.
+ create = false;
+ handle = CreateFileW(
+ name.c_str(), GENERIC_READ, 0, 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.
+ DWORD err = GetLastError();
+ if (err == ERROR_SHARING_VIOLATION) {
+ // The junction is held open by another process.
+ return CreateJunctionResult::kAccessDenied;
+ } else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
+ // Meanwhile the directory disappeared or one of its parent directories
+ // disappeared.
+ return CreateJunctionResult::kDisappeared;
+ }
+
+ // Some unknown error occurred.
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateFileW",
+ name, err);
+ }
+ return CreateJunctionResult::kError;
+ }
+ }
+
+ // We have an open handle to the file! It may still be other than a junction,
+ // so check its attributes.
+ BY_HANDLE_FILE_INFORMATION info;
+ if (!GetFileInformationByHandle(handle, &info)) {
+ DWORD err = GetLastError();
+ // Some unknown error occurred.
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
+ L"GetFileInformationByHandle", name, err);
+ }
+ return CreateJunctionResult::kError;
+ }
+
+ if (info.dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
+ DWORD err = GetLastError();
+ // Some unknown error occurred.
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
+ L"GetFileInformationByHandle", name, err);
+ }
+ return CreateJunctionResult::kError;
+ }
+
+ if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ // The path already exists and it's a junction. Do not overwrite, just check
+ // its target.
+ create = false;
+ }
+
+ if (create) {
+ if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ // Even though we managed to create the directory and it didn't exist
+ // before, another process changed it in the meantime so it's no longer a
+ // directory.
+ 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;
+ }
+ }
+ }
+
+ if (!create) {
+ // The path already exists. Check if it's a junction.
+ if (!(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+ return CreateJunctionResult::kAlreadyExistsButNotJunction;
+ }
}
uint8_t reparse_buffer_bytes[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
JunctionDescription* reparse_buffer =
reinterpret_cast<JunctionDescription*>(reparse_buffer_bytes);
- memset(reparse_buffer_bytes, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
-
- // "\??\" is meaningful to the kernel, it's a synomym for the "\DosDevices\"
- // object path. (NOT to be confused with "\\?\" which is meaningful for the
- // Win32 API.) We need to use this prefix to tell the kernel where the reparse
- // point is pointing to.
- memcpy(reparse_buffer->PathBuffer, L"\\??\\", 4 * sizeof(WCHAR));
- memcpy(reparse_buffer->PathBuffer + 4, target.c_str(),
- target.size() * sizeof(WCHAR));
-
- // In addition to their target, junctions also have another string which is a
- // user-visible name of where the junction points, as listed by "dir". This
- // can be any string and won't affect the usability of the junction.
- // MKLINK uses the target path without the "\??\" prefix as the display name,
- // so let's do that here too. This is also in line with how UNIX behaves.
- // Using a dummy or fake display name would be pure evil, it would make the
- // output of `dir` look like:
- // 2017-01-18 01:37 PM <JUNCTION> juncname [dummy string]
- memcpy(reparse_buffer->PathBuffer + 4 + target.size() + 1, target.c_str(),
- target.size() * sizeof(WCHAR));
-
- reparse_buffer->write.SubstituteNameOffset = 0;
- reparse_buffer->write.SubstituteNameLength =
- (4 + target.size()) * sizeof(WCHAR);
- reparse_buffer->write.PrintNameOffset =
- reparse_buffer->write.SubstituteNameLength +
- /* null-terminator */ sizeof(WCHAR);
- reparse_buffer->write.PrintNameLength = target.size() * sizeof(WCHAR);
-
- reparse_buffer->header.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
- reparse_buffer->header.ReparseDataLength =
- sizeof(JunctionDescription::WriteDesc) +
- reparse_buffer->write.SubstituteNameLength +
- reparse_buffer->write.PrintNameLength +
- /* 2 null-terminators */ (2 * sizeof(WCHAR));
- reparse_buffer->header.Reserved = 0;
-
- DWORD bytes_returned;
- if (!::DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, reparse_buffer,
- reparse_buffer->header.ReparseDataLength +
- sizeof(JunctionDescription::Header),
- NULL, 0, &bytes_returned, NULL)) {
- DWORD err_code = GetLastError();
- return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeviceIoControl", L"",
- err_code);
+ if (create) {
+ // The junction doesn't exist yet, and we have an open handle to the
+ // candidate directory with write access and no sharing. Proceed to turn the
+ // directory into a junction.
+
+ memset(reparse_buffer_bytes, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+
+ // "\??\" is meaningful to the kernel, it's a synomym for the "\DosDevices\"
+ // object path. (NOT to be confused with "\\?\" which is meaningful for the
+ // Win32 API.) We need to use this prefix to tell the kernel where the
+ // reparse point is pointing to.
+ memcpy(reparse_buffer->PathBuffer, L"\\??\\", 4 * sizeof(WCHAR));
+ memcpy(reparse_buffer->PathBuffer + 4, target.c_str(),
+ target.size() * sizeof(WCHAR));
+
+ // In addition to their target, junctions also have another string which is
+ // a user-visible name of where the junction points, as listed by "dir".
+ // This can be any string and won't affect the usability of the junction.
+ // MKLINK uses the target path without the "\??\" prefix as the display
+ // name, so let's do that here too. This is also in line with how UNIX
+ // behaves. Using a dummy or fake display name would be misleading, it would
+ // make the output of `dir` look like:
+ // 2017-01-18 01:37 PM <JUNCTION> juncname [dummy string]
+ memcpy(reparse_buffer->PathBuffer + 4 + target.size() + 1, target.c_str(),
+ target.size() * sizeof(WCHAR));
+
+ reparse_buffer->descriptor.SubstituteNameOffset = 0;
+ reparse_buffer->descriptor.SubstituteNameLength =
+ (4 + target.size()) * sizeof(WCHAR);
+ reparse_buffer->descriptor.PrintNameOffset =
+ reparse_buffer->descriptor.SubstituteNameLength +
+ /* null-terminator */ sizeof(WCHAR);
+ reparse_buffer->descriptor.PrintNameLength = target.size() * sizeof(WCHAR);
+
+ reparse_buffer->header.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
+ reparse_buffer->header.ReparseDataLength =
+ sizeof(JunctionDescription::Descriptor) +
+ reparse_buffer->descriptor.SubstituteNameLength +
+ reparse_buffer->descriptor.PrintNameLength +
+ /* 2 null-terminators */ (2 * sizeof(WCHAR));
+ reparse_buffer->header.Reserved = 0;
+
+ DWORD bytes_returned;
+ if (!::DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, reparse_buffer,
+ reparse_buffer->header.ReparseDataLength +
+ sizeof(JunctionDescription::Header),
+ NULL, 0, &bytes_returned, NULL)) {
+ DWORD err = GetLastError();
+ if (err == ERROR_DIR_NOT_EMPTY) {
+ return CreateJunctionResult::kAlreadyExistsButNotJunction;
+ }
+ // Some unknown error occurred.
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeviceIoControl",
+ name, err);
+ }
+ return CreateJunctionResult::kError;
+ }
+ } else {
+ // The junction already exists. Check if it points to the right target.
+
+ DWORD bytes_returned;
+ if (!::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ reparse_buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
+ &bytes_returned, NULL)) {
+ DWORD err = GetLastError();
+ // Some unknown error occurred.
+ if (error) {
+ *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeviceIoControl",
+ name, err);
+ }
+ return CreateJunctionResult::kError;
+ }
+
+ WCHAR* actual_target = reparse_buffer->PathBuffer +
+ reparse_buffer->descriptor.SubstituteNameOffset +
+ /* "\??\" prefix */ 4;
+ if (reparse_buffer->descriptor.SubstituteNameLength !=
+ (/* "\??\" prefix */ 4 + target.size()) * sizeof(WCHAR) ||
+ _wcsnicmp(actual_target, target.c_str(), target.size()) != 0) {
+ return CreateJunctionResult::kAlreadyExistsWithDifferentTarget;
+ }
}
- return L"";
+
+ return CreateJunctionResult::kSuccess;
}
int DeletePath(const wstring& path, wstring* error) {
diff --git a/src/main/native/windows/file.h b/src/main/native/windows/file.h
index f19ac2d92f..ce13908140 100644
--- a/src/main/native/windows/file.h
+++ b/src/main/native/windows/file.h
@@ -50,6 +50,19 @@ enum {
DELETE_PATH_ERROR = 4,
};
+struct CreateJunctionResult {
+ enum {
+ kSuccess = 0,
+ kError = 1,
+ kTargetNameTooLong = 2,
+ kParentMissing = 3,
+ kAlreadyExistsWithDifferentTarget = 4,
+ kAlreadyExistsButNotJunction = 5,
+ kAccessDenied = 6,
+ kDisappeared = 7,
+ };
+};
+
// Determines whether `path` is a junction (or directory symlink).
//
// `path` should be an absolute, normalized, Windows-style path, with "\\?\"
@@ -86,13 +99,22 @@ wstring GetLongPath(const WCHAR* path, unique_ptr<WCHAR[]>* result);
HANDLE OpenDirectory(const WCHAR* path, bool read_write);
// Creates a junction at `name`, pointing to `target`.
-// Returns the empty string upon success, or a human-readable error message upon
-// failure.
+// Returns CreateJunctionResult::kSuccess if it could create the junction, or if
+// the junction already exists with the same target.
+// If the junction's name already exists as an empty directory, this function
+// will turn it into a junction and return kSuccess.
+// Otherwise returns one of the other CreateJunctionResult::k* constants for
+// known error cases, or CreateJunctionResult::kError for unknown error cases.
+// When the function returns CreateJunctionResult::kError, and `error` is
+// non-null, the function writes an error message into `error`. If the return
+// value is anything other than CreateJunctionResult::kError, then this function
+// ignores the `error` argument.
+//
// Neither `junction_name` nor `junction_target` needs to have a "\\?\" prefix,
// not even if they are longer than MAX_PATH, though it's okay if they do. This
// function will add the right prefixes as necessary.
-wstring CreateJunction(const wstring& junction_name,
- const wstring& junction_target);
+int CreateJunction(const wstring& junction_name, const wstring& junction_target,
+ wstring* error);
// Deletes the file, junction, or empty directory at `path`.
// Returns DELETE_PATH_SUCCESS if it successfully deleted the path, otherwise
diff --git a/src/test/cpp/util/file_windows_test.cc b/src/test/cpp/util/file_windows_test.cc
index c048e07950..6c1ec2f476 100644
--- a/src/test/cpp/util/file_windows_test.cc
+++ b/src/test/cpp/util/file_windows_test.cc
@@ -39,6 +39,7 @@
namespace blaze_util {
using bazel::windows::CreateJunction;
+using bazel::windows::CreateJunctionResult;
using std::string;
using std::unique_ptr;
using std::wstring;
@@ -66,7 +67,8 @@ class FileWindowsTest : public ::testing::Test {
wstring wtarget; \
EXPECT_TRUE(AsWindowsPath(name, &wname, nullptr)); \
EXPECT_TRUE(AsWindowsPath(target, &wtarget, nullptr)); \
- EXPECT_EQ(L"", CreateJunction(wname, wtarget)); \
+ EXPECT_EQ(CreateJunction(wname, wtarget, nullptr), \
+ CreateJunctionResult::kSuccess); \
}
// Asserts that dir1 can be created with some content, and dir2 doesn't exist.
diff --git a/src/test/cpp/util/path_windows_test.cc b/src/test/cpp/util/path_windows_test.cc
index 965d885c08..0638ebb06e 100644
--- a/src/test/cpp/util/path_windows_test.cc
+++ b/src/test/cpp/util/path_windows_test.cc
@@ -36,7 +36,6 @@
namespace blaze_util {
-using bazel::windows::CreateJunction;
using std::string;
using std::unique_ptr;
using std::wstring;
diff --git a/src/test/native/windows/file_test.cc b/src/test/native/windows/file_test.cc
index cfbb5d2f4a..4624fec39c 100644
--- a/src/test/native/windows/file_test.cc
+++ b/src/test/native/windows/file_test.cc
@@ -30,7 +30,8 @@
namespace bazel {
namespace windows {
-#define TOSTRING(x) #x
+#define TOSTRING1(x) #x
+#define TOSTRING(x) TOSTRING1(x)
#define TOWSTRING1(x) L##x
#define TOWSTRING(x) TOWSTRING1(x)
#define WLINE TOWSTRING(TOSTRING(__LINE__))
@@ -61,10 +62,14 @@ TEST_F(WindowsFileOperationsTest, TestCreateJunction) {
// Create junctions from all combinations of UNC-prefixed or non-prefixed name
// and target paths.
- ASSERT_EQ(L"", CreateJunction(name + L"1", target));
- ASSERT_EQ(L"", CreateJunction(name + L"2", target.substr(4)));
- ASSERT_EQ(L"", CreateJunction(name.substr(4) + L"3", target));
- ASSERT_EQ(L"", CreateJunction(name.substr(4) + L"4", target.substr(4)));
+ ASSERT_EQ(CreateJunction(name + L"1", target, nullptr),
+ CreateJunctionResult::kSuccess);
+ ASSERT_EQ(CreateJunction(name + L"2", target.substr(4), nullptr),
+ CreateJunctionResult::kSuccess);
+ ASSERT_EQ(CreateJunction(name.substr(4) + L"3", target, nullptr),
+ CreateJunctionResult::kSuccess);
+ ASSERT_EQ(CreateJunction(name.substr(4) + L"4", target.substr(4), nullptr),
+ CreateJunctionResult::kSuccess);
// Assert creation of the junctions.
ASSERT_EQ(IS_JUNCTION_YES,
@@ -113,6 +118,92 @@ TEST_F(WindowsFileOperationsTest, TestCreateJunction) {
::GetFileAttributesW((name + L"4\\bar").c_str()));
}
+TEST_F(WindowsFileOperationsTest, TestCanCreateNonDanglingJunction) {
+ wstring tmp(kUncPrefix + GetTestTmpDirW());
+ wstring name = tmp + L"\\junc" WLINE;
+ wstring target = tmp + L"\\target" WLINE;
+ EXPECT_TRUE(CreateDirectoryW(target.c_str(), NULL));
+ ASSERT_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kSuccess);
+}
+
+TEST_F(WindowsFileOperationsTest, TestCanCreateDanglingJunction) {
+ wstring tmp(kUncPrefix + GetTestTmpDirW());
+ wstring name = tmp + L"\\junc" WLINE;
+ wstring target = tmp + L"\\target" WLINE;
+ ASSERT_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kSuccess);
+}
+
+TEST_F(WindowsFileOperationsTest, TestCreateJunctionChecksExistingJunction) {
+ wstring tmp(kUncPrefix + GetTestTmpDirW());
+ wstring name = tmp + L"\\junc" WLINE;
+ wstring target = tmp + L"\\target" WLINE;
+ EXPECT_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kSuccess);
+
+ ASSERT_EQ(CreateJunction(name, target + WLINE, nullptr),
+ CreateJunctionResult::kAlreadyExistsWithDifferentTarget);
+ ASSERT_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kSuccess);
+}
+
+TEST_F(WindowsFileOperationsTest, TestCannotCreateJunctionFromEmptyDirectory) {
+ wstring tmp(kUncPrefix + GetTestTmpDirW());
+ wstring name = tmp + L"\\junc" WLINE;
+ wstring target = tmp + L"\\target" WLINE;
+ EXPECT_TRUE(CreateDirectoryW(name.c_str(), NULL));
+ ASSERT_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kAlreadyExistsButNotJunction);
+}
+
+TEST_F(WindowsFileOperationsTest,
+ TestCannotCreateJunctionFromNonEmptyDirectory) {
+ wstring tmp(kUncPrefix + GetTestTmpDirW());
+ wstring name = tmp + L"\\junc" WLINE;
+ wstring target = tmp + L"\\target" WLINE;
+ EXPECT_TRUE(CreateDirectoryW(name.c_str(), NULL));
+ EXPECT_TRUE(blaze_util::CreateDummyFile(name + L"\\hello.txt"));
+ ASSERT_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kAlreadyExistsButNotJunction);
+}
+
+TEST_F(WindowsFileOperationsTest, TestCannotCreateJunctionFromExistingFile) {
+ wstring tmp(kUncPrefix + GetTestTmpDirW());
+ wstring name = tmp + L"\\junc" WLINE;
+ wstring target = tmp + L"\\target" WLINE;
+ EXPECT_TRUE(blaze_util::CreateDummyFile(name));
+ ASSERT_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kAlreadyExistsButNotJunction);
+}
+
+TEST_F(WindowsFileOperationsTest, TestCannotCreateJunctionIfNameIsBusy) {
+ wstring tmp(kUncPrefix + GetTestTmpDirW());
+ wstring name = tmp + L"\\junc" WLINE;
+ wstring target = tmp + L"\\target" WLINE;
+ EXPECT_TRUE(CreateDirectoryW(name.c_str(), NULL));
+ 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 = CreateJunction(name, target, nullptr);
+ CloseHandle(h);
+ ASSERT_EQ(actual, CreateJunctionResult::kAccessDenied);
+}
+
+TEST_F(WindowsFileOperationsTest, TestCanCreateJunctionIfTargetIsBusy) {
+ wstring tmp(kUncPrefix + GetTestTmpDirW());
+ wstring name = tmp + L"\\junc" WLINE;
+ wstring target = tmp + L"\\target" WLINE;
+ EXPECT_TRUE(CreateDirectoryW(target.c_str(), NULL));
+ 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 = CreateJunction(name, target, nullptr);
+ CloseHandle(h);
+ ASSERT_EQ(actual, CreateJunctionResult::kSuccess);
+}
+
TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingFile) {
wstring tmp(kUncPrefix + GetTestTmpDirW());
wstring path = tmp + L"\\file" WLINE;
@@ -132,7 +223,8 @@ TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingJunction) {
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_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kSuccess);
ASSERT_EQ(DeletePath(name.c_str(), nullptr), DELETE_PATH_SUCCESS);
}
@@ -141,7 +233,8 @@ TEST_F(WindowsFileOperationsTest, TestCanDeleteExistingJunctionWithoutTarget) {
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_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kSuccess);
EXPECT_TRUE(RemoveDirectoryW(target.c_str()));
// The junction still exists, its target does not.
EXPECT_NE(GetFileAttributesW(name.c_str()), INVALID_FILE_ATTRIBUTES);
@@ -204,7 +297,8 @@ TEST_F(WindowsFileOperationsTest, TestCannotDeleteBusyJunction) {
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_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kSuccess);
// Open the junction itself (do not follow symlinks).
HANDLE h = CreateFileW(
name.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
@@ -220,7 +314,8 @@ TEST_F(WindowsFileOperationsTest, TestCanDeleteJunctionWhoseTargetIsBusy) {
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_EQ(CreateJunction(name, target, nullptr),
+ CreateJunctionResult::kSuccess);
// Open the junction's target (follow symlinks).
HANDLE h = CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
@@ -230,6 +325,7 @@ TEST_F(WindowsFileOperationsTest, TestCanDeleteJunctionWhoseTargetIsBusy) {
ASSERT_EQ(actual, DELETE_PATH_SUCCESS);
}
+#undef TOSTRING1
#undef TOSTRING
#undef TOWSTRING1
#undef TOWSTRING
diff --git a/src/tools/launcher/java_launcher.cc b/src/tools/launcher/java_launcher.cc
index 82eeb0bcab..9e79627fa7 100644
--- a/src/tools/launcher/java_launcher.cc
+++ b/src/tools/launcher/java_launcher.cc
@@ -226,8 +226,9 @@ wstring JavaBinaryLauncher::CreateClasspathJar(const wstring& classpath) {
junction = junction_base_dir_norm + L"\\" +
std::to_wstring(junction_count++);
- wstring error(bazel::windows::CreateJunction(junction, jar_dir));
- if (!error.empty()) {
+ wstring error;
+ if (bazel::windows::CreateJunction(junction, jar_dir, &error) !=
+ bazel::windows::CreateJunctionResult::kSuccess) {
die(L"CreateClasspathJar failed: %s", error.c_str());
}