-// 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.
}
}
-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;
}
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)
{
// 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;
}
+
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()
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;
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); }
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); }
return ret;
}
- string_t to_string(int value);
string_t get_timestamp();
bool getcwd(string_t* recv);
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);
#include <fcntl.h>
#include <fnmatch.h>
#include <ctime>
+#include <locale>
+#include <codecvt>
#include <pwd.h>
#include "config.h"
#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;
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() == '/';
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);
return realpath(&extraction_dir);
}
-
static bool wchar_convert_helper(DWORD code_page, const char* cstr, int len, pal::string_t* out)
{
out->clear();
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)
{
_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");
AppPaths,
AppNIPaths,
RuntimeIdentifier,
-
+ BundleProbe,
// Sentinel value - new values should be defined above
Last
};
#include "deps_resolver.h"
#include <error_codes.h>
#include <trace.h>
+#include "bundle/runner.h"
+#include "bundle/file_entry.h"
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)
}
}
+ // 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;
}
--- /dev/null
+<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>
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+// 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();
+ }
+ }
+ }
+}