Skip to content

Commit c57c6fb

Browse files
committed
Add test and copy implementation to realpath2
1 parent 57608c3 commit c57c6fb

4 files changed

Lines changed: 113 additions & 0 deletions

File tree

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,31 @@ public PortableAppActivation(SharedTestState fixture)
2020
sharedTestState = fixture;
2121
}
2222

23+
[Fact]
24+
public void Muxer_behind_symlink()
25+
{
26+
var fixture = sharedTestState.PortableAppFixture_Built
27+
.Copy();
28+
29+
var dotnetLoc = fixture.BuiltDotnet.DotnetExecutablePath;
30+
var appDll = fixture.TestProject.AppDll;
31+
var testDir = Directory.GetParent(fixture.TestProject.Location).ToString();
32+
33+
var exeSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : "";
34+
35+
using var symlink = new SymLink(Path.Combine(testDir, "dn" + exeSuffix), dotnetLoc);
36+
37+
var cmd = Command.Create(symlink.SrcPath, new[] { appDll })
38+
.CaptureStdOut()
39+
.CaptureStdErr()
40+
.EnvironmentVariable("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1")
41+
.MultilevelLookup(false); // Avoid looking at machine state by default
42+
43+
cmd.Execute()
44+
.Should().Pass()
45+
.And.HaveStdOutContaining("Hello World");
46+
}
47+
2348
[Fact]
2449
public void Muxer_activation_of_Build_Output_Portable_DLL_with_DepsJson_and_RuntimeConfig_Local_Succeeds()
2550
{

src/native/corehost/hostmisc/pal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ namespace pal
269269

270270
bool touch_file(const string_t& path);
271271
bool realpath(string_t* path, bool skip_error_logging = false);
272+
bool fullpath(string_t* path, bool skip_error_logging = false);
272273
bool file_exists(const string_t& path);
273274
inline bool directory_exists(const string_t& path) { return file_exists(path); }
274275
void readdir(const string_t& path, const string_t& pattern, std::vector<string_t>* list);

src/native/corehost/hostmisc/pal.unix.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,11 @@ bool pal::getenv(const pal::char_t* name, pal::string_t* recv)
945945
return (recv->length() > 0);
946946
}
947947

948+
bool pal::fullpath(pal::string_t* path, bool skip_error_logging)
949+
{
950+
return realpath(path, skip_error_logging);
951+
}
952+
948953
bool pal::realpath(pal::string_t* path, bool skip_error_logging)
949954
{
950955
auto resolved = ::realpath(path->c_str(), nullptr);

src/native/corehost/hostmisc/pal.windows.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,88 @@ bool pal::clr_palstring(const char* cstr, pal::string_t* out)
730730

731731
// Return if path is valid and file exists, return true and adjust path as appropriate.
732732
bool pal::realpath(string_t* path, bool skip_error_logging)
733+
{
734+
return fullpath(path, skip_error_logging);
735+
}
736+
737+
typedef std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::CloseHandle)> SmartHandle;
738+
739+
// Like realpath, but resolves symlinks.
740+
bool pal::realpath2(string_t* path, bool skip_error_logging)
741+
{
742+
if (path->empty())
743+
{
744+
return false;
745+
}
746+
747+
// Use CreateFileW + GetFinalPathNameByHandleW to resolve symlinks
748+
749+
SmartHandle file(
750+
::CreateFileW(
751+
path->c_str(),
752+
0, // Querying only
753+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
754+
nullptr, // default security
755+
OPEN_EXISTING, // existing file
756+
FILE_ATTRIBUTE_NORMAL, // normal file
757+
nullptr), // No attribute template
758+
&::CloseHandle);
759+
760+
char_t buf[MAX_PATH];
761+
size_t size;
762+
763+
if (file.get() == INVALID_HANDLE_VALUE)
764+
{
765+
// If we get "access denied" that may mean the path represents a directory.
766+
// Even if not, we can fall back to GetFullPathNameW, which doesn't require a HANDLE
767+
768+
auto error = ::GetLastError();
769+
file.release();
770+
if (ERROR_ACCESS_DENIED != error)
771+
{
772+
goto invalidPath;
773+
}
774+
}
775+
else
776+
{
777+
size = ::GetFinalPathNameByHandleW(file.get(), buf, MAX_PATH, FILE_NAME_NORMALIZED);
778+
// If size is 0, this call failed. Fall back to GetFullPathNameW, below
779+
if (size != 0)
780+
{
781+
string_t str;
782+
if (size < MAX_PATH)
783+
{
784+
str.assign(buf);
785+
}
786+
else
787+
{
788+
str.resize(size, 0);
789+
size = ::GetFinalPathNameByHandleW(file.get(), (LPWSTR)str.data(), static_cast<uint32_t>(size), FILE_NAME_NORMALIZED);
790+
assert(size <= str.size());
791+
792+
if (size == 0)
793+
{
794+
goto invalidPath;
795+
}
796+
}
797+
798+
// Remove the \\?\ prefix, unless it is necessary or was already there
799+
if (LongFile::IsExtended(str) && !LongFile::IsExtended(*path) &&
800+
!LongFile::ShouldNormalize(str.substr(LongFile::ExtendedPrefix.size())))
801+
{
802+
str.erase(0, LongFile::ExtendedPrefix.size());
803+
}
804+
805+
*path = str;
806+
return true;
807+
}
808+
}
809+
810+
// If the above fails, fall back to fullpath
811+
return fullpath(path, skip_error_logging);
812+
}
813+
814+
bool pal::fullpath(string_t* path, bool skip_error_logging)
733815
{
734816
if (path->empty())
735817
{

0 commit comments

Comments
 (0)