Redesign corehost per spec

This commit is contained in:
Senthil 2015-12-10 19:06:55 -08:00
parent 12351cd37b
commit 504e109947
16 changed files with 1159 additions and 526 deletions

View file

@ -10,14 +10,16 @@ include_directories(inc)
set(SOURCES set(SOURCES
src/args.cpp src/args.cpp
src/main.cpp src/main.cpp
src/tpafile.cpp src/deps_resolver.cpp
src/trace.cpp src/trace.cpp
src/utils.cpp src/utils.cpp
src/coreclr.cpp src/coreclr.cpp
src/servicing_index.cpp
inc/args.h inc/args.h
inc/pal.h inc/pal.h
inc/tpafile.h inc/servicing_index.h
inc/deps_resolver.h
inc/trace.h inc/trace.h
inc/coreclr.h inc/coreclr.h
inc/utils.h) inc/utils.h)

View file

@ -4,14 +4,20 @@
#ifndef ARGS_H #ifndef ARGS_H
#define ARGS_H #define ARGS_H
#include "utils.h"
#include "pal.h" #include "pal.h"
#include "trace.h" #include "trace.h"
struct arguments_t struct arguments_t
{ {
pal::string_t own_path; pal::string_t own_path;
pal::string_t app_dir;
pal::string_t dotnet_servicing;
pal::string_t dotnet_runtime_servicing;
pal::string_t dotnet_home;
pal::string_t dotnet_packages;
pal::string_t dotnet_packages_cache;
pal::string_t managed_application; pal::string_t managed_application;
pal::string_t clr_path;
int app_argc; int app_argc;
const pal::char_t** app_argv; const pal::char_t** app_argv;

View file

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#ifndef DEPS_RESOLVER_H
#define DEPS_RESOLVER_H
#include <vector>
#include "pal.h"
#include "trace.h"
#include "servicing_index.h"
struct deps_entry_t
{
pal::string_t library_type;
pal::string_t library_name;
pal::string_t library_version;
pal::string_t library_hash;
pal::string_t asset_type;
pal::string_t asset_name;
pal::string_t relative_path;
bool is_serviceable;
// Given a "base" dir, yield the relative path in the package layout.
bool to_full_path(const pal::string_t& root, pal::string_t* str) const;
// Given a "base" dir, yield the relative path in the package layout only if
// the hash matches contents of the hash file.
bool to_hash_matched_path(const pal::string_t& root, pal::string_t* str) const;
};
// Probe paths to be resolved for ordering
struct probe_paths_t
{
pal::string_t tpa;
pal::string_t native;
pal::string_t culture;
};
class deps_resolver_t
{
public:
deps_resolver_t(const arguments_t& args)
: m_svc(args.dotnet_servicing)
{
m_deps_valid = parse_deps_file(args);
}
bool valid() { return m_deps_valid; }
bool resolve_probe_paths(
const pal::string_t& app_dir,
const pal::string_t& package_dir,
const pal::string_t& package_cache_dir,
const pal::string_t& clr_dir,
probe_paths_t* probe_paths);
private:
bool load();
bool parse_deps_file(const arguments_t& args);
// Resolve order for TPA lookup.
void resolve_tpa_list(
const pal::string_t& app_dir,
const pal::string_t& package_dir,
const pal::string_t& package_cache_dir,
const pal::string_t& clr_dir,
pal::string_t* output);
// Resolve order for culture and native DLL lookup.
void resolve_probe_dirs(
const pal::string_t& asset_type,
const pal::string_t& app_dir,
const pal::string_t& package_dir,
const pal::string_t& package_cache_dir,
const pal::string_t& clr_dir,
pal::string_t* output);
// Populate local assemblies from app_dir listing.
void get_local_assemblies(const pal::string_t& dir);
// Servicing index to resolve serviced assembly paths.
servicing_index_t m_svc;
// Map of simple name -> full path of local assemblies populated in priority
// order of their extensions.
std::unordered_map<pal::string_t, pal::string_t> m_local_assemblies;
// Entries in the dep file
std::vector<deps_entry_t> m_deps_entries;
// The dep file path
pal::string_t m_deps_path;
// Is the deps file valid
bool m_deps_valid;
};
#endif // DEPS_RESOLVER_H

View file

@ -12,6 +12,9 @@
#include <cstring> #include <cstring>
#include <cstdarg> #include <cstdarg>
#include <tuple> #include <tuple>
#include <unordered_map>
#include <memory>
#include <algorithm>
#if defined(_WIN32) #if defined(_WIN32)
@ -65,7 +68,13 @@ namespace pal
typedef wchar_t char_t; typedef wchar_t char_t;
typedef std::wstring string_t; typedef std::wstring string_t;
typedef std::wstringstream stringstream_t; typedef std::wstringstream stringstream_t;
typedef std::ifstream ifstream_t; // TODO: Agree on the correct encoding of the files: The PoR for now is to
// temporarily wchar for Windows and char for Unix. Current implementation
// implicitly expects the contents on both Windows and Unix as char and
// converts them to wchar in code for Windows. This line should become:
// typedef std::basic_ifstream<pal::char_t> ifstream_t.
typedef std::basic_ifstream<char> ifstream_t;
typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
typedef HRESULT hresult_t; typedef HRESULT hresult_t;
typedef HMODULE dll_t; typedef HMODULE dll_t;
typedef FARPROC proc_t; typedef FARPROC proc_t;
@ -77,11 +86,14 @@ namespace pal
pal::string_t to_palstring(const std::string& str); pal::string_t to_palstring(const std::string& str);
std::string to_stdstring(const pal::string_t& str); std::string to_stdstring(const pal::string_t& str);
void to_palstring(const char* str, pal::string_t* out);
void to_stdstring(const pal::char_t* str, std::string* out);
#else #else
typedef char char_t; typedef char char_t;
typedef std::string string_t; typedef std::string string_t;
typedef std::stringstream stringstream_t; typedef std::stringstream stringstream_t;
typedef std::ifstream ifstream_t; typedef std::basic_ifstream<char> ifstream_t;
typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
typedef int hresult_t; typedef int hresult_t;
typedef void* dll_t; typedef void* dll_t;
typedef void* proc_t; typedef void* proc_t;
@ -92,24 +104,26 @@ namespace pal
inline void err_vprintf(const char_t* format, va_list vl) { ::vfprintf(stderr, format, vl); ::fputc('\n', stderr); } inline void err_vprintf(const char_t* format, va_list vl) { ::vfprintf(stderr, format, vl); ::fputc('\n', stderr); }
inline pal::string_t to_palstring(const std::string& str) { return str; } inline pal::string_t to_palstring(const std::string& str) { return str; }
inline std::string to_stdstring(const pal::string_t& str) { return str; } inline std::string to_stdstring(const pal::string_t& str) { return str; }
inline void to_palstring(const char* str, pal::string_t* out) { out->assign(str); }
inline void to_stdstring(const pal::char_t* str, std::string* out) { out->assign(str); }
#endif #endif
bool realpath(string_t* path);
bool realpath(string_t& path);
bool file_exists(const string_t& path); bool file_exists(const string_t& path);
std::vector<pal::string_t> readdir(const string_t& path); inline bool directory_exists(const string_t& path) { return file_exists(path); }
void readdir(const string_t& path, std::vector<pal::string_t>* list);
bool get_own_executable_path(string_t& recv); bool get_own_executable_path(string_t* recv);
bool getenv(const char_t* name, string_t& recv); bool getenv(const char_t* name, string_t* recv);
bool get_default_packages_directory(string_t& recv); bool get_default_packages_directory(string_t* recv);
bool is_path_rooted(const string_t& path); bool is_path_rooted(const string_t& path);
int xtoi(const char_t* input); int xtoi(const char_t* input);
bool load_library(const char_t* path, dll_t& dll); bool load_library(const char_t* path, dll_t* dll);
proc_t get_symbol(dll_t library, const char* name); proc_t get_symbol(dll_t library, const char* name);
void unload_library(dll_t library); void unload_library(dll_t library);
bool find_coreclr(pal::string_t& recv); bool find_coreclr(pal::string_t* recv);
} }
#endif // PAL_H #endif // PAL_H

View file

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include "utils.h"
#include "args.h"
class servicing_index_t
{
public:
servicing_index_t(const pal::string_t& svc_dir);
bool find_redirection(const pal::string_t& package_name,
const pal::string_t& package_version,
const pal::string_t& package_relative,
pal::string_t* redirection);
private:
void ensure_redirections();
std::unordered_map<pal::string_t, pal::string_t> m_redirections;
pal::string_t m_patch_root;
pal::string_t m_index_file;
bool m_parsed;
};

View file

@ -1,41 +0,0 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#ifndef TPAFILE_H
#define TPAFILE_H
#include <vector>
#include "pal.h"
#include "trace.h"
struct tpaentry_t
{
pal::string_t library_type;
pal::string_t library_name;
pal::string_t library_version;
pal::string_t library_hash;
pal::string_t asset_type;
pal::string_t asset_name;
pal::string_t relative_path;
};
class tpafile
{
public:
bool load(pal::string_t path);
void add_from_local_dir(const pal::string_t& dir);
void add_package_dir(pal::string_t dir);
void add_native_search_path(pal::string_t dir);
void write_tpa_list(pal::string_t& output);
void write_native_paths(pal::string_t& output);
private:
std::vector<tpaentry_t> m_entries;
std::vector<pal::string_t> m_native_search_paths;
std::vector<pal::string_t> m_package_search_paths;
};
#endif // TPAFILE_H

View file

@ -10,7 +10,7 @@ bool ends_with(const pal::string_t& value, const pal::string_t& suffix);
pal::string_t get_executable(const pal::string_t& filename); pal::string_t get_executable(const pal::string_t& filename);
pal::string_t get_directory(const pal::string_t& path); pal::string_t get_directory(const pal::string_t& path);
pal::string_t get_filename(const pal::string_t& path); pal::string_t get_filename(const pal::string_t& path);
void append_path(pal::string_t& path1, const pal::char_t* path2); void append_path(pal::string_t* path1, const pal::char_t* path2);
bool coreclr_exists_in_dir(const pal::string_t& candidate); bool coreclr_exists_in_dir(const pal::string_t& candidate);
void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl);
#endif #endif

View file

@ -3,10 +3,12 @@
#include "args.h" #include "args.h"
#include "utils.h" #include "utils.h"
#include "coreclr.h"
arguments_t::arguments_t() : arguments_t::arguments_t() :
managed_application(_X("")), managed_application(_X("")),
clr_path(_X("")), own_path(_X("")),
app_dir(_X("")),
app_argc(0), app_argc(0),
app_argv(nullptr) app_argv(nullptr)
{ {
@ -24,10 +26,22 @@ void display_help()
bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& args) bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& args)
{ {
// Get the full name of the application // Read trace environment variable
if (!pal::get_own_executable_path(args.own_path) || !pal::realpath(args.own_path)) pal::string_t trace_str;
if (pal::getenv(_X("COREHOST_TRACE"), &trace_str))
{ {
trace::error(_X("failed to locate current executable")); auto trace_val = pal::xtoi(trace_str.c_str());
if (trace_val > 0)
{
trace::enable();
trace::info(_X("Tracing enabled"));
}
}
// Get the full name of the application
if (!pal::get_own_executable_path(&args.own_path) || !pal::realpath(&args.own_path))
{
trace::error(_X("Failed to locate current executable"));
return false; return false;
} }
@ -43,6 +57,12 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg
return false; return false;
} }
args.managed_application = pal::string_t(argv[1]); args.managed_application = pal::string_t(argv[1]);
if (!pal::realpath(&args.managed_application))
{
trace::error(_X("Failed to locate managed application: %s"), args.managed_application.c_str());
return false;
}
args.app_dir = get_directory(args.managed_application);
args.app_argc = argc - 2; args.app_argc = argc - 2;
args.app_argv = &argv[2]; args.app_argv = &argv[2];
} }
@ -54,39 +74,20 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg
managed_app.append(get_executable(own_name)); managed_app.append(get_executable(own_name));
managed_app.append(_X(".dll")); managed_app.append(_X(".dll"));
args.managed_application = managed_app; args.managed_application = managed_app;
if (!pal::realpath(&args.managed_application))
{
trace::error(_X("Failed to locate managed application: %s"), args.managed_application.c_str());
return false;
}
args.app_dir = own_dir;
args.app_argv = &argv[1]; args.app_argv = &argv[1];
args.app_argc = argc - 1; args.app_argc = argc - 1;
} }
// Read trace environment variable pal::getenv(_X("DOTNET_PACKAGES"), &args.dotnet_packages);
pal::string_t trace_str; pal::getenv(_X("DOTNET_PACKAGES_CACHE"), &args.dotnet_packages_cache);
if (pal::getenv(_X("COREHOST_TRACE"), trace_str)) pal::getenv(_X("DOTNET_SERVICING"), &args.dotnet_servicing);
{ pal::getenv(_X("DOTNET_RUNTIME_SERVICING"), &args.dotnet_runtime_servicing);
auto trace_val = pal::xtoi(trace_str.c_str()); pal::getenv(_X("DOTNET_HOME"), &args.dotnet_home);
if (trace_val > 0)
{
trace::enable();
trace::info(_X("tracing enabled"));
}
}
// Read CLR path from environment variable
pal::string_t home_str;
pal::getenv(_X("DOTNET_HOME"), home_str);
if (!home_str.empty())
{
append_path(home_str, _X("runtime"));
append_path(home_str, _X("coreclr"));
args.clr_path.assign(home_str);
}
else
{
// Use platform-specific search algorithm
if (pal::find_coreclr(home_str))
{
args.clr_path.assign(home_str);
}
}
return true; return true;
} }

View file

@ -41,9 +41,9 @@ bool coreclr::bind(const pal::string_t& libcoreclr_path)
assert(g_coreclr == nullptr); assert(g_coreclr == nullptr);
pal::string_t coreclr_dll_path(libcoreclr_path); pal::string_t coreclr_dll_path(libcoreclr_path);
append_path(coreclr_dll_path, LIBCORECLR_NAME); append_path(&coreclr_dll_path, LIBCORECLR_NAME);
if (!pal::load_library(coreclr_dll_path.c_str(), g_coreclr)) if (!pal::load_library(coreclr_dll_path.c_str(), &g_coreclr))
{ {
return false; return false;
} }

View file

@ -0,0 +1,597 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include <set>
#include <functional>
#include <cassert>
#include "trace.h"
#include "deps_resolver.h"
#include "utils.h"
namespace
{
// -----------------------------------------------------------------------------
// Read a single field from the deps entry
//
// Parameters:
// line - A deps file entry line
// buf - The temporary buffer to use while parsing (with size to contain "line")
// ofs - The offset that this method will read from "line" on invocation and
// the offset that has been consumed by this method upon successful exit
// field - The current field read from the line
//
// Assumption:
// The line should be in a CSV format, with commas separating the fields.
// The fields themselves will be quoted. The escape character is '\\'
//
// Returns:
// True if parsed successfully. Else, false
//
// Note:
// Callers cannot call with the same "line" upon an unsuccessful exit.
bool read_field(const pal::string_t& line, pal::char_t* buf, unsigned* ofs, pal::string_t* field)
{
unsigned& offset = *ofs;
pal::string_t& value_recv = *field;
// The first character should be a '"'
if (line[offset] != '"')
{
trace::error(_X("Error reading TPA file"));
return false;
}
offset++;
auto buf_offset = 0;
// Iterate through characters in the string
for (; offset < line.length(); offset++)
{
// Is this a '\'?
if (line[offset] == '\\')
{
// Skip this character and read the next character into the buffer
offset++;
buf[buf_offset] = line[offset];
}
// Is this a '"'?
else if (line[offset] == '\"')
{
// Done! Advance to the pointer after the input
offset++;
break;
}
else
{
// Take the character
buf[buf_offset] = line[offset];
}
buf_offset++;
}
buf[buf_offset] = '\0';
value_recv.assign(buf);
// Consume the ',' if we have one
if (line[offset] == ',')
{
offset++;
}
return true;
}
// -----------------------------------------------------------------------------
// A uniqifying append helper that doesn't let two entries with the same
// "asset_name" be part of the "output" paths.
//
void add_tpa_asset(
const pal::string_t& asset_name,
const pal::string_t& asset_path,
std::set<pal::string_t>* items,
pal::string_t* output)
{
if (items->count(asset_name))
{
return;
}
trace::verbose(_X("Adding tpa entry: %s"), asset_path.c_str());
output->append(asset_path);
output->push_back(PATH_SEPARATOR);
items->insert(asset_name);
}
// -----------------------------------------------------------------------------
// Add mscorlib from the CLR directory. Even if CLR is serviced, we should pick
// mscorlib from the CLR directory. If mscorlib could not be found in the CLR
// location, then leave it to the CLR to pick the right mscorlib.
//
void add_mscorlib_to_tpa(const pal::string_t& clr_dir, std::set<pal::string_t>* items, pal::string_t* output)
{
pal::string_t mscorlib_ni_path = clr_dir + DIR_SEPARATOR + _X("mscorlib.ni.dll");
if (pal::file_exists(mscorlib_ni_path))
{
add_tpa_asset(_X("mscorlib"), mscorlib_ni_path, items, output);
return;
}
pal::string_t mscorlib_path = clr_dir + DIR_SEPARATOR + _X("mscorlib.dll");
if (pal::file_exists(mscorlib_path))
{
add_tpa_asset(_X("mscorlib"), mscorlib_ni_path, items, output);
return;
}
}
// -----------------------------------------------------------------------------
// A uniqifying append helper that doesn't let two "paths" to be identical in
// the "output" string.
//
void add_unique_path(
const pal::string_t& type,
const pal::string_t& path,
std::set<pal::string_t>* existing,
pal::string_t* output)
{
if (existing->count(path))
{
return;
}
trace::verbose(_X("Adding to %s path: %s"), type.c_str(), path.c_str());
output->append(path);
output->push_back(PATH_SEPARATOR);
existing->insert(path);
}
} // end of anonymous namespace
// -----------------------------------------------------------------------------
// 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
{
pal::string_t& candidate = *str;
candidate.clear();
// 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() +
library_name.length() +
library_version.length() +
pal_relative_path.length() + 3);
candidate.assign(base);
append_path(&candidate, library_name.c_str());
append_path(&candidate, library_version.c_str());
append_path(&candidate, pal_relative_path.c_str());
bool exists = pal::file_exists(candidate);
if (!exists)
{
candidate.clear();
}
return exists;
}
// -----------------------------------------------------------------------------
// 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 nupkg file name. Just reserve approx 8 char_t's for the algorithm name.
pal::string_t nupkg_filename;
nupkg_filename.reserve(library_name.length() + 1 + library_version.length() + 16);
nupkg_filename.append(library_name);
nupkg_filename.append(_X("."));
nupkg_filename.append(library_version);
nupkg_filename.append(_X(".nupkg."));
nupkg_filename.append(library_hash.substr(0, pos));
// Build the hash file path str.
pal::string_t hash_file;
hash_file.reserve(base.length() + library_name.length() + library_version.length() + nupkg_filename.length() + 3);
hash_file.assign(base);
append_path(&hash_file, library_name.c_str());
append_path(&hash_file, library_version.c_str());
append_path(&hash_file, nupkg_filename.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;
pal::to_palstring(hash.c_str(), &pal_hash);
// 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);
}
// -----------------------------------------------------------------------------
// Load the deps file and parse its "entry" lines which contain the "fields" of
// the entry. Populate an array of these entries.
//
bool deps_resolver_t::load()
{
// If file doesn't exist, then assume parsed.
if (!pal::file_exists(m_deps_path))
{
return true;
}
// Somehow the file stream could not be opened. This is an error.
pal::ifstream_t file(m_deps_path);
if (!file.good())
{
return false;
}
// Parse the "entry" lines of the deps file.
std::string stdline;
while (std::getline(file, stdline))
{
pal::string_t line;
pal::to_palstring(stdline.c_str(), &line);
deps_entry_t entry;
pal::string_t is_serviceable;
pal::string_t* fields[] = {
&entry.library_type,
&entry.library_name,
&entry.library_version,
&entry.library_hash,
&entry.asset_type,
&entry.asset_name,
&entry.relative_path,
// TODO: Add when the deps file support is enabled.
// &is_serviceable
};
std::vector<pal::char_t> buf(line.length());
for (unsigned i = 0, offset = 0; i < sizeof(fields) / sizeof(fields[0]); ++i)
{
if (!(read_field(line, buf.data(), &offset, fields[i])))
{
return false;
}
}
// Serviceable, if not false, default is true.
entry.is_serviceable = pal::strcasecmp(is_serviceable.c_str(), _X("false")) != 0;
// TODO: Deps file does not follow spec. It uses '\\', should use '/'
replace_char(&entry.relative_path, _X('\\'), _X('/'));
m_deps_entries.push_back(entry);
}
return true;
}
// -----------------------------------------------------------------------------
// Resolve path to the deps file from "args" and parse the deps file.
//
// Returns:
// True if the file parse is successful or if file doesn't exist. False,
// when there is an error parsing the file.
//
bool deps_resolver_t::parse_deps_file(const arguments_t& args)
{
const auto& app_base = args.app_dir;
auto app_name = get_filename(args.managed_application);
m_deps_path.reserve(app_base.length() + 1 + app_name.length() + 5);
m_deps_path.append(app_base);
m_deps_path.push_back(DIR_SEPARATOR);
m_deps_path.append(app_name, 0, app_name.find_last_of(_X(".")));
m_deps_path.append(_X(".deps"));
return load();
}
// -----------------------------------------------------------------------------
// Load local assemblies by priority order of their file extensions and
// unique-fied by their simple name.
//
void deps_resolver_t::get_local_assemblies(const pal::string_t& dir)
{
trace::verbose(_X("Adding files from dir %s"), dir.c_str());
// Managed extensions in priority order, pick DLL over EXE and NI over IL.
const pal::string_t managed_ext[] = { _X(".ni.dll"), _X(".dll"), _X(".ni.exe"), _X(".exe") };
// List of files in the dir
std::vector<pal::string_t> files;
pal::readdir(dir, &files);
for (const auto& file : files)
{
for (const auto& ext : managed_ext)
{
// Nothing to do if file length is smaller than expected ext.
if (file.length() <= ext.length())
{
continue;
}
auto file_name = file.substr(0, file.length() - ext.length());
auto file_ext = file.substr(file_name.length());
// Ext did not match expected ext, skip this file.
if (pal::strcasecmp(file_ext.c_str(), ext.c_str()))
{
continue;
}
// TODO: Do a case insensitive lookup.
// Already added entry for this asset, by priority order skip this ext
if (m_local_assemblies.count(file_name))
{
trace::verbose(_X("Skipping %s because the %s already exists in local assemblies"), file.c_str(), m_local_assemblies.find(file_name)->second.c_str());
continue;
}
// Add entry for this asset
pal::string_t file_path = dir + DIR_SEPARATOR + file;
trace::verbose(_X("Adding %s to local assembly set from %s"), file_name.c_str(), file_path.c_str());
m_local_assemblies.emplace(file_name, file_path);
}
}
}
// -----------------------------------------------------------------------------
// Resolve the TPA list order.
//
// Description:
// First, add mscorlib to the TPA. Then for each deps entry, check if they
// are serviced. If they are not serviced, then look if they are present
// app local. Worst case, default to the primary and seconday package
// caches. Finally, for cases where deps file may not be present or if deps
// did not have an entry for an app local assembly, just use them from the
// app dir in the TPA path.
//
// Parameters:
// app_dir - The application local directory
// package_dir - The directory path to where packages are restored
// package_cache_dir - The directory path to secondary cache for packages
// clr_dir - The directory where the host loads the CLR
//
// Returns:
// output - Pointer to a string that will hold the resolved TPA paths
//
void deps_resolver_t::resolve_tpa_list(
const pal::string_t& app_dir,
const pal::string_t& package_dir,
const pal::string_t& package_cache_dir,
const pal::string_t& clr_dir,
pal::string_t* output)
{
// Obtain the local assemblies in the app dir.
get_local_assemblies(app_dir);
std::set<pal::string_t> items;
add_mscorlib_to_tpa(clr_dir, &items, output);
for (const deps_entry_t& entry : m_deps_entries)
{
// Is this asset a "runtime" type?
if (entry.asset_type != _X("runtime") || items.count(entry.asset_name))
{
continue;
}
pal::string_t candidate;
// Is this a serviceable entry and is there an entry in the servicing index?
if (entry.is_serviceable && entry.library_type == _X("Package") &&
m_svc.find_redirection(entry.library_name, entry.library_version, entry.relative_path, &candidate))
{
add_tpa_asset(entry.asset_name, candidate, &items, output);
}
// Is this entry present locally?
else if (m_local_assemblies.count(entry.asset_name))
{
// TODO: Case insensitive look up?
add_tpa_asset(entry.asset_name, m_local_assemblies.find(entry.asset_name)->second, &items, output);
}
// Is this entry present in the package restore dir?
else if (entry.to_full_path(package_dir, &candidate))
{
add_tpa_asset(entry.asset_name, candidate, &items, output);
}
// Is this entry present in the secondary package cache?
else if (entry.to_hash_matched_path(package_cache_dir, &candidate))
{
add_tpa_asset(entry.asset_name, candidate, &items, output);
}
}
// Finally, if the deps file wasn't present or has missing entries, then
// add the app local assemblies to the TPA.
for (const auto& kv : m_local_assemblies)
{
add_tpa_asset(kv.first, kv.second, &items, output);
}
}
// -----------------------------------------------------------------------------
// Resolve the directories order for culture/native lookup
//
// Description:
// This general purpose function specifies priority order of directory lookup
// for both native images and culture specific resource images. Lookup for
// culture assemblies is done by looking up two levels above from the file
// path. Lookup for native images is done by looking up one level from the
// file path.
//
// Parameters:
// asset_type - The type of the asset that needs lookup, currently
// supports "culture" and "native"
// app_dir - The application local directory
// package_dir - The directory path to where packages are restored
// package_cache_dir - The directory path to secondary cache for packages
// clr_dir - The directory where the host loads the CLR
//
// Returns:
// output - Pointer to a string that will hold the resolved lookup dirs
//
void deps_resolver_t::resolve_probe_dirs(
const pal::string_t& asset_type,
const pal::string_t& app_dir,
const pal::string_t& package_dir,
const pal::string_t& package_cache_dir,
const pal::string_t& clr_dir,
pal::string_t* output)
{
assert(asset_type == _X("culture") || asset_type == _X("native"));
// For culture assemblies, we need to provide the base directory of the culture path.
// For example: .../Foo/en-US/Bar.dll, then, the resolved path is .../Foo
std::function<pal::string_t(const pal::string_t&)> culture = [] (const pal::string_t& str) {
return get_directory(get_directory(str));
};
// For native assemblies, obtain the directory path from the file path
std::function<pal::string_t(const pal::string_t&)> native = [] (const pal::string_t& str) {
return get_directory(str);
};
std::function<pal::string_t(const pal::string_t&)>& action = (asset_type == _X("culture")) ? culture : native;
std::set<pal::string_t> items;
// Fill the "output" with serviced DLL directories if they are serviceable
// and have an entry present.
for (const deps_entry_t& entry : m_deps_entries)
{
pal::string_t redirection_path;
if (entry.is_serviceable && entry.asset_type == asset_type && entry.library_type == _X("Package") &&
m_svc.find_redirection(entry.library_name, entry.library_version, entry.relative_path, &redirection_path))
{
add_unique_path(asset_type, action(redirection_path), &items, output);
}
}
// App local path
add_unique_path(asset_type, app_dir, &items, output);
// Take care of the packages cached path
for (const deps_entry_t& entry : m_deps_entries)
{
if (entry.asset_type != asset_type)
{
continue;
}
pal::string_t candidate;
// Package restore directory
if (entry.to_full_path(package_dir, &candidate))
{
add_unique_path(asset_type, action(candidate), &items, output);
}
// Secondary cache
else if (entry.to_hash_matched_path(package_cache_dir, &candidate))
{
add_unique_path(asset_type, action(candidate), &items, output);
}
}
// CLR path
add_unique_path(asset_type, clr_dir, &items, output);
}
// -----------------------------------------------------------------------------
// Entrypoint to resolve TPA, native and culture path ordering to pass to CoreCLR.
//
// Parameters:
// app_dir - The application local directory
// package_dir - The directory path to where packages are restored
// package_cache_dir - The directory path to secondary cache for packages
// clr_dir - The directory where the host loads the CLR
// probe_paths - Pointer to struct containing fields that will contain
// resolved path ordering.
//
//
bool deps_resolver_t::resolve_probe_paths(
const pal::string_t& app_dir,
const pal::string_t& package_dir,
const pal::string_t& package_cache_dir,
const pal::string_t& clr_dir,
probe_paths_t* probe_paths)
{
resolve_tpa_list(app_dir, package_dir, package_cache_dir, clr_dir, &probe_paths->tpa);
resolve_probe_dirs(_X("native"), app_dir, package_dir, package_cache_dir, clr_dir, &probe_paths->native);
resolve_probe_dirs(_X("culture"), app_dir, package_dir, package_cache_dir, clr_dir, &probe_paths->culture);
return true;
}

View file

@ -4,121 +4,191 @@
#include "pal.h" #include "pal.h"
#include "args.h" #include "args.h"
#include "trace.h" #include "trace.h"
#include "tpafile.h" #include "deps_resolver.h"
#include "utils.h" #include "utils.h"
#include "coreclr.h" #include "coreclr.h"
void get_tpafile_path(const pal::string_t& app_base, const pal::string_t& app_name, pal::string_t& tpapath) enum StatusCode
{ {
tpapath.reserve(app_base.length() + app_name.length() + 5); Failure = 0x87FF0000,
InvalidArgFailure = Failure | 0x1,
CoreClrResolveFailure = Failure | 0x2,
CoreClrBindFailure = Failure | 0x3,
CoreClrInitFailure = Failure | 0x4,
CoreClrExeFailure = Failure | 0x5,
ResolverInitFailure = Failure | 0x6,
ResolverResolveFailure = Failure | 0x7,
};
tpapath.append(app_base); // ----------------------------------------------------------------------
tpapath.push_back(DIR_SEPARATOR); // resolve_clr_path: Resolve CLR Path in priority order
//
// Remove the extension from the app_name // Description:
auto ext_location = app_name.find_last_of('.'); // Check if CoreCLR library exists in runtime servicing dir or app
if (ext_location != std::string::npos) // local or DOTNET_HOME directory in that order of priority. If these
// fail to locate CoreCLR, then check platform-specific search.
//
// Returns:
// "true" if path to the CoreCLR dir can be resolved in "clr_path"
// parameter. Else, returns "false" with "clr_path" unmodified.
//
bool resolve_clr_path(const arguments_t& args, pal::string_t* clr_path)
{ {
tpapath.append(app_name.substr(0, ext_location)); const pal::string_t* dirs[] = {
} &args.dotnet_runtime_servicing, // DOTNET_RUNTIME_SERVICING
else &args.app_dir, // APP LOCAL
&args.dotnet_home // DOTNET_HOME
};
for (int i = 0; i < sizeof(dirs) / sizeof(dirs[0]); ++i)
{ {
tpapath.append(app_name); if (dirs[i]->empty())
} {
tpapath.append(_X(".deps")); continue;
} }
int run(arguments_t args, pal::string_t app_base, tpafile tpa) // App dir should contain coreclr, so skip appending path.
pal::string_t cur_dir = *dirs[i];
if (dirs[i] != &args.app_dir)
{ {
tpa.add_from_local_dir(app_base); append_path(&cur_dir, _X("runtime"));
append_path(&cur_dir, _X("coreclr"));
}
// Found coreclr in priority order.
if (coreclr_exists_in_dir(cur_dir))
{
clr_path->assign(cur_dir);
return true;
}
}
// Use platform-specific search algorithm
pal::string_t home_dir = args.dotnet_home;
if (pal::find_coreclr(&home_dir))
{
clr_path->assign(home_dir);
return true;
}
return false;
}
int run(const arguments_t& args, const pal::string_t& clr_path)
{
// Load the deps resolver
deps_resolver_t resolver(args);
if (!resolver.valid())
{
trace::error(_X("Invalid .deps file"));
return StatusCode::ResolverInitFailure;
}
// Add packages directory // Add packages directory
pal::string_t packages_dir; pal::string_t packages_dir = args.dotnet_packages;
if (!pal::get_default_packages_directory(packages_dir)) if (!pal::directory_exists(packages_dir))
{ {
trace::info(_X("did not find local packages directory")); (void)pal::get_default_packages_directory(&packages_dir);
// We can continue, the app may have it's dependencies locally
} }
else trace::info(_X("Package directory: %s"), packages_dir.empty() ? _X("not specified") : packages_dir.c_str());
probe_paths_t probe_paths;
if (!resolver.resolve_probe_paths(args.app_dir, packages_dir, args.dotnet_packages_cache, clr_path, &probe_paths))
{ {
trace::info(_X("using packages directory: %s"), packages_dir.c_str()); return StatusCode::ResolverResolveFailure;
tpa.add_package_dir(packages_dir);
} }
// Add native search path
trace::info(_X("using native search path: %s"), packages_dir.c_str());
tpa.add_native_search_path(args.clr_path);
// Build TPA list and search paths
pal::string_t tpalist;
tpa.write_tpa_list(tpalist);
pal::string_t search_paths;
tpa.write_native_paths(search_paths);
// Build CoreCLR properties // Build CoreCLR properties
const char* property_keys[] = { const char* property_keys[] = {
"TRUSTED_PLATFORM_ASSEMBLIES", "TRUSTED_PLATFORM_ASSEMBLIES",
"APP_PATHS", "APP_PATHS",
"APP_NI_PATHS", "APP_NI_PATHS",
"NATIVE_DLL_SEARCH_DIRECTORIES", "NATIVE_DLL_SEARCH_DIRECTORIES",
"AppDomainCompatSwitch" "PLATFORM_RESOURCE_ROOTS",
"AppDomainCompatSwitch",
// TODO: pipe this from corehost.json
"SERVER_GC"
}; };
auto tpa_cstr = pal::to_stdstring(tpalist); auto tpa_paths_cstr = pal::to_stdstring(probe_paths.tpa);
auto app_base_cstr = pal::to_stdstring(app_base); auto app_base_cstr = pal::to_stdstring(args.app_dir);
auto search_paths_cstr = pal::to_stdstring(search_paths); auto native_dirs_cstr = pal::to_stdstring(probe_paths.native);
auto culture_dirs_cstr = pal::to_stdstring(probe_paths.culture);
const char* property_values[] = { const char* property_values[] = {
// TRUSTED_PLATFORM_ASSEMBLIES // TRUSTED_PLATFORM_ASSEMBLIES
tpa_cstr.c_str(), tpa_paths_cstr.c_str(),
// APP_PATHS // APP_PATHS
app_base_cstr.c_str(), app_base_cstr.c_str(),
// APP_NI_PATHS // APP_NI_PATHS
app_base_cstr.c_str(), app_base_cstr.c_str(),
// NATIVE_DLL_SEARCH_DIRECTORIES // NATIVE_DLL_SEARCH_DIRECTORIES
search_paths_cstr.c_str(), native_dirs_cstr.c_str(),
// PLATFORM_RESOURCE_ROOTS
culture_dirs_cstr.c_str(),
// AppDomainCompatSwitch // AppDomainCompatSwitch
"UseLatestBehaviorWhenTFMNotSpecified" "UseLatestBehaviorWhenTFMNotSpecified",
// SERVER_GC
"1"
}; };
// Dump TPA list size_t property_size = sizeof(property_keys) / sizeof(property_keys[0]);
trace::verbose(_X("TPA List: %s"), tpalist.c_str());
// Dump native search paths
trace::verbose(_X("Native Paths: %s"), search_paths.c_str());
// Bind CoreCLR // Bind CoreCLR
if (!coreclr::bind(args.clr_path)) if (!coreclr::bind(clr_path))
{ {
trace::error(_X("failed to bind to coreclr")); trace::error(_X("Failed to bind to coreclr"));
return 1; return StatusCode::CoreClrBindFailure;
} }
// Verbose logging
if (trace::is_enabled())
{
for (size_t i = 0; i < property_size; ++i)
{
pal::string_t key, val;
pal::to_palstring(property_keys[i], &key);
pal::to_palstring(property_values[i], &val);
trace::verbose(_X("Property %s = %s"), key.c_str(), val.c_str());
}
}
std::string own_path;
pal::to_stdstring(args.own_path.c_str(), &own_path);
// Initialize CoreCLR // Initialize CoreCLR
coreclr::host_handle_t host_handle; coreclr::host_handle_t host_handle;
coreclr::domain_id_t domain_id; coreclr::domain_id_t domain_id;
auto hr = coreclr::initialize( auto hr = coreclr::initialize(
pal::to_stdstring(args.own_path).c_str(), own_path.c_str(),
"clrhost", "clrhost",
property_keys, property_keys,
property_values, property_values,
sizeof(property_keys) / sizeof(property_keys[0]), property_size,
&host_handle, &host_handle,
&domain_id); &domain_id);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
trace::error(_X("failed to initialize CoreCLR, HRESULT: 0x%X"), hr); trace::error(_X("Failed to initialize CoreCLR, HRESULT: 0x%X"), hr);
return 1; return StatusCode::CoreClrInitFailure;
} }
// Convert the args (probably not the most performant way to do this...) if (trace::is_enabled())
auto argv_strs = new std::string[args.app_argc]; {
auto argv = new const char*[args.app_argc]; pal::string_t arg_str;
for (int i = 0; i < args.app_argc; i++) for (int i = 0; i < args.app_argc; i++)
{ {
argv_strs[i] = pal::to_stdstring(pal::string_t(args.app_argv[i])); arg_str.append(args.app_argv[i]);
arg_str.append(_X(","));
}
trace::info(_X("Launch host: %s app: %s, argc: %d args: %s"), args.own_path.c_str(),
args.managed_application.c_str(), args.app_argc, arg_str.c_str());
}
// Initialize with empty strings
std::vector<std::string> argv_strs(args.app_argc);
std::vector<const char*> argv(args.app_argc);
for (int i = 0; i < args.app_argc; i++)
{
pal::to_stdstring(args.app_argv[i], &argv_strs[i]);
argv[i] = argv_strs[i].c_str(); argv[i] = argv_strs[i].c_str();
} }
@ -127,21 +197,21 @@ int run(arguments_t args, pal::string_t app_base, tpafile tpa)
hr = coreclr::execute_assembly( hr = coreclr::execute_assembly(
host_handle, host_handle,
domain_id, domain_id,
args.app_argc, argv.size(),
argv, argv.data(),
pal::to_stdstring(args.managed_application).c_str(), pal::to_stdstring(args.managed_application).c_str(),
&exit_code); &exit_code);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
trace::error(_X("failed to execute managed app, HRESULT: 0x%X"), hr); trace::error(_X("Failed to execute managed app, HRESULT: 0x%X"), hr);
return 1; return StatusCode::CoreClrExeFailure;
} }
// Shut down the CoreCLR // Shut down the CoreCLR
hr = coreclr::shutdown(host_handle, domain_id); hr = coreclr::shutdown(host_handle, domain_id);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
trace::warning(_X("failed to shut down CoreCLR, HRESULT: 0x%X"), hr); trace::warning(_X("Failed to shut down CoreCLR, HRESULT: 0x%X"), hr);
} }
coreclr::unload(); coreclr::unload();
@ -155,70 +225,19 @@ int __cdecl wmain(const int argc, const pal::char_t* argv[])
int main(const int argc, const pal::char_t* argv[]) int main(const int argc, const pal::char_t* argv[])
#endif #endif
{ {
// Take care of arguments
arguments_t args; arguments_t args;
if (!parse_arguments(argc, argv, args)) if (!parse_arguments(argc, argv, args))
{ {
return 1; return StatusCode::InvalidArgFailure;
} }
// Resolve paths // Resolve CLR path
if (!pal::realpath(args.managed_application)) pal::string_t clr_path;
if (!resolve_clr_path(args, &clr_path))
{ {
trace::error(_X("failed to locate managed application: %s"), args.managed_application.c_str()); trace::error(_X("Could not resolve coreclr path"));
return 1; return StatusCode::CoreClrResolveFailure;
} }
trace::info(_X("preparing to launch managed application: %s"), args.managed_application.c_str()); return run(args, clr_path);
trace::info(_X("host path: %s"), args.own_path.c_str());
pal::string_t argstr;
for (int i = 0; i < args.app_argc; i++)
{
argstr.append(args.app_argv[i]);
argstr.append(_X(","));
}
trace::info(_X("App argc: %d"), args.app_argc);
trace::info(_X("App argv: %s"), argstr.c_str());
auto app_base = get_directory(args.managed_application);
auto app_name = get_filename(args.managed_application);
// App-local coreclr wins
{
pal::string_t candidate;
candidate.assign(app_base);
append_path(candidate, LIBCORECLR_NAME);
if (pal::file_exists(candidate))
{
args.clr_path.assign(app_base);
}
}
if (args.clr_path.empty())
{
trace::error(_X("failed to locate CLR files, set the DOTNET_HOME environment variable"));
return 1;
}
if (!pal::realpath(args.clr_path))
{
trace::error(_X("failed to locate CLR files at %s"), args.clr_path.c_str());
return 1;
}
trace::info(_X("using CLR files from: %s"), args.clr_path.c_str());
trace::info(_X("preparing to launch: %s"), app_name.c_str());
trace::info(_X("using app base: %s"), app_base.c_str());
// Check for and load deps file
pal::string_t tpafile_path;
get_tpafile_path(app_base, app_name, tpafile_path);
trace::info(_X("checking for .deps File at: %s"), tpafile_path.c_str());
tpafile tpa;
if (!tpa.load(tpafile_path))
{
trace::error(_X("invalid .deps file"));
return 1;
}
return run(args, app_base, tpa);
} }

View file

@ -5,6 +5,7 @@
#include "utils.h" #include "utils.h"
#include "trace.h" #include "trace.h"
#include <cassert>
#include <dlfcn.h> #include <dlfcn.h>
#include <dirent.h> #include <dirent.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -19,7 +20,7 @@
#define symlinkEntrypointExecutable "/proc/curproc/exe" #define symlinkEntrypointExecutable "/proc/curproc/exe"
#endif #endif
bool pal::find_coreclr(pal::string_t& recv) bool pal::find_coreclr(pal::string_t* recv)
{ {
pal::string_t candidate; pal::string_t candidate;
pal::string_t test; pal::string_t test;
@ -28,24 +29,24 @@ bool pal::find_coreclr(pal::string_t& recv)
// TODO: These paths should be consistent // TODO: These paths should be consistent
candidate.assign("/usr/share/dotnet/runtime/coreclr"); candidate.assign("/usr/share/dotnet/runtime/coreclr");
if (coreclr_exists_in_dir(candidate)) { if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate); recv->assign(candidate);
return true; return true;
} }
candidate.assign("/usr/local/share/dotnet/runtime/coreclr"); candidate.assign("/usr/local/share/dotnet/runtime/coreclr");
if (coreclr_exists_in_dir(candidate)) { if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate); recv->assign(candidate);
return true; return true;
} }
return false; return false;
} }
bool pal::load_library(const char_t* path, dll_t& dll) bool pal::load_library(const char_t* path, dll_t* dll)
{ {
dll = dlopen(path, RTLD_LAZY); *dll = dlopen(path, RTLD_LAZY);
if (dll == nullptr) if (*dll == nullptr)
{ {
trace::error(_X("failed to load %s, error: %s"), path, dlerror()); trace::error(_X("Failed to load %s, error: %s"), path, dlerror());
return false; return false;
} }
return true; return true;
@ -56,7 +57,7 @@ pal::proc_t pal::get_symbol(dll_t library, const char* name)
auto result = dlsym(library, name); auto result = dlsym(library, name);
if (result == nullptr) if (result == nullptr)
{ {
trace::error(_X("failed to resolve library symbol %s, error: %s"), name, dlerror()); trace::error(_X("Failed to resolve library symbol %s, error: %s"), name, dlerror());
} }
return result; return result;
} }
@ -65,7 +66,7 @@ void pal::unload_library(dll_t library)
{ {
if (dlclose(library) != 0) if (dlclose(library) != 0)
{ {
trace::warning(_X("failed to unload library, error: %s"), dlerror()); trace::warning(_X("Failed to unload library, error: %s"), dlerror());
} }
} }
@ -79,19 +80,20 @@ bool pal::is_path_rooted(const pal::string_t& path)
return path.front() == '/'; return path.front() == '/';
} }
bool pal::get_default_packages_directory(pal::string_t& recv) bool pal::get_default_packages_directory(pal::string_t* recv)
{ {
recv->clear();
if (!pal::getenv("HOME", recv)) if (!pal::getenv("HOME", recv))
{ {
return false; return false;
} }
append_path(recv, _X(".dnx")); append_path(&*recv, _X(".dnx"));
append_path(recv, _X("packages")); append_path(&*recv, _X("packages"));
return true; return true;
} }
#if defined(__APPLE__) #if defined(__APPLE__)
bool pal::get_own_executable_path(pal::string_t& recv) bool pal::get_own_executable_path(pal::string_t* recv)
{ {
uint32_t path_length = 0; uint32_t path_length = 0;
if (_NSGetExecutablePath(nullptr, &path_length) == -1) if (_NSGetExecutablePath(nullptr, &path_length) == -1)
@ -99,28 +101,28 @@ bool pal::get_own_executable_path(pal::string_t& recv)
char path_buf[path_length]; char path_buf[path_length];
if (_NSGetExecutablePath(path_buf, &path_length) == 0) if (_NSGetExecutablePath(path_buf, &path_length) == 0)
{ {
recv.assign(path_buf); recv->assign(path_buf);
return true; return true;
} }
} }
return false; return false;
} }
#else #else
bool pal::get_own_executable_path(pal::string_t& recv) bool pal::get_own_executable_path(pal::string_t* recv)
{ {
// Just return the symlink to the exe from /proc // Just return the symlink to the exe from /proc
// We'll call realpath on it later // We'll call realpath on it later
recv.assign(symlinkEntrypointExecutable); recv->assign(symlinkEntrypointExecutable);
return true; return true;
} }
#endif #endif
bool pal::getenv(const pal::char_t* name, pal::string_t& recv) bool pal::getenv(const pal::char_t* name, pal::string_t* recv)
{ {
auto result = ::getenv(name); auto result = ::getenv(name);
if (result != nullptr) if (result != nullptr)
{ {
recv.assign(result); recv->assign(result);
} }
// We don't return false. Windows does have a concept of an error reading the variable, // We don't return false. Windows does have a concept of an error reading the variable,
@ -128,10 +130,10 @@ bool pal::getenv(const pal::char_t* name, pal::string_t& recv)
return true; return true;
} }
bool pal::realpath(pal::string_t& path) bool pal::realpath(pal::string_t* path)
{ {
pal::char_t buf[PATH_MAX]; pal::char_t buf[PATH_MAX];
auto resolved = ::realpath(path.c_str(), buf); auto resolved = ::realpath(path->c_str(), buf);
if (resolved == nullptr) if (resolved == nullptr)
{ {
if (errno == ENOENT) if (errno == ENOENT)
@ -141,19 +143,25 @@ bool pal::realpath(pal::string_t& path)
perror("realpath()"); perror("realpath()");
return false; return false;
} }
path.assign(resolved); path->assign(resolved);
return true; return true;
} }
bool pal::file_exists(const pal::string_t& path) bool pal::file_exists(const pal::string_t& path)
{ {
if (path.empty())
{
return false;
}
struct stat buffer; struct stat buffer;
return (::stat(path.c_str(), &buffer) == 0); return (::stat(path.c_str(), &buffer) == 0);
} }
std::vector<pal::string_t> pal::readdir(const pal::string_t& path) void pal::readdir(const pal::string_t& path, std::vector<pal::string_t>* list)
{ {
std::vector<pal::string_t> files; assert(list != nullptr);
std::vector<pal::string_t>& files = *list;
auto dir = opendir(path.c_str()); auto dir = opendir(path.c_str());
if (dir != nullptr) if (dir != nullptr)
@ -197,6 +205,4 @@ std::vector<pal::string_t> pal::readdir(const pal::string_t& path)
files.push_back(pal::string_t(entry->d_name)); files.push_back(pal::string_t(entry->d_name));
} }
} }
return files;
} }

View file

@ -5,23 +5,24 @@
#include "trace.h" #include "trace.h"
#include "utils.h" #include "utils.h"
#include <cassert>
#include <locale> #include <locale>
#include <codecvt> #include <codecvt>
static std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> g_converter; static std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> g_converter;
bool pal::find_coreclr(pal::string_t& recv) bool pal::find_coreclr(pal::string_t* recv)
{ {
pal::string_t candidate; pal::string_t candidate;
pal::string_t test; pal::string_t test;
// Try %LocalAppData%\dotnet // Try %LocalAppData%\dotnet
if (pal::getenv(_X("LocalAppData"), candidate)) { if (pal::getenv(_X("LocalAppData"), &candidate)) {
append_path(candidate, _X("dotnet")); append_path(&candidate, _X("dotnet"));
append_path(candidate, _X("runtime")); append_path(&candidate, _X("runtime"));
append_path(candidate, _X("coreclr")); append_path(&candidate, _X("coreclr"));
if (coreclr_exists_in_dir(candidate)) { if (coreclr_exists_in_dir(candidate)) {
recv.assign(candidate); recv->assign(candidate);
return true; return true;
} }
} }
@ -30,12 +31,12 @@ bool pal::find_coreclr(pal::string_t& recv)
return false; return false;
} }
bool pal::load_library(const char_t* path, dll_t& dll) bool pal::load_library(const char_t* path, dll_t* dll)
{ {
dll = ::LoadLibraryW(path); *dll = ::LoadLibraryW(path);
if (dll == nullptr) if (*dll == nullptr)
{ {
trace::error(_X("failed to load coreclr.dll from %s, HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError())); trace::error(_X("Failed to load coreclr.dll from %s, HRESULT: 0x%X"), path, HRESULT_FROM_WIN32(GetLastError()));
return false; return false;
} }
@ -43,15 +44,15 @@ bool pal::load_library(const char_t* path, dll_t& dll)
HMODULE dummy_module; HMODULE dummy_module;
if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path, &dummy_module)) if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, path, &dummy_module))
{ {
trace::error(_X("failed to pin library: %s")); trace::error(_X("Failed to pin library: %s"));
return false; return false;
} }
if (trace::is_enabled()) if (trace::is_enabled())
{ {
pal::char_t buf[PATH_MAX]; pal::char_t buf[PATH_MAX];
::GetModuleFileNameW(dll, buf, PATH_MAX); ::GetModuleFileNameW(*dll, buf, PATH_MAX);
trace::info(_X("loaded library from %s"), buf); trace::info(_X("Loaded library from %s"), buf);
} }
return true; return true;
@ -67,14 +68,15 @@ void pal::unload_library(dll_t library)
// No-op. On windows, we pin the library, so it can't be unloaded. // No-op. On windows, we pin the library, so it can't be unloaded.
} }
bool pal::get_default_packages_directory(string_t& recv) bool pal::get_default_packages_directory(string_t* recv)
{ {
recv->clear();
if (!pal::getenv(_X("USERPROFILE"), recv)) if (!pal::getenv(_X("USERPROFILE"), recv))
{ {
return false; return false;
} }
append_path(recv, _X(".dnx")); append_path(&*recv, _X(".dnx"));
append_path(recv, _X("packages")); append_path(&*recv, _X("packages"));
return true; return true;
} }
@ -83,7 +85,7 @@ bool pal::is_path_rooted(const string_t& path)
return path.length() >= 2 && path[1] == L':'; return path.length() >= 2 && path[1] == L':';
} }
bool pal::getenv(const char_t* name, string_t& recv) bool pal::getenv(const char_t* name, string_t* recv)
{ {
auto length = ::GetEnvironmentVariableW(name, nullptr, 0); auto length = ::GetEnvironmentVariableW(name, nullptr, 0);
if (length == 0) if (length == 0)
@ -94,17 +96,17 @@ bool pal::getenv(const char_t* name, string_t& recv)
// Leave the receiver empty and return success // Leave the receiver empty and return success
return true; return true;
} }
trace::error(_X("failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError())); trace::error(_X("Failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
return false; return false;
} }
auto buf = new char_t[length]; auto buf = new char_t[length];
if (::GetEnvironmentVariableW(name, buf, length) == 0) if (::GetEnvironmentVariableW(name, buf, length) == 0)
{ {
trace::error(_X("failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError())); trace::error(_X("Failed to read enviroment variable '%s', HRESULT: 0x%X"), name, HRESULT_FROM_WIN32(GetLastError()));
return false; return false;
} }
recv.assign(buf); recv->assign(buf);
delete[] buf; delete[] buf;
return true; return true;
@ -115,14 +117,14 @@ int pal::xtoi(const char_t* input)
return ::_wtoi(input); return ::_wtoi(input);
} }
bool pal::get_own_executable_path(string_t& recv) bool pal::get_own_executable_path(string_t* recv)
{ {
char_t program_path[MAX_PATH]; char_t program_path[MAX_PATH];
DWORD dwModuleFileName = ::GetModuleFileNameW(NULL, program_path, MAX_PATH); DWORD dwModuleFileName = ::GetModuleFileNameW(NULL, program_path, MAX_PATH);
if (dwModuleFileName == 0 || dwModuleFileName >= MAX_PATH) { if (dwModuleFileName == 0 || dwModuleFileName >= MAX_PATH) {
return false; return false;
} }
recv.assign(program_path); recv->assign(program_path);
return true; return true;
} }
@ -136,21 +138,36 @@ pal::string_t pal::to_palstring(const std::string& str)
return g_converter.from_bytes(str); return g_converter.from_bytes(str);
} }
bool pal::realpath(string_t& path) void pal::to_palstring(const char* str, pal::string_t* out)
{
out->assign(g_converter.from_bytes(str));
}
void pal::to_stdstring(const pal::char_t* str, std::string* out)
{
out->assign(g_converter.to_bytes(str));
}
bool pal::realpath(string_t* path)
{ {
char_t buf[MAX_PATH]; char_t buf[MAX_PATH];
auto res = ::GetFullPathNameW(path.c_str(), MAX_PATH, buf, nullptr); auto res = ::GetFullPathNameW(path->c_str(), MAX_PATH, buf, nullptr);
if (res == 0 || res > MAX_PATH) if (res == 0 || res > MAX_PATH)
{ {
trace::error(_X("error resolving path: %s"), path.c_str()); trace::error(_X("Error resolving path: %s"), path->c_str());
return false; return false;
} }
path.assign(buf); path->assign(buf);
return true; return true;
} }
bool pal::file_exists(const string_t& path) bool pal::file_exists(const string_t& path)
{ {
if (path.empty())
{
return false;
}
WIN32_FIND_DATAW data; WIN32_FIND_DATAW data;
auto find_handle = ::FindFirstFileW(path.c_str(), &data); auto find_handle = ::FindFirstFileW(path.c_str(), &data);
bool found = find_handle != INVALID_HANDLE_VALUE; bool found = find_handle != INVALID_HANDLE_VALUE;
@ -158,9 +175,11 @@ bool pal::file_exists(const string_t& path)
return found; return found;
} }
std::vector<pal::string_t> pal::readdir(const string_t& path) void pal::readdir(const string_t& path, std::vector<pal::string_t>* list)
{ {
std::vector<string_t> files; assert(list != nullptr);
std::vector<string_t>& files = *list;
string_t search_string(path); string_t search_string(path);
search_string.push_back(DIR_SEPARATOR); search_string.push_back(DIR_SEPARATOR);
@ -174,6 +193,4 @@ std::vector<pal::string_t> pal::readdir(const string_t& path)
files.push_back(filepath); files.push_back(filepath);
} while (::FindNextFileW(handle, &data)); } while (::FindNextFileW(handle, &data));
::FindClose(handle); ::FindClose(handle);
return files;
} }

View file

@ -0,0 +1,128 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include "trace.h"
#include "servicing_index.h"
static const pal::char_t* DOTNET_SERVICING_INDEX_TXT = _X("dotnet_servicing_index.txt");
servicing_index_t::servicing_index_t(const pal::string_t& svc_dir)
{
m_patch_root = svc_dir;
if (!m_patch_root.empty())
{
m_index_file.assign(m_patch_root);
append_path(&m_index_file, DOTNET_SERVICING_INDEX_TXT);
}
m_parsed = m_index_file.empty() || !pal::file_exists(m_index_file);
}
bool servicing_index_t::find_redirection(
const pal::string_t& package_name,
const pal::string_t& package_version,
const pal::string_t& package_relative,
pal::string_t* redirection)
{
ensure_redirections();
redirection->clear();
if (m_redirections.empty())
{
return false;
}
pal::stringstream_t stream;
stream << package_name << _X("|") << package_version << _X("|") << package_relative;
auto iter = m_redirections.find(stream.str());
if (iter != m_redirections.end())
{
pal::string_t full_path = m_patch_root;
append_path(&full_path, iter->second.c_str());
if (pal::file_exists(full_path))
{
*redirection = full_path;
trace::verbose(_X("Servicing %s with %s"), stream.str().c_str(), redirection->c_str());
return true;
}
trace::verbose(_X("Serviced file %s doesn't exist"), full_path.c_str());
}
trace::verbose(_X("Entry %s not serviced or file doesn't exist"), stream.str().c_str());
return false;
}
void servicing_index_t::ensure_redirections()
{
if (m_parsed)
{
return;
}
pal::ifstream_t fstream(m_index_file);
if (!fstream.good())
{
return;
}
pal::stringstream_t sstream;
std::string line;
while (std::getline(fstream, line))
{
pal::string_t str;
pal::to_palstring(line.c_str(), &str);
// Can interpret line as "package"?
pal::string_t prefix = _X("package|");
if (str.find(prefix) != 0)
{
continue;
}
pal::string_t name, version, relative;
pal::string_t* tokens[] = { &name, &version, &relative };
pal::string_t delim[] = { pal::string_t(_X("|")), pal::string_t(_X("|")), pal::string_t(_X("=")) };
bool bad_line = false;
size_t from = prefix.length();
for (size_t cur = 0; cur < (sizeof(delim) / sizeof(delim[0])); ++cur)
{
size_t pos = str.find(delim[cur], from);
if (pos == pal::string_t::npos)
{
bad_line = true;
break;
}
tokens[cur]->assign(str.substr(from, pos - from));
from = pos + 1;
}
if (bad_line)
{
trace::error(_X("Invalid line in servicing index. Skipping..."));
continue;
}
// Save redirection for this package.
sstream.str(_X(""));
sstream << name << _X("|") << version << _X("|") << relative;
if (trace::is_enabled())
{
trace::verbose(_X("Adding servicing entry %s => %s"), sstream.str().c_str(), str.substr(from).c_str());
}
// Store just the filename.
pal::string_t redir = str.substr(from);
if (_X('/') != DIR_SEPARATOR)
{
replace_char(&redir, _X('/'), DIR_SEPARATOR);
}
m_redirections.emplace(sstream.str(), redir);
}
m_parsed = true;
}

View file

@ -1,251 +0,0 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include <set>
#include <functional>
#include "trace.h"
#include "tpafile.h"
#include "utils.h"
bool read_field(pal::string_t line, int& offset, pal::string_t& value_recv)
{
// The first character should be a '"'
if (line[offset] != '"')
{
trace::error(_X("error reading TPA file"));
return false;
}
offset++;
// Set up destination buffer (it can't be bigger than the original line)
pal::char_t buf[PATH_MAX];
auto buf_offset = 0;
// Iterate through characters in the string
for (; offset < line.length(); offset++)
{
// Is this a '\'?
if (line[offset] == '\\')
{
// Skip this character and read the next character into the buffer
offset++;
buf[buf_offset] = line[offset];
}
// Is this a '"'?
else if (line[offset] == '\"')
{
// Done! Advance to the pointer after the input
offset++;
break;
}
else
{
// Take the character
buf[buf_offset] = line[offset];
}
buf_offset++;
}
buf[buf_offset] = '\0';
value_recv.assign(buf);
// Consume the ',' if we have one
if (line[offset] == ',')
{
offset++;
}
return true;
}
bool tpafile::load(pal::string_t path)
{
// Check if the file exists, if not, there is nothing to add
if (!pal::file_exists(path))
{
return true;
}
// Open the file
pal::ifstream_t file(path);
if (!file.good())
{
// Failed to open the file!
return false;
}
// Read lines from the file
while (true)
{
std::string line;
std::getline(file, line);
auto line_palstr = pal::to_palstring(line);
if (file.eof())
{
break;
}
auto offset = 0;
tpaentry_t entry;
// Read fields
if (!(read_field(line_palstr, offset, entry.library_type))) return false;
if (!(read_field(line_palstr, offset, entry.library_name))) return false;
if (!(read_field(line_palstr, offset, entry.library_version))) return false;
if (!(read_field(line_palstr, offset, entry.library_hash))) return false;
if (!(read_field(line_palstr, offset, entry.asset_type))) return false;
if (!(read_field(line_palstr, offset, entry.asset_name))) return false;
if (!(read_field(line_palstr, offset, entry.relative_path))) return false;
m_entries.push_back(entry);
}
return true;
}
void tpafile::add_from_local_dir(const pal::string_t& dir)
{
trace::verbose(_X("adding files from %s to TPA"), dir.c_str());
const pal::char_t * const tpa_extensions[] = {
_X(".ni.dll"), // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir
_X(".dll"),
_X(".ni.exe"),
_X(".exe"),
};
std::set<pal::string_t> added_assemblies;
// Get directory entries
auto files = pal::readdir(dir);
for (auto ext : tpa_extensions)
{
auto len = pal::strlen(ext);
for (auto file : files)
{
// Can't be a match if it's the same length as the extension :)
if (file.length() > len)
{
// Extract the same amount of text from the end of file name
auto file_ext = file.substr(file.length() - len, len);
// Check if this file name matches
if (pal::strcasecmp(ext, file_ext.c_str()) == 0)
{
// Get the assembly name by stripping the extension
// and add it to the set so we can de-dupe
auto asm_name = file.substr(0, file.length() - len);
// TODO(anurse): Also check if already in TPA file
if (added_assemblies.find(asm_name) == added_assemblies.end())
{
added_assemblies.insert(asm_name);
tpaentry_t entry;
entry.asset_type = pal::string_t(_X("runtime"));
entry.library_name = pal::string_t(asm_name);
entry.library_version = pal::string_t(_X(""));
pal::string_t relpath(dir);
relpath.push_back(DIR_SEPARATOR);
relpath.append(file);
entry.relative_path = relpath;
entry.asset_name = asm_name;
trace::verbose(_X("adding %s to TPA list from %s"), asm_name.c_str(), relpath.c_str());
m_entries.push_back(entry);
}
}
}
}
}
}
void tpafile::write_tpa_list(pal::string_t& output)
{
std::set<pal::string_t> items;
for (auto entry : m_entries)
{
if (pal::strcmp(entry.asset_type.c_str(), _X("runtime")) == 0 && items.find(entry.asset_name) == items.end())
{
// Resolve the full path
for (auto search_path : m_package_search_paths)
{
pal::string_t candidate;
candidate.reserve(search_path.length() +
entry.library_name.length() +
entry.library_version.length() +
entry.relative_path.length() + 3);
candidate.append(search_path);
append_path(candidate, entry.library_name.c_str());
append_path(candidate, entry.library_version.c_str());
append_path(candidate, entry.relative_path.c_str());
if (pal::file_exists(candidate))
{
trace::verbose(_X("adding tpa entry: %s"), candidate.c_str());
output.append(candidate);
output.push_back(PATH_SEPARATOR);
items.insert(entry.asset_name);
break;
}
}
}
}
}
void tpafile::write_native_paths(pal::string_t& output)
{
std::set<pal::string_t> items;
for (auto search_path : m_native_search_paths)
{
if (items.find(search_path) == items.end())
{
trace::verbose(_X("adding native search path: %s"), search_path.c_str());
output.append(search_path);
output.push_back(PATH_SEPARATOR);
items.insert(search_path);
}
}
for (auto entry : m_entries)
{
auto dir = entry.relative_path.substr(0, entry.relative_path.find_last_of(DIR_SEPARATOR));
if (pal::strcmp(entry.asset_type.c_str(), _X("native")) == 0 && items.find(dir) == items.end())
{
// Resolve the full path
for (auto search_path : m_package_search_paths)
{
pal::string_t candidate;
candidate.reserve(search_path.length() +
entry.library_name.length() +
entry.library_version.length() +
dir.length() + 3);
candidate.append(search_path);
append_path(candidate, entry.library_name.c_str());
append_path(candidate, entry.library_version.c_str());
append_path(candidate, get_directory(entry.relative_path).c_str());
if (pal::file_exists(candidate))
{
trace::verbose(_X("adding native search path: %s"), candidate.c_str());
output.append(candidate);
output.push_back(PATH_SEPARATOR);
items.insert(dir);
break;
}
}
}
}
}
void tpafile::add_package_dir(pal::string_t dir)
{
m_package_search_paths.push_back(dir);
}
void tpafile::add_native_search_path(pal::string_t dir)
{
m_native_search_paths.push_back(dir);
}

View file

@ -7,7 +7,7 @@
bool coreclr_exists_in_dir(const pal::string_t& candidate) bool coreclr_exists_in_dir(const pal::string_t& candidate)
{ {
pal::string_t test(candidate); pal::string_t test(candidate);
append_path(test, LIBCORECLR_NAME); append_path(&test, LIBCORECLR_NAME);
trace::verbose(_X("checking for CoreCLR in default location: %s"), test.c_str()); trace::verbose(_X("checking for CoreCLR in default location: %s"), test.c_str());
return pal::file_exists(test); return pal::file_exists(test);
} }
@ -18,19 +18,19 @@ bool ends_with(const pal::string_t& value, const pal::string_t& suffix)
(0 == value.compare(value.length() - suffix.length(), suffix.length(), suffix)); (0 == value.compare(value.length() - suffix.length(), suffix.length(), suffix));
} }
void append_path(pal::string_t& path1, const pal::char_t* path2) void append_path(pal::string_t* path1, const pal::char_t* path2)
{ {
if (pal::is_path_rooted(path2)) if (pal::is_path_rooted(path2))
{ {
path1.assign(path2); path1->assign(path2);
} }
else else
{ {
if (path1.back() != DIR_SEPARATOR) if (path1->empty() || path1->back() != DIR_SEPARATOR)
{ {
path1.push_back(DIR_SEPARATOR); path1->push_back(DIR_SEPARATOR);
} }
path1.append(path2); path1->append(path2);
} }
} }
@ -70,3 +70,12 @@ pal::string_t get_directory(const pal::string_t& path)
return path.substr(0, path_sep); return path.substr(0, path_sep);
} }
void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl)
{
int pos = 0;
while ((pos = path->find(match, pos)) != pal::string_t::npos)
{
(*path)[pos] = repl;
}
}