diff options
author | Laszlo Csomor <laszlocsomor@google.com> | 2017-02-15 13:07:52 +0000 |
---|---|---|
committer | Dmitry Lomov <dslomov@google.com> | 2017-02-15 14:47:42 +0000 |
commit | d7d4def945b38133c43e3df3ea7c450b229d2bfb (patch) | |
tree | b370d2c91c525a692e4eee01e2fbe6331e7e1df2 /src/main/native | |
parent | 2a0bb37397e1354eaf06b7697913f71443332175 (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.cc | 121 | ||||
-rw-r--r-- | src/main/native/windows_file_operations.h | 18 |
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_ |