Skip to content

Commit 7057719

Browse files
authored
Revert change to follow symlinks of dotnet host (#115315)
#99576 changed the host to first resolve symlinks before resolving the application directory. This means that relative loads happen relative to the pointed-at file, not the symbolic link. This was a breaking change made to match the symbolic link behavior on all platforms. Unfortunately, it seems a number of users have taken a dependency on the Windows-specific behavior. This PR reverts the change and puts back in place the old Windows behavior.
1 parent 39ecf52 commit 7057719

File tree

3 files changed

+223
-18
lines changed

3 files changed

+223
-18
lines changed

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

Lines changed: 221 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.IO;
67
using System.Linq;
8+
using System.Runtime.InteropServices;
79

810
using FluentAssertions;
911
using Microsoft.DotNet.Cli.Build.Framework;
@@ -21,6 +23,165 @@ public SymbolicLinks(SymbolicLinks.SharedTestState fixture)
2123
sharedTestState = fixture;
2224
}
2325

26+
[Theory]
27+
[InlineData("a/b/SymlinkToFrameworkDependentApp")]
28+
[InlineData("a/SymlinkToFrameworkDependentApp")]
29+
public void Symlink_all_files_fx(string symlinkRelativePath)
30+
{
31+
using var testDir = TestArtifact.Create("symlink");
32+
Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));
33+
34+
// Symlink every file in the app directory
35+
var symlinks = new List<SymLink>();
36+
try
37+
{
38+
foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location))
39+
{
40+
var fileName = Path.GetFileName(file);
41+
var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName);
42+
Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
43+
symlinks.Add(new SymLink(symlinkPath, file));
44+
}
45+
46+
var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))
47+
.CaptureStdErr()
48+
.CaptureStdOut()
49+
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
50+
.Execute();
51+
52+
// This should succeed on all platforms, but for different reasons:
53+
// * Windows: The apphost will look next to the symlink for the app dll and find the symlinked dll
54+
// * Unix: The apphost will look next to the resolved apphost for the app dll and find the real thing
55+
result
56+
.Should().Pass()
57+
.And.HaveStdOutContaining("Hello World");
58+
}
59+
finally
60+
{
61+
foreach (var symlink in symlinks)
62+
{
63+
symlink.Dispose();
64+
}
65+
}
66+
}
67+
68+
[Theory]
69+
[InlineData("a/b/SymlinkToFrameworkDependentApp")]
70+
[InlineData("a/SymlinkToFrameworkDependentApp")]
71+
public void Symlink_split_files_fx(string symlinkRelativePath)
72+
{
73+
using var testDir = TestArtifact.Create("symlink");
74+
75+
// Split the app into two directories, one for the apphost and one for the rest of the files
76+
var appHostDir = Path.Combine(testDir.Location, "apphost");
77+
var appFilesDir = Path.Combine(testDir.Location, "appfiles");
78+
Directory.CreateDirectory(appHostDir);
79+
Directory.CreateDirectory(appFilesDir);
80+
81+
var appHostName = Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe);
82+
83+
File.Copy(
84+
sharedTestState.FrameworkDependentApp.AppExe,
85+
Path.Combine(appHostDir, appHostName));
86+
87+
foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location))
88+
{
89+
var fileName = Path.GetFileName(file);
90+
if (fileName != appHostName)
91+
{
92+
File.Copy(file, Path.Combine(appFilesDir, fileName));
93+
}
94+
}
95+
96+
// Symlink all of the above into a single directory
97+
var targetPath = Path.Combine(testDir.Location, symlinkRelativePath);
98+
Directory.CreateDirectory(targetPath);
99+
var symlinks = new List<SymLink>();
100+
try
101+
{
102+
foreach (var file in Directory.EnumerateFiles(appFilesDir))
103+
{
104+
var fileName = Path.GetFileName(file);
105+
var symlinkPath = Path.Combine(targetPath, fileName);
106+
Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
107+
symlinks.Add(new SymLink(symlinkPath, file));
108+
}
109+
symlinks.Add(new SymLink(
110+
Path.Combine(targetPath, appHostName),
111+
Path.Combine(appHostDir, appHostName)));
112+
113+
var result = Command.Create(Path.Combine(targetPath, appHostName))
114+
.CaptureStdErr()
115+
.CaptureStdOut()
116+
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
117+
.Execute();
118+
119+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
120+
{
121+
// On Windows, the apphost will look next to the symlink for the app dll and find the symlinks
122+
result
123+
.Should().Pass()
124+
.And.HaveStdOutContaining("Hello World");
125+
}
126+
else
127+
{
128+
// On Unix, the apphost will not find the app files next to the symlink
129+
result
130+
.Should().Fail()
131+
.And.HaveStdErrContaining("The application to execute does not exist");
132+
}
133+
}
134+
finally
135+
{
136+
foreach (var symlink in symlinks)
137+
{
138+
symlink.Dispose();
139+
}
140+
}
141+
}
142+
143+
[Theory]
144+
[InlineData("a/b/SymlinkToFrameworkDependentApp")]
145+
[InlineData("a/SymlinkToFrameworkDependentApp")]
146+
public void Symlink_all_files_self_contained(string symlinkRelativePath)
147+
{
148+
using var testDir = TestArtifact.Create("symlink");
149+
Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));
150+
151+
// Symlink every file in the app directory
152+
var symlinks = new List<SymLink>();
153+
try
154+
{
155+
foreach (var file in Directory.EnumerateFiles(sharedTestState.SelfContainedApp.Location))
156+
{
157+
var fileName = Path.GetFileName(file);
158+
var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName);
159+
Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
160+
symlinks.Add(new SymLink(symlinkPath, file));
161+
}
162+
163+
var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))
164+
.CaptureStdErr()
165+
.CaptureStdOut()
166+
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
167+
.Execute();
168+
169+
// This should succeed on all platforms, but for different reasons:
170+
// * Windows: The apphost will look next to the symlink for the files and find the symlinks
171+
// * Unix: The apphost will look next to the resolved apphost for the files and find the real thing
172+
result
173+
.Should().Pass()
174+
.And.HaveStdOutContaining("Hello World");
175+
}
176+
finally
177+
{
178+
foreach (var symlink in symlinks)
179+
{
180+
symlink.Dispose();
181+
}
182+
}
183+
}
184+
24185
[Theory]
25186
[InlineData ("a/b/SymlinkToApphost")]
26187
[InlineData ("a/SymlinkToApphost")]
@@ -33,12 +194,23 @@ public void Run_apphost_behind_symlink(string symlinkRelativePath)
33194
var symlinkFullPath = Path.Combine(testDir.Location, symlinkRelativePath);
34195

35196
using var symlink = new SymLink(symlinkFullPath, sharedTestState.SelfContainedApp.AppExe);
36-
Command.Create(symlinkFullPath)
197+
var result = Command.Create(symlinkFullPath)
37198
.CaptureStdErr()
38199
.CaptureStdOut()
39-
.Execute()
40-
.Should().Pass()
41-
.And.HaveStdOutContaining("Hello World");
200+
.Execute();
201+
202+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
203+
{
204+
result
205+
.Should().Fail()
206+
.And.HaveStdErrContaining("The application to execute does not exist");
207+
}
208+
else
209+
{
210+
result
211+
.Should().Pass()
212+
.And.HaveStdOutContaining("Hello World");
213+
}
42214
}
43215
}
44216

@@ -63,12 +235,23 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa
63235
Directory.CreateDirectory(Path.GetDirectoryName(symlink1Path));
64236
using var symlink1 = new SymLink(symlink1Path, symlink2Path);
65237

66-
Command.Create(symlink1.SrcPath)
238+
var result = Command.Create(symlink1.SrcPath)
67239
.CaptureStdErr()
68240
.CaptureStdOut()
69-
.Execute()
70-
.Should().Pass()
71-
.And.HaveStdOutContaining("Hello World");
241+
.Execute();
242+
243+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
244+
{
245+
result
246+
.Should().Fail()
247+
.And.HaveStdErrContaining("The application to execute does not exist");
248+
}
249+
else
250+
{
251+
result
252+
.Should().Pass()
253+
.And.HaveStdOutContaining("Hello World");
254+
}
72255
}
73256
}
74257

@@ -86,13 +269,24 @@ public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePat
86269
Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));
87270

88271
using var symlink = new SymLink(Path.Combine(testDir.Location, symlinkRelativePath), sharedTestState.FrameworkDependentApp.AppExe);
89-
Command.Create(symlink.SrcPath)
272+
var result = Command.Create(symlink.SrcPath)
90273
.CaptureStdErr()
91274
.CaptureStdOut()
92275
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
93-
.Execute()
94-
.Should().Pass()
95-
.And.HaveStdOutContaining("Hello World");
276+
.Execute();
277+
278+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
279+
{
280+
result
281+
.Should().Fail()
282+
.And.HaveStdErrContaining("The application to execute does not exist");
283+
}
284+
else
285+
{
286+
result
287+
.Should().Pass()
288+
.And.HaveStdOutContaining("Hello World");
289+
}
96290
}
97291
}
98292

@@ -144,12 +338,23 @@ public void Put_dotnet_behind_symlink()
144338
var dotnetSymlink = Path.Combine(testDir.Location, Binaries.DotNet.FileName);
145339

146340
using var symlink = new SymLink(dotnetSymlink, TestContext.BuiltDotNet.DotnetExecutablePath);
147-
Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll)
341+
var result = Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll)
148342
.CaptureStdErr()
149343
.CaptureStdOut()
150-
.Execute()
151-
.Should().Pass()
152-
.And.HaveStdOutContaining("Hello World");
344+
.Execute();
345+
346+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
347+
{
348+
result
349+
.Should().Fail()
350+
.And.HaveStdErrContaining($"[{Path.Combine(testDir.Location, "host", "fxr")}] does not exist");
351+
}
352+
else
353+
{
354+
result
355+
.Should().Pass()
356+
.And.HaveStdOutContaining("Hello World");
357+
}
153358
}
154359
}
155360

src/native/corehost/corehost.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ int exe_start(const int argc, const pal::char_t* argv[])
128128
// Use realpath to find the path of the host, resolving any symlinks.
129129
// hostfxr (for dotnet) and the app dll (for apphost) are found relative to the host.
130130
pal::string_t host_path;
131-
if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path))
131+
if (!pal::get_own_executable_path(&host_path) || !pal::fullpath(&host_path))
132132
{
133133
trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str());
134134
return StatusCode::CurrentHostFindFailure;

src/native/corehost/fxr_resolver.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ bool fxr_resolver::try_get_path(
9595
bool search_global = (search & search_location_global) != 0;
9696
pal::string_t default_install_location;
9797
pal::string_t dotnet_root_env_var_name;
98-
if (search_app_relative && pal::realpath(app_relative_dotnet_root))
98+
if (search_app_relative && pal::fullpath(app_relative_dotnet_root))
9999
{
100100
trace::info(_X("Using app-relative location [%s] as runtime location."), app_relative_dotnet_root->c_str());
101101
out_dotnet_root->assign(*app_relative_dotnet_root);

0 commit comments

Comments
 (0)