diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs index d440d3eae2caa2..5afab32a0881d2 100644 --- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs +++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using FluentAssertions; using Microsoft.DotNet.Cli.Build.Framework; @@ -21,6 +23,165 @@ public SymbolicLinks(SymbolicLinks.SharedTestState fixture) sharedTestState = fixture; } + [Theory] + [InlineData("a/b/SymlinkToFrameworkDependentApp")] + [InlineData("a/SymlinkToFrameworkDependentApp")] + public void Symlink_all_files_fx(string symlinkRelativePath) + { + using var testDir = TestArtifact.Create("symlink"); + Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath))); + + // Symlink every file in the app directory + var symlinks = new List(); + try + { + foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location)) + { + var fileName = Path.GetFileName(file); + var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName); + Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath)); + symlinks.Add(new SymLink(symlinkPath, file)); + } + + var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(TestContext.BuiltDotNet.BinPath) + .Execute(); + + // This should succeed on all platforms, but for different reasons: + // * Windows: The apphost will look next to the symlink for the app dll and find the symlinked dll + // * Unix: The apphost will look next to the resolved apphost for the app dll and find the real thing + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } + finally + { + foreach (var symlink in symlinks) + { + symlink.Dispose(); + } + } + } + + [Theory] + [InlineData("a/b/SymlinkToFrameworkDependentApp")] + [InlineData("a/SymlinkToFrameworkDependentApp")] + public void Symlink_split_files_fx(string symlinkRelativePath) + { + using var testDir = TestArtifact.Create("symlink"); + + // Split the app into two directories, one for the apphost and one for the rest of the files + var appHostDir = Path.Combine(testDir.Location, "apphost"); + var appFilesDir = Path.Combine(testDir.Location, "appfiles"); + Directory.CreateDirectory(appHostDir); + Directory.CreateDirectory(appFilesDir); + + var appHostName = Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe); + + File.Copy( + sharedTestState.FrameworkDependentApp.AppExe, + Path.Combine(appHostDir, appHostName)); + + foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location)) + { + var fileName = Path.GetFileName(file); + if (fileName != appHostName) + { + File.Copy(file, Path.Combine(appFilesDir, fileName)); + } + } + + // Symlink all of the above into a single directory + var targetPath = Path.Combine(testDir.Location, symlinkRelativePath); + Directory.CreateDirectory(targetPath); + var symlinks = new List(); + try + { + foreach (var file in Directory.EnumerateFiles(appFilesDir)) + { + var fileName = Path.GetFileName(file); + var symlinkPath = Path.Combine(targetPath, fileName); + Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath)); + symlinks.Add(new SymLink(symlinkPath, file)); + } + symlinks.Add(new SymLink( + Path.Combine(targetPath, appHostName), + Path.Combine(appHostDir, appHostName))); + + var result = Command.Create(Path.Combine(targetPath, appHostName)) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(TestContext.BuiltDotNet.BinPath) + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On Windows, the apphost will look next to the symlink for the app dll and find the symlinks + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } + else + { + // On Unix, the apphost will not find the app files next to the symlink + result + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); + } + } + finally + { + foreach (var symlink in symlinks) + { + symlink.Dispose(); + } + } + } + + [Theory] + [InlineData("a/b/SymlinkToFrameworkDependentApp")] + [InlineData("a/SymlinkToFrameworkDependentApp")] + public void Symlink_all_files_self_contained(string symlinkRelativePath) + { + using var testDir = TestArtifact.Create("symlink"); + Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath))); + + // Symlink every file in the app directory + var symlinks = new List(); + try + { + foreach (var file in Directory.EnumerateFiles(sharedTestState.SelfContainedApp.Location)) + { + var fileName = Path.GetFileName(file); + var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName); + Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath)); + symlinks.Add(new SymLink(symlinkPath, file)); + } + + var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe))) + .CaptureStdErr() + .CaptureStdOut() + .DotNetRoot(TestContext.BuiltDotNet.BinPath) + .Execute(); + + // This should succeed on all platforms, but for different reasons: + // * Windows: The apphost will look next to the symlink for the files and find the symlinks + // * Unix: The apphost will look next to the resolved apphost for the files and find the real thing + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } + finally + { + foreach (var symlink in symlinks) + { + symlink.Dispose(); + } + } + } + [Theory] [InlineData ("a/b/SymlinkToApphost")] [InlineData ("a/SymlinkToApphost")] @@ -33,12 +194,23 @@ public void Run_apphost_behind_symlink(string symlinkRelativePath) var symlinkFullPath = Path.Combine(testDir.Location, symlinkRelativePath); using var symlink = new SymLink(symlinkFullPath, sharedTestState.SelfContainedApp.AppExe); - Command.Create(symlinkFullPath) + var result = Command.Create(symlinkFullPath) .CaptureStdErr() .CaptureStdOut() - .Execute() - .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); + } + else + { + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } } } @@ -63,12 +235,23 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa Directory.CreateDirectory(Path.GetDirectoryName(symlink1Path)); using var symlink1 = new SymLink(symlink1Path, symlink2Path); - Command.Create(symlink1.SrcPath) + var result = Command.Create(symlink1.SrcPath) .CaptureStdErr() .CaptureStdOut() - .Execute() - .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); + } + else + { + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } } } @@ -86,13 +269,24 @@ public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePat Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath))); using var symlink = new SymLink(Path.Combine(testDir.Location, symlinkRelativePath), sharedTestState.FrameworkDependentApp.AppExe); - Command.Create(symlink.SrcPath) + var result = Command.Create(symlink.SrcPath) .CaptureStdErr() .CaptureStdOut() .DotNetRoot(TestContext.BuiltDotNet.BinPath) - .Execute() - .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result + .Should().Fail() + .And.HaveStdErrContaining("The application to execute does not exist"); + } + else + { + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } } } @@ -144,12 +338,23 @@ public void Put_dotnet_behind_symlink() var dotnetSymlink = Path.Combine(testDir.Location, Binaries.DotNet.FileName); using var symlink = new SymLink(dotnetSymlink, TestContext.BuiltDotNet.DotnetExecutablePath); - Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll) + var result = Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll) .CaptureStdErr() .CaptureStdOut() - .Execute() - .Should().Pass() - .And.HaveStdOutContaining("Hello World"); + .Execute(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + result + .Should().Fail() + .And.HaveStdErrContaining($"[{Path.Combine(testDir.Location, "host", "fxr")}] does not exist"); + } + else + { + result + .Should().Pass() + .And.HaveStdOutContaining("Hello World"); + } } } diff --git a/src/native/corehost/corehost.cpp b/src/native/corehost/corehost.cpp index c8a94312c5d6d4..c233a52237a9fb 100644 --- a/src/native/corehost/corehost.cpp +++ b/src/native/corehost/corehost.cpp @@ -115,7 +115,7 @@ int exe_start(const int argc, const pal::char_t* argv[]) // 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)) + 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 StatusCode::CoreHostCurHostFindFailure; diff --git a/src/native/corehost/fxr_resolver.cpp b/src/native/corehost/fxr_resolver.cpp index a69bf7ced896a5..0d1cbddf9deb5c 100644 --- a/src/native/corehost/fxr_resolver.cpp +++ b/src/native/corehost/fxr_resolver.cpp @@ -95,7 +95,7 @@ bool fxr_resolver::try_get_path( bool search_global = (search & search_location_global) != 0; pal::string_t default_install_location; pal::string_t dotnet_root_env_var_name; - if (search_app_relative && pal::realpath(app_relative_dotnet_root)) + if (search_app_relative && pal::fullpath(app_relative_dotnet_root)) { trace::info(_X("Using app-relative location [%s] as runtime location."), app_relative_dotnet_root->c_str()); out_dotnet_root->assign(*app_relative_dotnet_root);