|  | // 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. | 
|  |  | 
|  | #ifndef WIN32_LEAN_AND_MEAN | 
|  | #define WIN32_LEAN_AND_MEAN | 
|  | #endif | 
|  |  | 
|  | #include "src/main/native/windows/file.h" | 
|  |  | 
|  | #include <WinIoCtl.h> | 
|  | #include <stdint.h>  // uint8_t | 
|  | #include <windows.h> | 
|  |  | 
|  | #include <memory> | 
|  | #include <sstream> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "src/main/native/windows/util.h" | 
|  |  | 
|  | namespace bazel { | 
|  | namespace windows { | 
|  |  | 
|  | using std::unique_ptr; | 
|  | using std::wstring; | 
|  |  | 
|  | wstring AddUncPrefixMaybe(const wstring& path) { | 
|  | return path.empty() || IsDevNull(path.c_str()) || HasUncPrefix(path.c_str()) | 
|  | ? path | 
|  | : (wstring(L"\\\\?\\") + path); | 
|  | } | 
|  |  | 
|  | wstring RemoveUncPrefixMaybe(const wstring& path) { | 
|  | return bazel::windows::HasUncPrefix(path.c_str()) ? path.substr(4) : path; | 
|  | } | 
|  |  | 
|  | bool IsAbsoluteNormalizedWindowsPath(const wstring& p) { | 
|  | if (p.empty()) { | 
|  | return false; | 
|  | } | 
|  | if (IsDevNull(p.c_str())) { | 
|  | return true; | 
|  | } | 
|  | if (p.find_first_of('/') != wstring::npos) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return HasDriveSpecifierPrefix(p.c_str()) && p.find(L".\\") != 0 && | 
|  | p.find(L"\\.\\") == wstring::npos && p.find(L"\\.") != p.size() - 2 && | 
|  | p.find(L"..\\") != 0 && p.find(L"\\..\\") == wstring::npos && | 
|  | p.find(L"\\..") != p.size() - 3; | 
|  | } | 
|  |  | 
|  | static wstring uint32asHexString(uint32_t value) { | 
|  | WCHAR attr_str[8]; | 
|  | for (int i = 0; i < 8; ++i) { | 
|  | attr_str[7 - i] = L"0123456789abcdef"[value & 0xF]; | 
|  | value >>= 4; | 
|  | } | 
|  | return wstring(attr_str, 8); | 
|  | } | 
|  |  | 
|  | int IsSymlinkOrJunction(const WCHAR* path, bool* result, wstring* error) { | 
|  | if (!IsAbsoluteNormalizedWindowsPath(path)) { | 
|  | if (error) { | 
|  | *error = | 
|  | MakeErrorMessage(WSTR(__FILE__), __LINE__, L"IsSymlinkOrJunction", | 
|  | path, L"expected an absolute Windows path"); | 
|  | } | 
|  | return IsSymlinkOrJunctionResult::kError; | 
|  | } | 
|  |  | 
|  | DWORD attrs = ::GetFileAttributesW(path); | 
|  | if (attrs == INVALID_FILE_ATTRIBUTES) { | 
|  | DWORD err = GetLastError(); | 
|  | if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { | 
|  | return IsSymlinkOrJunctionResult::kDoesNotExist; | 
|  | } | 
|  |  | 
|  | if (error) { | 
|  | *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | L"IsSymlinkOrJunction", path, err); | 
|  | } | 
|  | return IsSymlinkOrJunctionResult::kError; | 
|  | } else { | 
|  | *result = (attrs & FILE_ATTRIBUTE_REPARSE_POINT); | 
|  | return IsSymlinkOrJunctionResult::kSuccess; | 
|  | } | 
|  | } | 
|  |  | 
|  | wstring GetLongPath(const WCHAR* path, unique_ptr<WCHAR[]>* result) { | 
|  | if (!IsAbsoluteNormalizedWindowsPath(path)) { | 
|  | return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"GetLongPath", path, | 
|  | L"expected an absolute Windows path"); | 
|  | } | 
|  |  | 
|  | std::wstring wpath(AddUncPrefixMaybe(path)); | 
|  | DWORD size = ::GetLongPathNameW(wpath.c_str(), NULL, 0); | 
|  | if (size == 0) { | 
|  | DWORD err_code = GetLastError(); | 
|  | return MakeErrorMessage(WSTR(__FILE__), __LINE__, L"GetLongPathNameW", path, | 
|  | err_code); | 
|  | } | 
|  | result->reset(new WCHAR[size]); | 
|  | ::GetLongPathNameW(wpath.c_str(), result->get(), size); | 
|  | return L""; | 
|  | } | 
|  |  | 
|  | #pragma pack(push, 4) | 
|  | // See https://msdn.microsoft.com/en-us/windows/desktop/ff552012 | 
|  | typedef struct _REPARSE_DATA_BUFFER { | 
|  | ULONG ReparseTag; | 
|  | USHORT ReparseDataLength; | 
|  | USHORT Reserved; | 
|  | union { | 
|  | struct { | 
|  | USHORT SubstituteNameOffset; | 
|  | USHORT SubstituteNameLength; | 
|  | USHORT PrintNameOffset; | 
|  | USHORT PrintNameLength; | 
|  | ULONG Flags; | 
|  | WCHAR PathBuffer[1]; | 
|  | } SymbolicLinkReparseBuffer; | 
|  | struct { | 
|  | USHORT SubstituteNameOffset; | 
|  | USHORT SubstituteNameLength; | 
|  | USHORT PrintNameOffset; | 
|  | USHORT PrintNameLength; | 
|  | WCHAR PathBuffer[1]; | 
|  | } MountPointReparseBuffer; | 
|  | struct { | 
|  | UCHAR DataBuffer[1]; | 
|  | } GenericReparseBuffer; | 
|  | } DUMMYUNIONNAME; | 
|  | } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; | 
|  | #pragma pack(pop) | 
|  |  | 
|  | int CreateJunction(const wstring& junction_name, const wstring& junction_target, | 
|  | wstring* error) { | 
|  | if (!IsAbsoluteNormalizedWindowsPath(junction_name)) { | 
|  | if (error) { | 
|  | *error = MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"CreateJunction", junction_name, | 
|  | L"expected an absolute Windows path for junction_name"); | 
|  | } | 
|  | return CreateJunctionResult::kError; | 
|  | } | 
|  | if (!IsAbsoluteNormalizedWindowsPath(junction_target)) { | 
|  | if (error) { | 
|  | *error = MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"CreateJunction", junction_target, | 
|  | L"expected an absolute Windows path for junction_target"); | 
|  | } | 
|  | return CreateJunctionResult::kError; | 
|  | } | 
|  |  | 
|  | const WCHAR* target = HasUncPrefix(junction_target.c_str()) | 
|  | ? junction_target.c_str() + 4 | 
|  | : junction_target.c_str(); | 
|  | const size_t target_size = HasUncPrefix(junction_target.c_str()) | 
|  | ? junction_target.size() - 4 | 
|  | : junction_target.size(); | 
|  | // The entire REPARSE_DATA_BUFFER cannot be larger than | 
|  | // MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes. | 
|  | // | 
|  | // The structure's layout is: | 
|  | //   [8 bytes] : ReparseTag, ReparseDataLength, Reserved | 
|  | //   [8 bytes] : MountPointReparseBuffer members before PathBuffer | 
|  | //   ---- start of MountPointReparseBuffer.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 - | 
|  | offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer)) / | 
|  | sizeof(WCHAR) - | 
|  | /* one "\??\" prefix */ 4 - | 
|  | /* two null terminators */ 2) / | 
|  | /* two copies of the string are stored */ 2; | 
|  | if (target_size > kMaxJunctionTargetLen) { | 
|  | 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 a directory. | 
|  | // If CreateDirectoryW succeeds, we'll try to set the junction's target. | 
|  | // If CreateDirectoryW fails, we don't care about the exact reason -- could be | 
|  | // that the directory already exists, or we have no access to create a | 
|  | // directory, or the path was invalid to begin with. Either way set `create` | 
|  | // to false, meaning we'll just attempt to open the path for metadata-reading | 
|  | // and check if it's a junction pointing to the desired target. | 
|  | bool create = CreateDirectoryW(name.c_str(), NULL) != 0; | 
|  |  | 
|  | AutoHandle handle; | 
|  | if (create) { | 
|  | handle = CreateFileW( | 
|  | name.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, | 
|  | OPEN_EXISTING, | 
|  | FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); | 
|  | } | 
|  |  | 
|  | if (!handle.IsValid()) { | 
|  | // We can't open the directory for writing: either we didn't even try to | 
|  | // (`create` was false), or the path disappeared, or it 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 without | 
|  | // any read or write access (we can still read its metadata) and maximum | 
|  | // sharing, and check its target. | 
|  | create = false; | 
|  | handle = CreateFileW( | 
|  | name.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | 
|  | NULL, OPEN_EXISTING, | 
|  | FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); | 
|  | if (!handle.IsValid()) { | 
|  | // We can't open the directory at all: either it disappeared, or it turned | 
|  | // into a file, or the path is invalid, or another process holds it open | 
|  | // without any 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; | 
|  | } | 
|  |  | 
|  | // The path seems to exist yet we cannot open it for metadata-reading. | 
|  | // Report as much information as we have, then give up. | 
|  | 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. | 
|  | // Though this is a case for kAlreadyExistsButNotJunction, let's instead | 
|  | // print the attributes and return kError, to give more information to | 
|  | // the user. | 
|  | if (error) { | 
|  | *error = MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"GetFileInformationByHandle", name, | 
|  | wstring(L"attrs=0x") + uint32asHexString(info.dwFileAttributes)); | 
|  | } | 
|  | return CreateJunctionResult::kError; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | 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]; | 
|  | PREPARSE_DATA_BUFFER reparse_buffer = | 
|  | reinterpret_cast<PREPARSE_DATA_BUFFER>(reparse_buffer_bytes); | 
|  | 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); | 
|  |  | 
|  | reparse_buffer->MountPointReparseBuffer.SubstituteNameOffset = 0; | 
|  | reparse_buffer->MountPointReparseBuffer.SubstituteNameLength = | 
|  | (4 + target_size) * sizeof(WCHAR); | 
|  | reparse_buffer->MountPointReparseBuffer.PrintNameOffset = | 
|  | reparse_buffer->MountPointReparseBuffer.SubstituteNameLength + | 
|  | /* null-terminator */ sizeof(WCHAR); | 
|  | reparse_buffer->MountPointReparseBuffer.PrintNameLength = | 
|  | target_size * sizeof(WCHAR); | 
|  |  | 
|  | reparse_buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; | 
|  | reparse_buffer->ReparseDataLength = | 
|  | 4 * sizeof(USHORT) + | 
|  | reparse_buffer->MountPointReparseBuffer.SubstituteNameLength + | 
|  | reparse_buffer->MountPointReparseBuffer.PrintNameLength + | 
|  | /* 2 null-terminators */ (2 * sizeof(WCHAR)); | 
|  | reparse_buffer->Reserved = 0; | 
|  |  | 
|  | // "\??\" 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((uint8_t*)reparse_buffer->MountPointReparseBuffer.PathBuffer + | 
|  | reparse_buffer->MountPointReparseBuffer.SubstituteNameOffset, | 
|  | L"\\??\\", 4 * sizeof(WCHAR)); | 
|  | memcpy((uint8_t*)reparse_buffer->MountPointReparseBuffer.PathBuffer + | 
|  | reparse_buffer->MountPointReparseBuffer.SubstituteNameOffset + | 
|  | 4 * sizeof(WCHAR), | 
|  | target, | 
|  | reparse_buffer->MountPointReparseBuffer.SubstituteNameLength - | 
|  | 4 * 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((uint8_t*)reparse_buffer->MountPointReparseBuffer.PathBuffer + | 
|  | reparse_buffer->MountPointReparseBuffer.PrintNameOffset, | 
|  | target, reparse_buffer->MountPointReparseBuffer.PrintNameLength); | 
|  |  | 
|  | DWORD bytes_returned; | 
|  | if (!::DeviceIoControl( | 
|  | handle, FSCTL_SET_REPARSE_POINT, reparse_buffer, | 
|  | reparse_buffer->ReparseDataLength + | 
|  | offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer), | 
|  | 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->MountPointReparseBuffer.PathBuffer + | 
|  | reparse_buffer->MountPointReparseBuffer.SubstituteNameOffset + | 
|  | /* "\??\" prefix */ 4; | 
|  | if (reparse_buffer->MountPointReparseBuffer.SubstituteNameLength != | 
|  | (/* "\??\" prefix */ 4 + target_size) * sizeof(WCHAR) || | 
|  | _wcsnicmp(actual_target, target, target_size) != 0) { | 
|  | return CreateJunctionResult::kAlreadyExistsWithDifferentTarget; | 
|  | } | 
|  | } | 
|  |  | 
|  | return CreateJunctionResult::kSuccess; | 
|  | } | 
|  |  | 
|  | int ReadSymlinkOrJunction(const wstring& path, wstring* result, | 
|  | wstring* error) { | 
|  | if (!IsAbsoluteNormalizedWindowsPath(path)) { | 
|  | if (error) { | 
|  | *error = MakeErrorMessage( | 
|  | WSTR(__FILE__), __LINE__, L"ReadSymlinkOrJunction", path, | 
|  | L"expected an absolute Windows path for 'path'"); | 
|  | } | 
|  | return ReadSymlinkOrJunctionResult::kError; | 
|  | } | 
|  |  | 
|  | AutoHandle handle(CreateFileW( | 
|  | AddUncPrefixMaybe(path).c_str(), 0, | 
|  | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, | 
|  | OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, | 
|  | NULL)); | 
|  | if (!handle.IsValid()) { | 
|  | DWORD err = GetLastError(); | 
|  | if (err == ERROR_SHARING_VIOLATION) { | 
|  | // The path is held open by another process. | 
|  | return ReadSymlinkOrJunctionResult::kAccessDenied; | 
|  | } else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { | 
|  | // Path or a parent directory does not exist. | 
|  | return ReadSymlinkOrJunctionResult::kDoesNotExist; | 
|  | } | 
|  |  | 
|  | // The path seems to exist yet we cannot open it for metadata-reading. | 
|  | // Report as much information as we have, then give up. | 
|  | if (error) { | 
|  | *error = | 
|  | MakeErrorMessage(WSTR(__FILE__), __LINE__, L"CreateFileW", path, err); | 
|  | } | 
|  | return ReadSymlinkOrJunctionResult::kError; | 
|  | } | 
|  |  | 
|  | uint8_t raw_buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; | 
|  | PREPARSE_DATA_BUFFER buf = reinterpret_cast<PREPARSE_DATA_BUFFER>(raw_buf); | 
|  | DWORD bytes_returned; | 
|  | if (!::DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, buf, | 
|  | MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytes_returned, | 
|  | NULL)) { | 
|  | DWORD err = GetLastError(); | 
|  | if (err == ERROR_NOT_A_REPARSE_POINT) { | 
|  | return ReadSymlinkOrJunctionResult::kNotALink; | 
|  | } | 
|  |  | 
|  | // Some unknown error occurred. | 
|  | if (error) { | 
|  | *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeviceIoControl", | 
|  | path, err); | 
|  | } | 
|  | return ReadSymlinkOrJunctionResult::kError; | 
|  | } | 
|  |  | 
|  | switch (buf->ReparseTag) { | 
|  | case IO_REPARSE_TAG_SYMLINK: { | 
|  | wchar_t* p = | 
|  | (wchar_t*)(((uint8_t*)buf->SymbolicLinkReparseBuffer.PathBuffer) + | 
|  | buf->SymbolicLinkReparseBuffer.SubstituteNameOffset); | 
|  | *result = wstring(p, buf->SymbolicLinkReparseBuffer.SubstituteNameLength / | 
|  | sizeof(WCHAR)); | 
|  | return ReadSymlinkOrJunctionResult::kSuccess; | 
|  | } | 
|  | case IO_REPARSE_TAG_MOUNT_POINT: { | 
|  | wchar_t* p = | 
|  | (wchar_t*)(((uint8_t*)buf->MountPointReparseBuffer.PathBuffer) + | 
|  | buf->MountPointReparseBuffer.SubstituteNameOffset); | 
|  | *result = wstring( | 
|  | p, buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); | 
|  | return ReadSymlinkOrJunctionResult::kSuccess; | 
|  | } | 
|  | default: | 
|  | return ReadSymlinkOrJunctionResult::kUnknownLinkType; | 
|  | } | 
|  | } | 
|  |  | 
|  | struct DirectoryStatus { | 
|  | enum { | 
|  | kDoesNotExist = 0, | 
|  | kDirectoryEmpty = 1, | 
|  | kDirectoryNotEmpty = 2, | 
|  | kChildMarkedForDeletionExists = 3, | 
|  | }; | 
|  | }; | 
|  |  | 
|  | // Check whether the directory and its child elements truly exist, or are marked | 
|  | // for deletion. The result could be: | 
|  | // 1. The give path doesn't exist | 
|  | // 2. The directory is empty | 
|  | // 3. The directory contains valid files or dirs, so not empty | 
|  | // 4. The directory contains only files or dirs marked for deletion. | 
|  | int CheckDirectoryStatus(const wstring& path) { | 
|  | static const wstring kDot(L"."); | 
|  | static const wstring kDotDot(L".."); | 
|  | bool found_valid_file = false; | 
|  | bool found_child_marked_for_deletion = false; | 
|  | WIN32_FIND_DATAW metadata; | 
|  | HANDLE handle = ::FindFirstFileW((path + L"\\*").c_str(), &metadata); | 
|  | if (handle == INVALID_HANDLE_VALUE) { | 
|  | return DirectoryStatus::kDoesNotExist; | 
|  | } | 
|  | do { | 
|  | if (kDot != metadata.cFileName && kDotDot != metadata.cFileName) { | 
|  | std::wstring child = path + L"\\" + metadata.cFileName; | 
|  | DWORD attributes = GetFileAttributesW(child.c_str()); | 
|  | if (attributes != INVALID_FILE_ATTRIBUTES) { | 
|  | // If there is a valid file under the directory, | 
|  | // then the directory is truely not empty. | 
|  | // We should just return kDirectoryNotEmpty. | 
|  | found_valid_file = true; | 
|  | break; | 
|  | } else { | 
|  | DWORD error_code = GetLastError(); | 
|  | // If the file or directory is in deleting process, | 
|  | // GetFileAttributesW returns ERROR_ACCESS_DENIED, | 
|  | // If it's already deleted at the time we check, | 
|  | // GetFileAttributesW returns ERROR_FILE_NOT_FOUND. | 
|  | // If GetFileAttributesW fails with other reason, we consider there is a | 
|  | // valid file that we cannot open, thus return kDirectoryNotEmpty | 
|  | if (error_code != ERROR_ACCESS_DENIED && | 
|  | error_code != ERROR_FILE_NOT_FOUND) { | 
|  | found_valid_file = true; | 
|  | break; | 
|  | } else if (error_code == ERROR_ACCESS_DENIED) { | 
|  | found_child_marked_for_deletion = true; | 
|  | } | 
|  | } | 
|  | } | 
|  | } while (::FindNextFileW(handle, &metadata)); | 
|  | ::FindClose(handle); | 
|  | if (found_valid_file) { | 
|  | return DirectoryStatus::kDirectoryNotEmpty; | 
|  | } | 
|  | if (found_child_marked_for_deletion) { | 
|  | return DirectoryStatus::kChildMarkedForDeletionExists; | 
|  | } | 
|  | return DirectoryStatus::kDirectoryEmpty; | 
|  | } | 
|  |  | 
|  | int GetResultFromErrorCode(const wchar_t* function_name, const wstring& path, | 
|  | DWORD err, wstring* error) { | 
|  | if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { | 
|  | // The file disappeared, or one of its parent directories disappeared, | 
|  | // or one of its parent directories is no longer a directory. | 
|  | return DeletePathResult::kDoesNotExist; | 
|  | } | 
|  | if (err == ERROR_ACCESS_DENIED || err == ERROR_SHARING_VIOLATION) { | 
|  | return DeletePathResult::kAccessDenied; | 
|  | } | 
|  | // Some unknown error occurred. | 
|  | if (error) { | 
|  | *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | function_name, path, err); | 
|  | } | 
|  | return DeletePathResult::kError; | 
|  | } | 
|  |  | 
|  | int DeletePath(const wstring& path, wstring* error) { | 
|  | if (!IsAbsoluteNormalizedWindowsPath(path)) { | 
|  | if (error) { | 
|  | *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, L"DeletePath", path, | 
|  | L"expected an absolute Windows path"); | 
|  | } | 
|  | return DeletePathResult::kError; | 
|  | } | 
|  |  | 
|  | const std::wstring winpath(AddUncPrefixMaybe(path)); | 
|  | const wchar_t* wpath = winpath.c_str(); | 
|  |  | 
|  | DWORD attr = GetFileAttributesW(wpath); | 
|  | DWORD err; | 
|  | if (attr == INVALID_FILE_ATTRIBUTES) { | 
|  | return GetResultFromErrorCode(L"GetFileAttributesW", path, | 
|  | GetLastError(), error); | 
|  | } | 
|  |  | 
|  | if (attr & FILE_ATTRIBUTE_DIRECTORY) { | 
|  | // It's a directory or a junction, RemoveDirectoryW should be used. | 
|  | // | 
|  | // Sometimes a deleted directory lingers in its parent dir | 
|  | // after the deleting handle has already been closed. | 
|  | // In this case we check the content of the parent directory, | 
|  | // if we don't find any valid file, we try to delete it again after 5 ms. | 
|  | // But we don't want to hang infinitely because another application | 
|  | // can hold the handle for a long time. So try at most 20 times, | 
|  | // which means a process time of 100-120ms. | 
|  | // Inspired by | 
|  | // https://github.com/Alexpux/Cygwin/commit/28fa2a72f810670a0562ea061461552840f5eb70 | 
|  | // Useful link: https://stackoverflow.com/questions/31606978 | 
|  | int count; | 
|  | for (count = 0; count < 20 && !RemoveDirectoryW(wpath); ++count) { | 
|  | // Failed to delete the directory. | 
|  | err = GetLastError(); | 
|  | if (err == ERROR_SHARING_VIOLATION || err == ERROR_ACCESS_DENIED) { | 
|  | // The junction or directory is in use by another process, or we have | 
|  | // no permission to delete it. | 
|  | return DeletePathResult::kAccessDenied; | 
|  | } else if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { | 
|  | // The directory or one of its parent directories disappeared or is no | 
|  | // longer a directory. | 
|  | return DeletePathResult::kDoesNotExist; | 
|  | } else if (err == ERROR_DIR_NOT_EMPTY) { | 
|  | // We got ERROR_DIR_NOT_EMPTY error, but maybe the child files and | 
|  | // dirs are already marked for deletion, let's check the status of the | 
|  | // child elements to see if we should retry the delete operation. | 
|  | switch (CheckDirectoryStatus(winpath)) { | 
|  | case DirectoryStatus::kDirectoryNotEmpty: | 
|  | // The directory is truely not empty. | 
|  | return DeletePathResult::kDirectoryNotEmpty; | 
|  | case DirectoryStatus::kDirectoryEmpty: | 
|  | // If no children are pending deletion then the directory is now | 
|  | // empty. We can try deleting it again without waiting. | 
|  | continue; | 
|  | case DirectoryStatus::kChildMarkedForDeletionExists: | 
|  | // If all child elements are marked for deletion, then wait 5 ms for | 
|  | // the system to delete the files and try deleting the directory | 
|  | // again. | 
|  | Sleep(5L); | 
|  | continue; | 
|  | case DirectoryStatus::kDoesNotExist: | 
|  | // This case should never happen, because ERROR_DIR_NOT_EMPTY | 
|  | // means the directory exists. But if it does happen, return an | 
|  | // error message. | 
|  | if (error) { | 
|  | *error = | 
|  | MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | L"RemoveDirectoryW", path, GetLastError()); | 
|  | } | 
|  | return DeletePathResult::kError; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Some unknown error occurred. | 
|  | if (error) { | 
|  | *error = MakeErrorMessage(WSTR(__FILE__), __LINE__, | 
|  | L"RemoveDirectoryW", path, err); | 
|  | } | 
|  | return DeletePathResult::kError; | 
|  | } | 
|  |  | 
|  | if (count == 20) { | 
|  | // After trying 20 times, the "deleted" sub-directories or files still | 
|  | // won't go away, so just return kDirectoryNotEmpty error. | 
|  | return DeletePathResult::kDirectoryNotEmpty; | 
|  | } | 
|  | } else { | 
|  | // It's a regular file or symlink, DeleteFileW should be used. | 
|  | if (attr & FILE_ATTRIBUTE_READONLY) { | 
|  | // Remove the read-only attribute. | 
|  | attr &= ~FILE_ATTRIBUTE_READONLY; | 
|  | if (!SetFileAttributesW(wpath, attr)) { | 
|  | return GetResultFromErrorCode(L"SetFileAttributesW", path, | 
|  | GetLastError(), error); | 
|  | } | 
|  | } | 
|  | if (!DeleteFileW(wpath)) { | 
|  | // Failed to delete the file or symlink. | 
|  | return GetResultFromErrorCode(L"DeleteFileW", path, | 
|  | GetLastError(), error); | 
|  | } | 
|  | } | 
|  |  | 
|  | return DeletePathResult::kSuccess; | 
|  | } | 
|  |  | 
|  | template <typename C> | 
|  | std::basic_string<C> NormalizeImpl(const std::basic_string<C>& p) { | 
|  | if (p.empty()) { | 
|  | return p; | 
|  | } | 
|  | typedef std::basic_string<C> Str; | 
|  | static const Str kDot(1, '.'); | 
|  | static const Str kDotDot(2, '.'); | 
|  | std::vector<std::pair<typename Str::size_type, typename Str::size_type> > | 
|  | segments; | 
|  | typename Str::size_type seg_start = Str::npos; | 
|  | bool first = true; | 
|  | bool abs = false; | 
|  | bool starts_with_dot = false; | 
|  | for (typename Str::size_type i = HasUncPrefix(p.c_str()) ? 4 : 0; | 
|  | i <= p.size(); ++i) { | 
|  | if (seg_start == Str::npos) { | 
|  | if (i < p.size() && p[i] != '/' && p[i] != '\\') { | 
|  | seg_start = i; | 
|  | } | 
|  | } else { | 
|  | if (i == p.size() || (p[i] == '/' || p[i] == '\\')) { | 
|  | // The current character ends a segment. | 
|  | typename Str::size_type len = i - seg_start; | 
|  | if (first) { | 
|  | first = false; | 
|  | abs = len == 2 && | 
|  | ((p[seg_start] >= 'A' && p[seg_start] <= 'Z') || | 
|  | (p[seg_start] >= 'a' && p[seg_start] <= 'z')) && | 
|  | p[seg_start + 1] == ':'; | 
|  | segments.push_back(std::make_pair(seg_start, len)); | 
|  | starts_with_dot = !abs && p.compare(seg_start, len, kDot) == 0; | 
|  | } else { | 
|  | if (p.compare(seg_start, len, kDot) == 0) { | 
|  | if (segments.empty()) { | 
|  | // Retain "." if that is the first (and possibly only segment). | 
|  | segments.push_back(std::make_pair(seg_start, len)); | 
|  | starts_with_dot = true; | 
|  | } | 
|  | } else { | 
|  | if (starts_with_dot) { | 
|  | // Delete the existing "." if that was the only path segment. | 
|  | segments.clear(); | 
|  | starts_with_dot = false; | 
|  | } | 
|  | if (p.compare(seg_start, len, kDotDot) == 0) { | 
|  | if (segments.empty() || | 
|  | p.compare(segments.back().first, segments.back().second, | 
|  | kDotDot) == 0) { | 
|  | // Preserve ".." if the path is relative and there are only ".." | 
|  | // segment(s) at the front. | 
|  | segments.push_back(std::make_pair(seg_start, len)); | 
|  | } else if (!abs || segments.size() > 1) { | 
|  | // Remove the last segment unless the path is already at the | 
|  | // root directory. | 
|  | segments.pop_back(); | 
|  | }  // Ignore ".." otherwise. | 
|  | } else { | 
|  | // This is a normal path segment, i.e. neither "." nor ".." | 
|  | segments.push_back(std::make_pair(seg_start, len)); | 
|  | } | 
|  | } | 
|  | } | 
|  | // Indicate that there's no segment started. | 
|  | seg_start = Str::npos; | 
|  | } | 
|  | } | 
|  | } | 
|  | std::basic_stringstream<C> res; | 
|  | first = true; | 
|  | for (const auto& i : segments) { | 
|  | Str s = p.substr(i.first, i.second); | 
|  | if (first) { | 
|  | first = false; | 
|  | } else { | 
|  | res << '\\'; | 
|  | } | 
|  | res << s; | 
|  | } | 
|  | if (abs && segments.size() == 1) { | 
|  | res << '\\'; | 
|  | } | 
|  | return res.str(); | 
|  | } | 
|  |  | 
|  | std::string Normalize(const std::string& p) { return NormalizeImpl(p); } | 
|  |  | 
|  | std::wstring Normalize(const std::wstring& p) { return NormalizeImpl(p); } | 
|  |  | 
|  | bool GetCwd(std::wstring* result, DWORD* err_code) { | 
|  | // Maximum path is 32767 characters, with null terminator that is 0x8000. | 
|  | static constexpr DWORD kMaxPath = 0x8000; | 
|  | WCHAR buf[kMaxPath]; | 
|  | DWORD len = GetCurrentDirectoryW(kMaxPath, buf); | 
|  | if (len > 0 && len < kMaxPath) { | 
|  | *result = buf; | 
|  | return true; | 
|  | } else { | 
|  | if (err_code) { | 
|  | *err_code = GetLastError(); | 
|  | } | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace windows | 
|  | }  // namespace bazel |