| // 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 <ctype.h> // isalpha |
| #include <wchar.h> // wcslen |
| #include <wctype.h> // iswalpha |
| #include <windows.h> |
| |
| #include <memory> // unique_ptr |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| |
| #include "src/main/cpp/util/errors.h" |
| #include "src/main/cpp/util/exit_code.h" |
| #include "src/main/cpp/util/file.h" |
| #include "src/main/cpp/util/logging.h" |
| #include "src/main/cpp/util/path.h" |
| #include "src/main/cpp/util/path_platform.h" |
| #include "src/main/cpp/util/strings.h" |
| #include "src/main/native/windows/file.h" |
| #include "src/main/native/windows/util.h" |
| |
| namespace blaze_util { |
| |
| using bazel::windows::AutoHandle; |
| using bazel::windows::GetLongPath; |
| using bazel::windows::HasUncPrefix; |
| using std::basic_string; |
| using std::pair; |
| using std::string; |
| using std::unique_ptr; |
| using std::vector; |
| using std::wstring; |
| |
| static constexpr DWORD kAllShare = |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; |
| |
| // Returns true if `path` refers to a directory or (non-dangling) junction. |
| // `path` must be a normalized Windows path, with UNC prefix (and absolute) if |
| // necessary. |
| bool IsDirectoryW(const wstring& path); |
| |
| // Returns true the file or junction at `path` is successfully deleted. |
| // Returns false otherwise, or if `path` doesn't exist or is a directory. |
| // `path` must be a normalized Windows path, with UNC prefix (and absolute) if |
| // necessary. |
| static bool UnlinkPathW(const wstring& path); |
| |
| static bool CanReadFileW(const wstring& path); |
| |
| template <typename char_type> |
| static bool IsPathSeparator(char_type ch) { |
| return ch == '/' || ch == '\\'; |
| } |
| |
| class WindowsPipe : public IPipe { |
| public: |
| WindowsPipe(const HANDLE& read_handle, const HANDLE& write_handle) |
| : _read_handle(read_handle), _write_handle(write_handle) {} |
| |
| WindowsPipe() = delete; |
| |
| bool Send(const void* buffer, int size) override { |
| DWORD actually_written = 0; |
| return ::WriteFile(_write_handle, buffer, size, &actually_written, |
| nullptr) == TRUE; |
| } |
| |
| int Receive(void* buffer, int size, int* error) override { |
| DWORD actually_read = 0; |
| BOOL result = |
| ::ReadFile(_read_handle, buffer, size, &actually_read, nullptr); |
| if (error != nullptr) { |
| // TODO(laszlocsomor): handle the error mode that is errno=EINTR on Linux. |
| *error = result ? IPipe::SUCCESS : IPipe::OTHER_ERROR; |
| } |
| return result ? actually_read : -1; |
| } |
| |
| private: |
| AutoHandle _read_handle; |
| AutoHandle _write_handle; |
| }; |
| |
| IPipe* CreatePipe() { |
| // The pipe HANDLEs can be inherited. |
| SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; |
| HANDLE read_handle = INVALID_HANDLE_VALUE; |
| HANDLE write_handle = INVALID_HANDLE_VALUE; |
| if (!CreatePipe(&read_handle, &write_handle, &sa, 0)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "CreatePipe failed: " << GetLastErrorString(); |
| } |
| return new WindowsPipe(read_handle, write_handle); |
| } |
| |
| class WindowsFileMtime : public IFileMtime { |
| public: |
| WindowsFileMtime() |
| : near_future_(GetFuture(9)), distant_future_(GetFuture(10)) {} |
| |
| bool IsUntampered(const Path& path) override; |
| bool SetToNow(const Path& path) override; |
| bool SetToNowIfPossible(const Path& path) override; |
| bool SetToDistantFuture(const Path& path) override; |
| |
| private: |
| // 9 years in the future. |
| const FILETIME near_future_; |
| // 10 years in the future. |
| const FILETIME distant_future_; |
| |
| static FILETIME GetNow(); |
| static FILETIME GetFuture(WORD years); |
| static bool Set(const Path& path, FILETIME time); |
| }; |
| |
| bool WindowsFileMtime::IsUntampered(const Path& path) { |
| if (path.IsEmpty() || path.IsNull()) { |
| return false; |
| } |
| |
| // Get attributes, to check if the file exists. (It may still be a dangling |
| // junction.) |
| DWORD attrs = GetFileAttributesW(path.AsNativePath().c_str()); |
| if (attrs == INVALID_FILE_ATTRIBUTES) { |
| return false; |
| } |
| |
| bool is_directory = attrs & FILE_ATTRIBUTE_DIRECTORY; |
| AutoHandle handle(CreateFileW( |
| /* lpFileName */ path.AsNativePath().c_str(), |
| /* dwDesiredAccess */ GENERIC_READ, |
| /* dwShareMode */ FILE_SHARE_READ, |
| /* lpSecurityAttributes */ nullptr, |
| /* dwCreationDisposition */ OPEN_EXISTING, |
| /* dwFlagsAndAttributes */ |
| // Per CreateFile's documentation on MSDN, opening directories requires |
| // the FILE_FLAG_BACKUP_SEMANTICS flag. |
| is_directory ? FILE_FLAG_BACKUP_SEMANTICS : FILE_ATTRIBUTE_NORMAL, |
| /* hTemplateFile */ nullptr)); |
| |
| if (!handle.IsValid()) { |
| return false; |
| } |
| |
| if (is_directory) { |
| return true; |
| } else { |
| BY_HANDLE_FILE_INFORMATION info; |
| if (!GetFileInformationByHandle(handle, &info)) { |
| return false; |
| } |
| |
| // Compare the mtime with `near_future_`, not with `GetNow()` or |
| // `distant_future_`. |
| // This way we don't need to call GetNow() every time we want to compare |
| // (and thus convert a SYSTEMTIME to FILETIME), and we also don't need to |
| // worry about potentially unreliable FILETIME equality check (in case it |
| // uses floats or something crazy). |
| return CompareFileTime(&near_future_, &info.ftLastWriteTime) == -1; |
| } |
| } |
| |
| bool WindowsFileMtime::SetToNow(const Path& path) { |
| return Set(path, GetNow()); |
| } |
| |
| bool WindowsFileMtime::SetToNowIfPossible(const Path& path) { |
| bool okay = this->SetToNow(path); |
| if (!okay) { |
| // `SetToNow` is backed by `CreateFileW` + `SetFileTime`; the former can |
| // return `ERROR_ACCESS_DENIED` if there's a permissions issue: |
| if (GetLastError() == ERROR_ACCESS_DENIED) { |
| okay = true; |
| } |
| } |
| |
| return okay; |
| } |
| |
| bool WindowsFileMtime::SetToDistantFuture(const Path& path) { |
| return Set(path, distant_future_); |
| } |
| |
| bool WindowsFileMtime::Set(const Path& path, FILETIME time) { |
| AutoHandle handle(::CreateFileW( |
| /* lpFileName */ path.AsNativePath().c_str(), |
| /* dwDesiredAccess */ FILE_WRITE_ATTRIBUTES, |
| /* dwShareMode */ FILE_SHARE_READ, |
| /* lpSecurityAttributes */ nullptr, |
| /* dwCreationDisposition */ OPEN_EXISTING, |
| /* dwFlagsAndAttributes */ |
| IsDirectoryW(path.AsNativePath()) |
| ? (FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS) |
| : FILE_ATTRIBUTE_NORMAL, |
| /* hTemplateFile */ nullptr)); |
| if (!handle.IsValid()) { |
| return false; |
| } |
| return ::SetFileTime( |
| /* hFile */ handle, |
| /* lpCreationTime */ nullptr, |
| /* lpLastAccessTime */ nullptr, |
| /* lpLastWriteTime */ &time) == TRUE; |
| } |
| |
| FILETIME WindowsFileMtime::GetNow() { |
| FILETIME now; |
| GetSystemTimeAsFileTime(&now); |
| return now; |
| } |
| |
| FILETIME WindowsFileMtime::GetFuture(WORD years) { |
| FILETIME result; |
| GetSystemTimeAsFileTime(&result); |
| |
| // 1 year in FILETIME. |
| constexpr ULONGLONG kOneYear = 365ULL * 24 * 60 * 60 * 10000000; |
| |
| ULARGE_INTEGER result_value; |
| result_value.LowPart = result.dwLowDateTime; |
| result_value.HighPart = result.dwHighDateTime; |
| result_value.QuadPart += kOneYear * years; |
| result.dwLowDateTime = result_value.LowPart; |
| result.dwHighDateTime = result_value.HighPart; |
| return result; |
| } |
| |
| IFileMtime* CreateFileMtime() { return new WindowsFileMtime(); } |
| |
| static bool OpenFileForReading(const Path& path, HANDLE* result) { |
| *result = ::CreateFileW( |
| /* lpFileName */ path.AsNativePath().c_str(), |
| /* dwDesiredAccess */ GENERIC_READ, |
| /* dwShareMode */ kAllShare, |
| /* lpSecurityAttributes */ nullptr, |
| /* dwCreationDisposition */ OPEN_EXISTING, |
| /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, |
| /* hTemplateFile */ nullptr); |
| return true; |
| } |
| |
| int ReadFromHandle(file_handle_type handle, void* data, size_t size, |
| int* error) { |
| DWORD actually_read = 0; |
| bool success = ::ReadFile(handle, data, size, &actually_read, nullptr); |
| if (error != nullptr) { |
| // TODO(laszlocsomor): handle the error cases that are errno=EINTR and |
| // errno=EAGAIN on Linux. |
| *error = success ? ReadFileResult::SUCCESS : ReadFileResult::OTHER_ERROR; |
| } |
| return success ? actually_read : -1; |
| } |
| |
| bool ReadFile(const string& filename, string* content, int max_size) { |
| return ReadFile(filename, content, nullptr, max_size); |
| } |
| |
| bool ReadFile(const string& filename, string* content, string* error_message, |
| int max_size) { |
| if (IsDevNull(filename.c_str())) { |
| // mimic read(2) behavior: we can always read 0 bytes from /dev/null |
| content->clear(); |
| return true; |
| } |
| |
| // In order for try-imports to be ignored gracefully we need to check for an |
| // error with the path and return false rather than die. |
| std::string errorText; |
| Path path = Path(filename, &errorText); |
| if (!errorText.empty()) { |
| std::string message = "Path is not valid: " + filename + " :" + errorText; |
| BAZEL_LOG(WARNING) << message; |
| if (error_message != nullptr) { |
| *error_message = std::move(message); |
| } |
| return false; |
| } |
| return ReadFile(path, content, max_size); |
| } |
| |
| bool ReadFile(const Path& path, std::string* content, int max_size) { |
| if (path.IsEmpty()) { |
| return false; |
| } |
| // TODO(laszlocsomor): remove the following check; it won't allow opening NUL. |
| if (path.IsNull()) { |
| return true; |
| } |
| |
| HANDLE handle; |
| if (!OpenFileForReading(path, &handle)) { |
| return false; |
| } |
| |
| AutoHandle autohandle(handle); |
| if (!autohandle.IsValid()) { |
| return false; |
| } |
| content->clear(); |
| return ReadFrom(handle, content, max_size); |
| } |
| |
| bool ReadFile(const string& filename, void* data, size_t size) { |
| return ReadFile(Path(filename), data, size); |
| } |
| |
| bool ReadFile(const Path& path, void* data, size_t size) { |
| if (path.IsEmpty()) { |
| return false; |
| } |
| if (path.IsNull()) { |
| // mimic read(2) behavior: we can always read 0 bytes from /dev/null |
| return true; |
| } |
| HANDLE handle; |
| if (!OpenFileForReading(path, &handle)) { |
| return false; |
| } |
| |
| AutoHandle autohandle(handle); |
| if (!autohandle.IsValid()) { |
| return false; |
| } |
| return ReadFrom(handle, data, size); |
| } |
| |
| bool WriteFile(const void* data, size_t size, const string& filename, |
| unsigned int perm) { |
| if (IsDevNull(filename.c_str())) { |
| return true; // mimic write(2) behavior with /dev/null |
| } |
| return WriteFile(data, size, Path(filename), perm); |
| } |
| |
| bool WriteFile(const void* data, size_t size, const Path& path, |
| unsigned int perm) { |
| UnlinkPathW(path.AsNativePath()); // We don't care about the success of this. |
| AutoHandle handle(::CreateFileW( |
| /* lpFileName */ path.AsNativePath().c_str(), |
| /* dwDesiredAccess */ GENERIC_WRITE, |
| /* dwShareMode */ FILE_SHARE_READ, |
| /* lpSecurityAttributes */ nullptr, |
| /* dwCreationDisposition */ CREATE_ALWAYS, |
| /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, |
| /* hTemplateFile */ nullptr)); |
| if (!handle.IsValid()) { |
| return false; |
| } |
| |
| // TODO(laszlocsomor): respect `perm` and set the file permissions accordingly |
| DWORD actually_written = 0; |
| ::WriteFile(handle, data, size, &actually_written, nullptr); |
| return actually_written == size; |
| } |
| |
| int WriteToStdOutErr(const void* data, size_t size, bool to_stdout) { |
| DWORD written = 0; |
| HANDLE h = ::GetStdHandle(to_stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); |
| if (h == INVALID_HANDLE_VALUE) { |
| return WriteResult::OTHER_ERROR; |
| } |
| |
| if (::WriteFile(h, data, size, &written, nullptr)) { |
| return (written == size) ? WriteResult::SUCCESS : WriteResult::OTHER_ERROR; |
| } else { |
| return (GetLastError() == ERROR_NO_DATA) ? WriteResult::BROKEN_PIPE |
| : WriteResult::OTHER_ERROR; |
| } |
| } |
| |
| int RenameDirectory(const std::string& old_name, const std::string& new_name) { |
| wstring wold_name; |
| string error; |
| if (!AsAbsoluteWindowsPath(old_name, &wold_name, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "RenameDirectory(" << old_name << ", " << new_name |
| << "): AsAbsoluteWindowsPath(" << old_name << ") failed: " << error; |
| return kRenameDirectoryFailureOtherError; |
| } |
| |
| wstring wnew_name; |
| if (!AsAbsoluteWindowsPath(new_name, &wnew_name, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "RenameDirectory(" << old_name << ", " << new_name |
| << "): AsAbsoluteWindowsPath(" << new_name << ") failed: " << error; |
| return kRenameDirectoryFailureOtherError; |
| } |
| |
| if (!::MoveFileExW(wold_name.c_str(), wnew_name.c_str(), |
| MOVEFILE_COPY_ALLOWED | MOVEFILE_FAIL_IF_NOT_TRACKABLE | |
| MOVEFILE_WRITE_THROUGH)) { |
| return GetLastError() == ERROR_ALREADY_EXISTS |
| ? kRenameDirectoryFailureNotEmpty |
| : kRenameDirectoryFailureOtherError; |
| } |
| return kRenameDirectorySuccess; |
| } |
| |
| static bool UnlinkPathW(const wstring& path) { |
| DWORD attrs = ::GetFileAttributesW(path.c_str()); |
| if (attrs == INVALID_FILE_ATTRIBUTES) { |
| // Path does not exist. |
| return false; |
| } |
| if (attrs & FILE_ATTRIBUTE_DIRECTORY) { |
| if (!(attrs & FILE_ATTRIBUTE_REPARSE_POINT)) { |
| // Path is a directory; unlink(2) also cannot remove directories. |
| return false; |
| } |
| // Otherwise it's a junction, remove using RemoveDirectoryW. |
| return ::RemoveDirectoryW(path.c_str()) == TRUE; |
| } else { |
| // Otherwise it's a file, remove using DeleteFileW. |
| return ::DeleteFileW(path.c_str()) == TRUE; |
| } |
| } |
| |
| bool UnlinkPath(const string& file_path) { |
| if (IsDevNull(file_path.c_str())) { |
| return false; |
| } |
| return UnlinkPath(Path(file_path)); |
| } |
| |
| bool UnlinkPath(const Path& path) { return UnlinkPathW(path.AsNativePath()); } |
| |
| static bool RealPath(const WCHAR* path, unique_ptr<WCHAR[]>* result = nullptr) { |
| // Attempt opening the path, which may be anything -- a file, a directory, a |
| // symlink, even a dangling symlink is fine. |
| // Follow reparse points, getting us that much closer to the real path. |
| AutoHandle h(CreateFileW(path, 0, kAllShare, nullptr, OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS, nullptr)); |
| if (!h.IsValid()) { |
| // Path does not exist or it's a dangling junction/symlink. |
| return false; |
| } |
| |
| if (!result) { |
| // The caller is only interested in whether the file exists, they aren't |
| // interested in its real path. Since we just successfully opened the file |
| // we already know it exists. |
| // Also, GetFinalPathNameByHandleW is slow so avoid calling it if we can. |
| return true; |
| } |
| |
| // kMaxPath value: according to MSDN, maximum path length is 32767, and with |
| // an extra null terminator that's exactly 0x8000. |
| static constexpr size_t kMaxPath = 0x8000; |
| std::unique_ptr<WCHAR[]> buf(new WCHAR[kMaxPath]); |
| DWORD res = GetFinalPathNameByHandleW(h, buf.get(), kMaxPath, 0); |
| if (res > 0 && res < kMaxPath) { |
| *result = std::move(buf); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| bool ReadDirectorySymlink(const blaze_util::Path& name, string* result) { |
| unique_ptr<WCHAR[]> result_ptr; |
| if (!RealPath(name.AsNativePath().c_str(), &result_ptr)) { |
| return false; |
| } |
| *result = WstringToCstring(RemoveUncPrefixMaybe(result_ptr.get())); |
| return true; |
| } |
| |
| bool PathExists(const string& path) { return PathExists(Path(path)); } |
| |
| bool PathExists(const Path& path) { |
| if (path.IsEmpty()) { |
| return false; |
| } |
| if (path.IsNull()) { |
| return true; |
| } |
| return RealPath(path.AsNativePath().c_str(), nullptr); |
| } |
| |
| string MakeCanonical(const char* path) { |
| if (IsDevNull(path)) { |
| return "NUL"; |
| } |
| if (path == nullptr || path[0] == 0) { |
| return ""; |
| } |
| |
| std::wstring wpath; |
| string error; |
| if (!AsAbsoluteWindowsPath(path, &wpath, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "MakeCanonical(" << path |
| << "): AsAbsoluteWindowsPath failed: " << error; |
| } |
| |
| std::unique_ptr<WCHAR[]> long_realpath; |
| if (!RealPath(wpath.c_str(), &long_realpath)) { |
| return ""; |
| } |
| |
| // Convert the path to lower-case. |
| size_t size = |
| wcslen(long_realpath.get()) - (HasUncPrefix(long_realpath.get()) ? 4 : 0); |
| unique_ptr<WCHAR[]> lcase_realpath(new WCHAR[size + 1]); |
| const WCHAR* p_from = RemoveUncPrefixMaybe(long_realpath.get()); |
| WCHAR* p_to = lcase_realpath.get(); |
| while (size-- > 0) { |
| *p_to++ = towlower(*p_from++); |
| } |
| *p_to = 0; |
| return WstringToCstring(lcase_realpath.get()); |
| } |
| |
| static bool CanReadFileW(const wstring& path) { |
| AutoHandle handle(CreateFileW(path.c_str(), GENERIC_READ, kAllShare, nullptr, |
| OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); |
| return handle.IsValid(); |
| } |
| |
| bool CanReadFile(const std::string& path) { |
| return CanReadFile(Path(path)); |
| } |
| |
| bool CanReadFile(const Path& path) { |
| return CanReadFileW(path.AsNativePath()); |
| } |
| |
| bool CanExecuteFile(const std::string& path) { |
| return CanExecuteFile(Path(path)); |
| } |
| |
| bool CanExecuteFile(const Path& path) { |
| std::wstring p = path.AsNativePath(); |
| if (p.size() < 4) { |
| return false; |
| } |
| std::wstring ext = p.substr(p.size() - 4); |
| return CanReadFileW(p) && |
| (ext == L".exe" || ext == L".com" || ext == L".cmd" || ext == L".bat"); |
| } |
| |
| bool CanAccessDirectory(const std::string& path) { |
| return CanAccessDirectory(Path(path)); |
| } |
| |
| bool CanAccessDirectory(const Path& path) { |
| DWORD attr = ::GetFileAttributesW(path.AsNativePath().c_str()); |
| if ((attr == INVALID_FILE_ATTRIBUTES) || !(attr & FILE_ATTRIBUTE_DIRECTORY)) { |
| // The path doesn't exist or is not a directory. |
| return false; |
| } |
| |
| // The only easy way to know if a directory is writable is by attempting to |
| // open a file for writing in it. |
| // File name with Thread ID avoids races among concurrent Bazel processes. |
| std::string dummy_name = "bazel_directory_access_test_"; |
| dummy_name += std::to_string(::GetCurrentThreadId()); |
| |
| Path dummy_path = path.GetRelative(dummy_name); |
| |
| // Attempt to open the dummy file for read/write access. |
| // If the file happens to exist, no big deal, we won't overwrite it thanks to |
| // OPEN_ALWAYS. |
| HANDLE handle = ::CreateFileW( |
| /* lpFileName */ dummy_path.AsNativePath().c_str(), |
| /* dwDesiredAccess */ GENERIC_WRITE | GENERIC_READ, |
| /* dwShareMode */ kAllShare, |
| /* lpSecurityAttributes */ nullptr, |
| /* dwCreationDisposition */ OPEN_ALWAYS, |
| /* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL, |
| /* hTemplateFile */ nullptr); |
| DWORD err = GetLastError(); |
| if (handle == INVALID_HANDLE_VALUE) { |
| // We couldn't open the file, and not because the dummy file already exists. |
| // Consequently it is because `path` doesn't exist. |
| return false; |
| } |
| // The fact that we could open the file, regardless of it existing beforehand |
| // or not, means the directory also exists and we can read/write in it. |
| CloseHandle(handle); |
| if (err != ERROR_ALREADY_EXISTS) { |
| // The file didn't exist before, but due to OPEN_ALWAYS we created it just |
| // now, so do delete it. |
| ::DeleteFileW(dummy_path.AsNativePath().c_str()); |
| } // Otherwise the file existed before, leave it alone. |
| return true; |
| } |
| |
| bool IsDirectoryW(const wstring& path) { |
| // Attempt opening the path, which may be anything -- a file, a directory, a |
| // symlink, even a dangling symlink is fine. |
| // Follow reparse points in order to return false for dangling ones. |
| AutoHandle h(CreateFileW(path.c_str(), 0, kAllShare, nullptr, OPEN_EXISTING, |
| FILE_FLAG_BACKUP_SEMANTICS, nullptr)); |
| BY_HANDLE_FILE_INFORMATION info; |
| return h.IsValid() && GetFileInformationByHandle(h, &info) && |
| info.dwFileAttributes != INVALID_FILE_ATTRIBUTES && |
| (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); |
| } |
| |
| bool IsDirectory(const string& path) { return IsDirectory(Path(path)); } |
| |
| bool IsDirectory(const Path& path) { |
| if (path.IsEmpty() || path.IsNull()) { |
| return false; |
| } |
| return IsDirectoryW(path.AsNativePath()); |
| } |
| |
| void SyncFile(const string& path) { |
| // No-op on Windows native; unsupported by Cygwin. |
| // fsync always fails on Cygwin with "Permission denied" for some reason. |
| } |
| |
| void SyncFile(const Path& path) {} |
| |
| bool MakeDirectoriesW(const wstring& path, unsigned int mode) { |
| if (path.empty()) { |
| return false; |
| } |
| std::wstring abs_path; |
| std::string error; |
| if (!AsAbsoluteWindowsPath(path, &abs_path, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "MakeDirectoriesW(" << blaze_util::WstringToCstring(path) |
| << "): " << error; |
| } |
| if (IsRootDirectoryW(abs_path) || IsDirectoryW(abs_path)) { |
| return true; |
| } |
| wstring parent = SplitPathW(abs_path).first; |
| if (parent.empty()) { |
| // Since `abs_path` is not a root directory, there should have been at least |
| // one directory above it. |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "MakeDirectoriesW(" << blaze_util::WstringToCstring(abs_path) |
| << ") could not find dirname: " << GetLastErrorString(); |
| } |
| return MakeDirectoriesW(parent, mode) && |
| ::CreateDirectoryW(abs_path.c_str(), nullptr) == TRUE; |
| } |
| |
| bool MakeDirectories(const string& path, unsigned int mode) { |
| // TODO(laszlocsomor): respect `mode` to the extent that it's possible on |
| // Windows; it's currently ignored. |
| if (path.empty() || IsDevNull(path.c_str())) { |
| return false; |
| } |
| wstring wpath; |
| // According to MSDN, CreateDirectory's limit without the UNC prefix is |
| // 248 characters (so it could fit another filename before reaching MAX_PATH). |
| string error; |
| if (!AsAbsoluteWindowsPath(path, &wpath, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "MakeDirectories(" << path |
| << "): AsAbsoluteWindowsPath failed: " << error; |
| return false; |
| } |
| return MakeDirectoriesW(wpath, mode); |
| } |
| |
| bool MakeDirectories(const Path& path, unsigned int mode) { |
| return MakeDirectoriesW(path.AsNativePath(), mode); |
| } |
| |
| string CreateTempDir(const std::string &prefix) { |
| string result = prefix + blaze_util::ToString(GetCurrentProcessId()); |
| if (!blaze_util::MakeDirectories(result, 0777)) { |
| BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR) |
| << "couldn't create '" << result |
| << "': " << blaze_util::GetLastErrorString(); |
| } |
| return result; |
| } |
| |
| static bool RemoveContents(wstring path) { |
| static const wstring kDot(L"."); |
| static const wstring kDotDot(L".."); |
| |
| if (path.find(L"\\\\?\\") != 0) { |
| path = wstring(L"\\\\?\\") + path; |
| } |
| if (path.back() != '\\') { |
| path.push_back('\\'); |
| } |
| |
| WIN32_FIND_DATAW metadata; |
| HANDLE handle = FindFirstFileW((path + L"*").c_str(), &metadata); |
| if (handle == INVALID_HANDLE_VALUE) { |
| return true; // directory doesn't exist |
| } |
| |
| bool result = true; |
| do { |
| wstring childname = metadata.cFileName; |
| if (kDot != childname && kDotDot != childname) { |
| wstring childpath = path + childname; |
| if ((metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { |
| // If this is not a junction, delete its contents recursively. |
| // Finally delete this directory/junction too. |
| if (((metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 && |
| !RemoveContents(childpath)) || |
| !::RemoveDirectoryW(childpath.c_str())) { |
| result = false; |
| break; |
| } |
| } else { |
| if (!::DeleteFileW(childpath.c_str())) { |
| result = false; |
| break; |
| } |
| } |
| } |
| } while (FindNextFileW(handle, &metadata)); |
| FindClose(handle); |
| return result; |
| } |
| |
| static bool RemoveRecursivelyW(const wstring& path) { |
| DWORD attrs = ::GetFileAttributesW(path.c_str()); |
| if (attrs == INVALID_FILE_ATTRIBUTES) { |
| // Path does not exist. |
| return true; |
| } |
| if (attrs & FILE_ATTRIBUTE_DIRECTORY) { |
| if (!(attrs & FILE_ATTRIBUTE_REPARSE_POINT)) { |
| // Path is a directory; unlink(2) also cannot remove directories. |
| return RemoveContents(path) && ::RemoveDirectoryW(path.c_str()); |
| } |
| // Otherwise it's a junction, remove using RemoveDirectoryW. |
| return ::RemoveDirectoryW(path.c_str()) == TRUE; |
| } else { |
| // Otherwise it's a file, remove using DeleteFileW. |
| return ::DeleteFileW(path.c_str()) == TRUE; |
| } |
| } |
| |
| bool RemoveRecursively(const string& path) { |
| return RemoveRecursivelyW(Path(path).AsNativePath()); |
| } |
| |
| static inline void ToLowerW(WCHAR* p) { |
| while (*p) { |
| *p++ = towlower(*p); |
| } |
| } |
| |
| std::wstring GetCwdW() { |
| static constexpr size_t kBufSmall = MAX_PATH; |
| WCHAR buf[kBufSmall]; |
| DWORD len = GetCurrentDirectoryW(kBufSmall, buf); |
| if (len == 0) { |
| DWORD err = GetLastError(); |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "GetCurrentDirectoryW failed (error " << err << ")"; |
| } |
| |
| if (len < kBufSmall) { |
| ToLowerW(buf); |
| return std::wstring(buf); |
| } |
| |
| unique_ptr<WCHAR[]> buf_big(new WCHAR[len]); |
| len = GetCurrentDirectoryW(len, buf_big.get()); |
| if (len == 0) { |
| DWORD err = GetLastError(); |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "GetCurrentDirectoryW failed (error " << err << ")"; |
| } |
| ToLowerW(buf_big.get()); |
| return std::wstring(buf_big.get()); |
| } |
| |
| string GetCwd() { |
| return WstringToCstring(RemoveUncPrefixMaybe(GetCwdW().c_str())); |
| } |
| |
| bool ChangeDirectory(const string& path) { |
| string spath; |
| string error; |
| if (!AsShortWindowsPath(path, &spath, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ChangeDirectory(" << path << "): failed: " << error; |
| } |
| return ::SetCurrentDirectoryA(spath.c_str()) == TRUE; |
| } |
| |
| class DirectoryTreeWalkerW : public DirectoryEntryConsumerW { |
| public: |
| DirectoryTreeWalkerW(vector<wstring>* files, |
| _ForEachDirectoryEntryW walk_entries) |
| : _files(files), _walk_entries(walk_entries) {} |
| |
| void Consume(const wstring& path, bool follow_directory) override { |
| if (follow_directory) { |
| Walk(path); |
| } else { |
| _files->push_back(path); |
| } |
| } |
| |
| void Walk(const wstring& path) { _walk_entries(path, this); } |
| |
| private: |
| vector<wstring>* _files; |
| _ForEachDirectoryEntryW _walk_entries; |
| }; |
| |
| void ForEachDirectoryEntryW(const wstring& path, |
| DirectoryEntryConsumerW* consume) { |
| wstring wpath; |
| if (path.empty() || IsDevNull(path.c_str())) { |
| return; |
| } |
| string error; |
| if (!AsWindowsPath(path, &wpath, &error)) { |
| std::string err = GetLastErrorString(); |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ForEachDirectoryEntryW(" << WstringToCstring(path) |
| << "): AsWindowsPath failed: " << err; |
| } |
| |
| static const wstring kUncPrefix(L"\\\\?\\"); |
| static const wstring kDot(L"."); |
| static const wstring kDotDot(L".."); |
| // Always add an UNC prefix to ensure we can work with long paths. |
| if (!HasUncPrefix(wpath.c_str())) { |
| wpath = kUncPrefix + wpath; |
| } |
| // Unconditionally add a trailing backslash. We know `wpath` has no trailing |
| // backslash because it comes from AsWindowsPath whose output is always |
| // normalized (see NormalizeWindowsPath). |
| wpath.append(L"\\"); |
| WIN32_FIND_DATAW metadata; |
| HANDLE handle = ::FindFirstFileW((wpath + L"*").c_str(), &metadata); |
| if (handle == INVALID_HANDLE_VALUE) { |
| return; // directory does not exist or is empty |
| } |
| |
| do { |
| if (kDot != metadata.cFileName && kDotDot != metadata.cFileName) { |
| wstring wname = wpath + metadata.cFileName; |
| wstring name(/* omit prefix */ 4 + wname.c_str()); |
| bool is_dir = (metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; |
| bool is_junc = |
| (metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; |
| consume->Consume(name, is_dir && !is_junc); |
| } |
| } while (::FindNextFileW(handle, &metadata)); |
| ::FindClose(handle); |
| } |
| |
| void GetAllFilesUnderW(const wstring& path, vector<wstring>* result) { |
| _GetAllFilesUnderW(path, result, &ForEachDirectoryEntryW); |
| } |
| |
| void _GetAllFilesUnderW(const wstring& path, vector<wstring>* result, |
| _ForEachDirectoryEntryW walk_entries) { |
| DirectoryTreeWalkerW(result, walk_entries).Walk(path); |
| } |
| |
| void ForEachDirectoryEntry(const string &path, |
| DirectoryEntryConsumer *consume) { |
| wstring wpath; |
| if (path.empty() || IsDevNull(path.c_str())) { |
| return; |
| } |
| string error; |
| if (!AsWindowsPath(path, &wpath, &error)) { |
| BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) |
| << "ForEachDirectoryEntry(" << path |
| << "): AsWindowsPath failed: " << GetLastErrorString(); |
| } |
| |
| static const wstring kUncPrefix(L"\\\\?\\"); |
| static const wstring kDot(L"."); |
| static const wstring kDotDot(L".."); |
| // Always add an UNC prefix to ensure we can work with long paths. |
| if (!HasUncPrefix(wpath.c_str())) { |
| wpath = kUncPrefix + wpath; |
| } |
| // Unconditionally add a trailing backslash. We know `wpath` has no trailing |
| // backslash because it comes from AsWindowsPath whose output is always |
| // normalized (see NormalizeWindowsPath). |
| wpath.append(L"\\"); |
| WIN32_FIND_DATAW metadata; |
| HANDLE handle = ::FindFirstFileW((wpath + L"*").c_str(), &metadata); |
| if (handle == INVALID_HANDLE_VALUE) { |
| return; // directory does not exist or is empty |
| } |
| |
| do { |
| if (kDot != metadata.cFileName && kDotDot != metadata.cFileName) { |
| wstring wname = wpath + metadata.cFileName; |
| string name(WstringToCstring(/* omit prefix */ 4 + wname.c_str())); |
| bool is_dir = (metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; |
| bool is_junc = |
| (metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; |
| consume->Consume(name, is_dir && !is_junc); |
| } |
| } while (::FindNextFileW(handle, &metadata)); |
| ::FindClose(handle); |
| } |
| |
| } // namespace blaze_util |