diff --git a/src/installer/tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs b/src/installer/tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs index d3b2f3a876186b..b04653ddfd21bf 100644 --- a/src/installer/tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs +++ b/src/installer/tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs @@ -31,7 +31,7 @@ private void AbsolutePath() var bundleDir = Directory.GetParent(bundledApp.Path); bundleDir.Should().OnlyHaveFiles(new[] { - Binaries.GetExeFileNameForCurrentPlatform(app.Name), + Binaries.GetExeName(app.Name), $"{app.Name}.pdb" }); diff --git a/src/installer/tests/HostActivation.Tests/InvalidHost.cs b/src/installer/tests/HostActivation.Tests/InvalidHost.cs index 67f82ac1e8bcef..46d5125d9944af 100644 --- a/src/installer/tests/HostActivation.Tests/InvalidHost.cs +++ b/src/installer/tests/HostActivation.Tests/InvalidHost.cs @@ -90,16 +90,16 @@ public SharedTestState() BaseDirectory = TestArtifact.Create(nameof(InvalidHost)); Directory.CreateDirectory(BaseDirectory.Location); - RenamedDotNet = Path.Combine(BaseDirectory.Location, Binaries.GetExeFileNameForCurrentPlatform("renamed")); + RenamedDotNet = Path.Combine(BaseDirectory.Location, Binaries.GetExeName("renamed")); File.Copy(Binaries.DotNet.FilePath, RenamedDotNet); - UnboundAppHost = Path.Combine(BaseDirectory.Location, Binaries.GetExeFileNameForCurrentPlatform("unbound")); + UnboundAppHost = Path.Combine(BaseDirectory.Location, Binaries.GetExeName("unbound")); File.Copy(Binaries.AppHost.FilePath, UnboundAppHost); if (OperatingSystem.IsWindows()) { // Mark the apphost as GUI, but don't bind it to anything - this will cause it to fail - UnboundAppHostGUI = Path.Combine(BaseDirectory.Location, Binaries.GetExeFileNameForCurrentPlatform("unboundgui")); + UnboundAppHostGUI = Path.Combine(BaseDirectory.Location, Binaries.GetExeName("unboundgui")); File.Copy(Binaries.AppHost.FilePath, UnboundAppHostGUI); PEUtils.SetWindowsGraphicalUserInterfaceBit(UnboundAppHostGUI); } diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs index 8ad38fbdafb1e5..e6c70d5bbff0cf 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs @@ -116,7 +116,7 @@ public SharedTestState() } } - string comsxsName = Binaries.GetExeFileNameForCurrentPlatform("comsxs"); + string comsxsName = Binaries.GetExeName("comsxs"); ComSxsPath = Path.Combine(comsxsDirectory, comsxsName); File.Copy( Path.Combine(RepoDirectoriesProvider.Default.HostTestArtifacts, comsxsName), diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs index ae855079be71a8..196b114e81d464 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs @@ -21,7 +21,7 @@ public SharedTestStateBase() _baseDirArtifact = TestArtifact.Create("nativeHosting"); BaseDirectory = _baseDirArtifact.Location; - string nativeHostName = Binaries.GetExeFileNameForCurrentPlatform("nativehost"); + string nativeHostName = Binaries.GetExeName("nativehost"); NativeHostPath = Path.Combine(BaseDirectory, nativeHostName); // Copy over native host diff --git a/src/installer/tests/HostActivation.Tests/NativeUnitTests.cs b/src/installer/tests/HostActivation.Tests/NativeUnitTests.cs index 74c3f7a1e4c582..4b73a613a2e60e 100644 --- a/src/installer/tests/HostActivation.Tests/NativeUnitTests.cs +++ b/src/installer/tests/HostActivation.Tests/NativeUnitTests.cs @@ -15,7 +15,7 @@ public class NativeUnitTests [Fact] public void Native_Test_Fx_Ver() { - string testPath = Path.Combine(RepoDirectoriesProvider.Default.HostTestArtifacts, Binaries.GetExeFileNameForCurrentPlatform("test_fx_ver")); + string testPath = Path.Combine(RepoDirectoriesProvider.Default.HostTestArtifacts, Binaries.GetExeName("test_fx_ver")); Command testCommand = Command.Create(testPath); testCommand diff --git a/src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs b/src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs index b3e311a6cdd65d..0ebb0912af4f3f 100644 --- a/src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs +++ b/src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs @@ -95,7 +95,7 @@ public void RenameApphost() { var app = sharedTestState.App.Copy(); - var renamedAppExe = app.AppExe + Binaries.GetExeFileNameForCurrentPlatform("renamed"); + var renamedAppExe = app.AppExe + Binaries.GetExeName("renamed"); File.Move(app.AppExe, renamedAppExe, true); Command.Create(renamedAppExe) diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index 45ad4f9961af07..d440d3eae2caa2 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -22,11 +22,11 @@ public SymbolicLinks(SymbolicLinks.SharedTestState fixture) } [Theory] - [SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")] [InlineData ("a/b/SymlinkToApphost")] [InlineData ("a/SymlinkToApphost")] public void Run_apphost_behind_symlink(string symlinkRelativePath) { + symlinkRelativePath = Binaries.GetExeName(symlinkRelativePath); using (var testDir = TestArtifact.Create("symlink")) { Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath))); @@ -43,13 +43,14 @@ public void Run_apphost_behind_symlink(string symlinkRelativePath) } [Theory] - [SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")] [InlineData ("a/b/FirstSymlink", "c/d/SecondSymlink")] [InlineData ("a/b/FirstSymlink", "c/SecondSymlink")] [InlineData ("a/FirstSymlink", "c/d/SecondSymlink")] [InlineData ("a/FirstSymlink", "c/SecondSymlink")] public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePath, string secondSymlinkRelativePath) { + firstSymlinkRelativePath = Binaries.GetExeName(firstSymlinkRelativePath); + secondSymlinkRelativePath = Binaries.GetExeName(secondSymlinkRelativePath); using (var testDir = TestArtifact.Create("symlink")) { // second symlink -> apphost @@ -71,15 +72,14 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa } } - //[Theory] - //[InlineData("a/b/SymlinkToFrameworkDependentApp")] - //[InlineData("a/SymlinkToFrameworkDependentApp")] - [Fact(Skip = "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " + + [Theory] + [InlineData("a/b/SymlinkToFrameworkDependentApp")] + [InlineData("a/SymlinkToFrameworkDependentApp")] + [SkipOnPlatform(TestPlatforms.OSX, "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " + "CI failing to use stat on symbolic links on Linux (permission denied).")] - [SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")] - public void Run_framework_dependent_app_behind_symlink(/*string symlinkRelativePath*/) + public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePath) { - var symlinkRelativePath = string.Empty; + symlinkRelativePath = Binaries.GetExeName(symlinkRelativePath); using (var testDir = TestArtifact.Create("symlink")) { @@ -96,14 +96,14 @@ public void Run_framework_dependent_app_behind_symlink(/*string symlinkRelativeP } } - [Fact(Skip = "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " + - "CI failing to use stat on symbolic links on Linux (permission denied).")] - [SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")] + [Fact] + [SkipOnPlatform(TestPlatforms.OSX, "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " + + "CI failing to use stat on symbolic links on Linux (permission denied).")] public void Run_framework_dependent_app_with_runtime_behind_symlink() { using (var testDir = TestArtifact.Create("symlink")) { - var dotnetSymlink = Path.Combine(testDir.Location, "dotnet"); + var dotnetSymlink = Path.Combine(testDir.Location, Binaries.GetExeName("dotnet")); using var symlink = new SymLink(dotnetSymlink, TestContext.BuiltDotNet.BinPath); Command.Create(sharedTestState.FrameworkDependentApp.AppExe) @@ -117,7 +117,6 @@ public void Run_framework_dependent_app_with_runtime_behind_symlink() } [Fact] - [SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")] public void Put_app_directory_behind_symlink() { var app = sharedTestState.SelfContainedApp.Copy(); @@ -138,7 +137,6 @@ public void Put_app_directory_behind_symlink() } [Fact] - [SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")] public void Put_dotnet_behind_symlink() { using (var testDir = TestArtifact.Create("symlink")) @@ -156,7 +154,6 @@ public void Put_dotnet_behind_symlink() } [Fact] - [SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")] public void Put_app_directory_behind_symlink_and_use_dotnet() { var app = sharedTestState.SelfContainedApp.Copy(); @@ -177,7 +174,6 @@ public void Put_app_directory_behind_symlink_and_use_dotnet() } [Fact] - [SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")] public void Put_satellite_assembly_behind_symlink() { var app = sharedTestState.LocalizedApp.Copy(); diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs index b7a2d6ab08cfcc..0087effaa8cb23 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs @@ -23,7 +23,7 @@ public BundlerConsistencyTests(SharedTestState fixture) sharedTestState = fixture; } - private static string BundlerHostName = Binaries.GetExeFileNameForCurrentPlatform(SharedTestState.AppName); + private static string BundlerHostName = Binaries.GetExeName(SharedTestState.AppName); private Bundler CreateBundlerInstance(BundleOptions bundleOptions = BundleOptions.None, Version version = null, bool macosCodesign = true) => new Bundler(BundlerHostName, sharedTestState.App.GetUniqueSubdirectory("bundle"), bundleOptions, targetFrameworkVersion: version, macosCodesign: macosCodesign); diff --git a/src/installer/tests/TestUtils/Binaries.cs b/src/installer/tests/TestUtils/Binaries.cs index d722c5aafcdc11..70837a4557b4fc 100644 --- a/src/installer/tests/TestUtils/Binaries.cs +++ b/src/installer/tests/TestUtils/Binaries.cs @@ -15,7 +15,7 @@ namespace Microsoft.DotNet.CoreSetup.Test { public static class Binaries { - public static string GetExeFileNameForCurrentPlatform(string exeName) => + public static string GetExeName(string exeName) => exeName + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty); public static (string, string) GetSharedLibraryPrefixSuffix() @@ -37,7 +37,7 @@ public static string GetSharedLibraryFileNameForCurrentPlatform(string libraryNa public static class AppHost { - public static string FileName = GetExeFileNameForCurrentPlatform("apphost"); + public static string FileName = GetExeName("apphost"); public static string FilePath = Path.Combine(RepoDirectoriesProvider.Default.HostArtifacts, FileName); } @@ -52,7 +52,7 @@ public static class CoreClr public static class DotNet { - public static string FileName = GetExeFileNameForCurrentPlatform("dotnet"); + public static string FileName = GetExeName("dotnet"); public static string FilePath = Path.Combine(RepoDirectoriesProvider.Default.HostArtifacts, FileName); } @@ -84,7 +84,7 @@ public static class NetHost public static class SingleFileHost { - public static string FileName = GetExeFileNameForCurrentPlatform("singlefilehost"); + public static string FileName = GetExeName("singlefilehost"); public static string FilePath = Path.Combine(RepoDirectoriesProvider.Default.HostArtifacts, FileName); } diff --git a/src/installer/tests/TestUtils/SingleFileTestApp.cs b/src/installer/tests/TestUtils/SingleFileTestApp.cs index 756db09b6d942b..f90af69eaba115 100644 --- a/src/installer/tests/TestUtils/SingleFileTestApp.cs +++ b/src/installer/tests/TestUtils/SingleFileTestApp.cs @@ -92,7 +92,7 @@ public string Bundle(BundleOptions options, out Manifest manifest, Version? bund { string bundleDirectory = GetUniqueSubdirectory("bundle"); var bundler = new Bundler( - Binaries.GetExeFileNameForCurrentPlatform(AppName), + Binaries.GetExeName(AppName), bundleDirectory, options, targetFrameworkVersion: bundleVersion, diff --git a/src/installer/tests/TestUtils/SymbolicLinking.cs b/src/installer/tests/TestUtils/SymLink.cs similarity index 96% rename from src/installer/tests/TestUtils/SymbolicLinking.cs rename to src/installer/tests/TestUtils/SymLink.cs index c41146a5ff6504..e96e0f6b0fb994 100644 --- a/src/installer/tests/TestUtils/SymbolicLinking.cs +++ b/src/installer/tests/TestUtils/SymLink.cs @@ -35,7 +35,7 @@ private static bool MakeSymbolicLink(string symbolicLinkName, string targetFileN errorMessage = string.Empty; if (OperatingSystem.IsWindows()) { - if (!CreateSymbolicLink(symbolicLinkName, targetFileName, SymbolicLinkFlag.IsFile)) + if (!CreateSymbolicLink(symbolicLinkName, targetFileName, SymbolicLinkFlag.IsFile | SymbolicLinkFlag.AllowUnprivilegedCreate)) { int errno = Marshal.GetLastWin32Error(); errorMessage = $"CreateSymbolicLink failed with error number {errno}"; diff --git a/src/installer/tests/TestUtils/TestApp.cs b/src/installer/tests/TestUtils/TestApp.cs index 42b6d49a05417c..a98d0a77ec5f24 100644 --- a/src/installer/tests/TestUtils/TestApp.cs +++ b/src/installer/tests/TestUtils/TestApp.cs @@ -185,7 +185,7 @@ private void LoadAssets() { Directory.CreateDirectory(Location); AppDll = Path.Combine(Location, $"{AssemblyName}.dll"); - AppExe = Path.Combine(Location, Binaries.GetExeFileNameForCurrentPlatform(AssemblyName)); + AppExe = Path.Combine(Location, Binaries.GetExeName(AssemblyName)); DepsJson = Path.Combine(Location, $"{AssemblyName}.deps.json"); RuntimeConfigJson = Path.Combine(Location, $"{AssemblyName}.runtimeconfig.json"); RuntimeDevConfigJson = Path.Combine(Location, $"{AssemblyName}.runtimeconfig.dev.json"); diff --git a/src/native/corehost/corehost.cpp b/src/native/corehost/corehost.cpp index 481fe5987e3262..c8a94312c5d6d4 100644 --- a/src/native/corehost/corehost.cpp +++ b/src/native/corehost/corehost.cpp @@ -112,6 +112,8 @@ int exe_start(const int argc, const pal::char_t* argv[]) initialize_static_createdump(); #endif + // Use realpath to find the path of the host, resolving any symlinks. + // hostfxr (for dotnet) and the app dll (for apphost) are found relative to the host. pal::string_t host_path; if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path)) { @@ -148,7 +150,7 @@ int exe_start(const int argc, const pal::char_t* argv[]) { trace::info(_X("Detected Single-File app bundle")); } - else if (!pal::realpath(&app_path)) + else if (!pal::fullpath(&app_path)) { trace::error(_X("The application to execute does not exist: '%s'."), app_path.c_str()); return StatusCode::LibHostAppRootFindFailure; diff --git a/src/native/corehost/fxr/command_line.cpp b/src/native/corehost/fxr/command_line.cpp index 19d204f348898e..6b589292a7ec1d 100644 --- a/src/native/corehost/fxr/command_line.cpp +++ b/src/native/corehost/fxr/command_line.cpp @@ -145,7 +145,7 @@ namespace if (mode == host_mode_t::apphost) { app_candidate = host_info.app_path; - doesAppExist = bundle::info_t::is_single_file_bundle() || pal::realpath(&app_candidate); + doesAppExist = bundle::info_t::is_single_file_bundle() || pal::fullpath(&app_candidate); } else { @@ -169,7 +169,7 @@ namespace } } - doesAppExist = pal::realpath(&app_candidate); + doesAppExist = pal::fullpath(&app_candidate); if (!doesAppExist) { trace::verbose(_X("Application '%s' does not exist."), app_candidate.c_str()); diff --git a/src/native/corehost/fxr/fx_muxer.cpp b/src/native/corehost/fxr/fx_muxer.cpp index 02c80c590948a8..d4723ad5ed5f89 100644 --- a/src/native/corehost/fxr/fx_muxer.cpp +++ b/src/native/corehost/fxr/fx_muxer.cpp @@ -221,14 +221,14 @@ void get_runtime_config_paths_from_app(const pal::string_t& app, pal::string_t* get_runtime_config_paths(path, name, cfg, dev_cfg); } -// Convert "path" to realpath (merging working dir if needed) and append to "realpaths" out param. -void append_probe_realpath(const pal::string_t& path, std::vector* realpaths, const pal::string_t& tfm) +// Convert "path" to fullpath (merging working dir if needed) and append to "fullpaths" out param. +void append_probe_fullpath(const pal::string_t& path, std::vector* fullpaths, const pal::string_t& tfm) { pal::string_t probe_path = path; - if (pal::realpath(&probe_path, true)) + if (pal::fullpath(&probe_path, true)) { - realpaths->push_back(probe_path); + fullpaths->push_back(probe_path); } else { @@ -249,9 +249,9 @@ void append_probe_realpath(const pal::string_t& path, std::vector segment.append(tfm); probe_path.replace(pos_placeholder, placeholder.length(), segment); - if (pal::realpath(&probe_path, true)) + if (pal::fullpath(&probe_path, true)) { - realpaths->push_back(probe_path); + fullpaths->push_back(probe_path); } else { @@ -274,7 +274,7 @@ namespace const runtime_config_t::settings_t& override_settings) { // Check for the runtimeconfig.json file specified at the command line - if (!runtime_config.empty() && !pal::realpath(&runtime_config)) + if (!runtime_config.empty() && !pal::fullpath(&runtime_config)) { trace::error(_X("The specified runtimeconfig.json [%s] does not exist"), runtime_config.c_str()); return StatusCode::InvalidConfigFile; @@ -337,18 +337,18 @@ namespace return host_mode_t::muxer; } - std::vector get_probe_realpaths( + std::vector get_probe_fullpaths( const fx_definition_vector_t &fx_definitions, const std::vector &specified_probing_paths) { // The tfm is taken from the app. pal::string_t tfm = get_app(fx_definitions).get_runtime_config().get_tfm(); - // Append specified probe paths first and then config file probe paths into realpaths. - std::vector probe_realpaths; + // Append specified probe paths first and then config file probe paths into fullpaths. + std::vector probe_fullpaths; for (const auto& path : specified_probing_paths) { - append_probe_realpath(path, &probe_realpaths, tfm); + append_probe_fullpath(path, &probe_fullpaths, tfm); } // Each framework can add probe paths @@ -356,11 +356,11 @@ namespace { for (const auto& path : fx->get_runtime_config().get_probe_paths()) { - append_probe_realpath(path, &probe_realpaths, tfm); + append_probe_fullpath(path, &probe_fullpaths, tfm); } } - return probe_realpaths; + return probe_fullpaths; } int get_init_info_for_app( @@ -377,7 +377,7 @@ namespace // This check is for --depsfile option, which must be an actual file. pal::string_t deps_file = command_line::get_option_value(opts, known_options::deps_file, _X("")); - if (!deps_file.empty() && !pal::realpath(&deps_file)) + if (!deps_file.empty() && !pal::fullpath(&deps_file)) { trace::error(_X("The specified deps.json [%s] does not exist"), deps_file.c_str()); return StatusCode::InvalidArgFailure; @@ -485,17 +485,17 @@ namespace const known_options opts_probe_path = known_options::additional_probing_path; std::vector spec_probe_paths = opts.count(opts_probe_path) ? opts.find(opts_probe_path)->second : std::vector(); - std::vector probe_realpaths = get_probe_realpaths(fx_definitions, spec_probe_paths); + std::vector probe_fullpaths = get_probe_fullpaths(fx_definitions, spec_probe_paths); trace::verbose(_X("Executing as a %s app as per config file [%s]"), (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), app_config.get_path().c_str()); - if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, probe_realpaths, &hostpolicy_dir)) + if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, probe_fullpaths, &hostpolicy_dir)) { return StatusCode::CoreHostLibMissingFailure; } - init.reset(new corehost_init_t(host_command, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions, additional_properties)); + init.reset(new corehost_init_t(host_command, host_info, deps_file, additional_deps_serialized, probe_fullpaths, mode, fx_definitions, additional_properties)); return StatusCode::Success; } @@ -623,19 +623,19 @@ namespace if (rc != StatusCode::Success) return rc; - const std::vector probe_realpaths = get_probe_realpaths(fx_definitions, std::vector() /* specified_probing_paths */); + const std::vector probe_fullpaths = get_probe_fullpaths(fx_definitions, std::vector() /* specified_probing_paths */); trace::verbose(_X("Libhost loading occurring for a framework-dependent component per config file [%s]"), app_config.get_path().c_str()); const pal::string_t deps_file; - if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_realpaths, &hostpolicy_dir)) + if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_fullpaths, &hostpolicy_dir)) { return StatusCode::CoreHostLibMissingFailure; } const pal::string_t additional_deps_serialized; const std::vector> additional_properties; - init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions, additional_properties)); + init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_fullpaths, mode, fx_definitions, additional_properties)); return StatusCode::Success; } diff --git a/src/native/corehost/fxr/hostfxr.cpp b/src/native/corehost/fxr/hostfxr.cpp index 66b359eb1ddeed..3b09cb4885078c 100644 --- a/src/native/corehost/fxr/hostfxr.cpp +++ b/src/native/corehost/fxr/hostfxr.cpp @@ -547,7 +547,7 @@ namespace if (startup_info.host_path.empty()) { - if (!pal::get_own_executable_path(&startup_info.host_path) || !pal::realpath(&startup_info.host_path)) + if (!pal::get_own_executable_path(&startup_info.host_path) || !pal::fullpath(&startup_info.host_path)) { trace::error(_X("Failed to resolve full path of the current host [%s]"), startup_info.host_path.c_str()); return StatusCode::CoreHostCurHostFindFailure; @@ -561,7 +561,7 @@ namespace return StatusCode::CoreHostCurHostFindFailure; startup_info.dotnet_root = get_dotnet_root_from_fxr_path(mod_path); - if (!pal::realpath(&startup_info.dotnet_root)) + if (!pal::fullpath(&startup_info.dotnet_root)) { trace::error(_X("Failed to resolve full path of dotnet root [%s]"), startup_info.dotnet_root.c_str()); return StatusCode::CoreHostCurHostFindFailure; diff --git a/src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp b/src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp index 1a32e5a6a1b12f..4e75400a04a81b 100644 --- a/src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp +++ b/src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp @@ -110,15 +110,15 @@ namespace * Given a version and probing paths, find if package layout * directory containing hostpolicy exists. */ - bool resolve_hostpolicy_dir_from_probe_paths(const pal::string_t& version, const std::vector& probe_realpaths, pal::string_t* candidate) + bool resolve_hostpolicy_dir_from_probe_paths(const pal::string_t& version, const std::vector& probe_fullpaths, pal::string_t* candidate) { - if (probe_realpaths.empty() || version.empty()) + if (probe_fullpaths.empty() || version.empty()) { return false; } // Check if the package relative directory containing hostpolicy exists. - for (const auto& probe_path : probe_realpaths) + for (const auto& probe_path : probe_fullpaths) { trace::verbose(_X("Considering %s to probe for %s"), probe_path.c_str(), LIBHOSTPOLICY_NAME); if (to_hostpolicy_package_dir(probe_path, version, candidate)) @@ -129,8 +129,8 @@ namespace // Print detailed message about the file not found in the probe paths. trace::error(_X("Could not find required library %s in %d probing paths:"), - LIBHOSTPOLICY_NAME, probe_realpaths.size()); - for (const auto& path : probe_realpaths) + LIBHOSTPOLICY_NAME, probe_fullpaths.size()); + for (const auto& path : probe_fullpaths) { trace::error(_X(" %s"), path.c_str()); } @@ -233,7 +233,7 @@ bool hostpolicy_resolver::try_get_dir( const fx_definition_vector_t& fx_definitions, const pal::string_t& app_candidate, const pal::string_t& specified_deps_file, - const std::vector& probe_realpaths, + const std::vector& probe_fullpaths, pal::string_t* impl_dir) { bool is_framework_dependent = get_app(fx_definitions).get_runtime_config().get_is_framework_dependent(); @@ -299,7 +299,7 @@ bool hostpolicy_resolver::try_get_dir( // Start probing for hostpolicy in the specified probe paths. pal::string_t candidate; - if (resolve_hostpolicy_dir_from_probe_paths(version, probe_realpaths, &candidate)) + if (resolve_hostpolicy_dir_from_probe_paths(version, probe_fullpaths, &candidate)) { impl_dir->assign(candidate); return true; diff --git a/src/native/corehost/fxr_resolver.h b/src/native/corehost/fxr_resolver.h index bbbb6a0ef347db..b61f3b8fb4e6dc 100644 --- a/src/native/corehost/fxr_resolver.h +++ b/src/native/corehost/fxr_resolver.h @@ -34,7 +34,7 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb pal::dll_t fxr; pal::string_t host_path; - if (!pal::get_own_module_path(&host_path) || !pal::realpath(&host_path)) + if (!pal::get_own_module_path(&host_path) || !pal::fullpath(&host_path)) { trace::error(_X("Failed to resolve full path of the current host module [%s]"), host_path.c_str()); return StatusCode::CoreHostCurHostFindFailure; diff --git a/src/native/corehost/host_startup_info.cpp b/src/native/corehost/host_startup_info.cpp index 20bee5859ad794..d2fdcbe3585501 100644 --- a/src/native/corehost/host_startup_info.cpp +++ b/src/native/corehost/host_startup_info.cpp @@ -15,15 +15,15 @@ host_startup_info_t::host_startup_info_t( , dotnet_root(dotnet_root_value) , app_path(app_path_value) {} -// Determine if string is a valid path, and if so then fix up by using realpath() +// Determine if string is a valid path, and if so then fix up by using fullpath() bool get_path_from_argv(pal::string_t *path) { - // Assume all paths will have at least one separator. We want to detect path vs. file before calling realpath - // because realpath will expand a filename into a full path containing the current directory which may be + // Assume all paths will have at least one separator. We want to detect path vs. file before calling fullpath + // because fullpath will expand a filename into a full path containing the current directory which may be // the wrong location when filename ends up being found in %PATH% and not the current directory. if (path->find(DIR_SEPARATOR) != pal::string_t::npos) { - return pal::realpath(path); + return pal::fullpath(path); } return false; @@ -86,7 +86,7 @@ const pal::string_t host_startup_info_t::get_app_name() const } // If argv[0] did not work, get the executable name - if (host_path->empty() && (!pal::get_own_executable_path(host_path) || !pal::realpath(host_path))) + if (host_path->empty() && (!pal::get_own_executable_path(host_path) || !pal::fullpath(host_path))) { trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path->c_str()); return StatusCode::LibHostCurExeFindFailure; diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 4a0d38c456a843..bde8446cc22cdf 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -281,7 +281,10 @@ namespace pal void* mmap_copy_on_write(const string_t& path, size_t* length = nullptr); bool touch_file(const string_t& path); + // Realpath resolves a fully-qualified path to the target. It always resolves through file symlinks (not necessarily directory symlinks). bool realpath(string_t* path, bool skip_error_logging = false); + // Fullpath resolves a fully-qualified path to the target. It may resolve through symlinks, depending on platform. + bool fullpath(string_t* path, bool skip_error_logging = false); bool file_exists(const string_t& path); inline bool directory_exists(const string_t& path) { return file_exists(path); } void readdir(const string_t& path, const string_t& pattern, std::vector* list); diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index b690b1f100ae84..613902b5eaf3ac 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -269,7 +269,7 @@ bool pal::get_default_breadcrumb_store(string_t* recv) { recv->clear(); pal::string_t ext; - if (pal::getenv(_X("CORE_BREADCRUMBS"), &ext) && pal::realpath(&ext)) + if (pal::getenv(_X("CORE_BREADCRUMBS"), &ext) && pal::fullpath(&ext)) { // We should have the path in ext. trace::info(_X("Realpath CORE_BREADCRUMBS [%s]"), ext.c_str()); @@ -301,7 +301,7 @@ bool pal::get_default_servicing_directory(string_t* recv) { recv->clear(); pal::string_t ext; - if (pal::getenv(_X("CORE_SERVICING"), &ext) && pal::realpath(&ext)) + if (pal::getenv(_X("CORE_SERVICING"), &ext) && pal::fullpath(&ext)) { // We should have the path in ext. trace::info(_X("Realpath CORE_SERVICING [%s]"), ext.c_str()); @@ -332,7 +332,7 @@ bool pal::get_default_servicing_directory(string_t* recv) bool is_read_write_able_directory(pal::string_t& dir) { - return pal::realpath(&dir) && + return pal::fullpath(&dir) && (access(dir.c_str(), R_OK | W_OK | X_OK) == 0); } @@ -960,6 +960,11 @@ bool pal::getenv(const pal::char_t* name, pal::string_t* recv) return (recv->length() > 0); } +bool pal::fullpath(pal::string_t* path, bool skip_error_logging) +{ + return realpath(path, skip_error_logging); +} + bool pal::realpath(pal::string_t* path, bool skip_error_logging) { auto resolved = ::realpath(path->c_str(), nullptr); diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index 073c6399d51c28..3a3db968b5bde4 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -261,7 +261,7 @@ bool pal::load_library(const string_t* in_path, dll_t* dll) if (LongFile::IsPathNotFullyQualified(path)) { - if (!pal::realpath(&path)) + if (!pal::fullpath(&path)) { trace::error(_X("Failed to load the dll from [%s], HRESULT: 0x%X"), path.c_str(), HRESULT_FROM_WIN32(GetLastError())); return false; @@ -736,7 +736,7 @@ bool get_extraction_base_parent_directory(pal::string_t& directory) assert(len < max_len); directory.assign(temp_path); - return pal::realpath(&directory); + return pal::fullpath(&directory); } bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir) @@ -750,7 +750,7 @@ bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir) append_path(&extraction_dir, _X(".net")); // Windows Temp-Path is already user-private. - if (realpath(&extraction_dir)) + if (fullpath(&extraction_dir)) { return true; } @@ -763,7 +763,7 @@ bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir) return false; } - return realpath(&extraction_dir); + return fullpath(&extraction_dir); } static bool wchar_convert_helper(DWORD code_page, const char* cstr, size_t len, pal::string_t* out) @@ -815,8 +815,92 @@ bool pal::clr_palstring(const char* cstr, pal::string_t* out) return wchar_convert_helper(CP_UTF8, cstr, ::strlen(cstr), out); } -// Return if path is valid and file exists, return true and adjust path as appropriate. -bool pal::realpath(string_t* path, bool skip_error_logging) +typedef std::unique_ptr::type, decltype(&::CloseHandle)> SmartHandle; + +// Like fullpath, but resolves file symlinks (note: not necessarily directory symlinks). +bool pal::realpath(pal::string_t* path, bool skip_error_logging) +{ + if (path->empty()) + { + return false; + } + + // Use CreateFileW + GetFinalPathNameByHandleW to resolve symlinks + // https://learn.microsoft.com/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted + SmartHandle file( + ::CreateFileW( + path->c_str(), + 0, // Querying only + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, // default security + OPEN_EXISTING, // existing file + FILE_ATTRIBUTE_NORMAL, // normal file + nullptr), // No attribute template + &::CloseHandle); + + pal::char_t buf[MAX_PATH]; + size_t size; + + if (file.get() == INVALID_HANDLE_VALUE) + { + // If we get "access denied" that may mean the path represents a directory. + // Even if not, we can fall back to GetFullPathNameW, which doesn't require a HANDLE + + auto error = ::GetLastError(); + file.release(); + if (ERROR_ACCESS_DENIED != error) + { + if (!skip_error_logging) + { + trace::error(_X("Error resolving full path [%s]. Error code: %d"), path->c_str(), error); + } + return false; + } + } + else + { + size = ::GetFinalPathNameByHandleW(file.get(), buf, MAX_PATH, FILE_NAME_NORMALIZED); + // If size is 0, this call failed. Fall back to GetFullPathNameW, below + if (size != 0) + { + pal::string_t str; + if (size < MAX_PATH) + { + str.assign(buf); + } + else + { + str.resize(size, 0); + size = ::GetFinalPathNameByHandleW(file.get(), (LPWSTR)str.data(), static_cast(size), FILE_NAME_NORMALIZED); + assert(size <= str.size()); + + if (size == 0) + { + if (!skip_error_logging) + { + trace::error(_X("Error resolving full path [%s]. Error code: %d"), path->c_str(), ::GetLastError()); + } + return false; + } + } + + // Remove the \\?\ prefix, unless it is necessary or was already there + if (LongFile::IsExtended(str) && !LongFile::IsExtended(*path) && + !LongFile::ShouldNormalize(str.substr(LongFile::ExtendedPrefix.size()))) + { + str.erase(0, LongFile::ExtendedPrefix.size()); + } + + *path = str; + return true; + } + } + + // If the above fails, fall back to fullpath + return pal::fullpath(path, skip_error_logging); +} + +bool pal::fullpath(string_t* path, bool skip_error_logging) { if (path->empty()) { @@ -891,7 +975,7 @@ bool pal::realpath(string_t* path, bool skip_error_logging) bool pal::file_exists(const string_t& path) { string_t tmp(path); - return pal::realpath(&tmp, true); + return pal::fullpath(&tmp, true); } static void readdir(const pal::string_t& path, const pal::string_t& pattern, bool onlydirectories, std::vector* list) @@ -903,7 +987,7 @@ static void readdir(const pal::string_t& path, const pal::string_t& pattern, boo if (LongFile::ShouldNormalize(normalized_path)) { - if (!pal::realpath(&normalized_path)) + if (!pal::fullpath(&normalized_path)) { return; } diff --git a/src/native/corehost/hostmisc/utils.cpp b/src/native/corehost/hostmisc/utils.cpp index 521574fe2a5fd4..7326f3b8b8ae17 100644 --- a/src/native/corehost/hostmisc/utils.cpp +++ b/src/native/corehost/hostmisc/utils.cpp @@ -320,7 +320,7 @@ bool get_file_path_from_env(const pal::char_t* env_key, pal::string_t* recv) pal::string_t file_path; if (pal::getenv(env_key, &file_path)) { - if (pal::realpath(&file_path)) + if (pal::fullpath(&file_path)) { recv->assign(file_path); return true; diff --git a/src/native/corehost/hostpolicy/args.cpp b/src/native/corehost/hostpolicy/args.cpp index a5182719ce0eaa..4c64ebde2a1d37 100644 --- a/src/native/corehost/hostpolicy/args.cpp +++ b/src/native/corehost/hostpolicy/args.cpp @@ -98,10 +98,10 @@ bool set_root_from_app(const pal::string_t& managed_application_path, // for very unlikely case where the main app.dll was itself excluded from the app bundle. // Note that unlike non-single-file we don't want to set the app_root to the location of the app.dll // it needs to stay the location of the single-file bundle. - return pal::realpath(&args.managed_application); + return pal::fullpath(&args.managed_application); } - if (pal::realpath(&args.managed_application)) + if (pal::fullpath(&args.managed_application)) { args.app_root = get_directory(args.managed_application); return true; diff --git a/src/native/corehost/hostpolicy/deps_format.cpp b/src/native/corehost/hostpolicy/deps_format.cpp index 89f0ded1599269..7a58f12bb067f2 100644 --- a/src/native/corehost/hostpolicy/deps_format.cpp +++ b/src/native/corehost/hostpolicy/deps_format.cpp @@ -76,7 +76,7 @@ namespace bool deps_file_exists(pal::string_t& deps_path) { - if (bundle::info_t::config_t::probe(deps_path) || pal::realpath(&deps_path, /*skip_error_logging*/ true)) + if (bundle::info_t::config_t::probe(deps_path) || pal::fullpath(&deps_path, /*skip_error_logging*/ true)) return true; trace::verbose(_X("Dependencies manifest does not exist at [%s]"), deps_path.c_str()); diff --git a/src/native/corehost/hostpolicy/deps_resolver.cpp b/src/native/corehost/hostpolicy/deps_resolver.cpp index 8bab2fa1d79f90..55d9bd8f948868 100644 --- a/src/native/corehost/hostpolicy/deps_resolver.cpp +++ b/src/native/corehost/hostpolicy/deps_resolver.cpp @@ -767,7 +767,7 @@ bool deps_resolver_t::resolve_probe_dirs( std::unordered_set items; pal::string_t core_servicing = m_core_servicing; - pal::realpath(&core_servicing, true); + pal::fullpath(&core_servicing, true); // Filter out non-serviced assets so the paths can be added after servicing paths. pal::string_t non_serviced; diff --git a/src/native/corehost/hostpolicy/hostpolicy_context.cpp b/src/native/corehost/hostpolicy/hostpolicy_context.cpp index 1a1f63dae073c4..63ef9cdbc0d32f 100644 --- a/src/native/corehost/hostpolicy/hostpolicy_context.cpp +++ b/src/native/corehost/hostpolicy/hostpolicy_context.cpp @@ -204,7 +204,7 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c } clr_path = probe_paths.coreclr; - if (clr_path.empty() || !pal::realpath(&clr_path)) + if (clr_path.empty() || !pal::fullpath(&clr_path)) { // in a single-file case we may not need coreclr, // otherwise fail early. diff --git a/src/native/corehost/hostpolicy/shared_store.cpp b/src/native/corehost/hostpolicy/shared_store.cpp index e8ea0d89f0be15..0279bde549e248 100644 --- a/src/native/corehost/hostpolicy/shared_store.cpp +++ b/src/native/corehost/hostpolicy/shared_store.cpp @@ -20,7 +20,7 @@ namespace pal::stringstream_t ss(path); while (std::getline(ss, tok, PATH_SEPARATOR)) { - if (pal::realpath(&tok)) + if (pal::fullpath(&tok)) { append_path(&tok, arch); append_path(&tok, tfm.c_str()); diff --git a/src/native/corehost/runtime_config.cpp b/src/native/corehost/runtime_config.cpp index d79a610f588b0c..38edc4a6945898 100644 --- a/src/native/corehost/runtime_config.cpp +++ b/src/native/corehost/runtime_config.cpp @@ -334,7 +334,7 @@ bool runtime_config_t::ensure_dev_config_parsed() trace::verbose(_X("Attempting to read dev runtime config: %s"), m_dev_path.c_str()); pal::string_t retval; - if (!pal::realpath(&m_dev_path, true)) + if (!pal::fullpath(&m_dev_path, true)) { // It is valid for the runtimeconfig.dev.json to not exist. return true; @@ -402,7 +402,7 @@ bool runtime_config_t::ensure_parsed() } trace::verbose(_X("Attempting to read runtime config: %s"), m_path.c_str()); - if (!bundle::info_t::config_t::probe(m_path) && !pal::realpath(&m_path, true)) + if (!bundle::info_t::config_t::probe(m_path) && !pal::fullpath(&m_path, true)) { // Not existing is not an error. trace::verbose(_X("Runtime config does not exist at [%s]"), m_path.c_str()); diff --git a/src/native/corehost/test/nativehost/host_context_test.cpp b/src/native/corehost/test/nativehost/host_context_test.cpp index b7c8c983d3a471..cf05ff2cc5d9de 100644 --- a/src/native/corehost/test/nativehost/host_context_test.cpp +++ b/src/native/corehost/test/nativehost/host_context_test.cpp @@ -917,7 +917,7 @@ bool host_context_test::non_context_mixed( hostfxr_exports hostfxr { hostfxr_path }; pal::string_t host_path; - if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path)) + if (!pal::get_own_executable_path(&host_path) || !pal::fullpath(&host_path)) { trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str()); return false; diff --git a/src/native/corehost/test/nativehost/nativehost.cpp b/src/native/corehost/test/nativehost/nativehost.cpp index 09ec47b259b7bf..f7497caf04e58f 100644 --- a/src/native/corehost/test/nativehost/nativehost.cpp +++ b/src/native/corehost/test/nativehost/nativehost.cpp @@ -65,7 +65,7 @@ int main(const int argc, const pal::char_t *argv[]) if (explicit_load) { pal::string_t nethost_path; - if (!pal::get_own_executable_path(&nethost_path) || !pal::realpath(&nethost_path)) + if (!pal::get_own_executable_path(&nethost_path) || !pal::fullpath(&nethost_path)) { std::cout << "Failed to get path to current executable" << std::endl; return EXIT_FAILURE;