/** * Given a version and probing paths, find if package layout * directory containing hostpolicy exists. */ bool resolve_hostpolicy_dir_from_probe_paths(const pal::string_t& version, const std::vector<pal::string_t>& probe_realpaths, pal::string_t* candidate) { if (probe_realpaths.empty() || version.empty()) { return false; } // Check if the package relative directory containing hostpolicy exists. for (const auto& probe_path : probe_realpaths) { trace::verbose(_X("Considering %s to probe for %s"), probe_path.c_str(), LIBHOSTPOLICY_NAME); if (to_hostpolicy_package_dir(probe_path, version, candidate)) { return true; } } // Print detailed message about the file not found in the probe paths. trace::error(_X("Could not find required library %s in %d probing paths:"), LIBHOSTPOLICY_NAME, probe_realpaths.size()); for (const auto& path : probe_realpaths) { trace::error(_X(" %s"), path.c_str()); } return false; }
void get_framework_and_sdk_locations(const pal::string_t& dotnet_dir, std::vector<pal::string_t>* locations) { bool multilevel_lookup = multilevel_lookup_enabled(); // Multi-level lookup will look for the most appropriate version in several locations // by following the priority rank below: // .exe directory // Global .NET directories // If it is not activated, then only .exe directory will be considered pal::string_t dotnet_dir_temp; if (!dotnet_dir.empty()) { // own_dir contains DIR_SEPARATOR appended that we need to remove. dotnet_dir_temp = dotnet_dir; remove_trailing_dir_seperator(&dotnet_dir_temp); locations->push_back(dotnet_dir_temp); } std::vector<pal::string_t> global_dirs; if (multilevel_lookup && pal::get_global_dotnet_dirs(&global_dirs)) { for (pal::string_t dir : global_dirs) { // avoid duplicate paths if (!pal::are_paths_equal_with_normalized_casing(dir, dotnet_dir_temp)) { locations->push_back(dir); } } } }
/** * Given a directory and a version, find if the package relative * dir under the given directory contains hostpolicy.dll */ bool to_hostpolicy_package_dir(const pal::string_t& dir, const pal::string_t& version, pal::string_t* candidate) { assert(!version.empty()); candidate->clear(); // Ensure the relative dir contains platform directory separators. pal::string_t rel_dir = _STRINGIFY(HOST_POLICY_PKG_REL_DIR); if (DIR_SEPARATOR != '/') { replace_char(&rel_dir, '/', DIR_SEPARATOR); } // Construct the path to directory containing hostpolicy. pal::string_t path = dir; append_path(&path, _STRINGIFY(HOST_POLICY_PKG_NAME)); // package name append_path(&path, version.c_str()); // package version append_path(&path, rel_dir.c_str()); // relative dir containing hostpolicy library // Check if "path" contains the required library. if (!library_exists_in_dir(path, LIBHOSTPOLICY_NAME, nullptr)) { trace::verbose(_X("Did not find %s in directory %s"), LIBHOSTPOLICY_NAME, path.c_str()); return false; } // "path" contains the directory containing hostpolicy library. *candidate = path; trace::verbose(_X("Found %s in directory %s"), LIBHOSTPOLICY_NAME, path.c_str()); return true; }
bool pal::file_exists(const pal::string_t& path) { if (path.empty()) { return false; } struct stat buffer; return (::stat(path.c_str(), &buffer) == 0); }
bool starts_with(const pal::string_t& value, const pal::string_t& prefix, bool match_case) { if (prefix.empty()) { // Cannot start with an empty string. return false; } auto cmp = match_case ? pal::strncmp : pal::strncasecmp; return (value.size() >= prefix.size()) && cmp(value.c_str(), prefix.c_str(), prefix.size()) == 0; }
/** * Given a nuget version, detect if a serviced hostpolicy is available at * platform servicing location. */ bool hostpolicy_exists_in_svc(const pal::string_t& version, pal::string_t* resolved_dir) { if (version.empty()) { return false; } pal::string_t svc_dir; pal::get_default_servicing_directory(&svc_dir); append_path(&svc_dir, _X("pkgs")); return to_hostpolicy_package_dir(svc_dir, version, resolved_dir); }
pal::string_t get_filename_without_ext(const pal::string_t& path) { if (path.empty()) { return path; } size_t name_pos = path.find_last_of(_X("/\\")); size_t dot_pos = path.rfind(_X('.')); size_t start_pos = (name_pos == pal::string_t::npos) ? 0 : (name_pos + 1); size_t count = (dot_pos == pal::string_t::npos || dot_pos < start_pos) ? pal::string_t::npos : (dot_pos - start_pos); return path.substr(start_pos, count); }
bool try_stou(const pal::string_t& str, unsigned* num) { if (str.empty()) { return false; } if (index_of_non_numeric(str, 0) != pal::string_t::npos) { return false; } *num = (unsigned)std::stoul(str); return true; }
bool try_stou(const pal::string_t& str, unsigned* num) { if (str.empty()) { return false; } if (str.find_first_not_of(_X("0123456789")) != pal::string_t::npos) { return false; } *num = (unsigned) std::stoul(str); return true; }
pal::string_t strip_file_ext(const pal::string_t& path) { if (path.empty()) { return path; } size_t sep_pos = path.rfind(_X("/\\")); size_t dot_pos = path.rfind(_X('.')); if (sep_pos != pal::string_t::npos && sep_pos > dot_pos) { return path; } return path.substr(0, dot_pos); }
//For longpath names on windows, if the paths are normalized they are always prefixed with //extended syntax, Windows does not do any more normalizations on this string and uses it as is //So we should ensure that there are NO adjacent DirectorySeparatorChar bool AssertRepeatingDirSeparator(const pal::string_t& path) { if (path.empty()) return true; pal::string_t path_to_check = path; if (LongFile::IsDevice(path)) { path_to_check.erase(0, LongFile::DevicePathPrefix.length()); } else if (LongFile::IsExtended(path)) { path_to_check.erase(0, LongFile::ExtendedPrefix.length()); } else if (LongFile::IsUNCExtended(path)) { path_to_check.erase(0, LongFile::UNCExtendedPathPrefix.length()); } else if (path_to_check.compare(0, LongFile::UNCPathPrefix.length(), LongFile::UNCPathPrefix) == 0) { path_to_check.erase(0, LongFile::UNCPathPrefix.length()); } pal::string_t dirSeparator; dirSeparator.push_back(LongFile::DirectorySeparatorChar); dirSeparator.push_back(LongFile::DirectorySeparatorChar); assert(path_to_check.find(dirSeparator) == pal::string_t::npos); pal::string_t altDirSeparator; altDirSeparator.push_back(LongFile::AltDirectorySeparatorChar); altDirSeparator.push_back(LongFile::AltDirectorySeparatorChar); assert(path_to_check.find(altDirSeparator) == pal::string_t::npos); pal::string_t combDirSeparator1; combDirSeparator1.push_back(LongFile::DirectorySeparatorChar); combDirSeparator1.push_back(LongFile::AltDirectorySeparatorChar); assert(path_to_check.find(combDirSeparator1) == pal::string_t::npos); pal::string_t combDirSeparator2; combDirSeparator2.push_back(LongFile::AltDirectorySeparatorChar); combDirSeparator2.push_back(LongFile::DirectorySeparatorChar); assert(path_to_check.find(combDirSeparator2) == pal::string_t::npos); assert(path_to_check.find(_X("..")) == pal::string_t::npos); return true; }
/** * Given path to app binary, say app.dll or app.exe, retrieve the app.deps.json. */ pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal::string_t& app) { pal::string_t deps_file; auto app_name = get_filename(app); deps_file.reserve(app_base.length() + 1 + app_name.length() + 5); deps_file.append(app_base); if (!app_base.empty() && app_base.back() != DIR_SEPARATOR) { deps_file.push_back(DIR_SEPARATOR); } deps_file.append(app_name, 0, app_name.find_last_of(_X("."))); deps_file.append(_X(".deps.json")); return deps_file; }
pal::string_t get_filename(const pal::string_t& path) { if (path.empty()) { return path; } auto name_pos = path.find_last_of(DIR_SEPARATOR); if (name_pos == pal::string_t::npos) { return path; } return path.substr(name_pos + 1); }
// ----------------------------------------------------------------------------- // Given a "base" directory, yield the relative path of this file in the package // layout. // // Parameters: // base - The base directory to look for the relative path of this entry // str - If the method returns true, contains the file path for this deps // entry relative to the "base" directory // // Returns: // If the file exists in the path relative to the "base" directory. // bool deps_entry_t::to_full_path(const pal::string_t& base, pal::string_t* str) const { str->clear(); // Base directory must be present to obtain full path if (base.empty()) { return false; } pal::string_t new_base = base; append_path(&new_base, library_name.c_str()); append_path(&new_base, library_version.c_str()); return to_rel_path(new_base, str); }
/** * Given FX location, app binary and specified --depsfile, return deps that contains hostpolicy.dll */ pal::string_t get_deps_file( const pal::string_t& fx_dir, const pal::string_t& app_candidate, const pal::string_t& specified_deps_file, const runtime_config_t& config) { if (config.get_portable()) { // Portable app's hostpolicy is resolved from FX deps return fx_dir + DIR_SEPARATOR + config.get_fx_name() + _X(".deps.json"); } else { // Standalone app's hostpolicy is from specified deps or from app deps. return !specified_deps_file.empty() ? specified_deps_file : get_deps_from_app_binary(app_candidate); } }
bool deps_entry_t::to_path(const pal::string_t& base, bool look_in_base, pal::string_t* str) const { pal::string_t& candidate = *str; candidate.clear(); // Base directory must be present to obtain full path if (base.empty()) { return false; } // Entry relative path contains '/' separator, sanitize it to use // platform separator. Perf: avoid extra copy if it matters. pal::string_t pal_relative_path = relative_path; if (_X('/') != DIR_SEPARATOR) { replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR); } // Reserve space for the path below candidate.reserve(base.length() + pal_relative_path.length() + 3); candidate.assign(base); pal::string_t sub_path = look_in_base ? get_filename(pal_relative_path) : pal_relative_path; append_path(&candidate, sub_path.c_str()); bool exists = pal::file_exists(candidate); const pal::char_t* query_type = look_in_base ? _X("Local") : _X("Relative"); if (!exists) { trace::verbose(_X(" %s path query did not exist %s"), query_type, candidate.c_str()); candidate.clear(); } else { trace::verbose(_X(" %s path query exists %s"), query_type, candidate.c_str()); } return exists; }
/** * Given own location, FX location, app binary and specified --depsfile and probe paths * return location that is expected to contain hostpolicy */ bool fx_muxer_t::resolve_hostpolicy_dir(host_mode_t mode, const pal::string_t& own_dir, const pal::string_t& fx_dir, const pal::string_t& app_candidate, const pal::string_t& specified_deps_file, const pal::string_t& specified_fx_version, const std::vector<pal::string_t>& probe_realpaths, const runtime_config_t& config, pal::string_t* impl_dir) { // Obtain deps file for the given configuration. pal::string_t resolved_deps = get_deps_file(fx_dir, app_candidate, specified_deps_file, config); // Resolve hostpolicy version out of the deps file. pal::string_t version = resolve_hostpolicy_version_from_deps(resolved_deps); if (trace::is_enabled() && version.empty() && pal::file_exists(resolved_deps)) { trace::warning(_X("Dependency manifest %s does not contain an entry for %s"), resolved_deps.c_str(), _STRINGIFY(HOST_POLICY_PKG_NAME)); } // Check if the given version of the hostpolicy exists in servicing. if (hostpolicy_exists_in_svc(version, impl_dir)) { return true; } // Get the expected directory that would contain hostpolicy. pal::string_t expected; if (config.get_portable()) { if (!pal::directory_exists(fx_dir)) { pal::string_t fx_version = specified_fx_version.empty() ? config.get_fx_version() : specified_fx_version; handle_missing_framework_error(config.get_fx_name(), fx_version, fx_dir); return false; } expected = fx_dir; } else { // Standalone apps can be activated by muxer or by standalone host or "corehost" // 1. When activated with dotnet.exe or corehost.exe, check for hostpolicy in the deps dir or // app dir. // 2. When activated with app.exe, the standalone host, check own directory. assert(mode == host_mode_t::muxer || mode == host_mode_t::standalone || mode == host_mode_t::split_fx); expected = (mode == host_mode_t::standalone) ? own_dir : get_directory(specified_deps_file.empty() ? app_candidate : specified_deps_file); } // Check if hostpolicy exists in "expected" directory. trace::verbose(_X("The expected %s directory is [%s]"), LIBHOSTPOLICY_NAME, expected.c_str()); if (library_exists_in_dir(expected, LIBHOSTPOLICY_NAME, nullptr)) { impl_dir->assign(expected); return true; } trace::verbose(_X("The %s was not found in [%s]"), LIBHOSTPOLICY_NAME, expected.c_str()); // Start probing for hostpolicy in the specified probe paths. pal::string_t candidate; if (resolve_hostpolicy_dir_from_probe_paths(version, probe_realpaths, &candidate)) { impl_dir->assign(candidate); return true; } // If it still couldn't be found, somebody upstack messed up. Flag an error for the "expected" location. trace::error(_X("A fatal error was encountered. The library '%s' required to execute the application was not found in '%s'."), LIBHOSTPOLICY_NAME, expected.c_str()); return false; }
pal::string_t fx_muxer_t::resolve_fx_dir(host_mode_t mode, const pal::string_t& own_dir, const runtime_config_t& config, const pal::string_t& specified_fx_version) { // No FX resolution for standalone apps. assert(mode != host_mode_t::standalone); // If invoking using FX dotnet.exe, use own directory. if (mode == host_mode_t::split_fx) { return own_dir; } assert(mode == host_mode_t::muxer); trace::verbose(_X("--- Resolving FX directory from muxer dir '%s', specified '%s'"), own_dir.c_str(), specified_fx_version.c_str()); const auto fx_name = config.get_fx_name(); const auto fx_ver = specified_fx_version.empty() ? config.get_fx_version() : specified_fx_version; fx_ver_t specified(-1, -1, -1); if (!fx_ver_t::parse(fx_ver, &specified, false)) { trace::error(_X("The specified framework version '%s' could not be parsed"), fx_ver.c_str()); return pal::string_t(); } auto fx_dir = own_dir; append_path(&fx_dir, _X("shared")); append_path(&fx_dir, fx_name.c_str()); bool do_roll_forward = false; if (specified_fx_version.empty()) { if (!specified.is_prerelease()) { // If production and no roll forward use given version. do_roll_forward = config.get_patch_roll_fwd(); } else { // Prerelease, but roll forward only if version doesn't exist. pal::string_t ver_dir = fx_dir; append_path(&ver_dir, fx_ver.c_str()); do_roll_forward = !pal::directory_exists(ver_dir); } } if (!do_roll_forward) { trace::verbose(_X("Did not roll forward because specified version='%s', patch_roll_fwd=%d, chose [%s]"), specified_fx_version.c_str(), config.get_patch_roll_fwd(), fx_ver.c_str()); append_path(&fx_dir, fx_ver.c_str()); } else { trace::verbose(_X("Attempting FX roll forward starting from [%s]"), fx_ver.c_str()); std::vector<pal::string_t> list; pal::readdir(fx_dir, &list); fx_ver_t most_compatible = specified; for (const auto& version : list) { trace::verbose(_X("Inspecting version... [%s]"), version.c_str()); fx_ver_t ver(-1, -1, -1); if (!specified.is_prerelease() && fx_ver_t::parse(version, &ver, true) && // true -- only prod. prevents roll forward to prerelease. ver.get_major() == specified.get_major() && ver.get_minor() == specified.get_minor()) { // Pick the greatest production that differs only in patch. most_compatible = std::max(ver, most_compatible); } if (specified.is_prerelease() && fx_ver_t::parse(version, &ver, false) && // false -- implies both production and prerelease. ver.is_prerelease() && // prevent roll forward to production. ver.get_major() == specified.get_major() && ver.get_minor() == specified.get_minor() && ver.get_patch() == specified.get_patch() && ver > specified) { // Pick the smallest prerelease that is greater than specified. most_compatible = (most_compatible == specified) ? ver : std::min(ver, most_compatible); } } pal::string_t most_compatible_str = most_compatible.as_str(); append_path(&fx_dir, most_compatible_str.c_str()); } trace::verbose(_X("Chose FX version [%s]"), fx_dir.c_str()); return fx_dir; }
// ----------------------------------------------------------------------------- // Given a "base" directory, yield the relative path of this file in the package // layout if the entry hash matches the hash file in the "base" directory // // Parameters: // base - The base directory to look for the relative path of this entry and // the hash file. // str - If the method returns true, contains the file path for this deps // entry relative to the "base" directory // // Description: // Looks for a file named "{PackageName}.{PackageVersion}.nupkg.{HashAlgorithm}" // If the deps entry's {HashAlgorithm}-{HashValue} matches the contents then // yields the relative path of this entry in the "base" dir. // // Returns: // If the file exists in the path relative to the "base" directory and there // was hash file match with this deps entry. // // See: to_full_path(base, str) // bool deps_entry_t::to_hash_matched_path(const pal::string_t& base, pal::string_t* str) const { pal::string_t& candidate = *str; candidate.clear(); // Base directory must be present to perform hash lookup. if (base.empty()) { return false; } // First detect position of hyphen in [Algorithm]-[Hash] in the string. size_t pos = library_hash.find(_X("-")); if (pos == 0 || pos == pal::string_t::npos) { trace::verbose(_X("Invalid hash %s value for deps file entry: %s"), library_hash.c_str(), library_name.c_str()); return false; } // Build the relative hash path (what is added to the package directory path). pal::string_t relative_hash_path; if (library_hash_path.empty()) { // Reserve approx 8 char_t's for the algorithm name. relative_hash_path.reserve(library_name.length() + 1 + library_version.length() + 16); relative_hash_path.append(library_name); relative_hash_path.append(_X(".")); relative_hash_path.append(library_version); relative_hash_path.append(_X(".nupkg.")); relative_hash_path.append(library_hash.substr(0, pos)); } else { relative_hash_path.assign(library_hash_path); } // Build the directory that contains the hash file. pal::string_t hash_file; if (library_path.empty()) { hash_file.reserve(base.length() + 1 + library_name.length() + 1 + library_version.length() + 1 + relative_hash_path.length()); hash_file.assign(base); append_path(&hash_file, library_name.c_str()); append_path(&hash_file, library_version.c_str()); } else { hash_file.reserve(base.length() + 1 + library_path.length() + 1 + relative_hash_path.length()); hash_file.assign(base); append_path(&hash_file, library_path.c_str()); } // Append the relative path to the hash file. append_path(&hash_file, relative_hash_path.c_str()); // Read the contents of the hash file. pal::ifstream_t fstream(hash_file); if (!fstream.good()) { trace::verbose(_X("The hash file is invalid [%s]"), hash_file.c_str()); return false; } // Obtain the hash from the file. std::string hash; hash.assign(pal::istreambuf_iterator_t(fstream), pal::istreambuf_iterator_t()); pal::string_t pal_hash; if (!pal::utf8_palstring(hash.c_str(), &pal_hash)) { return false; } // Check if contents match deps entry. pal::string_t entry_hash = library_hash.substr(pos + 1); if (entry_hash != pal_hash) { trace::verbose(_X("The file hash [%s][%d] did not match entry hash [%s][%d]"), pal_hash.c_str(), pal_hash.length(), entry_hash.c_str(), entry_hash.length()); return false; } // All good, just append the relative dir to base. return to_full_path(base, &candidate); }
bool LongFile::IsNormalized(const pal::string_t& path) { return path.empty() || LongFile::IsDevice(path) || LongFile::IsExtended(path) || LongFile::IsUNCExtended(path); }