| // Copyright 2016 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #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); | |
| if (attrs == INVALID_FILE_ATTRIBUTES) { | |
| return IS_JUNCTION_ERROR; | |
| } else { | |
| if ((attrs & FILE_ATTRIBUTE_DIRECTORY) && | |
| (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) { | |
| return IS_JUNCTION_YES; | |
| } else { | |
| return IS_JUNCTION_NO; | |
| } | |
| } | |
| } | |
| bool GetLongPath(const WCHAR* path, unique_ptr<WCHAR[]>* result) { | |
| DWORD size = ::GetLongPathNameW(path, NULL, 0); | |
| if (size == 0) { | |
| return false; | |
| } | |
| result->reset(new WCHAR[size]); | |
| ::GetLongPathNameW(path, result->get(), size); | |
| return true; | |
| } | |
| HANDLE OpenDirectory(const WCHAR* path, bool read_write) { | |
| return ::CreateFileW( | |
| /* lpFileName */ path, | |
| /* dwDesiredAccess */ | |
| read_write ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ, | |
| /* dwShareMode */ 0, | |
| /* lpSecurityAttributes */ NULL, | |
| /* dwCreationDisposition */ OPEN_EXISTING, | |
| /* dwFlagsAndAttributes */ FILE_FLAG_OPEN_REPARSE_POINT | | |
| FILE_FLAG_BACKUP_SEMANTICS, | |
| /* 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 |