diff options
author | 2018-03-14 08:22:13 -0700 | |
---|---|---|
committer | 2018-03-14 08:23:41 -0700 | |
commit | 369409995bd75eeb0683fd24f7585d2a90320796 (patch) | |
tree | 73cc53acb750f2f02ecb0d52d12542979559e6bf /src/main/cpp/util/file_windows.cc | |
parent | 6c64b64956c7f5c5cacc335457f6c57e0e26f273 (diff) |
Automated rollback of commit c2b332b45e6ea41a14ecbd3c5f30782bcdeec301.
*** Reason for rollback ***
Causes https://github.com/bazelbuild/bazel/issues/4847
*** Original change description ***
windows: replace custom JunctionResolver
There's a realpath(3)-like Windows API function
called GetFinalPathNameByHandle{A,W}.
This commit removes JunctionResolver, implements
RealPath using GetFinalPathNameByHandleW, and
replaces JunctionResolver usages with RealPath.
PiperOrigin-RevId: 189031288
Diffstat (limited to 'src/main/cpp/util/file_windows.cc')
-rw-r--r-- | src/main/cpp/util/file_windows.cc | 271 |
1 files changed, 214 insertions, 57 deletions
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc index e39faff7ff..fd63214574 100644 --- a/src/main/cpp/util/file_windows.cc +++ b/src/main/cpp/util/file_windows.cc @@ -29,27 +29,22 @@ namespace blaze_util { -using bazel::windows::AutoHandle; -using bazel::windows::GetLongPath; -using bazel::windows::HasUncPrefix; -using bazel::windows::OpenDirectory; using std::basic_string; using std::pair; using std::string; using std::unique_ptr; using std::wstring; +using bazel::windows::AutoHandle; +using bazel::windows::GetLongPath; +using bazel::windows::HasUncPrefix; +using bazel::windows::OpenDirectory; // Returns the current working directory as a Windows path. // The result may have a UNC prefix. static unique_ptr<WCHAR[]> GetCwdW(); -static unique_ptr<WCHAR[]> RealPath(const wstring& path, - DWORD* opt_attr = nullptr); - static char GetCurrentDrive(); -static bool IsDirectoryByAttributes(DWORD attrs); - // 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. @@ -128,8 +123,8 @@ class WindowsPipe : public IPipe { bool Send(const void* buffer, int size) override { DWORD actually_written = 0; - return ::WriteFile(_write_handle, buffer, size, &actually_written, NULL) == - TRUE; + return ::WriteFile(_write_handle, buffer, size, &actually_written, + NULL) == TRUE; } int Receive(void* buffer, int size, int* error) override { @@ -611,12 +606,6 @@ bool AsShortWindowsPath(const string& path, string* result) { return true; } -static HANDLE OpenFileForStat(const WCHAR* path) { - return CreateFileW(path, 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); -} - static bool OpenFileForReading(const string& filename, HANDLE* result) { if (filename.empty()) { return false; @@ -795,6 +784,128 @@ bool UnlinkPath(const string& file_path) { return UnlinkPathW(wpath); } +class JunctionResolver { + public: + JunctionResolver(); + + // Resolves junctions, or simply checks file existence (if not a junction). + // + // Returns true if `path` is not a junction and it exists. + // Returns true if `path` is a junction and can be successfully resolved and + // its target exists. + // Returns false otherwise. + // + // If `result` is not nullptr and the method returned false, then this will be + // reset to point to a new WCHAR buffer containing the final resolved path. + // If `path` was a junction, this will be the fully resolved path, otherwise + // it will be a copy of `path`. + bool Resolve(const WCHAR* path, std::unique_ptr<WCHAR[]>* result); + + private: + static const int kMaximumJunctionDepth; + + // This struct is a simplified version of REPARSE_DATA_BUFFER, defined by + // the <Ntifs.h> header file, which is not available on some systems. + // This struct removes the original one's union keeping only + // MountPointReparseBuffer, while also renames some fields to reflect how + // ::DeviceIoControl actually uses them when reading junction data. + typedef struct _ReparseMountPointData { + static const int kSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE; + + ULONG ReparseTag; + USHORT Dummy1; + USHORT Dummy2; + USHORT Dummy3; + USHORT Dummy4; + // Length of string in PathBuffer, in WCHARs, including the "\??\" prefix + // and the null-terminator. + // + // Reparse points use the "\??\" prefix instead of "\\?\", presumably + // because the junction is resolved by the kernel and it points to a Device + // Object path (which is what the kernel understands), and "\??" is a device + // path. ("\??" is shorthand for "\DosDevices" under which disk drives + // reside, e.g. "C:" is a symlink to "\DosDevices\C:" aka "\??\C:"). + // See (on 2017-01-04): + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff565384(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff557762(v=vs.85).aspx + USHORT Size; + USHORT Dummy5; + // First character of the string returned by ::DeviceIoControl. The rest of + // the string follows this in memory, that's why the caller must allocate + // kSize bytes and cast that data to ReparseMountPointData. + WCHAR PathBuffer[1]; + } ReparseMountPointData; + + uint8_t reparse_buffer_bytes_[ReparseMountPointData::kSize]; + ReparseMountPointData* reparse_buffer_; + + bool Resolve(const WCHAR* path, std::unique_ptr<WCHAR[]>* result, + int max_junction_depth); +}; + +// Maximum reparse point depth on Windows 8 and above is 63. +// Source (on 2016-12-20): +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365503(v=vs.85).aspx +const int JunctionResolver::kMaximumJunctionDepth = 63; + +JunctionResolver::JunctionResolver() + : reparse_buffer_( + reinterpret_cast<ReparseMountPointData*>(reparse_buffer_bytes_)) { + reparse_buffer_->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; +} + +bool JunctionResolver::Resolve(const WCHAR* path, unique_ptr<WCHAR[]>* result, + int max_junction_depth) { + DWORD attributes = ::GetFileAttributesW(path); + if (attributes == INVALID_FILE_ATTRIBUTES) { + // `path` does not exist. + return false; + } else { + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + // `path` is a junction. GetFileAttributesW succeeds for these even if + // their target does not exist. We need to resolve the target and check if + // that exists. (There seems to be no API function for this.) + if (max_junction_depth <= 0) { + // Too many levels of junctions. Simply say this file doesn't exist. + return false; + } + // Get a handle to the directory. + AutoHandle handle(OpenDirectory(path, /* read_write */ false)); + if (!handle.IsValid()) { + // Opening the junction failed for whatever reason. For all intents and + // purposes we can treat this file as if it didn't exist. + return false; + } + // Read out the junction data. + DWORD bytes_returned; + BOOL ok = ::DeviceIoControl( + handle, FSCTL_GET_REPARSE_POINT, NULL, 0, reparse_buffer_, + MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytes_returned, NULL); + if (!ok) { + // Reading the junction data failed. For all intents and purposes we can + // treat this file as if it didn't exist. + return false; + } + reparse_buffer_->PathBuffer[reparse_buffer_->Size - 1] = UNICODE_NULL; + // Check if the junction target exists. + return Resolve(reparse_buffer_->PathBuffer, result, + max_junction_depth - 1); + } + } + // `path` is a normal file or directory. + if (result) { + size_t len = wcslen(path) + 1; + result->reset(new WCHAR[len]); + memcpy(result->get(), path, len * sizeof(WCHAR)); + } + return true; +} + +bool JunctionResolver::Resolve(const WCHAR* path, unique_ptr<WCHAR[]>* result) { + return Resolve(path, result, kMaximumJunctionDepth); +} + bool ReadDirectorySymlink(const string& name, string* result) { wstring wname; if (!AsAbsoluteWindowsPath(name, &wname)) { @@ -802,11 +913,11 @@ bool ReadDirectorySymlink(const string& name, string* result) { "ReadDirectorySymlink(%s): AsAbsoluteWindowsPath", name.c_str()); return false; } - unique_ptr<WCHAR[]> realpath(RealPath(wname)); - if (realpath == nullptr) { + unique_ptr<WCHAR[]> result_ptr; + if (!JunctionResolver().Resolve(wname.c_str(), &result_ptr)) { return false; } - *result = WstringToCstring(RemoveUncPrefixMaybe(realpath.get())).get(); + *result = WstringToCstring(RemoveUncPrefixMaybe(result_ptr.get())).get(); return true; } @@ -823,29 +934,7 @@ bool PathExists(const string& path) { "PathExists(%s): AsAbsoluteWindowsPath", path.c_str()); return false; } - return AutoHandle(OpenFileForStat(wpath.c_str())).IsValid(); -} - -static unique_ptr<WCHAR[]> RealPath(const wstring& path, DWORD* opt_attr) { - AutoHandle handle(OpenFileForStat(path.c_str())); - if (!handle.IsValid()) { - return std::move(unique_ptr<WCHAR[]>()); - } - unique_ptr<WCHAR[]> result(new WCHAR[path.size() + 1]); - DWORD size = GetFinalPathNameByHandleW(handle, result.get(), path.size(), 0); - if (size > path.size()) { - result.reset(new WCHAR[size + 1]); - GetFinalPathNameByHandleW(handle, result.get(), size, 0); - } - if (opt_attr != nullptr) { - BY_HANDLE_FILE_INFORMATION info; - if (GetFileInformationByHandle(handle, &info)) { - *opt_attr = info.dwFileAttributes; - } else { - *opt_attr = INVALID_FILE_ATTRIBUTES; - } - } - return std::move(result); + return JunctionResolver().Resolve(wpath.c_str(), nullptr); } string MakeCanonical(const char* path) { @@ -860,11 +949,84 @@ string MakeCanonical(const char* path) { pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR, "MakeCanonical(%s): AsAbsoluteWindowsPath", path); } - unique_ptr<WCHAR[]> realpath(RealPath(wpath)); - if (realpath == nullptr) { + + // Resolve all segments of the path. Do this from leaf to root, so we always + // know that the path's tail is resolved and junctions may be found only in + // its head. + std::vector<wstring> realpath_reversed; + while (true) { + // Resolve the last segment. + unique_ptr<WCHAR[]> realpath; + if (!JunctionResolver().Resolve(wpath.c_str(), &realpath)) { + // The path doesn't exist or there are too many levels of indirection, + // so just give up. + return ""; + } + // The last segment is surely not a junction anymore. Split it off the path + // and keep resolving its ancestors until we reach the root directory. + pair<wstring, wstring> split(SplitPathW(realpath.get())); + if (split.second.empty()) { + // `wpath` was a root directory, we're done. + realpath_reversed.push_back(split.first); + break; + } else { + // `wpath` was not yet a root directory, split off the last segment and + // store it in the stack, keep resolving the rest. + realpath_reversed.push_back(split.second); + wpath = split.first; + } + } + + // Concatenate the segments in reverse order. + int segment_cnt = 0; + std::wstringstream builder; + for (auto segment = realpath_reversed.crbegin(); + segment != realpath_reversed.crend(); ++segment) { + if (segment_cnt < 2) { + segment_cnt++; + } else { + // Start appending '\' separator after not the first but the second + // segment, since the first segment is a drive name "c:\" and already + // has the separator. + builder << L"\\"; + } + builder << *segment; + } + wstring realpath(builder.str()); + if (HasUncPrefix(realpath.c_str())) { + // `realpath` has an UNC prefix if `path` did, or if `path` contained + // junctions. + // In the first case, the UNC prefix is the usual "\\?\", but in the second + // case it is "\??\", because that's what the junction resolution yields, + // because that's the prefix the filesystem uses for storing junction + // values. + // Since "\??\" is only meaningful for the kernel and not for usermode + // Win32 API functions, we need to replace this prefix with the usual "\\?\" + // one. + realpath[1] = L'\\'; + } + + // Resolve all 8dot3 style segments of the path, if any. The input path may + // have had some. Junctions may also refer to 8dot3 names. + unique_ptr<WCHAR[]> long_realpath; + wstring error(GetLongPath(realpath.c_str(), &long_realpath)); + if (!error.empty()) { + // TODO(laszlocsomor): refactor MakeCanonical to return an error message, + // return `error` here. return ""; } - return string(WstringToCstring(RemoveUncPrefixMaybe(realpath.get())).get()); + + // 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 string(WstringToCstring(lcase_realpath.get()).get()); } static bool CanReadFileW(const wstring& path) { @@ -918,7 +1080,7 @@ bool CanAccessDirectory(const std::string& path) { return false; } DWORD attr = ::GetFileAttributesW(wpath.c_str()); - if (!IsDirectoryByAttributes(attr)) { + if ((attr == INVALID_FILE_ATTRIBUTES) || !(attr & FILE_ATTRIBUTE_DIRECTORY)) { // The path doesn't exist or is not a directory. return false; } @@ -967,16 +1129,11 @@ bool IsDevNull(const char* path) { (path[2] == 'L' || path[2] == 'l') && path[3] == 0)); } -static bool IsDirectoryByAttributes(DWORD attrs) { - return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY); -} - static bool IsDirectoryW(const wstring& path) { DWORD attrs = ::GetFileAttributesW(path.c_str()); - return IsDirectoryByAttributes(attrs) && - (!(attrs & FILE_ATTRIBUTE_REPARSE_POINT) || - (RealPath(path, &attrs) != nullptr && - IsDirectoryByAttributes(attrs))); + return (attrs != INVALID_FILE_ATTRIBUTES) && + (attrs & FILE_ATTRIBUTE_DIRECTORY) && + JunctionResolver().Resolve(path.c_str(), nullptr); } bool IsDirectory(const string& path) { @@ -1071,8 +1228,8 @@ bool ChangeDirectory(const string& path) { ::SetCurrentDirectoryA(spath.c_str()) == TRUE; } -void ForEachDirectoryEntry(const string& path, - DirectoryEntryConsumer* consume) { +void ForEachDirectoryEntry(const string &path, + DirectoryEntryConsumer *consume) { wstring wpath; if (path.empty() || IsDevNull(path.c_str())) { return; |