From 0ea14d023cdba48cfbe9c25e5b5584a2f9a4d8c3 Mon Sep 17 00:00:00 2001 From: Rama Krishnan Raghupathy Date: Wed, 26 Apr 2017 20:17:52 -0700 Subject: [PATCH] Adds long path support in the host Add asserts for Normalized paths Commit migrated from https://github.com/dotnet/core-setup/commit/6186962c2546713d521dcee2afeb481fa589ecd0 --- src/installer/corehost/cli/coreclr.cpp | 2 +- src/installer/corehost/cli/dll/CMakeLists.txt | 4 +- src/installer/corehost/cli/exe/exe.cmake | 4 +- src/installer/corehost/cli/fxr/CMakeLists.txt | 4 +- src/installer/corehost/cli/fxr/hostfxr.cpp | 2 +- src/installer/corehost/common/longfile.h | 26 +++++ src/installer/corehost/common/longfile.windows.cpp | 126 +++++++++++++++++++++ src/installer/corehost/common/pal.h | 2 +- src/installer/corehost/common/pal.unix.cpp | 28 ++++- src/installer/corehost/common/pal.windows.cpp | 125 +++++++++++++++++--- src/installer/corehost/corehost.cpp | 2 +- 11 files changed, 297 insertions(+), 28 deletions(-) create mode 100644 src/installer/corehost/common/longfile.h create mode 100644 src/installer/corehost/common/longfile.windows.cpp diff --git a/src/installer/corehost/cli/coreclr.cpp b/src/installer/corehost/cli/coreclr.cpp index f7f9434..21f99b6 100644 --- a/src/installer/corehost/cli/coreclr.cpp +++ b/src/installer/corehost/cli/coreclr.cpp @@ -44,7 +44,7 @@ bool coreclr::bind(const pal::string_t& libcoreclr_path) pal::string_t coreclr_dll_path(libcoreclr_path); append_path(&coreclr_dll_path, LIBCORECLR_NAME); - if (!pal::load_library(coreclr_dll_path.c_str(), &g_coreclr)) + if (!pal::load_library(&coreclr_dll_path, &g_coreclr)) { return false; } diff --git a/src/installer/corehost/cli/dll/CMakeLists.txt b/src/installer/corehost/cli/dll/CMakeLists.txt index 6a12f6f..81d0ec6 100644 --- a/src/installer/corehost/cli/dll/CMakeLists.txt +++ b/src/installer/corehost/cli/dll/CMakeLists.txt @@ -45,7 +45,9 @@ set(SOURCES if(WIN32) - list(APPEND SOURCES ../../common/pal.windows.cpp) + list(APPEND SOURCES + ../../common/pal.windows.cpp + ../../common/longfile.windows.cpp) else() list(APPEND SOURCES ../../common/pal.unix.cpp) endif() diff --git a/src/installer/corehost/cli/exe/exe.cmake b/src/installer/corehost/cli/exe/exe.cmake index 1691964..0ab20c8 100644 --- a/src/installer/corehost/cli/exe/exe.cmake +++ b/src/installer/corehost/cli/exe/exe.cmake @@ -28,7 +28,9 @@ list(APPEND SOURCES ../../../common/utils.cpp) if(WIN32) - list(APPEND SOURCES ../../../common/pal.windows.cpp) + list(APPEND SOURCES + ../../../common/pal.windows.cpp + ../../../common/longfile.windows.cpp) else() list(APPEND SOURCES ../../../common/pal.unix.cpp) endif() diff --git a/src/installer/corehost/cli/fxr/CMakeLists.txt b/src/installer/corehost/cli/fxr/CMakeLists.txt index 9838576..235bd4b 100644 --- a/src/installer/corehost/cli/fxr/CMakeLists.txt +++ b/src/installer/corehost/cli/fxr/CMakeLists.txt @@ -42,7 +42,9 @@ set(SOURCES if(WIN32) - list(APPEND SOURCES ../../common/pal.windows.cpp) + list(APPEND SOURCES + ../../common/pal.windows.cpp + ../../common/longfile.windows.cpp) else() list(APPEND SOURCES ../../common/pal.unix.cpp) endif() diff --git a/src/installer/corehost/cli/fxr/hostfxr.cpp b/src/installer/corehost/cli/fxr/hostfxr.cpp index 8eb7c4d..cad1760 100644 --- a/src/installer/corehost/cli/fxr/hostfxr.cpp +++ b/src/installer/corehost/cli/fxr/hostfxr.cpp @@ -29,7 +29,7 @@ int load_host_library( } // Load library - if (!pal::load_library(host_path.c_str(), h_host)) + if (!pal::load_library(&host_path, h_host)) { trace::info(_X("Load library of %s failed"), host_path.c_str()); return StatusCode::CoreHostLibLoadFailure; diff --git a/src/installer/corehost/common/longfile.h b/src/installer/corehost/common/longfile.h new file mode 100644 index 0000000..800317a --- /dev/null +++ b/src/installer/corehost/common/longfile.h @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef _LONG_FILE_SUPPORT +#define _LONG_FILE_SUPPORT + +class LongFile +{ +public: + static const pal::string_t ExtendedPrefix; + static const pal::string_t DevicePathPrefix; + static const pal::string_t UNCPathPrefix; + static const pal::string_t UNCExtendedPathPrefix; + static const pal::char_t VolumeSeparatorChar; + static const pal::char_t DirectorySeparatorChar; + static const pal::char_t AltDirectorySeparatorChar; +public: + static bool IsExtended(const pal::string_t& path); + static bool IsUNCExtended(const pal::string_t& path); + static bool ContainsDirectorySeparator(const pal::string_t & path); + static bool IsDirectorySeparator(const pal::char_t c); + static bool IsPathNotFullyQualified(const pal::string_t& path); + static bool IsDevice(const pal::string_t& path); + static bool ShouldNormalize(const pal::string_t& path); +}; +#endif //_LONG_FILE_SUPPORT diff --git a/src/installer/corehost/common/longfile.windows.cpp b/src/installer/corehost/common/longfile.windows.cpp new file mode 100644 index 0000000..7e51d72 --- /dev/null +++ b/src/installer/corehost/common/longfile.windows.cpp @@ -0,0 +1,126 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +//The logic in this file was ported from https://github.com/dotnet/coreclr/blob/54891e0650e69f08832f75a40dc102efc6115d38/src/utilcode/longfilepathwrappers.cpp +//Please reflect any change here into the above file too! +#include "pal.h" +#include "trace.h" +#include "utils.h" +#include "longfile.h" + +const pal::char_t LongFile::DirectorySeparatorChar = _X('\\'); +const pal::char_t LongFile::AltDirectorySeparatorChar = _X('/'); +const pal::char_t LongFile::VolumeSeparatorChar = _X(':'); +const pal::string_t LongFile::ExtendedPrefix = _X("\\\\?\\"); +const pal::string_t LongFile::DevicePathPrefix = _X("\\\\.\\"); +const pal::string_t LongFile::UNCExtendedPathPrefix = _X("\\\\?\\UNC\\"); +const pal::string_t LongFile::UNCPathPrefix = _X("\\\\"); + +bool ShouldNormalizeWorker(const pal::string_t& path) +{ + if (path.empty() || LongFile::IsDevice(path) || LongFile::IsExtended(path) || LongFile::IsUNCExtended(path)) + return false; + + if (!LongFile::IsPathNotFullyQualified(path) && path.size() < MAX_PATH) + return false; + + return true; +} + +//For longpath names on windows, if the paths are normalized they are always prefixed with +//extended syntax, Windows does not do any more normalizations on this string and uses it as is +//So we should ensure that there are NO adjacent DirectorySeparatorChar +bool AssertRepeatingDirSeparator(const pal::string_t& path) +{ + if (path.empty()) + return true; + + pal::string_t path_to_check = path; + if (LongFile::IsDevice(path)) + { + path_to_check.erase(0, LongFile::DevicePathPrefix.length()); + } + else if (LongFile::IsExtended(path)) + { + path_to_check.erase(0, LongFile::ExtendedPrefix.length()); + } + else if (LongFile::IsUNCExtended(path)) + { + path_to_check.erase(0, LongFile::UNCExtendedPathPrefix.length()); + } + else if (path_to_check.compare(0, LongFile::UNCPathPrefix.length(), LongFile::UNCPathPrefix) == 0) + { + path_to_check.erase(0, LongFile::UNCPathPrefix.length()); + } + + pal::string_t dirSeparator; + dirSeparator.push_back(LongFile::DirectorySeparatorChar); + dirSeparator.push_back(LongFile::DirectorySeparatorChar); + + assert(path_to_check.find(dirSeparator) == pal::string_t::npos); + + pal::string_t altDirSeparator; + altDirSeparator.push_back(LongFile::AltDirectorySeparatorChar); + altDirSeparator.push_back(LongFile::AltDirectorySeparatorChar); + + assert(path_to_check.find(altDirSeparator) == pal::string_t::npos); + + return true; +} +bool LongFile::ShouldNormalize(const pal::string_t& path) +{ + bool retval = ShouldNormalizeWorker(path); + assert(retval || AssertRepeatingDirSeparator(path)); + return retval; +} + +bool LongFile::IsExtended(const pal::string_t& path) +{ + return path.compare(0, ExtendedPrefix.length(), ExtendedPrefix) == 0; +} + +bool LongFile::IsUNCExtended(const pal::string_t& path) +{ + return path.compare(0, UNCExtendedPathPrefix.length(), UNCExtendedPathPrefix) == 0; +} + +bool LongFile::IsDevice(const pal::string_t& path) +{ + return path.compare(0, DevicePathPrefix.length(), DevicePathPrefix) == 0; +} + +// Relative here means it could be relative to current directory on the relevant drive +// NOTE: Relative segments ( \..\) are not considered relative +// Returns true if the path specified is relative to the current drive or working directory. +// Returns false if the path is fixed to a specific drive or UNC path. This method does no +// validation of the path (URIs will be returned as relative as a result). +// Handles paths that use the alternate directory separator. It is a frequent mistake to +// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + +bool LongFile::IsPathNotFullyQualified(const pal::string_t& path) +{ + if (path.length() < 2) + { + return true; // It isn't fixed, it must be relative. There is no way to specify a fixed path with one character (or less). + } + + if (IsDirectorySeparator(path[0])) + { + return !IsDirectorySeparator(path[1]); // There is no valid way to specify a relative path with two initial slashes + } + + return !((path.length() >= 3) //The only way to specify a fixed path that doesn't begin with two slashes is the drive, colon, slash format- "i.e. C:\" + && (path[1] == VolumeSeparatorChar) + && IsDirectorySeparator(path[2])); +} + +bool LongFile::ContainsDirectorySeparator(const pal::string_t & path) +{ + return path.find(DirectorySeparatorChar) != pal::string_t::npos || + path.find(AltDirectorySeparatorChar) != pal::string_t::npos; +} + +bool LongFile::IsDirectorySeparator(const pal::char_t c) +{ + return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar; +} diff --git a/src/installer/corehost/common/pal.h b/src/installer/corehost/common/pal.h index 4edbc4f..13f6f61 100644 --- a/src/installer/corehost/common/pal.h +++ b/src/installer/corehost/common/pal.h @@ -217,7 +217,7 @@ namespace pal int xtoi(const char_t* input); - bool load_library(const char_t* path, dll_t* dll); + bool load_library(const string_t* path, dll_t* dll); proc_t get_symbol(dll_t library, const char* name); void unload_library(dll_t library); } diff --git a/src/installer/corehost/common/pal.unix.cpp b/src/installer/corehost/common/pal.unix.cpp index 6c3af3f..2ae8aa0 100644 --- a/src/installer/corehost/common/pal.unix.cpp +++ b/src/installer/corehost/common/pal.unix.cpp @@ -51,7 +51,7 @@ bool pal::touch_file(const pal::string_t& path) bool pal::getcwd(pal::string_t* recv) { recv->clear(); - pal::char_t* buf = ::getcwd(nullptr, PATH_MAX + 1); + pal::char_t* buf = ::getcwd(nullptr, 0); if (buf == nullptr) { if (errno == ENOENT) @@ -66,9 +66,9 @@ bool pal::getcwd(pal::string_t* recv) return true; } -bool pal::load_library(const char_t* path, dll_t* dll) +bool pal::load_library(const string_t* path, dll_t* dll) { - *dll = dlopen(path, RTLD_LAZY); + *dll = dlopen(path->c_str(), RTLD_LAZY); if (*dll == nullptr) { trace::error(_X("Failed to load %s, error: %s"), path, dlerror()); @@ -442,12 +442,32 @@ bool pal::get_own_executable_path(pal::string_t* recv) mib[3] = -1; char buf[PATH_MAX]; size_t cb = sizeof(buf); - if (sysctl(mib, 4, buf, &cb, NULL, 0) == 0) + int error_code = 0; + error_code = sysctl(mib, 4, buf, &cb, NULL, 0); + if (error_code == 0) { recv->assign(buf); return true; } + // ENOMEM + if (error_code == ENOMEM) + { + size_t len = sysctl(mib, 4, NULL, NULL, NULL, 0); + std::unique_ptr buffer = new (std::nothrow) char[len]; + + if (buffer == NULL) + { + return false; + } + + error_code = sysctl(mib, 4, buffer, &len, NULL, 0); + if (error_code == 0) + { + recv->assign(buffer); + return true; + } + } return false; } #else diff --git a/src/installer/corehost/common/pal.windows.cpp b/src/installer/corehost/common/pal.windows.cpp index 984aa7f..4fc4596 100644 --- a/src/installer/corehost/common/pal.windows.cpp +++ b/src/installer/corehost/common/pal.windows.cpp @@ -4,12 +4,34 @@ #include "pal.h" #include "trace.h" #include "utils.h" +#include "longfile.h" #include #include #include #include +bool GetModuleFileNameWrapper(HMODULE hModule, pal::string_t* recv) +{ + pal::string_t path; + DWORD dwModuleFileName = MAX_PATH / 2; + + do + { + path.resize(dwModuleFileName * 2); + dwModuleFileName = GetModuleFileNameW(hModule, (LPWSTR)path.data(), path.size()); + } while (dwModuleFileName == path.size()); + + if (dwModuleFileName != 0) + { + *recv = path; + return true; + } + + return false; + +} + pal::string_t pal::to_lower(const pal::string_t& in) { pal::string_t ret = in; @@ -62,12 +84,27 @@ bool pal::getcwd(pal::string_t* recv) return false; } -bool pal::load_library(const char_t* path, dll_t* dll) +bool pal::load_library(const string_t* in_path, dll_t* dll) { + string_t path = *in_path; + // LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR: // In portable apps, coreclr would come from another directory than the host, // so make sure coreclr dependencies can be resolved from coreclr.dll load dir. - *dll = ::LoadLibraryExW(path, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + + if (LongFile::IsPathNotFullyQualified(path)) + { + if (!pal::realpath(&path)) + { + trace::error(_X("Failed to load the dll from [%s], HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError())); + return false; + } + } + + //Adding the assert to ensure relative paths which are not just filenames are not used for LoadLibrary Calls + assert(!LongFile::IsPathNotFullyQualified(path) || !LongFile::ContainsDirectorySeparator(path)); + + *dll = ::LoadLibraryExW(path.c_str(), NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); if (*dll == nullptr) { trace::error(_X("Failed to load the dll from [%s], HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError())); @@ -76,7 +113,7 @@ bool pal::load_library(const char_t* path, dll_t* dll) // Pin the module HMODULE dummy_module; - if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path, &dummy_module)) + if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path.c_str(), &dummy_module)) { trace::error(_X("Failed to pin library [%s] in [%s]"), path, _STRINGIFY(__FUNCTION__)); return false; @@ -84,8 +121,8 @@ bool pal::load_library(const char_t* path, dll_t* dll) if (trace::is_enabled()) { - pal::char_t buf[PATH_MAX]; - ::GetModuleFileNameW(*dll, buf, PATH_MAX); + string_t buf; + GetModuleFileNameWrapper(*dll, &buf); trace::info(_X("Loaded library from %s"), buf); } @@ -293,13 +330,7 @@ int pal::xtoi(const char_t* input) bool pal::get_own_executable_path(string_t* recv) { - char_t program_path[MAX_PATH]; - DWORD dwModuleFileName = ::GetModuleFileNameW(NULL, program_path, MAX_PATH); - if (dwModuleFileName == 0 || dwModuleFileName >= MAX_PATH) { - return false; - } - recv->assign(program_path); - return true; + return GetModuleFileNameWrapper(NULL, recv); } static bool wchar_convert_helper(DWORD code_page, const char* cstr, int len, pal::string_t* out) @@ -347,14 +378,53 @@ bool pal::clr_palstring(const char* cstr, pal::string_t* out) bool pal::realpath(string_t* path) { + + if (!LongFile::ShouldNormalize(*path)) + { + return true; + } + char_t buf[MAX_PATH]; - auto res = ::GetFullPathNameW(path->c_str(), MAX_PATH, buf, nullptr); - if (res == 0 || res > MAX_PATH) + auto size = ::GetFullPathNameW(path->c_str(), MAX_PATH, buf, nullptr); + + if (size == 0) { trace::error(_X("Error resolving full path [%s]"), path->c_str()); return false; } - path->assign(buf); + + if (size < MAX_PATH) + { + path->assign(buf); + return true; + } + + string_t str; + str.resize(size + LongFile::UNCExtendedPathPrefix.length(), 0); + + size = ::GetFullPathNameW(path->c_str() , size, (LPWSTR)str.data() , nullptr); + assert(size <= str.size()); + + if (size == 0) + { + trace::error(_X("Error resolving full path [%s]"), path->c_str()); + return false; + } + + const string_t* prefix = &LongFile::ExtendedPrefix; + //Check if the resolved path is a UNC. By default we assume relative path to resolve to disk + if (str.compare(0, LongFile::UNCPathPrefix.length(), LongFile::UNCPathPrefix) == 0) + { + prefix = &LongFile::UNCExtendedPathPrefix; + str.erase(0, LongFile::UNCPathPrefix.length()); + size = size - LongFile::UNCPathPrefix.length(); + } + + str.insert(0, *prefix); + str.resize(size + prefix->length()); + str.shrink_to_fit(); + *path = str; + return true; } @@ -365,11 +435,22 @@ bool pal::file_exists(const string_t& path) return false; } + auto pathstring = path.c_str(); + string_t normalized_path; + if (LongFile::ShouldNormalize(path)) + { + normalized_path = path; + if (!pal::realpath(&normalized_path)) + { + return false; + } + pathstring = normalized_path.c_str(); + } // We will attempt to fetch attributes for the file or folder in question that are // returned only if they exist. WIN32_FILE_ATTRIBUTE_DATA data; - if (GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &data) != 0) { + if (GetFileAttributesExW(pathstring, GetFileExInfoStandard, &data) != 0) { return true; } @@ -381,11 +462,21 @@ void pal::readdir(const string_t& path, const string_t& pattern, std::vector& files = *list; + string_t normalized_path(path); - string_t search_string(path); + if (LongFile::ShouldNormalize(normalized_path)) + { + if (!pal::realpath(&normalized_path)) + { + return; + } + } + + string_t search_string(normalized_path); append_path(&search_string, pattern.c_str()); WIN32_FIND_DATAW data = { 0 }; + auto handle = ::FindFirstFileExW(search_string.c_str(), FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0); if (handle == INVALID_HANDLE_VALUE) { diff --git a/src/installer/corehost/corehost.cpp b/src/installer/corehost/corehost.cpp index 114ebce..7e056e3 100644 --- a/src/installer/corehost/corehost.cpp +++ b/src/installer/corehost/corehost.cpp @@ -183,7 +183,7 @@ int run(const int argc, const pal::char_t* argv[]) return StatusCode::CoreHostLibMissingFailure; } - if (!pal::load_library(fxr_path.c_str(), &fxr)) + if (!pal::load_library(&fxr_path, &fxr)) { trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, fxr_path.c_str()); trace::error(_X(" - Installing .NET Core prerequisites might help resolve this problem.")); -- 2.7.4