diff options
author | Laszlo Csomor <laszlocsomor@google.com> | 2018-07-09 04:32:32 -0700 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-07-09 04:33:56 -0700 |
commit | c2b70f1e28b8fbc002efbac16d555f941c7d59a3 (patch) | |
tree | 79b2ab5eb0e1a7e4d971b94ba01e6415f7e6b5cd /src | |
parent | 4304542a7438acaeb09559e71a971e56fbecc4e5 (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.cc | 5 | ||||
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java | 37 | ||||
-rw-r--r-- | src/main/native/windows/file-jni.cc | 11 | ||||
-rw-r--r-- | src/main/native/windows/file.cc | 264 | ||||
-rw-r--r-- | src/main/native/windows/file.h | 30 | ||||
-rw-r--r-- | src/test/cpp/util/file_windows_test.cc | 4 | ||||
-rw-r--r-- | src/test/cpp/util/path_windows_test.cc | 1 | ||||
-rw-r--r-- | src/test/native/windows/file_test.cc | 114 | ||||
-rw-r--r-- | src/tools/launcher/java_launcher.cc | 5 |
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()); } |