Skip to content

Commit

Permalink
Single-File: Pass BUNDLE_PROBE property to the runtime (#34845)
Browse files Browse the repository at this point in the history
* Single-File: Pass BUNDLE_PROBE property to the runtime

As described in the [design doc](https://github.com/dotnet/designs/blob/master/accepted/2020/single-file/design.md#startup), pass the bundle_probe function pointer encoded as a string to the runtime.
  • Loading branch information
swaroop-sridhar authored Apr 24, 2020
1 parent af36c6d commit bfa10f1
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 19 deletions.
31 changes: 25 additions & 6 deletions src/installer/corehost/cli/bundle/runner.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

Expand Down Expand Up @@ -48,11 +48,11 @@ StatusCode runner_t::extract()
}
}

const file_entry_t* runner_t::probe(const pal::string_t& path) const
const file_entry_t* runner_t::probe(const pal::string_t &relative_path) const
{
for (const file_entry_t& entry : m_manifest.files)
{
if (entry.relative_path() == path)
if (pal::pathcmp(entry.relative_path(), relative_path) == 0)
{
return &entry;
}
Expand All @@ -61,10 +61,28 @@ const file_entry_t* runner_t::probe(const pal::string_t& path) const
return nullptr;
}

bool runner_t::probe(const pal::string_t& relative_path, int64_t* offset, int64_t* size) const
{
const bundle::file_entry_t* entry = probe(relative_path);

if (entry == nullptr)
{
return false;
}

assert(entry->offset() != 0);

*offset = entry->offset();
*size = entry->size();


return true;
}


bool runner_t::locate(const pal::string_t& relative_path, pal::string_t& full_path) const
{
const bundle::runner_t* app = bundle::runner_t::app();
const bundle::file_entry_t* entry = app->probe(relative_path);
const bundle::file_entry_t* entry = probe(relative_path);

if (entry == nullptr)
{
Expand All @@ -76,8 +94,9 @@ bool runner_t::locate(const pal::string_t& relative_path, pal::string_t& full_pa
// The json files are not queried by the host using this method.
assert(entry->needs_extraction());

full_path.assign(app->extraction_path());
full_path.assign(extraction_path());
append_path(&full_path, relative_path.c_str());

return true;
}

3 changes: 2 additions & 1 deletion src/installer/corehost/cli/bundle/runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace bundle

const pal::string_t& extraction_path() const { return m_extraction_path; }

const file_entry_t *probe(const pal::string_t& path) const;
bool probe(const pal::string_t& relative_path, int64_t* offset, int64_t* size) const;
bool locate(const pal::string_t& relative_path, pal::string_t& full_path) const;

static StatusCode process_manifest_and_extract()
Expand All @@ -40,6 +40,7 @@ namespace bundle
private:

StatusCode extract();
const file_entry_t* probe(const pal::string_t& relative_path) const;

manifest_t m_manifest;
pal::string_t m_extraction_path;
Expand Down
8 changes: 6 additions & 2 deletions src/installer/corehost/cli/hostmisc/pal.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ namespace pal
inline int strcasecmp(const char_t* str1, const char_t* str2) { return ::_wcsicmp(str1, str2); }
inline int strncmp(const char_t* str1, const char_t* str2, int len) { return ::wcsncmp(str1, str2, len); }
inline int strncasecmp(const char_t* str1, const char_t* str2, int len) { return ::_wcsnicmp(str1, str2, len); }

inline int pathcmp(const pal::string_t &path1, const pal::string_t &path2) { return strcasecmp(path1.c_str(), path2.c_str()); }
inline string_t to_string(int value) { return std::to_wstring(value); }

inline size_t strlen(const char_t* str) { return ::wcslen(str); }
inline FILE * file_open(const string_t& path, const char_t* mode) { return ::_wfopen(path.c_str(), mode); }

Expand Down Expand Up @@ -202,6 +204,8 @@ namespace pal
inline int strcasecmp(const char_t* str1, const char_t* str2) { return ::strcasecmp(str1, str2); }
inline int strncmp(const char_t* str1, const char_t* str2, int len) { return ::strncmp(str1, str2, len); }
inline int strncasecmp(const char_t* str1, const char_t* str2, int len) { return ::strncasecmp(str1, str2, len); }
inline int pathcmp(const pal::string_t& path1, const pal::string_t& path2) { return strcmp(path1.c_str(), path2.c_str()); }
inline string_t to_string(int value) { return std::to_string(value); }

inline size_t strlen(const char_t* str) { return ::strlen(str); }
inline FILE * file_open(const string_t& path, const char_t* mode) { return fopen(path.c_str(), mode); }
Expand Down Expand Up @@ -235,7 +239,6 @@ namespace pal
return ret;
}

string_t to_string(int value);
string_t get_timestamp();

bool getcwd(string_t* recv);
Expand Down Expand Up @@ -295,6 +298,7 @@ namespace pal
bool get_default_bundle_extraction_base_dir(string_t& extraction_dir);

int xtoi(const char_t* input);
bool unicode_palstring(const char16_t* str, pal::string_t* out);

bool get_loaded_library(const char_t *library_name, const char *symbol_name, /*out*/ dll_t *dll, /*out*/ string_t *path);
bool load_library(const string_t* path, dll_t* dll);
Expand Down
14 changes: 12 additions & 2 deletions src/installer/corehost/cli/hostmisc/pal.unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <fcntl.h>
#include <fnmatch.h>
#include <ctime>
#include <locale>
#include <codecvt>
#include <pwd.h>
#include "config.h"

Expand All @@ -39,8 +41,6 @@
#define DT_LNK 10
#endif

pal::string_t pal::to_string(int value) { return std::to_string(value); }

pal::string_t pal::to_lower(const pal::string_t& in)
{
pal::string_t ret = in;
Expand Down Expand Up @@ -254,6 +254,16 @@ int pal::xtoi(const char_t* input)
return atoi(input);
}

bool pal::unicode_palstring(const char16_t* str, pal::string_t* out)
{
out->clear();

std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conversion;
out->assign(conversion.to_bytes(str));

return true;
}

bool pal::is_path_rooted(const pal::string_t& path)
{
return path.front() == '/';
Expand Down
12 changes: 6 additions & 6 deletions src/installer/corehost/cli/hostmisc/pal.windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ pal::string_t pal::to_lower(const pal::string_t& in)
return ret;
}

pal::string_t pal::to_string(int value)
{
return std::to_wstring(value);
}

pal::string_t pal::get_timestamp()
{
std::time_t t = std::time(0);
Expand Down Expand Up @@ -605,7 +600,6 @@ bool pal::get_default_bundle_extraction_base_dir(pal::string_t& extraction_dir)
return realpath(&extraction_dir);
}


static bool wchar_convert_helper(DWORD code_page, const char* cstr, int len, pal::string_t* out)
{
out->clear();
Expand Down Expand Up @@ -649,6 +643,12 @@ bool pal::clr_palstring(const char* cstr, pal::string_t* out)
return wchar_convert_helper(CP_UTF8, cstr, ::strlen(cstr), out);
}

bool pal::unicode_palstring(const char16_t* str, pal::string_t* out)
{
out->assign((const wchar_t *)str);
return true;
}

// 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)
{
Expand Down
3 changes: 2 additions & 1 deletion src/installer/corehost/cli/hostpolicy/coreclr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ namespace
_X("STARTUP_HOOKS"),
_X("APP_PATHS"),
_X("APP_NI_PATHS"),
_X("RUNTIME_IDENTIFIER")
_X("RUNTIME_IDENTIFIER"),
_X("BUNDLE_PROBE")
};

static_assert((sizeof(PropertyNameMapping) / sizeof(*PropertyNameMapping)) == static_cast<size_t>(common_property::Last), "Invalid property count");
Expand Down
2 changes: 1 addition & 1 deletion src/installer/corehost/cli/hostpolicy/coreclr.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ enum class common_property
AppPaths,
AppNIPaths,
RuntimeIdentifier,

BundleProbe,
// Sentinel value - new values should be defined above
Last
};
Expand Down
49 changes: 49 additions & 0 deletions src/installer/corehost/cli/hostpolicy/hostpolicy_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "deps_resolver.h"
#include <error_codes.h>
#include <trace.h>
#include "bundle/runner.h"
#include "bundle/file_entry.h"

namespace
{
Expand All @@ -15,6 +17,43 @@ namespace
trace::error(_X("Duplicate runtime property found: %s"), property_key);
trace::error(_X("It is invalid to specify values for properties populated by the hosting layer in the the application's .runtimeconfig.json"));
}

// bundle_probe:
// Probe the app-bundle for the file 'path' and return its location ('offset', 'size') if found.
//
// This function is an API exported to the runtime via the BUNDLE_PROBE property.
// This function used by the runtime to probe for bundled assemblies
// This function assumes that the currently executing app is a single-file bundle.
//
// bundle_probe recieves its path argument as cha16_t* instead of pal::char_t*, because:
// * The host uses Unicode strings on Windows and UTF8 strings on Unix
// * The runtime uses Unicode strings on all platforms
// * Using a unicode encoded path presents a uniform interface to the runtime
// and minimizes the number if Unicode <-> UTF8 conversions necessary.
//
// The unicode char type is char16_t* instead of whcar_t*, because:
// * wchar_t is 16-bit encoding on Windows while it is 32-bit encoding on most Unix systems
// * The runtime uses 16-bit encoded unicode characters.

bool STDMETHODCALLTYPE bundle_probe(const char16_t* path, int64_t* offset, int64_t* size)
{
if (path == nullptr)
{
return false;
}

pal::string_t file_path;

if (!pal::unicode_palstring(path, &file_path))
{
trace::warning(_X("Failure probing contents of the application bundle."));
trace::warning(_X("Failed to convert path [%ls] to UTF8"), path);

return false;
}

return bundle::runner_t::app()->probe(file_path, offset, size);
}
}

int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs)
Expand Down Expand Up @@ -180,5 +219,15 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a
}
}

// Single-File Bundle Probe
if (bundle::info_t::is_single_file_bundle())
{
// Encode the bundle_probe function pointer as a string, and pass it to the runtime.
pal::stringstream_t ptr_stream;
ptr_stream << "0x" << std::hex << (size_t)(&bundle_probe);

coreclr_properties.add(common_property::BundleProbe, ptr_stream.str().c_str());
}

return StatusCode::Success;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NETCoreAppFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifier>$(TestTargetRid)</RuntimeIdentifier>
<RuntimeFrameworkVersion>$(MNAVersion)</RuntimeFrameworkVersion>
</PropertyGroup>

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

namespace BundleProbeTester
{
public static class Program
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool BundleProbeDelegate([MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr size, IntPtr offset);

unsafe static bool Probe(BundleProbeDelegate bundleProbe, string path, bool isExpected)
{
Int64 size, offset;
bool exists = bundleProbe(path, (IntPtr)(&offset), (IntPtr)(&size));

switch (exists, isExpected)
{
case (true, true):
if (size > 0 && offset > 0)
{
return true;
}

Console.WriteLine($"Invalid location obtained for {path} within bundle.");
return false;

case (true, false):
Console.WriteLine($"Unexpected file {path} found in bundle.");
return false;

case (false, true):
Console.WriteLine($"Expected file {path} not found in bundle.");
return false;

case (false, false):
return true;
}

return false; // dummy
}

public static int Main(string[] args)
{
bool isSingleFile = args.Length > 0 && args[0].Equals("SingleFile");
object probeObject = System.AppDomain.CurrentDomain.GetData("BUNDLE_PROBE");

if (!isSingleFile)
{
if (probeObject != null)
{
Console.WriteLine("BUNDLE_PROBE property passed in for a non-single-file app");
return -1;
}

Console.WriteLine("No BUNDLE_PROBE");
return 0;
}

if (probeObject == null)
{
Console.WriteLine("BUNDLE_PROBE property not passed in for a single-file app");
return -2;
}

string probeString = probeObject as string;
IntPtr probePtr = (IntPtr)Convert.ToUInt64(probeString, 16);
BundleProbeDelegate bundleProbeDelegate = Marshal.GetDelegateForFunctionPointer<BundleProbeDelegate>(probePtr);
bool success =
Probe(bundleProbeDelegate, "BundleProbeTester.dll", isExpected: true) &&
Probe(bundleProbeDelegate, "BundleProbeTester.runtimeconfig.json", isExpected: true) &&
Probe(bundleProbeDelegate, "System.Private.CoreLib.dll", isExpected: true) &&
Probe(bundleProbeDelegate, "hostpolicy.dll", isExpected: false) &&
Probe(bundleProbeDelegate, "--", isExpected: false) &&
Probe(bundleProbeDelegate, "", isExpected: false);

if (!success)
{
return -3;
}

Console.WriteLine("BUNDLE_PROBE OK");
return 0;
}
}
}
Loading

0 comments on commit bfa10f1

Please sign in to comment.