| // 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 <versionhelpers.h> |
| #include <windows.h> |
| |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| |
| #include "src/main/native/windows/util.h" |
| |
| #ifndef IO_REPARSE_TAG_PROJFS |
| #define IO_REPARSE_TAG_PROJFS 0x9000001C |
| #endif |
| |
| namespace bazel { |
| namespace windows { |
| |
| using std::unique_ptr; |
| using std::wstring; |
| |
| DWORD DetermineSymlinkPrivilegeFlag() { |
| DWORD val = 0; |
| DWORD valSize = sizeof(val); |
| // Check if developer mode is disabled |
| if (RegGetValueW( |
| HKEY_LOCAL_MACHINE, |
| L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", |
| L"AllowDevelopmentWithoutDevLicense", RRF_RT_DWORD, nullptr, &val, |
| &valSize) != ERROR_SUCCESS || |
| val == 0) { |
| return 0; |
| } |
| return SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; |
| } |
| |
| 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(), nullptr, 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(), nullptr) != 0; |
| |
| AutoHandle handle; |
| if (create) { |
| handle = CreateFileW( |
| name.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, |
| OPEN_EXISTING, |
| FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); |
| } |
| |
| 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, |
| nullptr, OPEN_EXISTING, |
| FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); |
| 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), |
| nullptr, 0, &bytes_returned, nullptr)) { |
| 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, nullptr, 0, |
| reparse_buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, |
| &bytes_returned, nullptr)) { |
| 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 CreateSymlink(const wstring& symlink_name, const wstring& symlink_target, |
| wstring* error) { |
| if (!IsAbsoluteNormalizedWindowsPath(symlink_name)) { |
| if (error) { |
| *error = MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"CreateSymlink", symlink_name, |
| L"expected an absolute Windows path for symlink_name"); |
| } |
| return CreateSymlinkResult::kError; |
| } |
| if (!IsAbsoluteNormalizedWindowsPath(symlink_target)) { |
| if (error) { |
| *error = MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"CreateSymlink", symlink_target, |
| L"expected an absolute Windows path for symlink_target"); |
| } |
| return CreateSymlinkResult::kError; |
| } |
| |
| const wstring name = AddUncPrefixMaybe(symlink_name); |
| const wstring target = AddUncPrefixMaybe(symlink_target); |
| |
| DWORD attrs = GetFileAttributesW(target.c_str()); |
| if (attrs & FILE_ATTRIBUTE_DIRECTORY) { |
| // Instead of creating a symlink to a directory use a Junction. |
| return CreateSymlinkResult::kTargetIsDirectory; |
| } |
| |
| if (!CreateSymbolicLinkW(name.c_str(), target.c_str(), |
| symlinkPrivilegeFlag)) { |
| if (GetLastError() == ERROR_INVALID_PARAMETER) { |
| // We are on a version of Windows that does not support this flag. |
| // Retry without the flag and return to error handling if necessary. |
| if (CreateSymbolicLinkW(name.c_str(), target.c_str(), 0)) { |
| return CreateSymlinkResult::kSuccess; |
| } |
| } |
| *error = MakeErrorMessage( |
| WSTR(__FILE__), __LINE__, L"CreateSymlink", symlink_target, |
| GetLastError() == ERROR_PRIVILEGE_NOT_HELD |
| ? L"createSymbolicLinkW failed (permission denied). Either " |
| "Windows developer mode or admin privileges are required." |
| : L"createSymbolicLinkW failed"); |
| return CreateSymlinkResult::kError; |
| } |
| return CreateSymlinkResult::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, nullptr, |
| OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, |
| nullptr)); |
| 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, nullptr, 0, buf, |
| MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytes_returned, |
| nullptr)) { |
| 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; |
| } |
| case IO_REPARSE_TAG_PROJFS: { |
| // Virtual File System for Git |
| return ReadSymlinkOrJunctionResult::kNotALink; |
| } |
| 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_READONLY) { |
| // Remove the read-only attribute. |
| attr &= ~FILE_ATTRIBUTE_READONLY; |
| if (!SetFileAttributesW(wpath, attr)) { |
| return GetResultFromErrorCode(L"SetFileAttributesW", 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 (!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 |