mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-10 01:50:53 +09:00
Single-File: Pass BUNDLE_PROBE property to the runtime (#34845)
* 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.
This commit is contained in:
parent
af36c6d6cd
commit
bfa10f1956
11 changed files with 283 additions and 19 deletions
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
@ -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;
|
||||
|
|
|
@ -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); }
|
||||
|
||||
|
@ -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); }
|
||||
|
@ -235,7 +239,6 @@ namespace pal
|
|||
return ret;
|
||||
}
|
||||
|
||||
string_t to_string(int value);
|
||||
string_t get_timestamp();
|
||||
|
||||
bool getcwd(string_t* recv);
|
||||
|
@ -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);
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <fcntl.h>
|
||||
#include <fnmatch.h>
|
||||
#include <ctime>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <pwd.h>
|
||||
#include "config.h"
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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() == '/';
|
||||
|
|
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -67,7 +67,7 @@ enum class common_property
|
|||
AppPaths,
|
||||
AppNIPaths,
|
||||
RuntimeIdentifier,
|
||||
|
||||
BundleProbe,
|
||||
// Sentinel value - new values should be defined above
|
||||
Last
|
||||
};
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// 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.IO;
|
||||
using Xunit;
|
||||
using Microsoft.DotNet.Cli.Build.Framework;
|
||||
using Microsoft.DotNet.CoreSetup.Test;
|
||||
using BundleTests.Helpers;
|
||||
using System.Threading;
|
||||
|
||||
namespace AppHost.Bundle.Tests
|
||||
{
|
||||
public class BundleProbe : IClassFixture<BundleProbe.SharedTestState>
|
||||
{
|
||||
private SharedTestState sharedTestState;
|
||||
|
||||
public BundleProbe(SharedTestState fixture)
|
||||
{
|
||||
sharedTestState = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private void Bundle_Probe_Not_Passed_For_Non_Single_File_App()
|
||||
{
|
||||
var fixture = sharedTestState.TestFixture.Copy();
|
||||
string appExe = BundleHelper.GetHostPath(fixture);
|
||||
|
||||
Command.Create(appExe)
|
||||
.CaptureStdErr()
|
||||
.CaptureStdOut()
|
||||
.Execute()
|
||||
.Should()
|
||||
.Pass()
|
||||
.And
|
||||
.HaveStdOutContaining("No BUNDLE_PROBE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private void Bundle_Probe_Passed_For_Single_File_App()
|
||||
{
|
||||
var fixture = sharedTestState.TestFixture.Copy();
|
||||
string singleFile = BundleHelper.BundleApp(fixture);
|
||||
|
||||
Command.Create(singleFile, "SingleFile")
|
||||
.CaptureStdErr()
|
||||
.CaptureStdOut()
|
||||
.Execute()
|
||||
.Should()
|
||||
.Pass()
|
||||
.And
|
||||
.HaveStdOutContaining("BUNDLE_PROBE OK");
|
||||
}
|
||||
|
||||
public class SharedTestState : IDisposable
|
||||
{
|
||||
public TestProjectFixture TestFixture { get; set; }
|
||||
public RepoDirectoriesProvider RepoDirectories { get; set; }
|
||||
|
||||
public SharedTestState()
|
||||
{
|
||||
RepoDirectories = new RepoDirectoriesProvider();
|
||||
TestFixture = new TestProjectFixture("BundleProbeTester", RepoDirectories);
|
||||
TestFixture
|
||||
.EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages)
|
||||
.PublishProject(runtime: TestFixture.CurrentRid,
|
||||
outputDirectory: BundleHelper.GetPublishPath(TestFixture));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
TestFixture.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue