Skip to content

Commit 3e572e9

Browse files
authored
Allow muxer behind Windows symlink (#99576)
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
1 parent 3250965 commit 3e572e9

31 files changed

+183
-93
lines changed

src/installer/tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ private void AbsolutePath()
3131
var bundleDir = Directory.GetParent(bundledApp.Path);
3232
bundleDir.Should().OnlyHaveFiles(new[]
3333
{
34-
Binaries.GetExeFileNameForCurrentPlatform(app.Name),
34+
Binaries.GetExeName(app.Name),
3535
$"{app.Name}.pdb"
3636
});
3737

src/installer/tests/HostActivation.Tests/InvalidHost.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,16 @@ public SharedTestState()
9090
BaseDirectory = TestArtifact.Create(nameof(InvalidHost));
9191
Directory.CreateDirectory(BaseDirectory.Location);
9292

93-
RenamedDotNet = Path.Combine(BaseDirectory.Location, Binaries.GetExeFileNameForCurrentPlatform("renamed"));
93+
RenamedDotNet = Path.Combine(BaseDirectory.Location, Binaries.GetExeName("renamed"));
9494
File.Copy(Binaries.DotNet.FilePath, RenamedDotNet);
9595

96-
UnboundAppHost = Path.Combine(BaseDirectory.Location, Binaries.GetExeFileNameForCurrentPlatform("unbound"));
96+
UnboundAppHost = Path.Combine(BaseDirectory.Location, Binaries.GetExeName("unbound"));
9797
File.Copy(Binaries.AppHost.FilePath, UnboundAppHost);
9898

9999
if (OperatingSystem.IsWindows())
100100
{
101101
// Mark the apphost as GUI, but don't bind it to anything - this will cause it to fail
102-
UnboundAppHostGUI = Path.Combine(BaseDirectory.Location, Binaries.GetExeFileNameForCurrentPlatform("unboundgui"));
102+
UnboundAppHostGUI = Path.Combine(BaseDirectory.Location, Binaries.GetExeName("unboundgui"));
103103
File.Copy(Binaries.AppHost.FilePath, UnboundAppHostGUI);
104104
PEUtils.SetWindowsGraphicalUserInterfaceBit(UnboundAppHostGUI);
105105
}

src/installer/tests/HostActivation.Tests/NativeHosting/ComhostSideBySide.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public SharedTestState()
116116
}
117117
}
118118

119-
string comsxsName = Binaries.GetExeFileNameForCurrentPlatform("comsxs");
119+
string comsxsName = Binaries.GetExeName("comsxs");
120120
ComSxsPath = Path.Combine(comsxsDirectory, comsxsName);
121121
File.Copy(
122122
Path.Combine(RepoDirectoriesProvider.Default.HostTestArtifacts, comsxsName),

src/installer/tests/HostActivation.Tests/NativeHosting/SharedTestStateBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public SharedTestStateBase()
2121
_baseDirArtifact = TestArtifact.Create("nativeHosting");
2222
BaseDirectory = _baseDirArtifact.Location;
2323

24-
string nativeHostName = Binaries.GetExeFileNameForCurrentPlatform("nativehost");
24+
string nativeHostName = Binaries.GetExeName("nativehost");
2525
NativeHostPath = Path.Combine(BaseDirectory, nativeHostName);
2626

2727
// Copy over native host

src/installer/tests/HostActivation.Tests/NativeUnitTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class NativeUnitTests
1515
[Fact]
1616
public void Native_Test_Fx_Ver()
1717
{
18-
string testPath = Path.Combine(RepoDirectoriesProvider.Default.HostTestArtifacts, Binaries.GetExeFileNameForCurrentPlatform("test_fx_ver"));
18+
string testPath = Path.Combine(RepoDirectoriesProvider.Default.HostTestArtifacts, Binaries.GetExeName("test_fx_ver"));
1919

2020
Command testCommand = Command.Create(testPath);
2121
testCommand

src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void RenameApphost()
9595
{
9696
var app = sharedTestState.App.Copy();
9797

98-
var renamedAppExe = app.AppExe + Binaries.GetExeFileNameForCurrentPlatform("renamed");
98+
var renamedAppExe = app.AppExe + Binaries.GetExeName("renamed");
9999
File.Move(app.AppExe, renamedAppExe, true);
100100

101101
Command.Create(renamedAppExe)

src/installer/tests/HostActivation.Tests/SymbolicLinks.cs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ public SymbolicLinks(SymbolicLinks.SharedTestState fixture)
2222
}
2323

2424
[Theory]
25-
[SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")]
2625
[InlineData ("a/b/SymlinkToApphost")]
2726
[InlineData ("a/SymlinkToApphost")]
2827
public void Run_apphost_behind_symlink(string symlinkRelativePath)
2928
{
29+
symlinkRelativePath = Binaries.GetExeName(symlinkRelativePath);
3030
using (var testDir = TestArtifact.Create("symlink"))
3131
{
3232
Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));
@@ -43,13 +43,14 @@ public void Run_apphost_behind_symlink(string symlinkRelativePath)
4343
}
4444

4545
[Theory]
46-
[SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")]
4746
[InlineData ("a/b/FirstSymlink", "c/d/SecondSymlink")]
4847
[InlineData ("a/b/FirstSymlink", "c/SecondSymlink")]
4948
[InlineData ("a/FirstSymlink", "c/d/SecondSymlink")]
5049
[InlineData ("a/FirstSymlink", "c/SecondSymlink")]
5150
public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePath, string secondSymlinkRelativePath)
5251
{
52+
firstSymlinkRelativePath = Binaries.GetExeName(firstSymlinkRelativePath);
53+
secondSymlinkRelativePath = Binaries.GetExeName(secondSymlinkRelativePath);
5354
using (var testDir = TestArtifact.Create("symlink"))
5455
{
5556
// second symlink -> apphost
@@ -71,15 +72,14 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa
7172
}
7273
}
7374

74-
//[Theory]
75-
//[InlineData("a/b/SymlinkToFrameworkDependentApp")]
76-
//[InlineData("a/SymlinkToFrameworkDependentApp")]
77-
[Fact(Skip = "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " +
75+
[Theory]
76+
[InlineData("a/b/SymlinkToFrameworkDependentApp")]
77+
[InlineData("a/SymlinkToFrameworkDependentApp")]
78+
[SkipOnPlatform(TestPlatforms.OSX, "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " +
7879
"CI failing to use stat on symbolic links on Linux (permission denied).")]
79-
[SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")]
80-
public void Run_framework_dependent_app_behind_symlink(/*string symlinkRelativePath*/)
80+
public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePath)
8181
{
82-
var symlinkRelativePath = string.Empty;
82+
symlinkRelativePath = Binaries.GetExeName(symlinkRelativePath);
8383

8484
using (var testDir = TestArtifact.Create("symlink"))
8585
{
@@ -96,14 +96,14 @@ public void Run_framework_dependent_app_behind_symlink(/*string symlinkRelativeP
9696
}
9797
}
9898

99-
[Fact(Skip = "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " +
100-
"CI failing to use stat on symbolic links on Linux (permission denied).")]
101-
[SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")]
99+
[Fact]
100+
[SkipOnPlatform(TestPlatforms.OSX, "Currently failing in OSX with \"No such file or directory\" when running Command.Create. " +
101+
"CI failing to use stat on symbolic links on Linux (permission denied).")]
102102
public void Run_framework_dependent_app_with_runtime_behind_symlink()
103103
{
104104
using (var testDir = TestArtifact.Create("symlink"))
105105
{
106-
var dotnetSymlink = Path.Combine(testDir.Location, "dotnet");
106+
var dotnetSymlink = Path.Combine(testDir.Location, Binaries.GetExeName("dotnet"));
107107

108108
using var symlink = new SymLink(dotnetSymlink, TestContext.BuiltDotNet.BinPath);
109109
Command.Create(sharedTestState.FrameworkDependentApp.AppExe)
@@ -117,7 +117,6 @@ public void Run_framework_dependent_app_with_runtime_behind_symlink()
117117
}
118118

119119
[Fact]
120-
[SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")]
121120
public void Put_app_directory_behind_symlink()
122121
{
123122
var app = sharedTestState.SelfContainedApp.Copy();
@@ -138,7 +137,6 @@ public void Put_app_directory_behind_symlink()
138137
}
139138

140139
[Fact]
141-
[SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")]
142140
public void Put_dotnet_behind_symlink()
143141
{
144142
using (var testDir = TestArtifact.Create("symlink"))
@@ -156,7 +154,6 @@ public void Put_dotnet_behind_symlink()
156154
}
157155

158156
[Fact]
159-
[SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")]
160157
public void Put_app_directory_behind_symlink_and_use_dotnet()
161158
{
162159
var app = sharedTestState.SelfContainedApp.Copy();
@@ -177,7 +174,6 @@ public void Put_app_directory_behind_symlink_and_use_dotnet()
177174
}
178175

179176
[Fact]
180-
[SkipOnPlatform(TestPlatforms.Windows, "Creating symbolic links requires administrative privilege on Windows, so skip test.")]
181177
public void Put_satellite_assembly_behind_symlink()
182178
{
183179
var app = sharedTestState.LocalizedApp.Copy();

src/installer/tests/Microsoft.NET.HostModel.Tests/Bundle/BundlerConsistencyTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public BundlerConsistencyTests(SharedTestState fixture)
2323
sharedTestState = fixture;
2424
}
2525

26-
private static string BundlerHostName = Binaries.GetExeFileNameForCurrentPlatform(SharedTestState.AppName);
26+
private static string BundlerHostName = Binaries.GetExeName(SharedTestState.AppName);
2727
private Bundler CreateBundlerInstance(BundleOptions bundleOptions = BundleOptions.None, Version version = null, bool macosCodesign = true)
2828
=> new Bundler(BundlerHostName, sharedTestState.App.GetUniqueSubdirectory("bundle"), bundleOptions, targetFrameworkVersion: version, macosCodesign: macosCodesign);
2929

src/installer/tests/TestUtils/Binaries.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace Microsoft.DotNet.CoreSetup.Test
1515
{
1616
public static class Binaries
1717
{
18-
public static string GetExeFileNameForCurrentPlatform(string exeName) =>
18+
public static string GetExeName(string exeName) =>
1919
exeName + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty);
2020

2121
public static (string, string) GetSharedLibraryPrefixSuffix()
@@ -37,7 +37,7 @@ public static string GetSharedLibraryFileNameForCurrentPlatform(string libraryNa
3737

3838
public static class AppHost
3939
{
40-
public static string FileName = GetExeFileNameForCurrentPlatform("apphost");
40+
public static string FileName = GetExeName("apphost");
4141
public static string FilePath = Path.Combine(RepoDirectoriesProvider.Default.HostArtifacts, FileName);
4242
}
4343

@@ -52,7 +52,7 @@ public static class CoreClr
5252

5353
public static class DotNet
5454
{
55-
public static string FileName = GetExeFileNameForCurrentPlatform("dotnet");
55+
public static string FileName = GetExeName("dotnet");
5656
public static string FilePath = Path.Combine(RepoDirectoriesProvider.Default.HostArtifacts, FileName);
5757
}
5858

@@ -84,7 +84,7 @@ public static class NetHost
8484

8585
public static class SingleFileHost
8686
{
87-
public static string FileName = GetExeFileNameForCurrentPlatform("singlefilehost");
87+
public static string FileName = GetExeName("singlefilehost");
8888
public static string FilePath = Path.Combine(RepoDirectoriesProvider.Default.HostArtifacts, FileName);
8989
}
9090

src/installer/tests/TestUtils/SingleFileTestApp.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public string Bundle(BundleOptions options, out Manifest manifest, Version? bund
9292
{
9393
string bundleDirectory = GetUniqueSubdirectory("bundle");
9494
var bundler = new Bundler(
95-
Binaries.GetExeFileNameForCurrentPlatform(AppName),
95+
Binaries.GetExeName(AppName),
9696
bundleDirectory,
9797
options,
9898
targetFrameworkVersion: bundleVersion,

src/installer/tests/TestUtils/SymbolicLinking.cs renamed to src/installer/tests/TestUtils/SymLink.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ private static bool MakeSymbolicLink(string symbolicLinkName, string targetFileN
3535
errorMessage = string.Empty;
3636
if (OperatingSystem.IsWindows())
3737
{
38-
if (!CreateSymbolicLink(symbolicLinkName, targetFileName, SymbolicLinkFlag.IsFile))
38+
if (!CreateSymbolicLink(symbolicLinkName, targetFileName, SymbolicLinkFlag.IsFile | SymbolicLinkFlag.AllowUnprivilegedCreate))
3939
{
4040
int errno = Marshal.GetLastWin32Error();
4141
errorMessage = $"CreateSymbolicLink failed with error number {errno}";

src/installer/tests/TestUtils/TestApp.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ private void LoadAssets()
185185
{
186186
Directory.CreateDirectory(Location);
187187
AppDll = Path.Combine(Location, $"{AssemblyName}.dll");
188-
AppExe = Path.Combine(Location, Binaries.GetExeFileNameForCurrentPlatform(AssemblyName));
188+
AppExe = Path.Combine(Location, Binaries.GetExeName(AssemblyName));
189189
DepsJson = Path.Combine(Location, $"{AssemblyName}.deps.json");
190190
RuntimeConfigJson = Path.Combine(Location, $"{AssemblyName}.runtimeconfig.json");
191191
RuntimeDevConfigJson = Path.Combine(Location, $"{AssemblyName}.runtimeconfig.dev.json");

src/native/corehost/corehost.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ int exe_start(const int argc, const pal::char_t* argv[])
112112
initialize_static_createdump();
113113
#endif
114114

115+
// Use realpath to find the path of the host, resolving any symlinks.
116+
// hostfxr (for dotnet) and the app dll (for apphost) are found relative to the host.
115117
pal::string_t host_path;
116118
if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path))
117119
{
@@ -148,7 +150,7 @@ int exe_start(const int argc, const pal::char_t* argv[])
148150
{
149151
trace::info(_X("Detected Single-File app bundle"));
150152
}
151-
else if (!pal::realpath(&app_path))
153+
else if (!pal::fullpath(&app_path))
152154
{
153155
trace::error(_X("The application to execute does not exist: '%s'."), app_path.c_str());
154156
return StatusCode::LibHostAppRootFindFailure;

src/native/corehost/fxr/command_line.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ namespace
145145
if (mode == host_mode_t::apphost)
146146
{
147147
app_candidate = host_info.app_path;
148-
doesAppExist = bundle::info_t::is_single_file_bundle() || pal::realpath(&app_candidate);
148+
doesAppExist = bundle::info_t::is_single_file_bundle() || pal::fullpath(&app_candidate);
149149
}
150150
else
151151
{
@@ -169,7 +169,7 @@ namespace
169169
}
170170
}
171171

172-
doesAppExist = pal::realpath(&app_candidate);
172+
doesAppExist = pal::fullpath(&app_candidate);
173173
if (!doesAppExist)
174174
{
175175
trace::verbose(_X("Application '%s' does not exist."), app_candidate.c_str());

0 commit comments

Comments
 (0)