// 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 #include #include #include #include "src/main/native/windows/file.h" #include "src/main/native/windows/util.h" namespace bazel { namespace windows { 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* 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(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 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 } // namespace bazel