-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix muxer handling of symlinks on Windows #87717
Changes from all commits
c57c6fb
2ec2e6b
392ab46
c905df4
cb0a3d9
6bd2cf8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,6 +104,7 @@ int exe_start(const int argc, const pal::char_t* argv[]) | |
pal::initialize_createdump(); | ||
|
||
pal::string_t host_path; | ||
// Use realpath to find the host path through symlinks | ||
if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path)) | ||
{ | ||
trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str()); | ||
|
@@ -139,7 +140,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::realpath(&app_path)) // Use realpath to find the app path through symlinks | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To properly test this we would need to setup two symlinks. The executable can be a symlink, we should look for the It's an interesting question if we should actually behave like this. We will use the |
||
{ | ||
trace::error(_X("The application to execute does not exist: '%s'."), app_path.c_str()); | ||
return StatusCode::LibHostAppRootFindFailure; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,6 +145,7 @@ namespace | |
if (mode == host_mode_t::apphost) | ||
{ | ||
app_candidate = host_info.app_path; | ||
// Use realpath since we want the path to the app through symlinks | ||
doesAppExist = bundle::info_t::is_single_file_bundle() || pal::realpath(&app_candidate); | ||
Comment on lines
147
to
149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe not in this PR, but it would be worth looking into this a bit higher level. For example in this case, the But there are other places which initialize the Would be nice to have a principle that it either is guaranteed resolved, or not (in which case the callers don't try to resolve at all). |
||
} | ||
else | ||
|
@@ -169,6 +170,7 @@ namespace | |
} | ||
} | ||
|
||
// Use realpath since we want the path to the app through symlinks | ||
doesAppExist = pal::realpath(&app_candidate); | ||
if (!doesAppExist) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -545,6 +545,7 @@ namespace | |
|
||
if (startup_info.host_path.empty()) | ||
{ | ||
// Use realpath to find the host_path behind symlinks | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also "realpath" the |
||
if (!pal::get_own_executable_path(&startup_info.host_path) || !pal::realpath(&startup_info.host_path)) | ||
{ | ||
trace::error(_X("Failed to resolve full path of the current host [%s]"), startup_info.host_path.c_str()); | ||
|
@@ -559,7 +560,8 @@ namespace | |
return StatusCode::CoreHostCurHostFindFailure; | ||
|
||
startup_info.dotnet_root = get_dotnet_root_from_fxr_path(mod_path); | ||
if (!pal::realpath(&startup_info.dotnet_root)) | ||
// We don't support directory symlinks, so use fullpath | ||
if (!pal::fullpath(&startup_info.dotnet_root)) | ||
{ | ||
trace::error(_X("Failed to resolve full path of dotnet root [%s]"), startup_info.dotnet_root.c_str()); | ||
return StatusCode::CoreHostCurHostFindFailure; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -269,6 +269,7 @@ namespace pal | |
|
||
bool touch_file(const string_t& path); | ||
bool realpath(string_t* path, bool skip_error_logging = false); | ||
bool fullpath(string_t* path, bool skip_error_logging = false); | ||
Comment on lines
271
to
+272
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comment what is the difference between the two - the naming is Unix based, but Unix doesn't have |
||
bool file_exists(const string_t& path); | ||
inline bool directory_exists(const string_t& path) { return file_exists(path); } | ||
void readdir(const string_t& path, const string_t& pattern, std::vector<string_t>* list); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -270,7 +270,7 @@ bool pal::get_default_breadcrumb_store(string_t* recv) | |
{ | ||
recv->clear(); | ||
pal::string_t ext; | ||
if (pal::getenv(_X("CORE_BREADCRUMBS"), &ext) && pal::realpath(&ext)) | ||
if (pal::getenv(_X("CORE_BREADCRUMBS"), &ext) && pal::fullpath(&ext)) | ||
{ | ||
// We should have the path in ext. | ||
trace::info(_X("Realpath CORE_BREADCRUMBS [%s]"), ext.c_str()); | ||
|
@@ -302,7 +302,7 @@ bool pal::get_default_servicing_directory(string_t* recv) | |
{ | ||
recv->clear(); | ||
pal::string_t ext; | ||
if (pal::getenv(_X("CORE_SERVICING"), &ext) && pal::realpath(&ext)) | ||
if (pal::getenv(_X("CORE_SERVICING"), &ext) && pal::fullpath(&ext)) | ||
{ | ||
// We should have the path in ext. | ||
trace::info(_X("Realpath CORE_SERVICING [%s]"), ext.c_str()); | ||
|
@@ -333,7 +333,7 @@ bool pal::get_default_servicing_directory(string_t* recv) | |
|
||
bool is_read_write_able_directory(pal::string_t& dir) | ||
{ | ||
return pal::realpath(&dir) && | ||
return pal::fullpath(&dir) && | ||
(access(dir.c_str(), R_OK | W_OK | X_OK) == 0); | ||
} | ||
|
||
|
@@ -945,6 +945,11 @@ bool pal::getenv(const pal::char_t* name, pal::string_t* recv) | |
return (recv->length() > 0); | ||
} | ||
|
||
bool pal::fullpath(pal::string_t* path, bool skip_error_logging) | ||
{ | ||
return realpath(path, skip_error_logging); | ||
} | ||
|
||
bool pal::realpath(pal::string_t* path, bool skip_error_logging) | ||
Comment on lines
+948
to
953
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this deserves a comment - especially since the implementation is "the other way round" from Windows. |
||
{ | ||
auto resolved = ::realpath(path->c_str(), nullptr); | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -174,7 +174,7 @@ bool pal::load_library(const string_t* in_path, dll_t* dll) | |||||
|
||||||
if (LongFile::IsPathNotFullyQualified(path)) | ||||||
{ | ||||||
if (!pal::realpath(&path)) | ||||||
if (!pal::fullpath(&path)) | ||||||
{ | ||||||
trace::error(_X("Failed to load the dll from [%s], HRESULT: 0x%X"), path.c_str(), HRESULT_FROM_WIN32(GetLastError())); | ||||||
return false; | ||||||
|
@@ -649,7 +649,7 @@ bool get_extraction_base_parent_directory(pal::string_t& directory) | |||||
assert(len < max_len); | ||||||
directory.assign(temp_path); | ||||||
|
||||||
return pal::realpath(&directory); | ||||||
return pal::fullpath(&directory); | ||||||
} | ||||||
|
||||||
bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir) | ||||||
|
@@ -663,7 +663,7 @@ bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir) | |||||
append_path(&extraction_dir, _X(".net")); | ||||||
// Windows Temp-Path is already user-private. | ||||||
|
||||||
if (realpath(&extraction_dir)) | ||||||
if (fullpath(&extraction_dir)) | ||||||
{ | ||||||
return true; | ||||||
} | ||||||
|
@@ -676,7 +676,7 @@ bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir) | |||||
return false; | ||||||
} | ||||||
|
||||||
return realpath(&extraction_dir); | ||||||
return fullpath(&extraction_dir); | ||||||
} | ||||||
|
||||||
static bool wchar_convert_helper(DWORD code_page, const char* cstr, size_t len, pal::string_t* out) | ||||||
|
@@ -728,8 +728,91 @@ bool pal::clr_palstring(const char* cstr, pal::string_t* out) | |||||
return wchar_convert_helper(CP_UTF8, cstr, ::strlen(cstr), out); | ||||||
} | ||||||
|
||||||
// Return if path is valid and file exists, return true and adjust path as appropriate. | ||||||
bool pal::realpath(string_t* path, bool skip_error_logging) | ||||||
typedef std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::CloseHandle)> SmartHandle; | ||||||
|
||||||
// Like realpath, but resolves symlinks. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should it be?
Suggested change
|
||||||
bool pal::realpath(pal::string_t* path, bool skip_error_logging) | ||||||
{ | ||||||
if (path->empty()) | ||||||
{ | ||||||
return false; | ||||||
} | ||||||
|
||||||
// Use CreateFileW + GetFinalPathNameByHandleW to resolve symlinks | ||||||
|
||||||
SmartHandle file( | ||||||
::CreateFileW( | ||||||
path->c_str(), | ||||||
0, // Querying only | ||||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | ||||||
nullptr, // default security | ||||||
OPEN_EXISTING, // existing file | ||||||
FILE_ATTRIBUTE_NORMAL, // normal file | ||||||
nullptr), // No attribute template | ||||||
&::CloseHandle); | ||||||
|
||||||
pal::char_t buf[MAX_PATH]; | ||||||
size_t size; | ||||||
|
||||||
if (file.get() == INVALID_HANDLE_VALUE) | ||||||
{ | ||||||
// If we get "access denied" that may mean the path represents a directory. | ||||||
// Even if not, we can fall back to GetFullPathNameW, which doesn't require a HANDLE | ||||||
|
||||||
auto error = ::GetLastError(); | ||||||
file.release(); | ||||||
if (ERROR_ACCESS_DENIED != error) | ||||||
{ | ||||||
goto invalidPath; | ||||||
} | ||||||
} | ||||||
else | ||||||
{ | ||||||
size = ::GetFinalPathNameByHandleW(file.get(), buf, MAX_PATH, FILE_NAME_NORMALIZED); | ||||||
// If size is 0, this call failed. Fall back to GetFullPathNameW, below | ||||||
if (size != 0) | ||||||
{ | ||||||
pal::string_t str; | ||||||
if (size < MAX_PATH) | ||||||
{ | ||||||
str.assign(buf); | ||||||
} | ||||||
else | ||||||
{ | ||||||
str.resize(size, 0); | ||||||
size = ::GetFinalPathNameByHandleW(file.get(), (LPWSTR)str.data(), static_cast<uint32_t>(size), FILE_NAME_NORMALIZED); | ||||||
assert(size <= str.size()); | ||||||
|
||||||
if (size == 0) | ||||||
{ | ||||||
goto invalidPath; | ||||||
} | ||||||
} | ||||||
|
||||||
// Remove the \\?\ prefix, unless it is necessary or was already there | ||||||
if (LongFile::IsExtended(str) && !LongFile::IsExtended(*path) && | ||||||
!LongFile::ShouldNormalize(str.substr(LongFile::ExtendedPrefix.size()))) | ||||||
{ | ||||||
str.erase(0, LongFile::ExtendedPrefix.size()); | ||||||
} | ||||||
Comment on lines
+792
to
+797
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just an observation: Can we try to make this somehow cheaper? (or maybe it's not worth it) - this will likely allocate twice ( Maybe I'm just too careful and it's not worth the complexity to optimize this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth adding a comment that the Win32 API call will typically return |
||||||
|
||||||
*path = str; | ||||||
return true; | ||||||
} | ||||||
} | ||||||
|
||||||
// If the above fails, fall back to fullpath | ||||||
return pal::fullpath(path, skip_error_logging); | ||||||
|
||||||
invalidPath: | ||||||
if (!skip_error_logging) | ||||||
{ | ||||||
trace::error(_X("Error resolving full path [%s]"), path->c_str()); | ||||||
} | ||||||
return false; | ||||||
} | ||||||
|
||||||
bool pal::fullpath(string_t* path, bool skip_error_logging) | ||||||
{ | ||||||
if (path->empty()) | ||||||
{ | ||||||
|
@@ -804,7 +887,7 @@ bool pal::realpath(string_t* path, bool skip_error_logging) | |||||
bool pal::file_exists(const string_t& path) | ||||||
{ | ||||||
string_t tmp(path); | ||||||
return pal::realpath(&tmp, true); | ||||||
return pal::fullpath(&tmp, true); | ||||||
} | ||||||
|
||||||
static void readdir(const pal::string_t& path, const pal::string_t& pattern, bool onlydirectories, std::vector<pal::string_t>* list) | ||||||
|
@@ -816,7 +899,7 @@ static void readdir(const pal::string_t& path, const pal::string_t& pattern, boo | |||||
|
||||||
if (LongFile::ShouldNormalize(normalized_path)) | ||||||
{ | ||||||
if (!pal::realpath(&normalized_path)) | ||||||
if (!pal::fullpath(&normalized_path)) | ||||||
{ | ||||||
return; | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -98,10 +98,12 @@ bool set_root_from_app(const pal::string_t& managed_application_path, | |
// for very unlikely case where the main app.dll was itself excluded from the app bundle. | ||
// Note that unlike non-single-file we don't want to set the app_root to the location of the app.dll | ||
// it needs to stay the location of the single-file bundle. | ||
return pal::realpath(&args.managed_application); | ||
// We use fullpath here instead of realpath since we don't care about the path post-symlink resolution. | ||
return pal::fullpath(&args.managed_application); | ||
} | ||
|
||
if (pal::realpath(&args.managed_application)) | ||
// We use realpath here because we want the final path through symlinks for the app_root. | ||
if (pal::fullpath(&args.managed_application)) | ||
Comment on lines
+105
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment and code disagree here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apparently we're missing a test for this case. |
||
{ | ||
args.app_root = get_directory(args.managed_application); | ||
return true; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we would add tests for all use cases of
realpath
in the code.