Skip to content

Commit

Permalink
Allow muxer behind Windows symlink (#99576)
Browse files Browse the repository at this point in the history
The current logic for the muxer (dotnet.exe) doesn't resolve through symlinks
on Windows. For instance, it looks next to the entry point path for things like
hostfxr. This means that you cannot use a symlink to dotnet.exe, as it will look
next to your symlink for the runtime, rather than next to the target of the symlink.

This PR fixes the problem just for the muxer. To do so, it introduces a new API:
fullpath. The existing realpath API has the behavior of always resolving symlinks
on Unix, and never resolving symlinks on Windows. The new `fullpath` API replicates
this behavior, while the old `realpath` API is changed to always resolve symlinks on
both Unix and Windows. Then, the realpath API is used in appropriate places when
resolving paths relative to the muxer to guarantee resolving through symlinks on
all platforms.

Fixes #83314
  • Loading branch information
agocke authored Aug 2, 2024
1 parent 3250965 commit 3e572e9
Show file tree
Hide file tree
Showing 31 changed files with 183 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
});

Expand Down
6 changes: 3 additions & 3 deletions src/installer/tests/HostActivation.Tests/InvalidHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 13 additions & 17 deletions src/installer/tests/HostActivation.Tests/SymbolicLinks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand All @@ -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
Expand All @@ -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"))
{
Expand All @@ -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)
Expand All @@ -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();
Expand All @@ -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"))
Expand All @@ -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();
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
8 changes: 4 additions & 4 deletions src/installer/tests/TestUtils/Binaries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/installer/tests/TestUtils/SingleFileTestApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}";
Expand Down
2 changes: 1 addition & 1 deletion src/installer/tests/TestUtils/TestApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
4 changes: 3 additions & 1 deletion src/native/corehost/corehost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/native/corehost/fxr/command_line.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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());
Expand Down
Loading

0 comments on commit 3e572e9

Please sign in to comment.