| // 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<Str::size_type, Str::size_type> > segments; | |
| Str::size_type seg_start = Str::npos; | |
| bool first = true; | |
| bool abs = false; | |
| bool starts_with_dot = false; | |
| for (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. | |
| 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 |