diff --git a/src/coreclr/binder/applicationcontext.cpp b/src/coreclr/binder/applicationcontext.cpp index e41b504c8feb61..7e0480c8b231e2 100644 --- a/src/coreclr/binder/applicationcontext.cpp +++ b/src/coreclr/binder/applicationcontext.cpp @@ -18,6 +18,7 @@ #include "utils.hpp" #include "ex.h" #include "clr/fs/path.h" +#include "hostinformation.h" using namespace clr::fs; namespace BINDER_SPACE @@ -102,69 +103,80 @@ namespace BINDER_SPACE { SString fileName; SString simpleName; - bool isNativeImage = false; HRESULT pathResult = S_OK; - IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ false, fileName, simpleName, isNativeImage)); + IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ false, fileName, simpleName)); if (pathResult == S_FALSE) { break; } - const SimpleNameToFileNameMapEntry *pExistingEntry = m_pTrustedPlatformAssemblyMap->LookupPtr(simpleName.GetUnicode()); + IF_FAIL_GO(hr = AddAssemblyMapEntry(simpleName, fileName)); + } - if (pExistingEntry != nullptr) - { - // - // We want to store only the first entry matching a simple name we encounter. - // The exception is if we first store an IL reference and later in the string - // we encounter a native image. Since we don't touch IL in the presence of - // native images, we replace the IL entry with the NI. - // - if ((pExistingEntry->m_wszILFileName != nullptr && !isNativeImage) || - (pExistingEntry->m_wszNIFileName != nullptr && isNativeImage)) - { - continue; - } - } + // + // Parse PlatformResourceRoots + // + sPlatformResourceRoots.Normalize(); + for (SString::Iterator i = sPlatformResourceRoots.Begin(); i != sPlatformResourceRoots.End(); ) + { + SString pathName; + HRESULT pathResult = S_OK; - LPWSTR wszSimpleName = nullptr; - if (pExistingEntry == nullptr) - { - wszSimpleName = new WCHAR[simpleName.GetCount() + 1]; - if (wszSimpleName == nullptr) - { - GO_WITH_HRESULT(E_OUTOFMEMORY); - } - wcscpy_s(wszSimpleName, simpleName.GetCount() + 1, simpleName.GetUnicode()); - } - else + IF_FAIL_GO(pathResult = GetNextPath(sPlatformResourceRoots, i, pathName)); + if (pathResult == S_FALSE) { - wszSimpleName = pExistingEntry->m_wszSimpleName; + break; } - LPWSTR wszFileName = new WCHAR[fileName.GetCount() + 1]; - if (wszFileName == nullptr) + if (Path::IsRelative(pathName)) { - GO_WITH_HRESULT(E_OUTOFMEMORY); + GO_WITH_HRESULT(E_INVALIDARG); } - wcscpy_s(wszFileName, fileName.GetCount() + 1, fileName.GetUnicode()); - SimpleNameToFileNameMapEntry mapEntry; - mapEntry.m_wszSimpleName = wszSimpleName; - if (isNativeImage) + m_platformResourceRoots.Append(pathName); + } + + // + // Parse AppPaths + // + sAppPaths.Normalize(); + for (SString::Iterator i = sAppPaths.Begin(); i != sAppPaths.End(); ) + { + SString pathName; + HRESULT pathResult = S_OK; + + IF_FAIL_GO(pathResult = GetNextPath(sAppPaths, i, pathName)); + if (pathResult == S_FALSE) { - mapEntry.m_wszNIFileName = wszFileName; - mapEntry.m_wszILFileName = pExistingEntry == nullptr ? nullptr : pExistingEntry->m_wszILFileName; + break; } - else + + if (Path::IsRelative(pathName)) { - mapEntry.m_wszILFileName = wszFileName; - mapEntry.m_wszNIFileName = pExistingEntry == nullptr ? nullptr : pExistingEntry->m_wszNIFileName; + GO_WITH_HRESULT(E_INVALIDARG); } - m_pTrustedPlatformAssemblyMap->AddOrReplace(mapEntry); + m_appPaths.Append(pathName); } + Exit: + return hr; + } + + HRESULT ApplicationContext::SetupBindingPaths(SString &sPlatformResourceRoots, + SString &sAppPaths, + BOOL fAcquireLock) + { + HRESULT hr = S_OK; + + CRITSEC_Holder contextLock(fAcquireLock ? GetCriticalSectionCookie() : NULL); + if (m_pTrustedPlatformAssemblyMap != nullptr) + { + GO_WITH_HRESULT(S_OK); + } + + m_pTrustedPlatformAssemblyMap = HostInformation::Instance().GetHostAssemblyNames(); + // // Parse PlatformResourceRoots // @@ -219,4 +231,45 @@ namespace BINDER_SPACE { return m_pTrustedPlatformAssemblyMap != nullptr; } + + HRESULT ApplicationContext::AddAssemblyMapEntry(SString& simpleName, SString& fileName) + { + HRESULT hr = S_OK; + const SimpleNameToFileNameMapEntry *pExistingEntry = m_pTrustedPlatformAssemblyMap->LookupPtr(simpleName.GetUnicode()); + + LPWSTR wszSimpleName = nullptr; + if (pExistingEntry == nullptr) + { + wszSimpleName = new WCHAR[simpleName.GetCount() + 1]; + if (wszSimpleName == nullptr) + { + return E_OUTOFMEMORY; + } + wcscpy_s(wszSimpleName, simpleName.GetCount() + 1, simpleName.GetUnicode()); + } + else + { + wszSimpleName = pExistingEntry->m_wszSimpleName; + } + + LPWSTR wszFileName = nullptr; + + if (fileName.GetCount() > 0) + { + wszFileName = new WCHAR[fileName.GetCount() + 1]; + if (wszFileName == nullptr) + { + return E_OUTOFMEMORY; + } + wcscpy_s(wszFileName, fileName.GetCount() + 1, fileName.GetUnicode()); + } + + SimpleNameToFileNameMapEntry mapEntry; + mapEntry.m_wszSimpleName = wszSimpleName; + mapEntry.m_wszILFileName = wszFileName; + + m_pTrustedPlatformAssemblyMap->AddOrReplace(mapEntry); + + return hr; + } }; diff --git a/src/coreclr/binder/assemblybindercommon.cpp b/src/coreclr/binder/assemblybindercommon.cpp index e1de7af70677e0..c31fac10008334 100644 --- a/src/coreclr/binder/assemblybindercommon.cpp +++ b/src/coreclr/binder/assemblybindercommon.cpp @@ -20,6 +20,7 @@ #include "bindertracing.h" #include "bindresult.inl" #include "failurecache.hpp" +#include "hostinformation.h" #include "utils.hpp" #include "stringarraylist.h" #include "configuration.h" @@ -298,9 +299,8 @@ namespace BINDER_SPACE { SString fileName; SString simpleName; - bool isNativeImage = false; HRESULT pathResult = S_OK; - IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ true, fileName, simpleName, isNativeImage)); + IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ true, fileName, simpleName)); if (pathResult == S_FALSE) { break; @@ -893,25 +893,23 @@ namespace BINDER_SPACE const SimpleNameToFileNameMapEntry *pTpaEntry = tpaMap->LookupPtr(simpleName.GetUnicode()); if (pTpaEntry != nullptr) { - if (pTpaEntry->m_wszNIFileName != nullptr) - { - SString fileName(pTpaEntry->m_wszNIFileName); + SString fileName; - hr = GetAssembly(fileName, - TRUE, // fIsInTPA - &pTPAAssembly); - BinderTracing::PathProbed(fileName, BinderTracing::PathSource::ApplicationAssemblies, hr); + // If supported, go ask the host to resolve the path to the assembly + if (pTpaEntry->m_wszILFileName == nullptr) + { + _ASSERTE(pTpaEntry->m_wszSimpleName != nullptr); + HostInformation::Instance().ResolveHostAssemblyPath(simpleName, fileName); } else { - _ASSERTE(pTpaEntry->m_wszILFileName != nullptr); - SString fileName(pTpaEntry->m_wszILFileName); - - hr = GetAssembly(fileName, - TRUE, // fIsInTPA - &pTPAAssembly); - BinderTracing::PathProbed(fileName, BinderTracing::PathSource::ApplicationAssemblies, hr); + fileName.Set(pTpaEntry->m_wszILFileName); } + + hr = GetAssembly(fileName, + TRUE, // fIsInTPA + &pTPAAssembly); + BinderTracing::PathProbed(fileName, BinderTracing::PathSource::ApplicationAssemblies, hr); pBindResult->SetAttemptResult(hr, pTPAAssembly); diff --git a/src/coreclr/binder/defaultassemblybinder.cpp b/src/coreclr/binder/defaultassemblybinder.cpp index 54a70e51f05ece..85a6960ac6a850 100644 --- a/src/coreclr/binder/defaultassemblybinder.cpp +++ b/src/coreclr/binder/defaultassemblybinder.cpp @@ -187,6 +187,19 @@ HRESULT DefaultAssemblyBinder::SetupBindingPaths(SString &sTrustedPlatformAssem return hr; } +HRESULT DefaultAssemblyBinder::SetupBindingPaths(SString &sPlatformResourceRoots, + SString &sAppPaths) +{ + HRESULT hr = S_OK; + + EX_TRY + { + hr = GetAppContext()->SetupBindingPaths(sPlatformResourceRoots, sAppPaths, TRUE /* fAcquireLock */); + } + EX_CATCH_HRESULT(hr); + return hr; +} + HRESULT DefaultAssemblyBinder::BindToSystem(BINDER_SPACE::Assembly** ppSystemAssembly) { HRESULT hr = S_OK; diff --git a/src/coreclr/binder/inc/applicationcontext.hpp b/src/coreclr/binder/inc/applicationcontext.hpp index 721f571af1e5d6..810dea25e8abc8 100644 --- a/src/coreclr/binder/inc/applicationcontext.hpp +++ b/src/coreclr/binder/inc/applicationcontext.hpp @@ -17,67 +17,10 @@ #include "bindertypes.hpp" #include "failurecache.hpp" #include "stringarraylist.h" +#include "simplefilenamemap.h" namespace BINDER_SPACE { - //============================================================================================= - // Data structures for Simple Name -> File Name hash - - // Entry in SHash table that maps namespace to list of files - struct SimpleNameToFileNameMapEntry - { - LPWSTR m_wszSimpleName; - LPWSTR m_wszILFileName; - LPWSTR m_wszNIFileName; - }; - - // SHash traits for Namespace -> FileNameList hash - class SimpleNameToFileNameMapTraits : public NoRemoveSHashTraits< DefaultSHashTraits< SimpleNameToFileNameMapEntry > > - { - public: - typedef PCWSTR key_t; - static const SimpleNameToFileNameMapEntry Null() { SimpleNameToFileNameMapEntry e; e.m_wszSimpleName = nullptr; return e; } - static bool IsNull(const SimpleNameToFileNameMapEntry & e) { return e.m_wszSimpleName == nullptr; } - static key_t GetKey(const SimpleNameToFileNameMapEntry & e) - { - key_t key; - key = e.m_wszSimpleName; - return key; - } - static count_t Hash(const key_t &str) - { - SString ssKey(SString::Literal, str); - return ssKey.HashCaseInsensitive(); - } - static BOOL Equals(const key_t &lhs, const key_t &rhs) { LIMITED_METHOD_CONTRACT; return (SString::_wcsicmp(lhs, rhs) == 0); } - - void OnDestructPerEntryCleanupAction(const SimpleNameToFileNameMapEntry & e) - { - if (e.m_wszILFileName == nullptr && e.m_wszNIFileName == nullptr) - { - // Don't delete simple name here since it's a filename only entry and will be cleaned up - // by the SimpleName -> FileName entry which reuses the same filename pointer. - return; - } - - if (e.m_wszSimpleName != nullptr) - { - delete [] e.m_wszSimpleName; - } - if (e.m_wszILFileName != nullptr) - { - delete [] e.m_wszILFileName; - } - if (e.m_wszNIFileName != nullptr) - { - delete [] e.m_wszNIFileName; - } - } - static const bool s_DestructPerEntryCleanupAction = true; - }; - - typedef SHash SimpleNameToFileNameMap; - class AssemblyHashTraits; typedef SHash ExecutionContext; @@ -95,6 +38,10 @@ namespace BINDER_SPACE /* in */ SString &sPlatformResourceRoots, /* in */ SString &sAppPaths, /* in */ BOOL fAcquireLock); + + HRESULT SetupBindingPaths(/* in */ SString &sPlatformResourceRoots, + /* in */ SString &sAppPaths, + /* in */ BOOL fAcquireLock); // Getters/Setter inline ExecutionContext *GetExecutionContext(); @@ -111,6 +58,9 @@ namespace BINDER_SPACE inline LONG GetVersion(); inline void IncrementVersion(); + private: + HRESULT AddAssemblyMapEntry(SString& simpleName, SString& fileName); + private: Volatile m_cVersion; SString m_applicationName; diff --git a/src/coreclr/binder/inc/defaultassemblybinder.h b/src/coreclr/binder/inc/defaultassemblybinder.h index 3d35854e09f3ff..72f95fe620fdd1 100644 --- a/src/coreclr/binder/inc/defaultassemblybinder.h +++ b/src/coreclr/binder/inc/defaultassemblybinder.h @@ -38,6 +38,9 @@ class DefaultAssemblyBinder final : public AssemblyBinder HRESULT SetupBindingPaths(SString &sTrustedPlatformAssemblies, SString &sPlatformResourceRoots, SString &sAppPaths); + + HRESULT SetupBindingPaths(SString &PlatformResourceRoots, + SString &AppPaths); HRESULT BindToSystem(BINDER_SPACE::Assembly **ppSystemAssembly); diff --git a/src/coreclr/binder/inc/utils.hpp b/src/coreclr/binder/inc/utils.hpp index 54a6c79d853bdb..75af40f400ebd7 100644 --- a/src/coreclr/binder/inc/utils.hpp +++ b/src/coreclr/binder/inc/utils.hpp @@ -29,7 +29,7 @@ namespace BINDER_SPACE BOOL IsFileNotFound(HRESULT hr); HRESULT GetNextPath(const SString& paths, SString::CIterator& startPos, SString& outPath); - HRESULT GetNextTPAPath(const SString& paths, SString::CIterator& startPos, bool dllOnly, SString& outPath, SString& simpleName, bool& isNativeImage); + HRESULT GetNextTPAPath(const SString& paths, SString::CIterator& startPos, bool dllOnly, SString& outPath, SString& simpleName); }; #endif diff --git a/src/coreclr/binder/utils.cpp b/src/coreclr/binder/utils.cpp index f1b916ec9dc1ab..86334a6e524521 100644 --- a/src/coreclr/binder/utils.cpp +++ b/src/coreclr/binder/utils.cpp @@ -140,11 +140,9 @@ namespace BINDER_SPACE return hr; } - HRESULT GetNextTPAPath(const SString& paths, SString::CIterator& startPos, bool dllOnly, SString& outPath, SString& simpleName, bool& isNativeImage) + HRESULT GetNextTPAPath(const SString& paths, SString::CIterator& startPos, bool dllOnly, SString& outPath, SString& simpleName) { HRESULT hr = S_OK; - isNativeImage = false; - HRESULT pathResult = S_OK; while(true) { @@ -178,25 +176,16 @@ namespace BINDER_SPACE GO_WITH_HRESULT(E_INVALIDARG); } - const SString sNiDll(SString::Literal, W(".ni.dll")); - const SString sNiExe(SString::Literal, W(".ni.exe")); const SString sDll(SString::Literal, W(".dll")); const SString sExe(SString::Literal, W(".exe")); - if (dllOnly && (outPath.EndsWithCaseInsensitive(sExe) || - outPath.EndsWithCaseInsensitive(sNiExe))) + if (dllOnly && (outPath.EndsWithCaseInsensitive(sExe))) { // Skip exe files when the caller requested only dlls continue; } - if (outPath.EndsWithCaseInsensitive(sNiDll) || - outPath.EndsWithCaseInsensitive(sNiExe)) - { - simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 7); - isNativeImage = true; - } - else if (outPath.EndsWithCaseInsensitive(sDll) || + if (outPath.EndsWithCaseInsensitive(sDll) || outPath.EndsWithCaseInsensitive(sExe)) { simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 4); diff --git a/src/coreclr/dlls/mscoree/exports.cpp b/src/coreclr/dlls/mscoree/exports.cpp index 8c28aa0545a85c..0422f6989ce547 100644 --- a/src/coreclr/dlls/mscoree/exports.cpp +++ b/src/coreclr/dlls/mscoree/exports.cpp @@ -277,7 +277,7 @@ int coreclr_initialize( if (hostContract != nullptr) { - HostInformation::SetContract(hostContract); + HostInformation::Instance().SetContract(hostContract); } if (pinvokeOverride != nullptr) diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index 4e506b95c9746b..413ca8730b8bbc 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -9,6 +9,7 @@ #include "dotenv.hpp" #include +#include using char_t = pal::char_t; using string_t = pal::string_t; @@ -28,6 +29,12 @@ struct configuration ::free((void*)entry_assembly_argv[i]); } ::free(entry_assembly_argv); + + for (uint32_t i = 0; i < host_assembly_count; ++i) + { + ::free(host_assemblies[i]); + } + ::free(host_assemblies); } // @@ -37,6 +44,10 @@ struct configuration // CLR path - user supplied location of coreclr binary and managed assemblies. string_t clr_path; + pal::string_utf8_t core_root; + + pal::string_utf8_t core_libraries; + // The full path to the Supplied managed entry assembly. string_t entry_assembly_fullpath; @@ -57,6 +68,13 @@ struct configuration // configured .env file to load dotenv dotenv_configuration; + + // the list of assembly names that the runtime knows about + uint32_t host_assembly_count; + char** host_assemblies; + + // contains the name minus extension as the key and the name+extension plus a reference to where it's at on disk as the value + std::unordered_map host_file_map; }; namespace envvar @@ -97,55 +115,57 @@ static void wait_for_debugger() } } -// N.B. It seems that CoreCLR doesn't always use the first instance of an assembly on the TPA list +// N.B. It seems that CoreCLR doesn't always use the first instance of an assembly // (for example, ni's may be preferred over il, even if they appear later). Therefore, when building -// the TPA only include the first instance of a simple assembly name to allow users the opportunity to +// the list of assemblies to be used by the runtime, only include the first instance of a simple assembly name to allow users the opportunity to // override Framework assemblies by placing dlls in %CORE_LIBRARIES%. -static string_t build_tpa(const string_t& core_root, const string_t& core_libraries) +// +// build the unordered_map -> store in config -> construct the host_runtime_assemblies for the contract -> store in config -> pass to runtime +static void build_host_assembly_list(const string_t& core_root, const string_t& core_libraries, std::unordered_map& name_file_map) { static const char_t* const tpa_extensions[] = { - W(".ni.dll"), // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir W(".dll"), - W(".ni.exe"), W(".exe"), nullptr }; - std::set name_set; - pal::stringstream_t tpa_list; - // Iterate over all extensions. for (const char_t* const* curr_ext = tpa_extensions; *curr_ext != nullptr; ++curr_ext) { const char_t* ext = *curr_ext; - const size_t ext_len = pal::strlen(ext); // Iterate over all supplied directories. + int dir_type = 0; for (const string_t& dir : { core_libraries, core_root }) { if (dir.empty()) continue; assert(dir.back() == pal::dir_delim); - string_t tmp = pal::build_file_list(dir, ext, [&](const char_t* file) - { - string_t file_local{ file }; + pal::add_files_from_directory(dir, ext, dir_type, name_file_map); + dir_type++; + } + } +} - // Strip the extension. - if (pal::string_ends_with(file_local, ext_len, ext)) - file_local = file_local.substr(0, file_local.length() - ext_len); +static void build_contract_assembly_list(std::unordered_map& name_file_map, char** assemblies, uint32_t& assemblyCount) +{ + assemblyCount = static_cast(name_file_map.size()); + assemblies = new char*[assemblyCount]; - // Return true if the file is new. - return name_set.insert(file_local).second; - }); + int32_t item_count = 0; + for (auto item = name_file_map.begin(); item != name_file_map.end(); ++item) + { + pal::string_utf8_t file_name = item->first; - // Add to the TPA. - tpa_list << tmp; - } - } + size_t len = file_name.size() + 1; + assemblies[item_count] = new char[len]; - return tpa_list.str(); + ::strncpy(assemblies[item_count], file_name.c_str(), len - 1); + assemblies[item_count][len - 1] = '\0'; + item_count++; + } } static bool try_get_export(pal::mod_t mod, const char* symbol, void** fptr) @@ -211,6 +231,63 @@ static void log_error_info(const char* line) std::fprintf(stderr, "%s\n", line); } +char** HOST_CONTRACT_CALLTYPE get_assemblies( + uint32_t* assembly_count, + void* contract_context) +{ + configuration* config = static_cast(contract_context); + *assembly_count = config->host_assembly_count; + return config->host_assemblies; +} + +void HOST_CONTRACT_CALLTYPE destroy_assemblies( + char** assemblies, + uint32_t assembly_count) +{ + for (uint32_t i = 0; i < assembly_count; ++i) + { + delete[] assemblies[i]; + } + delete[] assemblies; +} + +const char* HOST_CONTRACT_CALLTYPE resolve_assembly_to_path( + const char* assembly_name, + void* contract_context) +{ + configuration* config = static_cast(contract_context); + + auto assembly = config->host_file_map.find(assembly_name); + + pal::string_utf8_t full_file; + if (assembly != config->host_file_map.end()) + { + host_file_t file = assembly->second; + pal::string_utf8_t file_name = file.name; + + if (file.dtype == host_file_t::core_root) + { + full_file = config->core_root + file_name; + } + else if (file.dtype == host_file_t::core_libraries) + { + full_file = config->core_libraries + file_name; + } + else + { + full_file = file_name; + } + } + + size_t len = full_file.size() + 1; + char* ret = new char[len]; + + ::strncpy(ret, full_file.c_str(), len - 1); + ret[len - 1] = '\0'; + + return ret; +} + size_t HOST_CONTRACT_CALLTYPE get_runtime_property( const char* key, char* value_buffer, @@ -240,14 +317,25 @@ size_t HOST_CONTRACT_CALLTYPE get_runtime_property( return -1; } -static int run(const configuration& config) +static int run(configuration& config) { platform_specific_actions actions; // Check if debugger attach scenario was requested. if (config.wait_to_debug) wait_for_debugger(); - + + host_runtime_contract host_contract; + host_contract.size = sizeof(host_runtime_contract); + host_contract.context = (void*)&config; + host_contract.get_runtime_property = &get_runtime_property; + host_contract.get_assemblies = &get_assemblies; + host_contract.resolve_assembly_to_path = &resolve_assembly_to_path; + host_contract.bundle_probe = nullptr; + host_contract.pinvoke_override = nullptr; + + host_contract.entry_assembly = (char*)pal::convert_to_utf8(config.entry_assembly_fullpath.c_str()).c_str(); + config.dotenv_configuration.load_into_current_process(); string_t exe_path = pal::get_exe_path(); @@ -262,6 +350,9 @@ static int run(const configuration& config) // Accumulate path for native search path. pal::stringstream_t native_search_dirs; + std::set native_search_dirs_set; + + native_search_dirs_set.insert(app_path); native_search_dirs << app_path << pal::env_path_delim; // CORE_LIBRARIES @@ -269,6 +360,7 @@ static int run(const configuration& config) if (!core_libs.empty() && core_libs != app_path) { pal::ensure_trailing_delimiter(core_libs); + native_search_dirs_set.insert(core_libs); native_search_dirs << core_libs << pal::env_path_delim; } @@ -290,10 +382,15 @@ static int run(const configuration& config) else { pal::ensure_trailing_delimiter(core_root); + native_search_dirs_set.insert(core_root); native_search_dirs << core_root << pal::env_path_delim; } - string_t tpa_list = build_tpa(core_root, core_libs); + config.core_root = pal::convert_to_utf8(core_root.c_str()); + config.core_libraries = pal::convert_to_utf8(core_libs.c_str()); + + build_host_assembly_list(core_root, core_libs, config.host_file_map); + build_contract_assembly_list(config.host_file_map, config.host_assemblies, config.host_assembly_count); { // Load hostpolicy if requested. @@ -330,7 +427,6 @@ static int run(const configuration& config) (void)try_get_export(coreclr_mod, "coreclr_set_error_writer", (void**)&coreclr_set_error_writer_func); // Construct CoreCLR properties. - pal::string_utf8_t tpa_list_utf8 = pal::convert_to_utf8(tpa_list.c_str()); pal::string_utf8_t app_path_utf8 = pal::convert_to_utf8(app_path.c_str()); pal::string_utf8_t native_search_dirs_utf8 = pal::convert_to_utf8(native_search_dirs.str().c_str()); @@ -345,11 +441,6 @@ static int run(const configuration& config) std::vector propertyKeys; std::vector propertyValues; - // TRUSTED_PLATFORM_ASSEMBLIES - // - The list of complete paths to each of the fully trusted assemblies - propertyKeys.push_back("TRUSTED_PLATFORM_ASSEMBLIES"); - propertyValues.push_back(tpa_list_utf8.c_str()); - // APP_PATHS // - The list of paths which will be probed by the assembly loader propertyKeys.push_back("APP_PATHS"); @@ -369,12 +460,6 @@ static int run(const configuration& config) for (const pal::string_utf8_t& str : user_defined_values_utf8) propertyValues.push_back(str.c_str()); - host_runtime_contract host_contract = { - sizeof(host_runtime_contract), - (void*)&config, - &get_runtime_property, - nullptr, - nullptr }; propertyKeys.push_back(HOST_PROPERTY_RUNTIME_CONTRACT); std::stringstream ss; ss << "0x" << std::hex << (size_t)(&host_contract); diff --git a/src/coreclr/hosts/corerun/corerun.hpp b/src/coreclr/hosts/corerun/corerun.hpp index 38ced1eb87bbad..4757db4a131d1b 100644 --- a/src/coreclr/hosts/corerun/corerun.hpp +++ b/src/coreclr/hosts/corerun/corerun.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,22 @@ namespace pal const char_t env_path_delim = W(';'); const char_t nativelib_ext[] = W(".dll"); const char_t coreclr_lib[] = W("coreclr"); +} + +struct host_file_t +{ + enum host_dir_type + { + core_libraries, + core_root + }; + + pal::string_utf8_t name; // file name + extension + host_dir_type dtype; +}; + +namespace pal +{ inline int strcmp(const char_t* str1, const char_t* str2) { return wcscmp(str1, str2); } inline size_t strlen(const char_t* str) { return wcslen(str); } @@ -148,6 +165,57 @@ namespace pal // Forward declaration void ensure_trailing_delimiter(pal::string_t& dir); + inline bool string_ends_with(const string_t& str, size_t suffix_len, const char_t* suffix); + inline string_utf8_t convert_to_utf8(const char_t* str); + + inline void add_files_from_directory( + const string_t& directory, + const char_t* ext, + const int dir_type, + std::unordered_map& files) + { + assert(ext != nullptr); + + string_t dir_local = directory; + dir_local.append(W("*")); + dir_local.append(ext); + + const size_t ext_len = pal::strlen(ext); + + WIN32_FIND_DATA data; + HANDLE findHandle = ::FindFirstFileW(dir_local.data(), &data); + if (findHandle == INVALID_HANDLE_VALUE) + return; + + do + { + if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + // ToLower for case-insensitive comparisons + char_t* fileNameChar = data.cFileName; + while (*fileNameChar) + { + *fileNameChar = towlower(*fileNameChar); + fileNameChar++; + } + + string_t file_local{ data.cFileName }; + string_t file_name { data.cFileName }; + + // Strip the extension. + if (pal::string_ends_with(file_local, ext_len, ext)) + file_name = file_local.substr(0, file_local.length() - ext_len); + + host_file_t f; + f.name = convert_to_utf8(file_local.c_str()); + f.dtype = static_cast(dir_type); + + files.insert({convert_to_utf8(file_name.c_str()), f}); + } + } while (FALSE != ::FindNextFileW(findHandle, &data)); + + ::FindClose(findHandle); + } inline string_t build_file_list( const string_t& dir, @@ -353,6 +421,22 @@ namespace pal const char_t nativelib_ext[] = W(".so"); #endif const char_t coreclr_lib[] = W("libcoreclr"); +} + +struct host_file_t +{ + enum host_dir_type + { + core_libraries, + core_root + }; + + pal::string_t name; // file name + extension + host_dir_type dtype; +}; + +namespace pal +{ inline int strcmp(const char_t* str1, const char_t* str2) { return ::strcmp(str1, str2); } inline size_t strlen(const char_t* str) { return ::strlen(str); } @@ -516,8 +600,76 @@ namespace pal template bool string_ends_with(const string_t& str, const char_t(&suffix)[LEN]); bool string_ends_with(const string_t& str, size_t suffix_len, const char_t* suffix); + inline string_utf8_t convert_to_utf8(const char_t* str); void ensure_trailing_delimiter(pal::string_t& dir); + inline void add_files_from_directory( + const string_t& directory, + const char_t* ext, + const int dir_type, + std::unordered_map& files) + { + assert(ext != nullptr); + const size_t ext_len = pal::strlen(ext); + + DIR* dir = opendir(directory.c_str()); + if (dir == nullptr) + return; + + // For all entries in the directory + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) + { +#if HAVE_DIRENT_D_TYPE + int dirEntryType = entry->d_type; +#else + int dirEntryType = DT_UNKNOWN; +#endif + + // We are interested in files only + switch (dirEntryType) + { + case DT_REG: + break; + + // Handle symlinks and file systems that do not support d_type + case DT_LNK: + case DT_UNKNOWN: + { + string_t full_filename{directory}; + full_filename.append(entry->d_name); + if (!does_file_exist(full_filename.c_str())) + continue; + } + break; + + default: + continue; + } + + // Check if the extension matches the one we are looking for + if (!pal::string_ends_with(entry->d_name, ext_len, ext)) + continue; + + // Make sure if we have an assembly with multiple extensions present, + // we insert only one version of it. + string_t file_local{ entry->d_name }; + string_t file_name { entry->d_name }; + + // Strip the extension. + if (pal::string_ends_with(file_local, ext_len, ext)) + file_name = file_local.substr(0, file_local.length() - ext_len); + + host_file_t file; + file.name = convert_to_utf8(file_local.c_str()); + file.dtype = static_cast(dir_type); + + files.insert({convert_to_utf8(file_name.c_str()), file}); + } + + closedir(dir); + } + inline string_t build_file_list( const string_t& directory, const char_t* ext, diff --git a/src/coreclr/inc/hostinformation.h b/src/coreclr/inc/hostinformation.h index d57b4729d30e68..d5cade43fc49bb 100644 --- a/src/coreclr/inc/hostinformation.h +++ b/src/coreclr/inc/hostinformation.h @@ -5,12 +5,39 @@ #define _HOSTINFORMATION_H_ #include +#include "simplefilenamemap.h" +#include "sstring.h" class HostInformation { public: - static void SetContract(_In_ host_runtime_contract* hostContract); - static bool GetProperty(_In_z_ const char* name, SString& value); + static HostInformation& Instance() + { + static HostInformation instance; + return instance; + } + + ~HostInformation(); + + void SetContract(_In_ host_runtime_contract* hostContract); + bool GetProperty(_In_z_ const char* name, SString& value); + void GetEntryAssemblyName(/*Out*/ SString& entryAssemblyName); + + SimpleNameToFileNameMap* GetHostAssemblyNames(); + void ResolveHostAssemblyPath(const SString& simpleName, /*Out*/ SString& resolvedPath); + +private: + HostInformation() {} // Private constructor to prevent instantiation + HostInformation(const HostInformation&) = delete; // Delete copy constructor + HostInformation& operator=(const HostInformation&) = delete; // Delete assignment operator + +/* + LPCSTR ConvertToUTF8(LPCWSTR utf16String); + LPCWSTR ConvertToUnicode(const char* utf8String); +*/ + +private: + SimpleNameToFileNameMap* m_simpleFileNameMap; }; #endif // _HOSTINFORMATION_H_ diff --git a/src/coreclr/inc/simplefilenamemap.h b/src/coreclr/inc/simplefilenamemap.h new file mode 100644 index 00000000000000..574e7308452617 --- /dev/null +++ b/src/coreclr/inc/simplefilenamemap.h @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +//============================================================================================= +// Data structures for Simple Name -> File Name hash + +#ifndef __SIMPLEFILENAMEMAP_H_ +#define __SIMPLEFILENAMEMAP_H_ + +#include "clrtypes.h" +#include "shash.h" + +// Entry in SHash table that maps namespace to list of files +struct SimpleNameToFileNameMapEntry +{ + LPWSTR m_wszSimpleName; + LPWSTR m_wszILFileName; +}; + +// SHash traits for Namespace -> FileNameList hash +class SimpleNameToFileNameMapTraits : public NoRemoveSHashTraits< DefaultSHashTraits< SimpleNameToFileNameMapEntry > > +{ + public: + typedef PCWSTR key_t; + static const SimpleNameToFileNameMapEntry Null() { SimpleNameToFileNameMapEntry e; e.m_wszSimpleName = nullptr; return e; } + static bool IsNull(const SimpleNameToFileNameMapEntry & e) { return e.m_wszSimpleName == nullptr; } + static key_t GetKey(const SimpleNameToFileNameMapEntry & e) + { + key_t key; + key = e.m_wszSimpleName; + return key; + } + static count_t Hash(const key_t &str) + { + SString ssKey(SString::Literal, str); + return ssKey.HashCaseInsensitive(); + } + static BOOL Equals(const key_t &lhs, const key_t &rhs) { LIMITED_METHOD_CONTRACT; return (SString::_wcsicmp(lhs, rhs) == 0); } + + void OnDestructPerEntryCleanupAction(const SimpleNameToFileNameMapEntry & e) + { + if (e.m_wszILFileName == nullptr) + { + // Don't delete simple name here since it's a filename only entry and will be cleaned up + // by the SimpleName -> FileName entry which reuses the same filename pointer. + return; + } + + if (e.m_wszSimpleName != nullptr) + { + delete [] e.m_wszSimpleName; + } + if (e.m_wszILFileName != nullptr) + { + delete [] e.m_wszILFileName; + } + } + static const bool s_DestructPerEntryCleanupAction = true; +}; + +typedef SHash SimpleNameToFileNameMap; + +#endif // __SIMPLEFILENAMEMAP_H_ diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index c0e4091af38a86..77a2ccedcb52b9 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -40,6 +40,7 @@ #ifndef DACCESS_COMPILE +#include "hostinformation.h" #include extern void STDMETHODCALLTYPE EEShutDown(BOOL fIsDllUnloading); @@ -313,9 +314,13 @@ HRESULT CorHost2::ExecuteAssembly(DWORD dwAppDomainId, if (g_EntryAssemblyPath == NULL) { // Store the entry assembly path for diagnostic purposes (for example, dumps) - size_t len = u16_strlen(pwzAssemblyPath) + 1; + SString ssEntryAssemblyName; + HostInformation::Instance().GetEntryAssemblyName(ssEntryAssemblyName); + + const WCHAR* pEntryAssemblyName = ssEntryAssemblyName.GetUnicode(); + size_t len = u16_strlen(pEntryAssemblyName) + 1; NewArrayHolder path { new WCHAR[len] }; - wcscpy_s(path, len, pwzAssemblyPath); + wcscpy_s(path, len, pEntryAssemblyName); g_EntryAssemblyPath = path.Extract(); } @@ -626,6 +631,20 @@ HRESULT CorHost2::CreateAppDomainWithManager( pDomain->SetNativeDllSearchDirectories(pwzNativeDllSearchDirectories); + // If the tpa list has not been provided, use the host contract to get the list of assemblies being used. + // Otherwise, use the TPA list provided by the host. + if (pwzTrustedPlatformAssemblies == nullptr) + { + SString sPlatformResourceRoots(pwzPlatformResourceRoots); + SString sAppPaths(pwzAppPaths); + + DefaultAssemblyBinder *pBinder = pDomain->GetDefaultBinder(); + _ASSERTE(pBinder != NULL); + IfFailThrow(pBinder->SetupBindingPaths( + sPlatformResourceRoots, + sAppPaths)); + } + else { SString sTrustedPlatformAssemblies(pwzTrustedPlatformAssemblies); SString sPlatformResourceRoots(pwzPlatformResourceRoots); diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 95c568f50acbfe..38a9922875640f 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -76,7 +76,7 @@ ep_rt_entrypoint_assembly_name_get_utf8 (void) { const ep_char8_t* entrypoint_assembly_name_local; SString assembly_name; - if (HostInformation::GetProperty (HOST_PROPERTY_ENTRY_ASSEMBLY_NAME, assembly_name)) + if (HostInformation::Instance().GetProperty (HOST_PROPERTY_ENTRY_ASSEMBLY_NAME, assembly_name)) { entrypoint_assembly_name_local = reinterpret_cast(assembly_name.GetCopyOfUTF8String ()); } diff --git a/src/coreclr/vm/gcheaputilities.cpp b/src/coreclr/vm/gcheaputilities.cpp index 2f588ae6bdaec1..b8871abacb11dc 100644 --- a/src/coreclr/vm/gcheaputilities.cpp +++ b/src/coreclr/vm/gcheaputilities.cpp @@ -187,7 +187,7 @@ HMODULE LoadStandaloneGc(LPCWSTR libFileName, LPCWSTR libFilePath) } SString appBase; - if (HostInformation::GetProperty("APP_CONTEXT_BASE_DIRECTORY", appBase)) + if (HostInformation::Instance().GetProperty("APP_CONTEXT_BASE_DIRECTORY", appBase)) { PathString libPath = appBase.GetUnicode(); libPath.Append(libFileName); diff --git a/src/coreclr/vm/hostinformation.cpp b/src/coreclr/vm/hostinformation.cpp index b440f6f2168ab7..9f6850e5c9e38e 100644 --- a/src/coreclr/vm/hostinformation.cpp +++ b/src/coreclr/vm/hostinformation.cpp @@ -9,6 +9,14 @@ namespace host_runtime_contract* s_hostContract = nullptr; } +HostInformation::~HostInformation() +{ + if (m_simpleFileNameMap != nullptr) + { + delete m_simpleFileNameMap; + } +} + void HostInformation::SetContract(_In_ host_runtime_contract* hostContract) { _ASSERTE(s_hostContract == nullptr); @@ -40,3 +48,122 @@ bool HostInformation::GetProperty(_In_z_ const char* name, SString& value) return lenActual > 0 && lenActual <= len; } + +void HostInformation::GetEntryAssemblyName(SString& entryAssemblyName) +{ + if ((s_hostContract == nullptr) || (s_hostContract->entry_assembly == nullptr)) + { + entryAssemblyName.Set(W("")); + return; + } + + entryAssemblyName.SetUTF8(s_hostContract->entry_assembly); + entryAssemblyName.Normalize(); +} + +SimpleNameToFileNameMap* HostInformation::GetHostAssemblyNames() +{ + if (m_simpleFileNameMap != nullptr) + return m_simpleFileNameMap; + + m_simpleFileNameMap = new SimpleNameToFileNameMap(); + + if (s_hostContract == nullptr || s_hostContract->get_assemblies == nullptr) + return m_simpleFileNameMap; + + char** assemblies; + uint32_t assemblyCount = 0; + assemblies = s_hostContract->get_assemblies(&assemblyCount, s_hostContract->context); + + if (assemblies == nullptr) + return m_simpleFileNameMap; + + for (uint32_t i = 0; i < assemblyCount; i++) + { + SimpleNameToFileNameMapEntry mapEntry; + SString assemblyName; + + assemblyName.SetUTF8(assemblies[i]); + assemblyName.Normalize(); + + LPWSTR wszSimpleName = new WCHAR[assemblyName.GetCount() + 1]; + if (wszSimpleName == nullptr) + { + continue; + } + wcscpy_s(wszSimpleName, assemblyName.GetCount() + 1, assemblyName.GetUnicode()); + + mapEntry.m_wszSimpleName = wszSimpleName; + mapEntry.m_wszILFileName = nullptr; + + m_simpleFileNameMap->AddOrReplace(mapEntry); + } + + s_hostContract->destroy_assemblies(assemblies, assemblyCount); + + return m_simpleFileNameMap; +} + +void HostInformation::ResolveHostAssemblyPath(const SString& simpleName, SString& resolvedPath) +{ + if (s_hostContract == nullptr || s_hostContract->resolve_assembly_to_path == nullptr) + { + resolvedPath.Set(simpleName); + return; + } + + SString ssUtf8SimpleName(simpleName); + simpleName.ConvertToUTF8(ssUtf8SimpleName); + + LPCSTR assemblyPath = s_hostContract->resolve_assembly_to_path(ssUtf8SimpleName.GetUTF8(), s_hostContract->context); + + if (assemblyPath == nullptr) + { + resolvedPath.Set(simpleName); + } + else + { + resolvedPath.SetUTF8(assemblyPath); + resolvedPath.Normalize(); + } +} + +/* +LPCSTR HostInformation::ConvertToUTF8(LPCWSTR utf16String) +{ + int length = WideCharToMultiByte(CP_ACP, 0, utf16String, -1, NULL, 0, NULL, NULL); + + if (length <= 0) + return ""; + + char* ret = new char[length]; + + if (ret == nullptr) + { + return ""; + } + + length = WideCharToMultiByte(CP_ACP, 0, utf16String, -1, ret, length, NULL, NULL); + + return (length > 0) ? ret : ""; +} + +LPCWSTR HostInformation::ConvertToUnicode(const char* utf8String) +{ + int length = MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, NULL, 0); + + if (length <= 0) + return L""; + + LPWSTR ret = new WCHAR[length]; + + if (ret == nullptr) + { + return L""; + } + + length = MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, ret, length); + + return (length > 0) ? ret : L""; +} +*/ diff --git a/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs b/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs index 4ecf59b2761ace..a260db758f11ca 100644 --- a/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs +++ b/src/installer/tests/Assets/Projects/HostApiInvokerApp/HostRuntimeContract.cs @@ -18,6 +18,10 @@ internal struct host_runtime_contract public delegate* unmanaged[Stdcall] get_runtime_property; public delegate* unmanaged[Stdcall] bundle_probe; public IntPtr pinvoke_override; + public delegate* unmanaged[Stdcall] get_assemblies; + public delegate* unmanaged[Stdcall] destroy_assemblies; + public delegate* unmanaged[Stdcall] resolve_assembly_to_path; + public byte* entry_assembly; } #pragma warning restore CS0649 diff --git a/src/libraries/externals.csproj b/src/libraries/externals.csproj index 6aa753ec79b239..3425274d30475e 100644 --- a/src/libraries/externals.csproj +++ b/src/libraries/externals.csproj @@ -27,6 +27,8 @@ + <_HostSymbols Include="$(DotNetHostBinDir)$(LibPrefix)hostfxr$(LibSuffix)$(SymbolsSuffix)" /> + <_HostSymbols Include="$(DotNetHostBinDir)$(LibPrefix)hostpolicy$(LibSuffix)$(SymbolsSuffix)" /> @@ -82,6 +84,8 @@ + + @@ -120,6 +124,7 @@ + #else @@ -500,6 +501,7 @@ pal::string_t to_upper(const pal::char_t* in) { return ret; } + #define TEST_ONLY_MARKER "d38cc827-e34f-4453-9df4-1e796e9f1d07" // Retrieves environment variable which is only used for testing. diff --git a/src/native/corehost/hostmisc/utils.h b/src/native/corehost/hostmisc/utils.h index 5d782a070f8b8e..c8811b248875b3 100644 --- a/src/native/corehost/hostmisc/utils.h +++ b/src/native/corehost/hostmisc/utils.h @@ -6,6 +6,7 @@ #include "pal.h" #include "trace.h" +#include "host_runtime_contract.h" #include #include #include diff --git a/src/native/corehost/hostpolicy/deps_resolver.cpp b/src/native/corehost/hostpolicy/deps_resolver.cpp index 8bab2fa1d79f90..428183dc4c9d5c 100644 --- a/src/native/corehost/hostpolicy/deps_resolver.cpp +++ b/src/native/corehost/hostpolicy/deps_resolver.cpp @@ -2,16 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. #include +#include #include #include +#include #include +#include #include "deps_entry.h" #include "deps_format.h" #include "deps_resolver.h" + #include "shared_store.h" #include #include +#include namespace { @@ -42,6 +47,7 @@ namespace deps_entry_t::asset_types asset_type, const pal::string_t& path, std::unordered_set* existing, + std::deque* items, pal::string_t* serviced, pal::string_t* non_serviced, const pal::string_t& svc_dir) @@ -56,13 +62,16 @@ namespace trace::verbose(_X("Adding to %s path: %s"), deps_entry_t::s_known_asset_types[asset_type], path.c_str()); + // serviced paths come before non-serviced paths. if (utils::starts_with(path, svc_dir.c_str(), svc_dir.length(), false)) { + items->push_front(path); serviced->append(path); serviced->push_back(PATH_SEPARATOR); } else { + items->push_back(path); non_serviced->append(path); non_serviced->push_back(PATH_SEPARATOR); } @@ -92,10 +101,10 @@ namespace void add_tpa_asset( const deps_asset_t& asset, const pal::string_t& resolved_path, - name_to_resolved_asset_map_t* items) + std::unique_ptr& host_assemblies) { - name_to_resolved_asset_map_t::iterator existing = items->find(asset.name); - if (existing == items->end()) + name_to_resolved_asset_map_t::iterator existing = host_assemblies->find(asset.name); + if (existing == host_assemblies->end()) { if (trace::is_enabled()) { @@ -105,7 +114,7 @@ namespace asset.file_version.as_str().c_str()); } - items->emplace(asset.name, deps_resolved_asset_t(asset, resolved_path)); + host_assemblies->emplace(asset.name, deps_resolved_asset_t(asset, resolved_path)); } } @@ -116,13 +125,13 @@ namespace void get_dir_assemblies( const pal::string_t& dir, const pal::string_t& dir_name, - name_to_resolved_asset_map_t* items) + std::unique_ptr& host_assemblies) { version_t empty; trace::verbose(_X("Adding files from %s dir %s"), dir_name.c_str(), dir.c_str()); // Managed extensions in priority order, pick DLL over EXE and NI over IL. - const pal::string_t managed_ext[] = { _X(".ni.dll"), _X(".dll"), _X(".ni.exe"), _X(".exe") }; + const pal::string_t managed_ext[] = { _X(".dll"), _X(".exe") }; // List of files in the dir std::vector files; @@ -148,11 +157,11 @@ namespace } // Already added entry for this asset, by priority order skip this ext - if (items->count(file_name)) + if (host_assemblies->count(file_name)) { trace::verbose(_X("Skipping %s because the %s already exists in %s assemblies"), file.c_str(), - items->find(file_name)->second.asset.relative_path.c_str(), + host_assemblies->find(file_name)->second.asset.relative_path.c_str(), dir_name.c_str()); continue; @@ -172,7 +181,7 @@ namespace file_path.c_str()); deps_asset_t asset(file_name, file, empty, empty); - add_tpa_asset(asset, file_path, items); + add_tpa_asset(asset, file_path, host_assemblies); } } } @@ -411,11 +420,11 @@ bool report_missing_assembly_in_manifest(const deps_entry_t& entry, bool continu * Resolve the TPA assembly locations */ bool deps_resolver_t::resolve_tpa_list( - pal::string_t* output, + std::unique_ptr& host_assemblies, std::unordered_set* breadcrumb, bool ignore_missing_assemblies) -{ - name_to_resolved_asset_map_t items; +{ + host_assemblies = std::unique_ptr(new name_to_resolved_asset_map_t()); auto process_entry = [&](const pal::string_t& deps_dir, const deps_entry_t& entry, int fx_level) -> bool { @@ -436,8 +445,8 @@ bool deps_resolver_t::resolve_tpa_list( pal::string_t resolved_path; - name_to_resolved_asset_map_t::iterator existing = items.find(entry.asset.name); - if (existing == items.end()) + name_to_resolved_asset_map_t::iterator existing = host_assemblies->find(entry.asset.name); + if (existing == host_assemblies->end()) { bool found_in_bundle = false; if (probe_deps_entry(entry, deps_dir, fx_level, &resolved_path, found_in_bundle)) @@ -446,7 +455,7 @@ bool deps_resolver_t::resolve_tpa_list( // The runtime directly probes the bundle-manifest using a host-callback. if (!found_in_bundle) { - add_tpa_asset(entry.asset, resolved_path, &items); + add_tpa_asset(entry.asset, resolved_path, host_assemblies); } return true; @@ -487,12 +496,12 @@ bool deps_resolver_t::resolve_tpa_list( resolved_path.c_str(), entry.asset.assembly_version.as_str().c_str(), entry.asset.file_version.as_str().c_str()); existing_entry = nullptr; - items.erase(existing); + host_assemblies->erase(existing); if (!found_in_bundle) { deps_asset_t asset(entry.asset.name, entry.asset.relative_path, entry.asset.assembly_version, entry.asset.file_version); - add_tpa_asset(asset, resolved_path, &items); + add_tpa_asset(asset, resolved_path, host_assemblies); } } } @@ -524,7 +533,7 @@ bool deps_resolver_t::resolve_tpa_list( bundle::runner_t::app()->probe(managed_app_name) == nullptr) { deps_asset_t asset(get_filename_without_ext(m_managed_app), managed_app_name, version_t(), version_t()); - add_tpa_asset(asset, m_managed_app, &items); + add_tpa_asset(asset, m_managed_app, host_assemblies); } // Add the app's entries @@ -543,7 +552,7 @@ bool deps_resolver_t::resolve_tpa_list( if (!get_app_deps().exists()) { // Obtain the local assemblies in the app dir. - get_dir_assemblies(m_app_dir, _X("local"), &items); + get_dir_assemblies(m_app_dir, _X("local"), host_assemblies); } } @@ -581,13 +590,6 @@ bool deps_resolver_t::resolve_tpa_list( } } - // Convert the paths into a string and return it - for (const auto& item : items) - { - output->append(item.second.resolved_path); - output->push_back(PATH_SEPARATOR); - } - return true; } @@ -763,6 +765,9 @@ bool deps_resolver_t::resolve_probe_dirs( // Action for post processing the resolved path std::function& action = is_resources ? resources : native; + // Set to track the entries in probing_lookup_paths + std::deque probing_items; + // Set for de-duplication std::unordered_set items; @@ -802,7 +807,7 @@ bool deps_resolver_t::resolve_probe_dirs( if (!found_in_bundle) { init_known_entry_path(entry, candidate); - add_unique_path(asset_type, action(candidate), &items, output, &non_serviced, core_servicing); + add_unique_path(asset_type, action(candidate), &items, &probing_items, output, &non_serviced, core_servicing); } } else @@ -834,7 +839,7 @@ bool deps_resolver_t::resolve_probe_dirs( if (!get_app_deps().exists()) { // App local path - add_unique_path(asset_type, m_app_dir, &items, output, &non_serviced, core_servicing); + add_unique_path(asset_type, m_app_dir, &items, &probing_items, output, &non_serviced, core_servicing); // deps_resolver treats being able to get the coreclr path as optional, so we ignore the return value here. // The caller is responsible for checking whether coreclr path is set and handling as appropriate. @@ -872,12 +877,12 @@ bool deps_resolver_t::resolve_probe_dirs( if (bundle::info_t::is_single_file_bundle() && !is_resources) { auto bundle = bundle::runner_t::app(); - add_unique_path(asset_type, bundle->base_path(), &items, output, &non_serviced, core_servicing); + add_unique_path(asset_type, bundle->base_path(), &items, &probing_items, output, &non_serviced, core_servicing); // Add the extraction path if it exists. if (pal::directory_exists(bundle->extraction_path())) { - add_unique_path(asset_type, bundle->extraction_path(), &items, output, &non_serviced, core_servicing); + add_unique_path(asset_type, bundle->extraction_path(), &items, &probing_items, output, &non_serviced, core_servicing); } } @@ -893,14 +898,15 @@ bool deps_resolver_t::resolve_probe_dirs( // Parameters: // probe_paths - Pointer to struct containing fields that will contain // resolved path ordering. +// host_assemblies - The name + path of the assemblies that made up the TPA list // breadcrumb - set of breadcrumb paths - or null if no breadcrumbs should be collected. // ignore_missing_assemblies - if set to true, resolving TPA assemblies will not fail if an assembly can't be found on disk // instead such entry will simply be ignored. // // -bool deps_resolver_t::resolve_probe_paths(probe_paths_t* probe_paths, std::unordered_set* breadcrumb, bool ignore_missing_assemblies) +bool deps_resolver_t::resolve_probe_paths(probe_paths_t* probe_paths, std::unique_ptr& host_assemblies, std::unordered_set* breadcrumb, bool ignore_missing_assemblies) { - if (!resolve_tpa_list(&probe_paths->tpa, breadcrumb, ignore_missing_assemblies)) + if (!resolve_tpa_list(host_assemblies, breadcrumb, ignore_missing_assemblies)) { return false; } diff --git a/src/native/corehost/hostpolicy/deps_resolver.h b/src/native/corehost/hostpolicy/deps_resolver.h index 2a971ef0cfc33f..b5e45993bca664 100644 --- a/src/native/corehost/hostpolicy/deps_resolver.h +++ b/src/native/corehost/hostpolicy/deps_resolver.h @@ -5,6 +5,8 @@ #define DEPS_RESOLVER_H #include +#include +#include #include "pal.h" #include "args.h" @@ -14,6 +16,7 @@ #include "deps_entry.h" #include "runtime_config.h" #include "bundle/runner.h" +#include // Probe paths to be resolved for ordering struct probe_paths_t @@ -129,6 +132,7 @@ class deps_resolver_t bool resolve_probe_paths( probe_paths_t* probe_paths, + std::unique_ptr& host_assemblies, std::unordered_set* breadcrumb, bool ignore_missing_assemblies = false); @@ -207,7 +211,7 @@ class deps_resolver_t private: // Resolve order for TPA lookup. bool resolve_tpa_list( - pal::string_t* output, + std::unique_ptr& host_assemblies, std::unordered_set* breadcrumb, bool ignore_missing_assemblies); diff --git a/src/native/corehost/hostpolicy/hostpolicy.cpp b/src/native/corehost/hostpolicy/hostpolicy.cpp index a50069fbb3782e..3a14f7587c5623 100644 --- a/src/native/corehost/hostpolicy/hostpolicy.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy.cpp @@ -971,8 +971,16 @@ SHARED_API int HOSTPOLICY_CALLTYPE corehost_resolve_component_dependencies( // Don't write breadcrumbs since we're not executing the app, just resolving dependencies // doesn't guarantee that they will actually execute. + const std::shared_ptr context = get_hostpolicy_context(/*require_runtime*/ true); + if (context == nullptr) + { + trace::error(_X("Trying to get host_runtime_contract, but hostpolicy has not been initialized")); + return StatusCode::HostInvalidState; + } + probe_paths_t probe_paths; - if (!resolver.resolve_probe_paths(&probe_paths, nullptr, /* ignore_missing_assemblies */ true)) + std::unique_ptr host_assemblies = std::unique_ptr(new name_to_resolved_asset_map_t()); + if (!resolver.resolve_probe_paths(&probe_paths, host_assemblies, nullptr, /* ignore_missing_assemblies */ true)) { return StatusCode::ResolverResolveFailure; } diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index 1a1f63dae073c4..2258f90293c740 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -133,6 +133,76 @@ namespace return -1; } + + char** HOST_CONTRACT_CALLTYPE get_runtime_framework_assemblies( + uint32_t* assembly_count, + void* contract_context) + { + hostpolicy_context_t* context = static_cast(contract_context); + + char** assemblies; + *assembly_count = static_cast(context->host_assemblies->size()); + assemblies = new char*[*assembly_count]; + + int32_t item_count = 0; + for (auto item = context->host_assemblies->begin(); item != context->host_assemblies->end(); ++item) + { + pal::string_t file_name = item->second.asset.name; + + size_t len = file_name.size() + 1; + assemblies[item_count] = new char[len]; + + pal::pal_utf8string(file_name, assemblies[item_count], len); + item_count++; + } + + return assemblies; + } + + void HOST_CONTRACT_CALLTYPE destroy_assemblies( + char** assemblies, + uint32_t assembly_count) + { + for (uint32_t i = 0; i < assembly_count; ++i) + { + delete[] assemblies[i]; + } + + delete[] assemblies; + } + + const char* HOST_CONTRACT_CALLTYPE resolve_assembly_to_path( + const char* assembly_name, + void* contract_context) + { + hostpolicy_context_t* context = static_cast(contract_context); + + if (context->host_assemblies == nullptr) + return assembly_name; + + pal::string_t host_assembly_name; + if (!pal::clr_palstring(assembly_name, &host_assembly_name)) + { + trace::warning(_X("Failed to convert assembly_name [%hs] to UTF8"), assembly_name); + return assembly_name; + } + + char* ret; + name_to_resolved_asset_map_t::iterator host_assembly = context->host_assemblies->find(host_assembly_name); + if (host_assembly != context->host_assemblies->end()) + { + size_t len = host_assembly->second.resolved_path.size() + 1; + ret = new char[len]; + + pal::pal_utf8string(host_assembly->second.resolved_path, ret, len); + } + else + { + ret = const_cast(assembly_name); + } + + return ret; + } } bool hostpolicy_context_t::should_read_rid_fallback_graph(const hostpolicy_init_t &init) @@ -178,6 +248,36 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c return StatusCode::ResolverInitFailure; } + { + host_contract = { sizeof(host_runtime_contract), this }; + if (bundle::info_t::is_single_file_bundle()) + { + host_contract.bundle_probe = &bundle_probe; +#if defined(NATIVE_LIBS_EMBEDDED) + host_contract.pinvoke_override = &pinvoke_override; +#endif + } + + size_t entry_assembly_length = application.size() + 1; + + host_contract.entry_assembly = new char[entry_assembly_length]; + pal::pal_utf8string(application, host_contract.entry_assembly, entry_assembly_length); + + host_contract.get_runtime_property = &get_runtime_property; + host_contract.get_assemblies = &get_runtime_framework_assemblies; + host_contract.destroy_assemblies = &destroy_assemblies; + host_contract.resolve_assembly_to_path = &resolve_assembly_to_path; + pal::char_t buffer[STRING_LENGTH("0xffffffffffffffff")]; + pal::snwprintf(buffer, ARRAY_SIZE(buffer), _X("0x%zx"), (size_t)(&host_contract)); + if (!coreclr_properties.add(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT), buffer)) + { + log_duplicate_property_error(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT)); + return StatusCode::LibHostDuplicateProperty; + } + } + + host_assemblies = std::unique_ptr(new name_to_resolved_asset_map_t()); + probe_paths_t probe_paths; // Setup breadcrumbs. @@ -190,14 +290,14 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c breadcrumbs.insert(policy_name); breadcrumbs.insert(policy_name + _X(",") + policy_version); - if (!resolver.resolve_probe_paths(&probe_paths, &breadcrumbs)) + if (!resolver.resolve_probe_paths(&probe_paths, host_assemblies, &breadcrumbs)) { return StatusCode::ResolverResolveFailure; } } else { - if (!resolver.resolve_probe_paths(&probe_paths, nullptr)) + if (!resolver.resolve_probe_paths(&probe_paths, host_assemblies, nullptr)) { return StatusCode::ResolverResolveFailure; } @@ -223,6 +323,10 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c clr_dir = get_directory(clr_path); } +/* + TO DO: Figure out if single file is impacted by taking this code out and + not providing a TPA list. + // If this is a self-contained single-file bundle, // System.Private.CoreLib.dll is expected to be within the bundle, unless it is explicitly excluded from the bundle. // In all other cases, @@ -241,6 +345,7 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c probe_paths.tpa.append(corelib_path); } +*/ pal::string_t fx_deps_str; if (resolver.is_framework_dependent()) @@ -273,7 +378,7 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c // Build properties for CoreCLR instantiation pal::string_t app_base; resolver.get_app_dir(&app_base); - coreclr_properties.add(common_property::TrustedPlatformAssemblies, probe_paths.tpa.c_str()); + coreclr_properties.add(common_property::NativeDllSearchDirectories, probe_paths.native.c_str()); coreclr_properties.add(common_property::PlatformResourceRoots, probe_paths.resources.c_str()); coreclr_properties.add(common_property::AppContextBaseDirectory, app_base.c_str()); @@ -329,25 +434,5 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c coreclr_properties.add(common_property::StartUpHooks, startup_hooks.c_str()); } - { - host_contract = { sizeof(host_runtime_contract), this }; - if (bundle::info_t::is_single_file_bundle()) - { - host_contract.bundle_probe = &bundle_probe; -#if defined(NATIVE_LIBS_EMBEDDED) - host_contract.pinvoke_override = &pinvoke_override; -#endif - } - - host_contract.get_runtime_property = &get_runtime_property; - pal::char_t buffer[STRING_LENGTH("0xffffffffffffffff")]; - pal::snwprintf(buffer, ARRAY_SIZE(buffer), _X("0x%zx"), (size_t)(&host_contract)); - if (!coreclr_properties.add(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT), buffer)) - { - log_duplicate_property_error(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT)); - return StatusCode::LibHostDuplicateProperty; - } - } - return StatusCode::Success; } diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.h b/src/native/corehost/hostpolicy/hostpolicy_context.h index a7d8faf6f7a6f3..9a1fe5ac343db3 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.h +++ b/src/native/corehost/hostpolicy/hostpolicy_context.h @@ -9,6 +9,7 @@ #include "args.h" #include "coreclr.h" #include +#include "deps_resolver.h" #include #include "hostpolicy_init.h" @@ -30,6 +31,8 @@ struct hostpolicy_context_t host_runtime_contract host_contract; + std::unique_ptr host_assemblies; + int initialize(const hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs); public: // static diff --git a/src/native/corehost/test/mockcoreclr/mockcoreclr.cpp b/src/native/corehost/test/mockcoreclr/mockcoreclr.cpp index 3ea15bbf813495..4f0e1ba2cd8493 100644 --- a/src/native/corehost/test/mockcoreclr/mockcoreclr.cpp +++ b/src/native/corehost/test/mockcoreclr/mockcoreclr.cpp @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "mockcoreclr.h" +#include "host_runtime_contract.h" #include #include #include @@ -44,9 +45,20 @@ SHARED_API pal::hresult_t STDMETHODCALLTYPE coreclr_initialize( MockLogArg(hostHandle); MockLogArg(domainId); + host_runtime_contract* host_contract; + for (int i = 0; i < propertyCount; ++i) { - MockLogEntry("property", propertyKeys[i], propertyValues[i]); + if (strcmp(propertyKeys[i], "HOST_RUNTIME_CONTRACT") == 0) + { + char* endPtr; + uint64_t contractValue = strtoul(propertyValues[i], nullptr, 0); + host_contract = (host_runtime_contract*)contractValue; + } + else + { + MockLogEntry("property", propertyKeys[i], propertyValues[i]); + } } if (hostHandle != nullptr) @@ -54,6 +66,27 @@ SHARED_API pal::hresult_t STDMETHODCALLTYPE coreclr_initialize( *hostHandle = reinterpret_cast(static_cast(0xdeadbeef)); } + if (host_contract != nullptr) + { + // construct the formerly known as TPA list + char** assemblies; + uint32_t assemblyCount = 0; + std::stringstream asmss; + + assemblies = host_contract->get_assemblies(&assemblyCount, host_contract->context); + + for (int ac = 0; ac < assemblyCount; ac++) + { + const char* assemblyPath = host_contract->resolve_assembly_to_path(assemblies[ac], host_contract->context); + asmss << assemblyPath << ":"; + } + + delete assemblies; + + // for now - name this something else when tests start passing + MockLogEntry("property", "TRUSTED_PLATFORM_ASSEMBLIES", asmss.str()); + } + return StatusCode::Success; }