Adds long path support in the host
authorRama Krishnan Raghupathy <ramarag@microsoft.com>
Thu, 27 Apr 2017 03:17:52 +0000 (20:17 -0700)
committerRama Krishnan Raghupathy <ramarag@microsoft.com>
Tue, 9 May 2017 01:04:48 +0000 (18:04 -0700)
Add asserts for Normalized paths

Commit migrated from https://github.com/dotnet/core-setup/commit/6186962c2546713d521dcee2afeb481fa589ecd0

src/installer/corehost/cli/coreclr.cpp
src/installer/corehost/cli/dll/CMakeLists.txt
src/installer/corehost/cli/exe/exe.cmake
src/installer/corehost/cli/fxr/CMakeLists.txt
src/installer/corehost/cli/fxr/hostfxr.cpp
src/installer/corehost/common/longfile.h [new file with mode: 0644]
src/installer/corehost/common/longfile.windows.cpp [new file with mode: 0644]
src/installer/corehost/common/pal.h
src/installer/corehost/common/pal.unix.cpp
src/installer/corehost/common/pal.windows.cpp
src/installer/corehost/corehost.cpp

index f7f9434..21f99b6 100644 (file)
@@ -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;
     }
index 6a12f6f..81d0ec6 100644 (file)
@@ -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()
index 1691964..0ab20c8 100644 (file)
@@ -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()
index 9838576..235bd4b 100644 (file)
@@ -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()
index 8eb7c4d..cad1760 100644 (file)
@@ -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 (file)
index 0000000..800317a
--- /dev/null
@@ -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 (file)
index 0000000..7e51d72
--- /dev/null
@@ -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;
+}
index 4edbc4f..13f6f61 100644 (file)
@@ -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);
 }
index 6c3af3f..2ae8aa0 100644 (file)
@@ -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<char[]> 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
index 984aa7f..4fc4596 100644 (file)
@@ -4,12 +4,34 @@
 #include "pal.h"
 #include "trace.h"
 #include "utils.h"
+#include "longfile.h"
 
 #include <cassert>
 #include <locale>
 #include <codecvt>
 #include <ShlObj.h>
 
+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<pal
     assert(list != nullptr);
 
     std::vector<string_t>& 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)
     {
index 114ebce..7e056e3 100644 (file)
@@ -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."));