aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Laszlo Csomor <laszlocsomor@google.com>2018-02-22 04:27:37 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-02-22 04:29:16 -0800
commitc2b332b45e6ea41a14ecbd3c5f30782bcdeec301 (patch)
treeab1ecee09d48f02b46c909c4bf61212d66355286
parent1d8ad1a1394926dcc8a2edd43ea554656e907c5a (diff)
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. Change-Id: I69efd29c139e957a93496cd75a2db351ed4453e1 PiperOrigin-RevId: 186598195
-rw-r--r--src/main/cpp/util/file_windows.cc271
1 files changed, 57 insertions, 214 deletions
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index fd63214574..e39faff7ff 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -29,22 +29,27 @@
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.
@@ -123,8 +128,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 {
@@ -606,6 +611,12 @@ 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;
@@ -784,128 +795,6 @@ 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)) {
@@ -913,11 +802,11 @@ bool ReadDirectorySymlink(const string& name, string* result) {
"ReadDirectorySymlink(%s): AsAbsoluteWindowsPath", name.c_str());
return false;
}
- unique_ptr<WCHAR[]> result_ptr;
- if (!JunctionResolver().Resolve(wname.c_str(), &result_ptr)) {
+ unique_ptr<WCHAR[]> realpath(RealPath(wname));
+ if (realpath == nullptr) {
return false;
}
- *result = WstringToCstring(RemoveUncPrefixMaybe(result_ptr.get())).get();
+ *result = WstringToCstring(RemoveUncPrefixMaybe(realpath.get())).get();
return true;
}
@@ -934,7 +823,29 @@ bool PathExists(const string& path) {
"PathExists(%s): AsAbsoluteWindowsPath", path.c_str());
return false;
}
- return JunctionResolver().Resolve(wpath.c_str(), nullptr);
+ 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);
}
string MakeCanonical(const char* path) {
@@ -949,84 +860,11 @@ string MakeCanonical(const char* path) {
pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
"MakeCanonical(%s): AsAbsoluteWindowsPath", path);
}
-
- // 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.
+ unique_ptr<WCHAR[]> realpath(RealPath(wpath));
+ if (realpath == nullptr) {
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 string(WstringToCstring(lcase_realpath.get()).get());
+ return string(WstringToCstring(RemoveUncPrefixMaybe(realpath.get())).get());
}
static bool CanReadFileW(const wstring& path) {
@@ -1080,7 +918,7 @@ bool CanAccessDirectory(const std::string& path) {
return false;
}
DWORD attr = ::GetFileAttributesW(wpath.c_str());
- if ((attr == INVALID_FILE_ATTRIBUTES) || !(attr & FILE_ATTRIBUTE_DIRECTORY)) {
+ if (!IsDirectoryByAttributes(attr)) {
// The path doesn't exist or is not a directory.
return false;
}
@@ -1129,11 +967,16 @@ 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 (attrs != INVALID_FILE_ATTRIBUTES) &&
- (attrs & FILE_ATTRIBUTE_DIRECTORY) &&
- JunctionResolver().Resolve(path.c_str(), nullptr);
+ return IsDirectoryByAttributes(attrs) &&
+ (!(attrs & FILE_ATTRIBUTE_REPARSE_POINT) ||
+ (RealPath(path, &attrs) != nullptr &&
+ IsDirectoryByAttributes(attrs)));
}
bool IsDirectory(const string& path) {
@@ -1228,8 +1071,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;