aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/cpp/util/file_windows.cc
diff options
context:
space:
mode:
authorGravatar laszlocsomor <laszlocsomor@google.com>2018-03-14 08:22:13 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-03-14 08:23:41 -0700
commit369409995bd75eeb0683fd24f7585d2a90320796 (patch)
tree73cc53acb750f2f02ecb0d52d12542979559e6bf /src/main/cpp/util/file_windows.cc
parent6c64b64956c7f5c5cacc335457f6c57e0e26f273 (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.cc271
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;