diff --git a/src/installer/tests/HostActivation.Tests/FrameworkDependentAppLaunch.cs b/src/installer/tests/HostActivation.Tests/FrameworkDependentAppLaunch.cs index 2f486fda3bd327..57e7172faf0e43 100644 --- a/src/installer/tests/HostActivation.Tests/FrameworkDependentAppLaunch.cs +++ b/src/installer/tests/HostActivation.Tests/FrameworkDependentAppLaunch.cs @@ -191,6 +191,33 @@ public void AppHost_DisableCetCompat() .And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion); } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void AppHost_DotNetRoot_DevicePath() + { + string appExe = sharedTestState.App.AppExe; + + string dotnetPath = $@"\\?\{TestContext.BuiltDotNet.BinPath}"; + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(dotnetPath, TestContext.BuildArchitecture) + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello World") + .And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion); + + dotnetPath = $@"\\.\{TestContext.BuiltDotNet.BinPath}"; + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(dotnetPath, TestContext.BuildArchitecture) + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello World") + .And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion); + } + [Fact] public void RuntimeConfig_FilePath_Breaks_MAX_PATH_Threshold() { diff --git a/src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs b/src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs index 0ebb0912af4f3f..fe18bf10fdd73c 100644 --- a/src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs +++ b/src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs @@ -156,6 +156,29 @@ public void DotNetRoot_IncorrectLayout_Fails() .And.HaveStdErrContaining($"The required library {Binaries.HostFxr.FileName} could not be found."); } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void DevicePath() + { + string appExe = $@"\\?\{sharedTestState.App.AppExe}"; + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello World") + .And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion); + + appExe = $@"\\.\{sharedTestState.App.AppExe}"; + Command.Create(appExe) + .CaptureStdErr() + .CaptureStdOut() + .Execute() + .Should().Pass() + .And.HaveStdOutContaining("Hello World") + .And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion); + } + public class SharedTestState : IDisposable { public TestApp App { get; } diff --git a/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp b/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp index c8e5000805289a..84c726ddbf75c0 100644 --- a/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp +++ b/src/native/corehost/apphost/standalone/hostfxr_resolver.cpp @@ -118,7 +118,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 c233a52237a9fb..c359c30fd7dd89 100644 --- a/src/native/corehost/corehost.cpp +++ b/src/native/corehost/corehost.cpp @@ -200,6 +200,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 6092fea3cd8d9f..ff68220193d47b 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 88fc9f8781efdd..4764783b2afc7f 100644 --- a/src/native/corehost/fxr_resolver.h +++ b/src/native/corehost/fxr_resolver.h @@ -56,7 +56,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 bde8446cc22cdf..94166e65d6f63a 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -335,6 +335,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 613902b5eaf3ac..265ad809d9af19 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 3479c72aced1de..dacafb182899ec 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -641,9 +641,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 0e87b791cfbfc4..88cb29ce6bffdb 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() && utils::ends_with(path, DIR_SEPARATOR_STR 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))