aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/native
diff options
context:
space:
mode:
authorGravatar Laszlo Csomor <laszlocsomor@google.com>2017-02-15 13:07:52 +0000
committerGravatar Dmitry Lomov <dslomov@google.com>2017-02-15 14:47:42 +0000
commitd7d4def945b38133c43e3df3ea7c450b229d2bfb (patch)
treeb370d2c91c525a692e4eee01e2fbe6331e7e1df2 /src/main/native
parent2a0bb37397e1354eaf06b7697913f71443332175 (diff)
Bazel client, JNI, Windows: impl. CreateJunction
Implement a CreateJunction function in the Windows JNI library. Also move a bit of code from file_windows to the JNI library, where it is (also) needed. This implementation is an improved version of `blaze_util::SymlinkDirectories` in blaze_util_windows: this version handles Windows paths as `name` and `target`, and performs more validation (e.g. on the length of `target`), plus has more comments explaining the logic. In a subsequent change I'll start using this new function in blaze_util_windows. This method will also be helpful in tests: we will no longer have to shell out to mklink. See https://github.com/bazelbuild/bazel/issues/2107 -- Change-Id: I7e9b085fdc2ba47be83da5319bded02bd323e71b Reviewed-on: https://cr.bazel.build/8892 PiperOrigin-RevId: 147585207 MOS_MIGRATED_REVID=147585207
Diffstat (limited to 'src/main/native')
-rw-r--r--src/main/native/windows_file_operations.cc121
-rw-r--r--src/main/native/windows_file_operations.h18
2 files changed, 139 insertions, 0 deletions
diff --git a/src/main/native/windows_file_operations.cc b/src/main/native/windows_file_operations.cc
index b5780b2c86..0b02fb9238 100644
--- a/src/main/native/windows_file_operations.cc
+++ b/src/main/native/windows_file_operations.cc
@@ -15,13 +15,17 @@
#include <windows.h>
#include <memory>
+#include <sstream>
+#include <string>
#include "src/main/native/windows_file_operations.h"
#include "src/main/native/windows_util.h"
namespace windows_util {
+using std::string;
using std::unique_ptr;
+using std::wstring;
int IsJunctionOrDirectorySymlink(const WCHAR* path) {
DWORD attrs = ::GetFileAttributesW(path);
@@ -60,4 +64,121 @@ HANDLE OpenDirectory(const WCHAR* path, bool read_write) {
/* hTemplateFile */ NULL);
}
+#pragma pack(push, 4)
+typedef struct _JunctionDescription {
+ typedef struct _Header {
+ DWORD ReparseTag;
+ WORD ReparseDataLength;
+ WORD Reserved;
+ } Header;
+
+ typedef struct _WriteDesc {
+ WORD SubstituteNameOffset;
+ WORD SubstituteNameLength;
+ WORD PrintNameOffset;
+ WORD PrintNameLength;
+ } WriteDesc;
+
+ Header header;
+ WriteDesc write;
+ WCHAR PathBuffer[ANYSIZE_ARRAY];
+} JunctionDescription;
+#pragma pack(pop)
+
+string CreateJunction(const wstring& junction_name,
+ const wstring& junction_target) {
+ const wstring target = HasUncPrefix(junction_target.c_str())
+ ? junction_target.substr(4)
+ : junction_target;
+ // The entire JunctionDescription cannot be larger than
+ // MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes.
+ //
+ // The structure's layout is:
+ // [JunctionDescription::Header]
+ // [JunctionDescription::WriteDesc]
+ // ---- start of JunctionDescription::PathBuffer ----
+ // [4 WCHARs] : "\??\" prefix
+ // [target.size() WCHARs] : junction target name
+ // [1 WCHAR] : null-terminator
+ // [target.size() WCHARs] : junction target displayed name
+ // [1 WCHAR] : null-terminator
+ // The sum of these must not exceed MAXIMUM_REPARSE_DATA_BUFFER_SIZE.
+ // 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) -
+ /* 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) {
+ std::stringstream error;
+ error << "junction target is too long (" << target.size()
+ << " characters, limit: " << kMaxJunctionTargetLen << ")";
+ return error.str();
+ }
+ const wstring name = HasUncPrefix(junction_name.c_str())
+ ? junction_name
+ : (wstring(L"\\\\?\\") + junction_name);
+
+ // Junctions are directories, so create one
+ if (!::CreateDirectoryW(name.c_str(), NULL)) {
+ return string("CreateDirectoryW failed");
+ }
+
+ AutoHandle handle(OpenDirectory(name.c_str(), true));
+ if (!handle.IsValid()) {
+ return string("OpenDirectory failed");
+ }
+
+ char 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)) {
+ return string("DeviceIoControl(FSCTL_SET_REPARSE_POINT) failed");
+ }
+ return "";
+}
+
} // namespace windows_util
diff --git a/src/main/native/windows_file_operations.h b/src/main/native/windows_file_operations.h
index ded07b0a1f..9b2c76837a 100644
--- a/src/main/native/windows_file_operations.h
+++ b/src/main/native/windows_file_operations.h
@@ -17,10 +17,19 @@
#include <windows.h>
#include <memory>
+#include <string>
namespace windows_util {
+using std::string;
using std::unique_ptr;
+using std::wstring;
+
+template <typename char_type>
+bool HasUncPrefix(const char_type* path) {
+ return path[0] == '\\' && (path[1] == '\\' || path[1] == '?') &&
+ (path[2] == '.' || path[2] == '?') && path[3] == '\\';
+}
// Keep in sync with j.c.g.devtools.build.lib.windows.WindowsFileOperations
enum {
@@ -61,6 +70,15 @@ bool GetLongPath(const WCHAR* path, unique_ptr<WCHAR[]>* result);
// otherwise only for reading.
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.
+// 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.
+string CreateJunction(const wstring& junction_name,
+ const wstring& junction_target);
+
} // namespace windows_util
#endif // BAZEL_SRC_MAIN_NATIVE_WINDOWS_FILE_OPERATIONS_H_