diff --git a/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs b/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs index bd25648c5a46b5..9c54eb1b767ee6 100644 --- a/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs +++ b/src/installer/tests/HostActivation.Tests/PortableAppActivation.cs @@ -216,6 +216,33 @@ public void AppHost_FrameworkDependent_GlobalLocation_Succeeds(bool useRegistere } } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void AppHost_DotNetRoot_DevicePath() + { + string appExe = sharedTestState.PortableAppFixture_Published.TestProject.AppExe; + + string dotnetPath = $@"\\?\{sharedTestState.PortableAppFixture_Published.BuiltDotnet.BinPath}"; + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(dotnetPath, sharedTestState.RepoDirectories.BuildArchitecture) + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello World") + .And.HaveStdOutContaining(sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion); + + dotnetPath = $@"\\.\{sharedTestState.PortableAppFixture_Published.BuiltDotnet.BinPath}"; + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(dotnetPath, sharedTestState.RepoDirectories.BuildArchitecture) + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello World") + .And.HaveStdOutContaining(sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion); + } + [Fact] public void RuntimeConfig_FilePath_Breaks_MAX_PATH_Threshold() { diff --git a/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs b/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs index b5ad105a353051..04fbba687c4633 100644 --- a/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs +++ b/src/installer/tests/HostActivation.Tests/StandaloneAppActivation.cs @@ -254,6 +254,29 @@ public void Running_Publish_Output_Standalone_EXE_with_Bound_AppHost_Succeeds() } } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void DevicePath() + { + string appExe = $@"\\?\{sharedTestState.StandaloneAppFixture_Published.TestProject.AppExe}"; + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello World") + .And.HaveStdOutContaining(sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion); + + appExe = $@"\\.\{sharedTestState.StandaloneAppFixture_Published.TestProject.AppExe}"; + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello World") + .And.HaveStdOutContaining(sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion); + } + [Fact] [PlatformSpecific(TestPlatforms.Windows)] // GUI app host is only supported on Windows. public void Running_AppHost_with_GUI_No_Console() diff --git a/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp b/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp index d06be0719551f5..5befe7ee7d37a1 100644 --- a/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp +++ b/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp @@ -38,7 +38,7 @@ hostfxr_resolver_t::hostfxr_resolver_t(const pal::string_t& app_root) { m_status_code = StatusCode::CoreHostLibMissingFailure; } - else if (!pal::is_path_rooted(m_fxr_path)) + else if (!pal::is_path_fully_qualified(m_fxr_path)) { // We should always be loading hostfxr from an absolute path m_status_code = StatusCode::CoreHostLibMissingFailure; diff --git a/src/native/corehost/corehost.cpp b/src/native/corehost/corehost.cpp index 5edc2fbf5d5219..2abc1bf0fa46b3 100644 --- a/src/native/corehost/corehost.cpp +++ b/src/native/corehost/corehost.cpp @@ -187,6 +187,7 @@ int exe_start(const int argc, const pal::char_t* argv[]) int rc = fxr.status_code(); if (rc != StatusCode::Success) { + trace::error(_X("Failed to resolve %s [%s]. Error code: 0x%x"), LIBFXR_NAME, fxr.fxr_path().empty() ? _X("not found") : fxr.fxr_path().c_str(), rc); return rc; } diff --git a/src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp b/src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp index bb0da3238f42b6..4ec39be9b1676e 100644 --- a/src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp +++ b/src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp @@ -181,7 +181,7 @@ int hostpolicy_resolver::load( } // We should always be loading hostpolicy from an absolute path - if (!pal::is_path_rooted(host_path)) + if (!pal::is_path_fully_qualified(host_path)) return StatusCode::CoreHostLibMissingFailure; // Load library diff --git a/src/native/corehost/fxr_resolver.h b/src/native/corehost/fxr_resolver.h index ecbf37c12b1ffc..9061939728d510 100644 --- a/src/native/corehost/fxr_resolver.h +++ b/src/native/corehost/fxr_resolver.h @@ -45,7 +45,7 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb } // We should always be loading hostfxr from an absolute path - if (!pal::is_path_rooted(fxr_path)) + if (!pal::is_path_fully_qualified(fxr_path)) return StatusCode::CoreHostLibMissingFailure; // Load library diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index aa050d1aeb8526..cb01462d090e29 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -328,6 +328,7 @@ namespace pal bool get_default_breadcrumb_store(string_t* recv); bool is_path_rooted(const string_t& path); + bool is_path_fully_qualified(const string_t& path); // Returns a platform-specific, user-private directory // that can be used for extracting out components of a single-file app. diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index 34520aefd7365a..7fe2e83327b1b1 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -192,7 +192,7 @@ bool pal::get_loaded_library( { pal::string_t library_name_local; #if defined(TARGET_OSX) - if (!pal::is_path_rooted(library_name)) + if (!pal::is_path_fully_qualified(library_name)) library_name_local.append("@rpath/"); #endif library_name_local.append(library_name); @@ -200,7 +200,7 @@ bool pal::get_loaded_library( dll_t dll_maybe = dlopen(library_name_local.c_str(), RTLD_LAZY | RTLD_NOLOAD); if (dll_maybe == nullptr) { - if (pal::is_path_rooted(library_name)) + if (pal::is_path_fully_qualified(library_name)) return false; // dlopen on some systems only finds loaded libraries when given the full path @@ -265,6 +265,11 @@ bool pal::is_path_rooted(const pal::string_t& path) return path.front() == '/'; } +bool pal::is_path_fully_qualified(const pal::string_t& path) +{ + return is_path_rooted(path); +} + bool pal::get_default_breadcrumb_store(string_t* recv) { recv->clear(); diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index ca0608cd56935e..339c7795a7fb02 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -585,9 +585,31 @@ pal::string_t pal::get_current_os_rid_platform() return ridOS; } +namespace +{ + bool is_directory_separator(pal::char_t c) + { + return c == DIR_SEPARATOR || c == L'/'; + } +} + bool pal::is_path_rooted(const string_t& path) { - return path.length() >= 2 && path[1] == L':'; + return (path.length() >= 1 && is_directory_separator(path[0])) // UNC or device paths + || (path.length() >= 2 && path[1] == L':'); // Drive letter paths +} + +bool pal::is_path_fully_qualified(const string_t& path) +{ + if (path.length() < 2) + return false; + + // Check for UNC and DOS device paths + if (is_directory_separator(path[0])) + return path[1] == L'?' || is_directory_separator(path[1]); + + // Check for drive absolute path - for example C:\. + return path.length() >= 3 && path[1] == L':' && is_directory_separator(path[2]); } // Returns true only if an env variable can be read successfully to be non-empty. diff --git a/src/native/corehost/hostpolicy/deps_resolver.cpp b/src/native/corehost/hostpolicy/deps_resolver.cpp index a00f4dabc71545..440e7cd98c59ff 100644 --- a/src/native/corehost/hostpolicy/deps_resolver.cpp +++ b/src/native/corehost/hostpolicy/deps_resolver.cpp @@ -601,7 +601,7 @@ void deps_resolver_t::init_known_entry_path(const deps_entry_t& entry, const pal return; } - assert(pal::is_path_rooted(path)); + assert(pal::is_path_fully_qualified(path)); if (m_coreclr_path.empty() && ends_with(path, DIR_SEPARATOR + pal::string_t(LIBCORECLR_NAME), false)) { m_coreclr_path = path; diff --git a/src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp b/src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp index 8df8e395e3f259..adcebeb9a029c5 100644 --- a/src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp +++ b/src/native/corehost/hostpolicy/standalone/coreclr_resolver.cpp @@ -14,7 +14,7 @@ bool coreclr_resolver_t::resolve_coreclr(const pal::string_t& libcoreclr_path, c append_path(&coreclr_dll_path, LIBCORECLR_NAME); // We should always be loading coreclr from an absolute path - if (!pal::is_path_rooted(coreclr_dll_path)) + if (!pal::is_path_fully_qualified(coreclr_dll_path)) return false; if (!pal::load_library(&coreclr_dll_path, &coreclr_resolver_contract.coreclr))