From 47ec733ba79b196e4e09d0c89bad245155002353 Mon Sep 17 00:00:00 2001 From: Swaroop Sridhar Date: Fri, 1 May 2020 09:24:13 -0700 Subject: [release/5.0-preview4] Revert processing bundles in framework (#35679) This commit reverts: Revert "Single-File: Process bundles in the framework (#34274)" This reverts commit 78b303df8fbb242985d049a277d0d199cafd51b5. Revert "Single-File Bundler: Add a FileSize test (#35149)" This reverts commit 779588a509b81d909ac5d496c172949ec1f1ddcc. *Customer Scenario* Publishing apps as a self-contained single-file doesn't work as expected. * Publish needs to generate hostpolicy and hostfxr separate from the single file bundle * Cross-platform publishing is incorrect *Problem* Since Static-apphost is not yet ready, processing bundle content in hostpolicy means that hostpolicy and hostfxr DLLs need to be separate from the bundle. This causes self-contained single-file apps to not be a "single file" temporarily. The change also requires supporting changes from the SDK, to publish hostfxr and hostpolicy as separate files, and to invoke HostModel library with arguments that facilitate cross-platform publishing. *Solution* To solve these, problem, this change reverts: Revert "Single-File: Process bundles in the framework (#34274)" commit 78b303df8fbb242985d049a277d0d199cafd51b5. and a dependent test-only change: Revert "Single-File Bundler: Add a FileSize test (#35149)" commit 779588a509b81d909ac5d496c172949ec1f1ddcc. *Risk* Medium The change is contained to only host components: apphost, hostpolicy, and hostfxr. However, the change is big, and needs testing in runtime and SDK repos. *Testing* Manually tested the SDK by inserting apphost, hostfxr, hostpolicy, and hostmodel library from this build into the `dotnet/packs` preview-4 SDK from https://github.com/dotnet/sdk/pull/11518 build. Verified that: * Singlefile apps can be published and run OK for { Windows, Linux, Osx } x {netcoreapp3.0, netcoreapp3.1, netcoreapp5.0} * Cross-targeting builds of single-file apps build and run OK (ex: built on Windos, run on Mac). --- src/installer/corehost/cli/apphost/CMakeLists.txt | 19 +- .../corehost/cli/apphost/bundle/dir_utils.cpp | 142 ++++++++++++ .../corehost/cli/apphost/bundle/dir_utils.h | 24 ++ .../corehost/cli/apphost/bundle/extractor.cpp | 250 ++++++++++++++++++++ .../corehost/cli/apphost/bundle/extractor.h | 52 +++++ .../corehost/cli/apphost/bundle/file_entry.cpp | 35 +++ .../corehost/cli/apphost/bundle/file_entry.h | 72 ++++++ .../corehost/cli/apphost/bundle/file_type.h | 32 +++ .../corehost/cli/apphost/bundle/header.cpp | 53 +++++ src/installer/corehost/cli/apphost/bundle/header.h | 96 ++++++++ .../corehost/cli/apphost/bundle/manifest.cpp | 19 ++ .../corehost/cli/apphost/bundle/manifest.h | 24 ++ .../corehost/cli/apphost/bundle/marker.cpp | 33 +++ src/installer/corehost/cli/apphost/bundle/marker.h | 32 +++ .../corehost/cli/apphost/bundle/reader.cpp | 98 ++++++++ src/installer/corehost/cli/apphost/bundle/reader.h | 72 ++++++ .../corehost/cli/apphost/bundle/runner.cpp | 65 ++++++ src/installer/corehost/cli/apphost/bundle/runner.h | 40 ++++ .../corehost/cli/apphost/bundle_marker.cpp | 31 --- src/installer/corehost/cli/apphost/bundle_marker.h | 30 --- src/installer/corehost/cli/bundle/dir_utils.cpp | 142 ------------ src/installer/corehost/cli/bundle/dir_utils.h | 24 -- src/installer/corehost/cli/bundle/extractor.cpp | 258 --------------------- src/installer/corehost/cli/bundle/extractor.h | 52 ----- src/installer/corehost/cli/bundle/file_entry.cpp | 50 ---- src/installer/corehost/cli/bundle/file_entry.h | 73 ------ src/installer/corehost/cli/bundle/file_type.h | 32 --- src/installer/corehost/cli/bundle/header.cpp | 45 ---- src/installer/corehost/cli/bundle/header.h | 100 -------- src/installer/corehost/cli/bundle/info.cpp | 157 ------------- src/installer/corehost/cli/bundle/info.h | 91 -------- src/installer/corehost/cli/bundle/manifest.cpp | 21 -- src/installer/corehost/cli/bundle/manifest.h | 32 --- src/installer/corehost/cli/bundle/reader.cpp | 101 -------- src/installer/corehost/cli/bundle/reader.h | 73 ------ src/installer/corehost/cli/bundle/runner.cpp | 83 ------- src/installer/corehost/cli/bundle/runner.h | 49 ---- src/installer/corehost/cli/deps_entry.cpp | 118 +++------- src/installer/corehost/cli/deps_entry.h | 14 +- src/installer/corehost/cli/deps_format.cpp | 7 +- src/installer/corehost/cli/fxr/command_line.cpp | 3 +- src/installer/corehost/cli/fxr/corehost_init.cpp | 3 - src/installer/corehost/cli/fxr/corehost_init.h | 3 +- src/installer/corehost/cli/fxr/fx_muxer.cpp | 8 - src/installer/corehost/cli/fxr/hostfxr.cpp | 18 -- .../corehost/cli/fxr/hostpolicy_resolver.cpp | 1 + src/installer/corehost/cli/host_interface.h | 5 +- src/installer/corehost/cli/host_startup_info.h | 1 - .../corehost/cli/hostcommon/CMakeLists.txt | 6 - src/installer/corehost/cli/hostfxr.h | 7 - src/installer/corehost/cli/hostmisc/pal.h | 8 +- src/installer/corehost/cli/hostmisc/pal.unix.cpp | 30 +-- .../corehost/cli/hostmisc/pal.windows.cpp | 31 +-- src/installer/corehost/cli/hostmisc/utils.cpp | 19 +- src/installer/corehost/cli/hostmisc/utils.h | 2 - .../corehost/cli/hostpolicy/CMakeLists.txt | 10 - src/installer/corehost/cli/hostpolicy/args.cpp | 46 +--- .../corehost/cli/hostpolicy/deps_resolver.cpp | 6 +- .../corehost/cli/hostpolicy/deps_resolver.h | 12 - .../corehost/cli/hostpolicy/hostpolicy.cpp | 15 +- .../corehost/cli/hostpolicy/hostpolicy_init.cpp | 10 - .../corehost/cli/hostpolicy/hostpolicy_init.h | 1 - src/installer/corehost/cli/ijwhost/ijwthunk.cpp | 6 +- .../corehost/cli/json/rapidjson/document.h | 4 +- src/installer/corehost/cli/json_parser.cpp | 62 ++--- src/installer/corehost/cli/json_parser.h | 20 +- src/installer/corehost/cli/runtime_config.cpp | 5 +- .../cli/test/mockhostpolicy/mockhostpolicy.cpp | 1 - src/installer/corehost/corehost.cpp | 141 +++++------ .../Microsoft.NET.HostModel/Bundle/Bundler.cs | 18 +- .../Microsoft.NET.HostModel/Bundle/Manifest.cs | 2 +- .../Microsoft.NET.HostModel/Bundle/TargetInfo.cs | 17 +- .../Assets/TestProjects/AppWithSubDirs/Program.cs | 6 +- .../BundleExtractToSpecificPath.cs | 64 +++-- .../AppHost.Bundle.Tests/BundleRename.cs | 4 +- .../AppHost.Bundle.Tests/BundledAppWithSubDirs.cs | 34 +-- .../Helpers/BundleHelper.cs | 97 +------- .../BundleAndRun.cs | 5 +- .../BundleLegacy.cs | 1 + .../BundlerConsistencyTests.cs | 76 ++++-- src/installer/test/TestUtils/TestApp.cs | 2 - src/installer/test/TestUtils/TestProject.cs | 1 - 82 files changed, 1458 insertions(+), 2094 deletions(-) create mode 100644 src/installer/corehost/cli/apphost/bundle/dir_utils.cpp create mode 100644 src/installer/corehost/cli/apphost/bundle/dir_utils.h create mode 100644 src/installer/corehost/cli/apphost/bundle/extractor.cpp create mode 100644 src/installer/corehost/cli/apphost/bundle/extractor.h create mode 100644 src/installer/corehost/cli/apphost/bundle/file_entry.cpp create mode 100644 src/installer/corehost/cli/apphost/bundle/file_entry.h create mode 100644 src/installer/corehost/cli/apphost/bundle/file_type.h create mode 100644 src/installer/corehost/cli/apphost/bundle/header.cpp create mode 100644 src/installer/corehost/cli/apphost/bundle/header.h create mode 100644 src/installer/corehost/cli/apphost/bundle/manifest.cpp create mode 100644 src/installer/corehost/cli/apphost/bundle/manifest.h create mode 100644 src/installer/corehost/cli/apphost/bundle/marker.cpp create mode 100644 src/installer/corehost/cli/apphost/bundle/marker.h create mode 100644 src/installer/corehost/cli/apphost/bundle/reader.cpp create mode 100644 src/installer/corehost/cli/apphost/bundle/reader.h create mode 100644 src/installer/corehost/cli/apphost/bundle/runner.cpp create mode 100644 src/installer/corehost/cli/apphost/bundle/runner.h delete mode 100644 src/installer/corehost/cli/apphost/bundle_marker.cpp delete mode 100644 src/installer/corehost/cli/apphost/bundle_marker.h delete mode 100644 src/installer/corehost/cli/bundle/dir_utils.cpp delete mode 100644 src/installer/corehost/cli/bundle/dir_utils.h delete mode 100644 src/installer/corehost/cli/bundle/extractor.cpp delete mode 100644 src/installer/corehost/cli/bundle/extractor.h delete mode 100644 src/installer/corehost/cli/bundle/file_entry.cpp delete mode 100644 src/installer/corehost/cli/bundle/file_entry.h delete mode 100644 src/installer/corehost/cli/bundle/file_type.h delete mode 100644 src/installer/corehost/cli/bundle/header.cpp delete mode 100644 src/installer/corehost/cli/bundle/header.h delete mode 100644 src/installer/corehost/cli/bundle/info.cpp delete mode 100644 src/installer/corehost/cli/bundle/info.h delete mode 100644 src/installer/corehost/cli/bundle/manifest.cpp delete mode 100644 src/installer/corehost/cli/bundle/manifest.h delete mode 100644 src/installer/corehost/cli/bundle/reader.cpp delete mode 100644 src/installer/corehost/cli/bundle/reader.h delete mode 100644 src/installer/corehost/cli/bundle/runner.cpp delete mode 100644 src/installer/corehost/cli/bundle/runner.h diff --git a/src/installer/corehost/cli/apphost/CMakeLists.txt b/src/installer/corehost/cli/apphost/CMakeLists.txt index ba01e32ec47..d77f2bfa981 100644 --- a/src/installer/corehost/cli/apphost/CMakeLists.txt +++ b/src/installer/corehost/cli/apphost/CMakeLists.txt @@ -18,11 +18,26 @@ endif() set(SKIP_VERSIONING 1) set(SOURCES - ./bundle_marker.cpp + ./bundle/file_entry.cpp + ./bundle/manifest.cpp + ./bundle/header.cpp + ./bundle/marker.cpp + ./bundle/reader.cpp + ./bundle/extractor.cpp + ./bundle/runner.cpp + ./bundle/dir_utils.cpp ) set(HEADERS - ./bundle_marker.h + ./bundle/file_type.h + ./bundle/file_entry.h + ./bundle/manifest.h + ./bundle/header.h + ./bundle/marker.h + ./bundle/reader.h + ./bundle/extractor.h + ./bundle/runner.h + ./bundle/dir_utils.h ) if(CLR_CMAKE_TARGET_WIN32) diff --git a/src/installer/corehost/cli/apphost/bundle/dir_utils.cpp b/src/installer/corehost/cli/apphost/bundle/dir_utils.cpp new file mode 100644 index 00000000000..eede9529812 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/dir_utils.cpp @@ -0,0 +1,142 @@ +// 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. + +#include "dir_utils.h" +#include "error_codes.h" +#include "utils.h" + +using namespace bundle; + +bool dir_utils_t::has_dirs_in_path(const pal::string_t& path) +{ + return path.find_last_of(DIR_SEPARATOR) != pal::string_t::npos; +} + +void dir_utils_t::create_directory_tree(const pal::string_t &path) +{ + if (path.empty()) + { + return; + } + + if (pal::directory_exists(path)) + { + return; + } + + if (has_dirs_in_path(path)) + { + create_directory_tree(get_directory(path)); + } + + if (!pal::mkdir(path.c_str(), 0700)) // Owner - rwx + { + if (pal::directory_exists(path)) + { + // The directory was created since we last checked. + return; + } + + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Failed to create directory [%s] for extracting bundled files."), path.c_str()); + throw StatusCode::BundleExtractionIOError; + } +} + +void dir_utils_t::remove_directory_tree(const pal::string_t& path) +{ + if (path.empty()) + { + return; + } + + std::vector dirs; + pal::readdir_onlydirectories(path, &dirs); + + for (const pal::string_t &dir : dirs) + { + pal::string_t dir_path = path; + append_path(&dir_path, dir.c_str()); + + remove_directory_tree(dir_path); + } + + std::vector files; + pal::readdir(path, &files); + + for (const pal::string_t &file : files) + { + pal::string_t file_path = path; + append_path(&file_path, file.c_str()); + + if (!pal::remove(file_path.c_str())) + { + trace::warning(_X("Failed to remove temporary file [%s]."), file_path.c_str()); + } + } + + if (!pal::rmdir(path.c_str())) + { + trace::warning(_X("Failed to remove temporary directory [%s]."), path.c_str()); + } +} + +// Fixup a path to have current platform's directory separator. +void dir_utils_t::fixup_path_separator(pal::string_t& path) +{ + const pal::char_t bundle_dir_separator = '/'; + + if (bundle_dir_separator != DIR_SEPARATOR) + { + for (size_t pos = path.find(bundle_dir_separator); + pos != pal::string_t::npos; + pos = path.find(bundle_dir_separator, pos)) + { + path[pos] = DIR_SEPARATOR; + } + } +} + +// Retry the rename operation with some wait in between the attempts. +// This is an attempt to workaround for possible file locking caused by AV software. + +bool dir_utils_t::rename_with_retries(pal::string_t& old_name, pal::string_t& new_name, bool& dir_exists) +{ + for (int retry_count=0; retry_count < 500; retry_count++) + { + if (pal::rename(old_name.c_str(), new_name.c_str()) == 0) + { + return true; + } + bool should_retry = errno == EACCES; + + if (pal::directory_exists(new_name)) + { + // Check directory_exists() on each run, because a concurrent process may have + // created the new_name directory. + // + // The rename() operation above fails with errono == EACCESS if + // * Directory new_name already exists, or + // * Paths are invalid paths, or + // * Due to locking/permission problems. + // Therefore, we need to perform the directory_exists() check again. + + dir_exists = true; + return false; + } + + if (should_retry) + { + trace::info(_X("Retrying Rename [%s] to [%s] due to EACCES error"), old_name.c_str(), new_name.c_str()); + pal::sleep(100); + continue; + } + else + { + return false; + } + } + + return false; +} diff --git a/src/installer/corehost/cli/apphost/bundle/dir_utils.h b/src/installer/corehost/cli/apphost/bundle/dir_utils.h new file mode 100644 index 00000000000..ffe0acffb60 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/dir_utils.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef __DIR_UTIL_H__ +#define __DIR_UTIL_H__ + +#include +#include "pal.h" + +namespace bundle +{ + class dir_utils_t + { + public: + static bool has_dirs_in_path(const pal::string_t &path); + static void remove_directory_tree(const pal::string_t &path); + static void create_directory_tree(const pal::string_t &path); + static void fixup_path_separator(pal::string_t& path); + static bool rename_with_retries(pal::string_t& old_name, pal::string_t& new_name, bool &new_dir_exists); + }; +} + +#endif // __DIR_UTIL_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/extractor.cpp b/src/installer/corehost/cli/apphost/bundle/extractor.cpp new file mode 100644 index 00000000000..71bb2259e9d --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/extractor.cpp @@ -0,0 +1,250 @@ +// 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. + +#include "extractor.h" +#include "error_codes.h" +#include "dir_utils.h" +#include "pal.h" +#include "utils.h" + +using namespace bundle; + +pal::string_t& extractor_t::extraction_dir() +{ + if (m_extraction_dir.empty()) + { + // Compute the final extraction location as: + // m_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR///... + // + // If DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set in the environment, + // a default is choosen within the temporary directory. + + if (!pal::getenv(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR"), &m_extraction_dir)) + { + if (!pal::get_default_bundle_extraction_base_dir(m_extraction_dir)) + { + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Failed to determine location for extracting embedded files.")); + trace::error(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set, and a read-write temp-directory couldn't be created.")); + throw StatusCode::BundleExtractionFailure; + } + } + + pal::string_t host_name = strip_executable_ext(get_filename(m_bundle_path)); + append_path(&m_extraction_dir, host_name.c_str()); + append_path(&m_extraction_dir, m_bundle_id.c_str()); + + trace::info(_X("Files embedded within the bundled will be extracted to [%s] directory."), m_extraction_dir.c_str()); + } + + return m_extraction_dir; +} + +pal::string_t& extractor_t::working_extraction_dir() +{ + if (m_working_extraction_dir.empty()) + { + // Compute the working extraction location for this process, + // before the extracted files are committed to the final location + // working_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR// + + m_working_extraction_dir = get_directory(extraction_dir()); + pal::char_t pid[32]; + pal::snwprintf(pid, 32, _X("%x"), pal::get_pid()); + append_path(&m_working_extraction_dir, pid); + + trace::info(_X("Temporary directory used to extract bundled files is [%s]."), m_working_extraction_dir.c_str()); + } + + return m_working_extraction_dir; +} + +// Create a file to be extracted out on disk, including any intermediate sub-directories. +FILE* extractor_t::create_extraction_file(const pal::string_t& relative_path) +{ + pal::string_t file_path = working_extraction_dir(); + append_path(&file_path, relative_path.c_str()); + + // working_extraction_dir is assumed to exist, + // so we only create sub-directories if relative_path contains directories + if (dir_utils_t::has_dirs_in_path(relative_path)) + { + dir_utils_t::create_directory_tree(get_directory(file_path)); + } + + FILE* file = pal::file_open(file_path.c_str(), _X("wb")); + + if (file == nullptr) + { + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Failed to open file [%s] for writing."), file_path.c_str()); + throw StatusCode::BundleExtractionIOError; + } + + return file; +} + +// Extract one file from the bundle to disk. +void extractor_t::extract(const file_entry_t &entry, reader_t &reader) +{ + FILE* file = create_extraction_file(entry.relative_path()); + reader.set_offset(entry.offset()); + size_t size = entry.size(); + + if (fwrite(reader, 1, size, file) != size) + { + trace::error(_X("Failure extracting contents of the application bundle.")); + trace::error(_X("I/O failure when writing extracted files.")); + throw StatusCode::BundleExtractionIOError; + } + + fclose(file); +} + +void extractor_t::begin() +{ + // Files are extracted to a specific deterministic location on disk + // on first run, and are available for reuse by subsequent similar runs. + // + // The extraction should be fault tolerant with respect to: + // * Failures/crashes during extraction which result in partial-extraction + // * Race between two or more processes concurrently attempting extraction + // + // In order to solve these issues, we implement a extraction as a two-phase approach: + // 1) Files embedded in a bundle are extracted to a process-specific temporary + // extraction location (working_extraction_dir) + // 2) Upon successful extraction, working_extraction_dir is renamed to the actual + // extraction location (extraction_dir) + // + // This effectively creates a file-lock to protect against races and failed extractions. + + + dir_utils_t::create_directory_tree(working_extraction_dir()); +} + +void extractor_t::clean() +{ + dir_utils_t::remove_directory_tree(working_extraction_dir()); +} + +void extractor_t::commit_dir() +{ + // Commit an entire new extraction to the final extraction directory + // Retry the move operation with some wait in between the attempts. This is to workaround for possible file locking + // caused by AV software. Basically the extraction process above writes a bunch of executable files to disk + // and some AV software may decide to scan them on write. If this happens the files will be locked which blocks + // our ablity to move them. + + bool extracted_by_concurrent_process = false; + bool extracted_by_current_process = + dir_utils_t::rename_with_retries(working_extraction_dir(), extraction_dir(), extracted_by_concurrent_process); + + if (extracted_by_concurrent_process) + { + // Another process successfully extracted the dependencies + trace::info(_X("Extraction completed by another process, aborting current extraction.")); + clean(); + } + + if (!extracted_by_current_process && !extracted_by_concurrent_process) + { + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Failed to commit extracted files to directory [%s]."), extraction_dir().c_str()); + throw StatusCode::BundleExtractionFailure; + } + + trace::info(_X("Completed new extraction.")); +} + +void extractor_t::commit_file(const pal::string_t& relative_path) +{ + // Commit individual files to the final extraction directory. + + pal::string_t working_file_path = working_extraction_dir(); + append_path(&working_file_path, relative_path.c_str()); + + pal::string_t final_file_path = extraction_dir(); + append_path(&final_file_path, relative_path.c_str()); + + if (dir_utils_t::has_dirs_in_path(relative_path)) + { + dir_utils_t::create_directory_tree(get_directory(final_file_path)); + } + + bool extracted_by_concurrent_process = false; + bool extracted_by_current_process = + dir_utils_t::rename_with_retries(working_file_path, final_file_path, extracted_by_concurrent_process); + + if (extracted_by_concurrent_process) + { + // Another process successfully extracted the dependencies + trace::info(_X("Extraction completed by another process, aborting current extraction.")); + } + + if (!extracted_by_current_process && !extracted_by_concurrent_process) + { + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Failed to commit extracted files to directory [%s]."), extraction_dir().c_str()); + throw StatusCode::BundleExtractionFailure; + } + + trace::info(_X("Extraction recovered [%s]"), relative_path.c_str()); +} + +void extractor_t::extract_new(reader_t& reader) +{ + begin(); + for (const file_entry_t& entry : m_manifest.files) + { + extract(entry, reader); + } + commit_dir(); +} + +// Verify an existing extraction contains all files listed in the bundle manifest. +// If some files are missing, extract them individually. +void extractor_t::verify_recover_extraction(reader_t& reader) +{ + pal::string_t& ext_dir = extraction_dir(); + bool recovered = false; + + for (const file_entry_t& entry : m_manifest.files) + { + pal::string_t file_path = ext_dir; + append_path(&file_path, entry.relative_path().c_str()); + + if (!pal::file_exists(file_path)) + { + if (!recovered) + { + recovered = true; + begin(); + } + + extract(entry, reader); + commit_file(entry.relative_path()); + } + } + + if (recovered) + { + clean(); + } +} + +pal::string_t& extractor_t::extract(reader_t& reader) +{ + if (pal::directory_exists(extraction_dir())) + { + trace::info(_X("Reusing existing extraction of application bundle.")); + verify_recover_extraction(reader); + } + else + { + trace::info(_X("Starting new extraction of application bundle.")); + extract_new(reader); + } + + return m_extraction_dir; +} diff --git a/src/installer/corehost/cli/apphost/bundle/extractor.h b/src/installer/corehost/cli/apphost/bundle/extractor.h new file mode 100644 index 00000000000..73b75ecdd09 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/extractor.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef __EXTRACTOR_H__ +#define __EXTRACTOR_H__ + +#include "reader.h" +#include "manifest.h" + +namespace bundle +{ + class extractor_t + { + public: + extractor_t(const pal::string_t &bundle_id, + const pal::string_t& bundle_path, + const manifest_t &manifest) + :m_extraction_dir(), + m_working_extraction_dir(), + m_manifest(manifest) + { + m_bundle_id = bundle_id; + m_bundle_path = bundle_path; + } + + pal::string_t& extract(reader_t& reader); + + private: + pal::string_t& extraction_dir(); + pal::string_t& working_extraction_dir(); + + void extract_new(reader_t& reader); + void verify_recover_extraction(reader_t& reader); + + FILE* create_extraction_file(const pal::string_t& relative_path); + void extract(const file_entry_t& entry, reader_t& reader); + + void begin(); + void commit_file(const pal::string_t& relative_path); + void commit_dir(); + void clean(); + + pal::string_t m_bundle_id; + pal::string_t m_bundle_path; + pal::string_t m_extraction_dir; + pal::string_t m_working_extraction_dir; + const manifest_t& m_manifest; + }; +} + +#endif // __EXTRACTOR_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/file_entry.cpp b/src/installer/corehost/cli/apphost/bundle/file_entry.cpp new file mode 100644 index 00000000000..ba4b235fa93 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/file_entry.cpp @@ -0,0 +1,35 @@ +// 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. + +#include "file_entry.h" +#include "trace.h" +#include "dir_utils.h" +#include "error_codes.h" + +using namespace bundle; + +bool file_entry_t::is_valid() const +{ + return m_offset > 0 && m_size >= 0 && + static_cast(m_type) < file_type_t::__last; +} + +file_entry_t file_entry_t::read(reader_t &reader) +{ + // First read the fixed-sized portion of file-entry + const file_entry_fixed_t* fixed_data = reinterpret_cast(reader.read_direct(sizeof(file_entry_fixed_t))); + file_entry_t entry(fixed_data); + + if (!entry.is_valid()) + { + trace::error(_X("Failure processing application bundle; possible file corruption.")); + trace::error(_X("Invalid FileEntry detected.")); + throw StatusCode::BundleExtractionFailure; + } + + reader.read_path_string(entry.m_relative_path); + dir_utils_t::fixup_path_separator(entry.m_relative_path); + + return entry; +} diff --git a/src/installer/corehost/cli/apphost/bundle/file_entry.h b/src/installer/corehost/cli/apphost/bundle/file_entry.h new file mode 100644 index 00000000000..efe405cc9a6 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/file_entry.h @@ -0,0 +1,72 @@ +// 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. + +#ifndef __FILE_ENTRY_H__ +#define __FILE_ENTRY_H__ + +#include "file_type.h" +#include "reader.h" + +namespace bundle +{ + // FileEntry: Records information about embedded files. + // + // The bundle manifest records the following meta-data for each + // file embedded in the bundle: + // Fixed size portion (file_entry_fixed_t) + // - Offset + // - Size + // - File Entry Type + // Variable Size portion + // - relative path (7-bit extension encoded length prefixed string) + +#pragma pack(push, 1) + struct file_entry_fixed_t + { + int64_t offset; + int64_t size; + file_type_t type; + }; +#pragma pack(pop) + + class file_entry_t + { + public: + file_entry_t() + : m_offset(0) + , m_size(0) + , m_type(file_type_t::__last) + , m_relative_path() + { + } + + file_entry_t(const file_entry_fixed_t *fixed_data) + :m_relative_path() + { + // File_entries in the bundle-manifest are expected to be used + // beyond startup (for loading files directly from bundle, lazy extraction, etc.). + // The contents of fixed_data are copied on to file_entry in order to + // avoid memory mapped IO later. + + m_offset = fixed_data->offset; + m_size = fixed_data->size; + m_type = fixed_data->type; + } + + const pal::string_t relative_path() const { return m_relative_path; } + int64_t offset() const { return m_offset; } + int64_t size() const { return m_size; } + file_type_t type() const { return m_type; } + + static file_entry_t read(reader_t &reader); + + private: + int64_t m_offset; + int64_t m_size; + file_type_t m_type; + pal::string_t m_relative_path; // Path of an embedded file, relative to the extraction directory. + bool is_valid() const; + }; +} +#endif // __FILE_ENTRY_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/file_type.h b/src/installer/corehost/cli/apphost/bundle/file_type.h new file mode 100644 index 00000000000..acbfca06859 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/file_type.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef __FILE_TYPE_H__ +#define __FILE_TYPE_H__ + +#include + +namespace bundle +{ + // FileType: Identifies the type of file embedded into the bundle. + // + // The bundler differentiates a few kinds of files via the manifest, + // with respect to the way in which they'll be used by the runtime. + // + // Currently all files are extracted out to the disk, but future + // implementations will process certain file_types directly from the bundle. + + enum file_type_t : uint8_t + { + unknown, + assembly, + native_binary, + deps_json, + runtime_config_json, + symbols, + __last + }; +} + +#endif // __FILE_TYPE_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/header.cpp b/src/installer/corehost/cli/apphost/bundle/header.cpp new file mode 100644 index 00000000000..23695bb93f1 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/header.cpp @@ -0,0 +1,53 @@ +// 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. + +#include "header.h" +#include "reader.h" +#include "error_codes.h" +#include "trace.h" + +using namespace bundle; + +// The AppHost expects the bundle_header to be an exact_match for which it was built. +// The framework accepts backwards compatible header versions. +bool header_fixed_t::is_valid(bool exact_match) const +{ + if (num_embedded_files <= 0) + { + return false; + } + + if (exact_match) + { + return (major_version == header_t::major_version) && (minor_version == header_t::minor_version); + } + + return ((major_version < header_t::major_version) || + (major_version == header_t::major_version && minor_version <= header_t::minor_version)); +} + +header_t header_t::read(reader_t& reader, bool need_exact_version) +{ + const header_fixed_t* fixed_header = reinterpret_cast(reader.read_direct(sizeof(header_fixed_t))); + + if (!fixed_header->is_valid(need_exact_version)) + { + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Bundle header version compatibility check failed.")); + + throw StatusCode::BundleExtractionFailure; + } + + header_t header(fixed_header->num_embedded_files); + + // bundle_id is a component of the extraction path + reader.read_path_string(header.m_bundle_id); + + if (fixed_header->major_version > 1) + { + header.m_v2_header = reinterpret_cast(reader.read_direct(sizeof(header_fixed_v2_t))); + } + + return header; +} diff --git a/src/installer/corehost/cli/apphost/bundle/header.h b/src/installer/corehost/cli/apphost/bundle/header.h new file mode 100644 index 00000000000..1b1696219dc --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/header.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef __HEADER_H__ +#define __HEADER_H__ + +#include +#include "pal.h" +#include "reader.h" + +namespace bundle +{ + // The Bundle Header (v1) + // Fixed size thunk (header_fixed_t) + // - Major Version + // - Minor Version + // - Number of embedded files + // Variable size portion: + // - Bundle ID (7-bit extension encoded length prefixed string) + // The Bundle Header (v2) [additional content] + // Fixed size thunk (header_fixed_v2_t) + // - DepsJson Location (Offset, Size) + // - RuntimeConfig Location (Offset, Size) + // - Flags + +#pragma pack(push, 1) + struct header_fixed_t + { + public: + uint32_t major_version; + uint32_t minor_version; + int32_t num_embedded_files; + + bool is_valid(bool exact_match = false) const; + }; +#pragma pack(pop) + + // netcoreapp3_compat_mode flag is set on a .net5 app, which chooses to build single-file apps in .netcore3.x compat mode, + // This indicates that: + // All published files are bundled into the app; some of them will be extracted to disk. + // AppContext.BaseDirectory is set to the extraction directory (and not the AppHost directory). + enum header_flags_t : uint64_t + { + none = 0, + netcoreapp3_compat_mode = 1 + }; + +#pragma pack(push, 1) + struct location_t + { + public: + int64_t offset; + int64_t size; + }; + + // header_fixed_v2_t is available in single-file apps targetting .net5+ frameworks. + // It stores information that facilitates the host to process contents of the bundle without extraction. + // + // The location of deps.json and runtimeconfig.json is already available in the Bundle manifest. + // However, the data is cached here in order to simplify the bundle-processing performed by hostfxr. + struct header_fixed_v2_t + { + public: + location_t deps_json_location; + location_t runtimeconfig_json_location; + header_flags_t flags; + }; +#pragma pack(pop) + + struct header_t + { + public: + header_t(int32_t num_embedded_files = 0) + : m_num_embedded_files(num_embedded_files) + , m_bundle_id() + , m_v2_header(NULL) + + { + } + + static header_t read(reader_t& reader, bool need_exact_version); + const pal::string_t& bundle_id() { return m_bundle_id; } + int32_t num_embedded_files() { return m_num_embedded_files; } + + static const uint32_t major_version = 2; + static const uint32_t minor_version = 0; + + private: + int32_t m_num_embedded_files; + pal::string_t m_bundle_id; + const header_fixed_v2_t* m_v2_header; + + }; +} +#endif // __HEADER_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/manifest.cpp b/src/installer/corehost/cli/apphost/bundle/manifest.cpp new file mode 100644 index 00000000000..6de65d3a96b --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/manifest.cpp @@ -0,0 +1,19 @@ +// 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. + +#include "manifest.h" + +using namespace bundle; + +manifest_t manifest_t::read(reader_t& reader, int32_t num_files) +{ + manifest_t manifest; + + for (int32_t i = 0; i < num_files; i++) + { + manifest.files.emplace_back(file_entry_t::read(reader)); + } + + return manifest; +} diff --git a/src/installer/corehost/cli/apphost/bundle/manifest.h b/src/installer/corehost/cli/apphost/bundle/manifest.h new file mode 100644 index 00000000000..ee8cd1edab5 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/manifest.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef __MANIFEST_H__ +#define __MANIFEST_H__ + +#include +#include "file_entry.h" + +namespace bundle +{ + // Bundle Manifest contains: + // Series of file entries (for each embedded file) + + class manifest_t + { + public: + std::vector files; + + static manifest_t read(reader_t &reader, int32_t num_files); + }; +} +#endif // __MANIFEST_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/marker.cpp b/src/installer/corehost/cli/apphost/bundle/marker.cpp new file mode 100644 index 00000000000..d2302923226 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/marker.cpp @@ -0,0 +1,33 @@ +// 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. + +#include "marker.h" +#include "pal.h" +#include "trace.h" +#include "utils.h" + +using namespace bundle; + +int64_t marker_t::header_offset() +{ + // Contains the bundle_placeholder default value at compile time. + // If this is a single-file bundle, the last 8 bytes are replaced + // bundle-header replaced by "dotnet publish" with the offset + // where the bundle_header is located. + static volatile uint8_t placeholder[] = + { + // 8 bytes represent the bundle header-offset + // Zero for non-bundle apphosts (default). + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // 64 bytes represent the bundle signature: SHA-256 for ".net core bundle" + 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38, + 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32, + 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18, + 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae + }; + + volatile marker_t* marker = reinterpret_cast(placeholder); + + return marker->locator.bundle_header_offset; +} diff --git a/src/installer/corehost/cli/apphost/bundle/marker.h b/src/installer/corehost/cli/apphost/bundle/marker.h new file mode 100644 index 00000000000..52e185fbe79 --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/marker.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef __MARKER_H__ +#define __MARKER_H__ + +#include + +namespace bundle +{ +#pragma pack(push, 1) + union marker_t + { + public: + uint8_t placeholder[40]; + struct + { + int64_t bundle_header_offset; + uint8_t signature[32]; + } locator; + + static int64_t header_offset(); + static bool is_bundle() + { + return header_offset() != 0; + } + }; +#pragma pack(pop) + +} +#endif // __MARKER_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/reader.cpp b/src/installer/corehost/cli/apphost/bundle/reader.cpp new file mode 100644 index 00000000000..e2fa9b6574c --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/reader.cpp @@ -0,0 +1,98 @@ +// 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. + +#include "reader.h" +#include "error_codes.h" +#include "trace.h" + +using namespace bundle; + +const int8_t* reader_t::add_without_overflow(const int8_t* ptr, int64_t len) +{ + const int8_t* new_ptr = ptr + len; + + // The following check will fail in case len < 0 (which is also an error while reading) + // even if the actual arthmetic didn't overflow. + if (new_ptr < ptr) + { + trace::error(_X("Failure processing application bundle; possible file corruption.")); + trace::error(_X("Arithmetic overflow computing bundle-bounds.")); + throw StatusCode::BundleExtractionFailure; + } + + return new_ptr; +} + +void reader_t::set_offset(int64_t offset) +{ + if (offset < 0 || offset >= m_bound) + { + trace::error(_X("Failure processing application bundle; possible file corruption.")); + trace::error(_X("Arithmetic overflow while reading bundle.")); + throw StatusCode::BundleExtractionFailure; + } + + m_ptr = m_base_ptr + offset; +} + +void reader_t::bounds_check(int64_t len) +{ + const int8_t* post_read_ptr = add_without_overflow(m_ptr, len); + + // It is legal for post_read_ptr == m_bound_ptr after reading the last byte. + if (m_ptr < m_base_ptr || post_read_ptr > m_bound_ptr) + { + trace::error(_X("Failure processing application bundle; possible file corruption.")); + trace::error(_X("Bounds check failed while reading the bundle.")); + throw StatusCode::BundleExtractionFailure; + } +} + +// Handle the relatively uncommon scenario where the bundle ID or +// the relative-path of a file within the bundle is longer than 127 bytes +size_t reader_t::read_path_length() +{ + size_t length = 0; + + int8_t first_byte = read(); + + // If the high bit is set, it means there are more bytes to read. + if ((first_byte & 0x80) == 0) + { + length = first_byte; + } + else + { + int8_t second_byte = read(); + + if (second_byte & 0x80) + { + // There can be no more than two bytes in path_length + trace::error(_X("Failure processing application bundle; possible file corruption.")); + trace::error(_X("Path length encoding read beyond two bytes.")); + + throw StatusCode::BundleExtractionFailure; + } + + length = (second_byte << 7) | (first_byte & 0x7f); + } + + if (length <= 0 || length > PATH_MAX) + { + trace::error(_X("Failure processing application bundle; possible file corruption.")); + trace::error(_X("Path length is zero or too long.")); + throw StatusCode::BundleExtractionFailure; + } + + return length; +} + +void reader_t::read_path_string(pal::string_t &str) +{ + size_t size = read_path_length(); + std::unique_ptr buffer{ new uint8_t[size + 1] }; + read(buffer.get(), size); + buffer[size] = 0; // null-terminator + pal::clr_palstring(reinterpret_cast(buffer.get()), &str); +} diff --git a/src/installer/corehost/cli/apphost/bundle/reader.h b/src/installer/corehost/cli/apphost/bundle/reader.h new file mode 100644 index 00000000000..1824ece515a --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/reader.h @@ -0,0 +1,72 @@ +// 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. + +#ifndef __READER_H__ +#define __READER_H__ + +#include +#include "pal.h" + +namespace bundle +{ + // Helper class for reading sequentially from the memory-mapped bundle file. + struct reader_t + { + reader_t(const int8_t* base_ptr, int64_t bound) + : m_base_ptr(base_ptr) + , m_ptr(base_ptr) + , m_bound(bound) + , m_bound_ptr(add_without_overflow(base_ptr, bound)) + { + } + + public: + + void set_offset(int64_t offset); + + operator const int8_t*() const + { + return m_ptr; + } + + int8_t read() + { + bounds_check(); + return *m_ptr++; + } + + // Copy len bytes from m_ptr to dest + void read(void* dest, int64_t len) + { + bounds_check(len); + memcpy(dest, m_ptr, len); + m_ptr += len; + } + + // Return a pointer to the requested bytes within the memory-mapped file. + // Skip over len bytes. + const int8_t* read_direct(int64_t len) + { + bounds_check(len); + const int8_t *ptr = m_ptr; + m_ptr += len; + return ptr; + } + + size_t read_path_length(); + void read_path_string(pal::string_t &str); + + private: + + void bounds_check(int64_t len = 1); + static const int8_t* add_without_overflow(const int8_t* ptr, int64_t len); + + const int8_t* const m_base_ptr; + const int8_t* m_ptr; + const int64_t m_bound; + const int8_t* const m_bound_ptr; + }; +} + +#endif // __READER_H__ diff --git a/src/installer/corehost/cli/apphost/bundle/runner.cpp b/src/installer/corehost/cli/apphost/bundle/runner.cpp new file mode 100644 index 00000000000..50d74235d7a --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/runner.cpp @@ -0,0 +1,65 @@ +// 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. + +#include +#include "extractor.h" +#include "runner.h" +#include "trace.h" +#include "header.h" +#include "marker.h" +#include "manifest.h" + +using namespace bundle; + +void runner_t::map_host() +{ + m_bundle_map = (int8_t *) pal::map_file_readonly(m_bundle_path, m_bundle_length); + + if (m_bundle_map == nullptr) + { + trace::error(_X("Failure processing application bundle.")); + trace::error(_X("Couldn't memory map the bundle file for reading.")); + throw StatusCode::BundleExtractionIOError; + } +} + +void runner_t::unmap_host() +{ + if (!pal::unmap_file(m_bundle_map, m_bundle_length)) + { + trace::warning(_X("Failed to unmap bundle after extraction.")); + } +} + +// Current support for executing single-file bundles involves +// extraction of embedded files to actual files on disk. +// This method implements the file extraction functionality at startup. +StatusCode runner_t::extract() +{ + try + { + map_host(); + reader_t reader(m_bundle_map, m_bundle_length); + + // Read the bundle header + reader.set_offset(marker_t::header_offset()); + header_t header = header_t::read(reader, /* need_exact_version: */ true); + + // Read the bundle manifest + // Reader is at the correct offset + manifest_t manifest = manifest_t::read(reader, header.num_embedded_files()); + + // Extract the files + extractor_t extractor(header.bundle_id(), m_bundle_path, manifest); + m_extraction_dir = extractor.extract(reader); + + unmap_host(); + return StatusCode::Success; + } + catch (StatusCode e) + { + return e; + } +} + diff --git a/src/installer/corehost/cli/apphost/bundle/runner.h b/src/installer/corehost/cli/apphost/bundle/runner.h new file mode 100644 index 00000000000..07dadede0fd --- /dev/null +++ b/src/installer/corehost/cli/apphost/bundle/runner.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef __RUNNER_H__ +#define __RUNNER_H__ + +#include "error_codes.h" + +namespace bundle +{ + class runner_t + { + public: + runner_t(const pal::string_t& bundle_path) + : m_bundle_path(bundle_path) + , m_bundle_map(nullptr) + , m_bundle_length(0) + { + } + + StatusCode extract(); + + pal::string_t extraction_dir() + { + return m_extraction_dir; + } + + private: + void map_host(); + void unmap_host(); + + pal::string_t m_bundle_path; + pal::string_t m_extraction_dir; + int8_t* m_bundle_map; + size_t m_bundle_length; + }; +} + +#endif // __RUNNER_H__ diff --git a/src/installer/corehost/cli/apphost/bundle_marker.cpp b/src/installer/corehost/cli/apphost/bundle_marker.cpp deleted file mode 100644 index d8ea15bdd4f..00000000000 --- a/src/installer/corehost/cli/apphost/bundle_marker.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// 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. - -#include "bundle_marker.h" -#include "pal.h" -#include "trace.h" -#include "utils.h" - -int64_t bundle_marker_t::header_offset() -{ - // Contains the bundle_placeholder default value at compile time. - // If this is a single-file bundle, the last 8 bytes are replaced - // bundle-header replaced by "dotnet publish" with the offset - // where the bundle_header is located. - static volatile uint8_t placeholder[] = - { - // 8 bytes represent the bundle header-offset - // Zero for non-bundle apphosts (default). - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 64 bytes represent the bundle signature: SHA-256 for ".net core bundle" - 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38, - 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32, - 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18, - 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae - }; - - volatile bundle_marker_t* marker = reinterpret_cast(placeholder); - - return marker->locator.bundle_header_offset; -} diff --git a/src/installer/corehost/cli/apphost/bundle_marker.h b/src/installer/corehost/cli/apphost/bundle_marker.h deleted file mode 100644 index c5065fdac19..00000000000 --- a/src/installer/corehost/cli/apphost/bundle_marker.h +++ /dev/null @@ -1,30 +0,0 @@ -// 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. - -#ifndef __BUNDLE_MARKER_H__ -#define __BUNDLE_MARKER_H__ - -#include - -#pragma pack(push, 1) - union bundle_marker_t - { - public: - uint8_t placeholder[40]; - struct - { - int64_t bundle_header_offset; - uint8_t signature[32]; - } locator; - - static int64_t header_offset(); - static bool is_bundle() - { - return header_offset() != 0; - } - }; -#pragma pack(pop) - - -#endif // __BUNDLE_MARKER_H__ diff --git a/src/installer/corehost/cli/bundle/dir_utils.cpp b/src/installer/corehost/cli/bundle/dir_utils.cpp deleted file mode 100644 index eede9529812..00000000000 --- a/src/installer/corehost/cli/bundle/dir_utils.cpp +++ /dev/null @@ -1,142 +0,0 @@ -// 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. - -#include "dir_utils.h" -#include "error_codes.h" -#include "utils.h" - -using namespace bundle; - -bool dir_utils_t::has_dirs_in_path(const pal::string_t& path) -{ - return path.find_last_of(DIR_SEPARATOR) != pal::string_t::npos; -} - -void dir_utils_t::create_directory_tree(const pal::string_t &path) -{ - if (path.empty()) - { - return; - } - - if (pal::directory_exists(path)) - { - return; - } - - if (has_dirs_in_path(path)) - { - create_directory_tree(get_directory(path)); - } - - if (!pal::mkdir(path.c_str(), 0700)) // Owner - rwx - { - if (pal::directory_exists(path)) - { - // The directory was created since we last checked. - return; - } - - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Failed to create directory [%s] for extracting bundled files."), path.c_str()); - throw StatusCode::BundleExtractionIOError; - } -} - -void dir_utils_t::remove_directory_tree(const pal::string_t& path) -{ - if (path.empty()) - { - return; - } - - std::vector dirs; - pal::readdir_onlydirectories(path, &dirs); - - for (const pal::string_t &dir : dirs) - { - pal::string_t dir_path = path; - append_path(&dir_path, dir.c_str()); - - remove_directory_tree(dir_path); - } - - std::vector files; - pal::readdir(path, &files); - - for (const pal::string_t &file : files) - { - pal::string_t file_path = path; - append_path(&file_path, file.c_str()); - - if (!pal::remove(file_path.c_str())) - { - trace::warning(_X("Failed to remove temporary file [%s]."), file_path.c_str()); - } - } - - if (!pal::rmdir(path.c_str())) - { - trace::warning(_X("Failed to remove temporary directory [%s]."), path.c_str()); - } -} - -// Fixup a path to have current platform's directory separator. -void dir_utils_t::fixup_path_separator(pal::string_t& path) -{ - const pal::char_t bundle_dir_separator = '/'; - - if (bundle_dir_separator != DIR_SEPARATOR) - { - for (size_t pos = path.find(bundle_dir_separator); - pos != pal::string_t::npos; - pos = path.find(bundle_dir_separator, pos)) - { - path[pos] = DIR_SEPARATOR; - } - } -} - -// Retry the rename operation with some wait in between the attempts. -// This is an attempt to workaround for possible file locking caused by AV software. - -bool dir_utils_t::rename_with_retries(pal::string_t& old_name, pal::string_t& new_name, bool& dir_exists) -{ - for (int retry_count=0; retry_count < 500; retry_count++) - { - if (pal::rename(old_name.c_str(), new_name.c_str()) == 0) - { - return true; - } - bool should_retry = errno == EACCES; - - if (pal::directory_exists(new_name)) - { - // Check directory_exists() on each run, because a concurrent process may have - // created the new_name directory. - // - // The rename() operation above fails with errono == EACCESS if - // * Directory new_name already exists, or - // * Paths are invalid paths, or - // * Due to locking/permission problems. - // Therefore, we need to perform the directory_exists() check again. - - dir_exists = true; - return false; - } - - if (should_retry) - { - trace::info(_X("Retrying Rename [%s] to [%s] due to EACCES error"), old_name.c_str(), new_name.c_str()); - pal::sleep(100); - continue; - } - else - { - return false; - } - } - - return false; -} diff --git a/src/installer/corehost/cli/bundle/dir_utils.h b/src/installer/corehost/cli/bundle/dir_utils.h deleted file mode 100644 index ffe0acffb60..00000000000 --- a/src/installer/corehost/cli/bundle/dir_utils.h +++ /dev/null @@ -1,24 +0,0 @@ -// 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. - -#ifndef __DIR_UTIL_H__ -#define __DIR_UTIL_H__ - -#include -#include "pal.h" - -namespace bundle -{ - class dir_utils_t - { - public: - static bool has_dirs_in_path(const pal::string_t &path); - static void remove_directory_tree(const pal::string_t &path); - static void create_directory_tree(const pal::string_t &path); - static void fixup_path_separator(pal::string_t& path); - static bool rename_with_retries(pal::string_t& old_name, pal::string_t& new_name, bool &new_dir_exists); - }; -} - -#endif // __DIR_UTIL_H__ diff --git a/src/installer/corehost/cli/bundle/extractor.cpp b/src/installer/corehost/cli/bundle/extractor.cpp deleted file mode 100644 index 04c5205da9d..00000000000 --- a/src/installer/corehost/cli/bundle/extractor.cpp +++ /dev/null @@ -1,258 +0,0 @@ -// 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. - -#include "extractor.h" -#include "error_codes.h" -#include "dir_utils.h" -#include "pal.h" -#include "utils.h" - -using namespace bundle; - -pal::string_t& extractor_t::extraction_dir() -{ - if (m_extraction_dir.empty()) - { - // Compute the final extraction location as: - // m_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR///... - // - // If DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set in the environment, - // a default is choosen within the temporary directory. - - if (!pal::getenv(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR"), &m_extraction_dir)) - { - if (!pal::get_default_bundle_extraction_base_dir(m_extraction_dir)) - { - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Failed to determine location for extracting embedded files.")); - trace::error(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set, and a read-write temp-directory couldn't be created.")); - throw StatusCode::BundleExtractionFailure; - } - } - - pal::string_t host_name = strip_executable_ext(get_filename(m_bundle_path)); - append_path(&m_extraction_dir, host_name.c_str()); - append_path(&m_extraction_dir, m_bundle_id.c_str()); - - trace::info(_X("Files embedded within the bundled will be extracted to [%s] directory."), m_extraction_dir.c_str()); - } - - return m_extraction_dir; -} - -pal::string_t& extractor_t::working_extraction_dir() -{ - if (m_working_extraction_dir.empty()) - { - // Compute the working extraction location for this process, - // before the extracted files are committed to the final location - // working_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR// - - m_working_extraction_dir = get_directory(extraction_dir()); - pal::char_t pid[32]; - pal::snwprintf(pid, 32, _X("%x"), pal::get_pid()); - append_path(&m_working_extraction_dir, pid); - - trace::info(_X("Temporary directory used to extract bundled files is [%s]."), m_working_extraction_dir.c_str()); - } - - return m_working_extraction_dir; -} - -// Create a file to be extracted out on disk, including any intermediate sub-directories. -FILE* extractor_t::create_extraction_file(const pal::string_t& relative_path) -{ - pal::string_t file_path = working_extraction_dir(); - append_path(&file_path, relative_path.c_str()); - - // working_extraction_dir is assumed to exist, - // so we only create sub-directories if relative_path contains directories - if (dir_utils_t::has_dirs_in_path(relative_path)) - { - dir_utils_t::create_directory_tree(get_directory(file_path)); - } - - FILE* file = pal::file_open(file_path.c_str(), _X("wb")); - - if (file == nullptr) - { - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Failed to open file [%s] for writing."), file_path.c_str()); - throw StatusCode::BundleExtractionIOError; - } - - return file; -} - -// Extract one file from the bundle to disk. -void extractor_t::extract(const file_entry_t &entry, reader_t &reader) -{ - FILE* file = create_extraction_file(entry.relative_path()); - reader.set_offset(entry.offset()); - size_t size = entry.size(); - - if (fwrite(reader, 1, size, file) != size) - { - trace::error(_X("Failure extracting contents of the application bundle.")); - trace::error(_X("I/O failure when writing extracted files.")); - throw StatusCode::BundleExtractionIOError; - } - - fclose(file); -} - -void extractor_t::begin() -{ - // Files are extracted to a specific deterministic location on disk - // on first run, and are available for reuse by subsequent similar runs. - // - // The extraction should be fault tolerant with respect to: - // * Failures/crashes during extraction which result in partial-extraction - // * Race between two or more processes concurrently attempting extraction - // - // In order to solve these issues, we implement a extraction as a two-phase approach: - // 1) Files embedded in a bundle are extracted to a process-specific temporary - // extraction location (working_extraction_dir) - // 2) Upon successful extraction, working_extraction_dir is renamed to the actual - // extraction location (extraction_dir) - // - // This effectively creates a file-lock to protect against races and failed extractions. - - - dir_utils_t::create_directory_tree(working_extraction_dir()); -} - -void extractor_t::clean() -{ - dir_utils_t::remove_directory_tree(working_extraction_dir()); -} - -void extractor_t::commit_dir() -{ - // Commit an entire new extraction to the final extraction directory - // Retry the move operation with some wait in between the attempts. This is to workaround for possible file locking - // caused by AV software. Basically the extraction process above writes a bunch of executable files to disk - // and some AV software may decide to scan them on write. If this happens the files will be locked which blocks - // our ablity to move them. - - bool extracted_by_concurrent_process = false; - bool extracted_by_current_process = - dir_utils_t::rename_with_retries(working_extraction_dir(), extraction_dir(), extracted_by_concurrent_process); - - if (extracted_by_concurrent_process) - { - // Another process successfully extracted the dependencies - trace::info(_X("Extraction completed by another process, aborting current extraction.")); - clean(); - } - - if (!extracted_by_current_process && !extracted_by_concurrent_process) - { - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Failed to commit extracted files to directory [%s]."), extraction_dir().c_str()); - throw StatusCode::BundleExtractionFailure; - } - - trace::info(_X("Completed new extraction.")); -} - -void extractor_t::commit_file(const pal::string_t& relative_path) -{ - // Commit individual files to the final extraction directory. - - pal::string_t working_file_path = working_extraction_dir(); - append_path(&working_file_path, relative_path.c_str()); - - pal::string_t final_file_path = extraction_dir(); - append_path(&final_file_path, relative_path.c_str()); - - if (dir_utils_t::has_dirs_in_path(relative_path)) - { - dir_utils_t::create_directory_tree(get_directory(final_file_path)); - } - - bool extracted_by_concurrent_process = false; - bool extracted_by_current_process = - dir_utils_t::rename_with_retries(working_file_path, final_file_path, extracted_by_concurrent_process); - - if (extracted_by_concurrent_process) - { - // Another process successfully extracted the dependencies - trace::info(_X("Extraction completed by another process, aborting current extraction.")); - } - - if (!extracted_by_current_process && !extracted_by_concurrent_process) - { - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Failed to commit extracted files to directory [%s]."), extraction_dir().c_str()); - throw StatusCode::BundleExtractionFailure; - } - - trace::info(_X("Extraction recovered [%s]"), relative_path.c_str()); -} - -void extractor_t::extract_new(reader_t& reader) -{ - begin(); - for (const file_entry_t& entry : m_manifest.files) - { - if (entry.needs_extraction()) - { - extract(entry, reader); - } - } - commit_dir(); -} - -// Verify an existing extraction contains all files listed in the bundle manifest. -// If some files are missing, extract them individually. -void extractor_t::verify_recover_extraction(reader_t& reader) -{ - pal::string_t& ext_dir = extraction_dir(); - bool recovered = false; - - for (const file_entry_t& entry : m_manifest.files) - { - if (!entry.needs_extraction()) - { - continue; - } - - pal::string_t file_path = ext_dir; - append_path(&file_path, entry.relative_path().c_str()); - - if (!pal::file_exists(file_path)) - { - if (!recovered) - { - recovered = true; - begin(); - } - - extract(entry, reader); - commit_file(entry.relative_path()); - } - } - - if (recovered) - { - clean(); - } -} - -pal::string_t& extractor_t::extract(reader_t& reader) -{ - if (pal::directory_exists(extraction_dir())) - { - trace::info(_X("Reusing existing extraction of application bundle.")); - verify_recover_extraction(reader); - } - else - { - trace::info(_X("Starting new extraction of application bundle.")); - extract_new(reader); - } - - return m_extraction_dir; -} diff --git a/src/installer/corehost/cli/bundle/extractor.h b/src/installer/corehost/cli/bundle/extractor.h deleted file mode 100644 index 73b75ecdd09..00000000000 --- a/src/installer/corehost/cli/bundle/extractor.h +++ /dev/null @@ -1,52 +0,0 @@ -// 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. - -#ifndef __EXTRACTOR_H__ -#define __EXTRACTOR_H__ - -#include "reader.h" -#include "manifest.h" - -namespace bundle -{ - class extractor_t - { - public: - extractor_t(const pal::string_t &bundle_id, - const pal::string_t& bundle_path, - const manifest_t &manifest) - :m_extraction_dir(), - m_working_extraction_dir(), - m_manifest(manifest) - { - m_bundle_id = bundle_id; - m_bundle_path = bundle_path; - } - - pal::string_t& extract(reader_t& reader); - - private: - pal::string_t& extraction_dir(); - pal::string_t& working_extraction_dir(); - - void extract_new(reader_t& reader); - void verify_recover_extraction(reader_t& reader); - - FILE* create_extraction_file(const pal::string_t& relative_path); - void extract(const file_entry_t& entry, reader_t& reader); - - void begin(); - void commit_file(const pal::string_t& relative_path); - void commit_dir(); - void clean(); - - pal::string_t m_bundle_id; - pal::string_t m_bundle_path; - pal::string_t m_extraction_dir; - pal::string_t m_working_extraction_dir; - const manifest_t& m_manifest; - }; -} - -#endif // __EXTRACTOR_H__ diff --git a/src/installer/corehost/cli/bundle/file_entry.cpp b/src/installer/corehost/cli/bundle/file_entry.cpp deleted file mode 100644 index 775117fb9a1..00000000000 --- a/src/installer/corehost/cli/bundle/file_entry.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// 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. - -#include "file_entry.h" -#include "trace.h" -#include "dir_utils.h" -#include "error_codes.h" - -using namespace bundle; - -bool file_entry_t::is_valid() const -{ - return m_offset > 0 && m_size >= 0 && - static_cast(m_type) < file_type_t::__last; -} - -file_entry_t file_entry_t::read(reader_t &reader) -{ - // First read the fixed-sized portion of file-entry - const file_entry_fixed_t* fixed_data = reinterpret_cast(reader.read_direct(sizeof(file_entry_fixed_t))); - file_entry_t entry(fixed_data); - - if (!entry.is_valid()) - { - trace::error(_X("Failure processing application bundle; possible file corruption.")); - trace::error(_X("Invalid FileEntry detected.")); - throw StatusCode::BundleExtractionFailure; - } - - reader.read_path_string(entry.m_relative_path); - dir_utils_t::fixup_path_separator(entry.m_relative_path); - - return entry; -} - -bool file_entry_t::needs_extraction() const -{ - switch (m_type) - { - // Once the runtime can load assemblies from bundle, - // file_type_t::assembly should be in this category - case file_type_t::deps_json: - case file_type_t::runtime_config_json: - return false; - - default: - return true; - } -} diff --git a/src/installer/corehost/cli/bundle/file_entry.h b/src/installer/corehost/cli/bundle/file_entry.h deleted file mode 100644 index c89d3ce0a5f..00000000000 --- a/src/installer/corehost/cli/bundle/file_entry.h +++ /dev/null @@ -1,73 +0,0 @@ -// 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. - -#ifndef __FILE_ENTRY_H__ -#define __FILE_ENTRY_H__ - -#include "file_type.h" -#include "reader.h" - -namespace bundle -{ - // FileEntry: Records information about embedded files. - // - // The bundle manifest records the following meta-data for each - // file embedded in the bundle: - // Fixed size portion (file_entry_fixed_t) - // - Offset - // - Size - // - File Entry Type - // Variable Size portion - // - relative path (7-bit extension encoded length prefixed string) - -#pragma pack(push, 1) - struct file_entry_fixed_t - { - int64_t offset; - int64_t size; - file_type_t type; - }; -#pragma pack(pop) - - class file_entry_t - { - public: - file_entry_t() - : m_offset(0) - , m_size(0) - , m_type(file_type_t::__last) - , m_relative_path() - { - } - - file_entry_t(const file_entry_fixed_t *fixed_data) - :m_relative_path() - { - // File_entries in the bundle-manifest are expected to be used - // beyond startup (for loading files directly from bundle, lazy extraction, etc.). - // The contents of fixed_data are copied on to file_entry in order to - // avoid memory mapped IO later. - - m_offset = fixed_data->offset; - m_size = fixed_data->size; - m_type = fixed_data->type; - } - - const pal::string_t relative_path() const { return m_relative_path; } - int64_t offset() const { return m_offset; } - int64_t size() const { return m_size; } - file_type_t type() const { return m_type; } - bool needs_extraction() const; - - static file_entry_t read(reader_t &reader); - - private: - int64_t m_offset; - int64_t m_size; - file_type_t m_type; - pal::string_t m_relative_path; // Path of an embedded file, relative to the extraction directory. - bool is_valid() const; - }; -} -#endif // __FILE_ENTRY_H__ diff --git a/src/installer/corehost/cli/bundle/file_type.h b/src/installer/corehost/cli/bundle/file_type.h deleted file mode 100644 index acbfca06859..00000000000 --- a/src/installer/corehost/cli/bundle/file_type.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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. - -#ifndef __FILE_TYPE_H__ -#define __FILE_TYPE_H__ - -#include - -namespace bundle -{ - // FileType: Identifies the type of file embedded into the bundle. - // - // The bundler differentiates a few kinds of files via the manifest, - // with respect to the way in which they'll be used by the runtime. - // - // Currently all files are extracted out to the disk, but future - // implementations will process certain file_types directly from the bundle. - - enum file_type_t : uint8_t - { - unknown, - assembly, - native_binary, - deps_json, - runtime_config_json, - symbols, - __last - }; -} - -#endif // __FILE_TYPE_H__ diff --git a/src/installer/corehost/cli/bundle/header.cpp b/src/installer/corehost/cli/bundle/header.cpp deleted file mode 100644 index 7e6f778464d..00000000000 --- a/src/installer/corehost/cli/bundle/header.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// 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. - -#include "header.h" -#include "reader.h" -#include "error_codes.h" -#include "trace.h" - -using namespace bundle; - -bool header_fixed_t::is_valid() const -{ - if (num_embedded_files <= 0) - { - return false; - } - - // .net 5 host expects the version information to be 2.0 - // .net core 3 single-file bundles are handled within the netcoreapp3.x apphost, and are not processed here in the framework. - return (major_version == header_t::major_version) && (minor_version == header_t::minor_version); -} - -header_t header_t::read(reader_t& reader) -{ - const header_fixed_t* fixed_header = reinterpret_cast(reader.read_direct(sizeof(header_fixed_t))); - - if (!fixed_header->is_valid()) - { - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Bundle header version compatibility check failed.")); - - throw StatusCode::BundleExtractionFailure; - } - - header_t header(fixed_header->num_embedded_files); - - // bundle_id is a component of the extraction path - reader.read_path_string(header.m_bundle_id); - - const header_fixed_v2_t *v2_header = reinterpret_cast(reader.read_direct(sizeof(header_fixed_v2_t))); - header.m_v2_header = *v2_header; - - return header; -} diff --git a/src/installer/corehost/cli/bundle/header.h b/src/installer/corehost/cli/bundle/header.h deleted file mode 100644 index f9ec85f4221..00000000000 --- a/src/installer/corehost/cli/bundle/header.h +++ /dev/null @@ -1,100 +0,0 @@ -// 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. - -#ifndef __HEADER_H__ -#define __HEADER_H__ - -#include -#include "pal.h" -#include "reader.h" - -namespace bundle -{ - // The Bundle Header (v1) - // Fixed size thunk (header_fixed_t) - // - Major Version - // - Minor Version - // - Number of embedded files - // Variable size portion: - // - Bundle ID (7-bit extension encoded length prefixed string) - // The Bundle Header (v2) [additional content] - // Fixed size thunk (header_fixed_v2_t) - // - DepsJson Location (Offset, Size) - // - RuntimeConfig Location (Offset, Size) - // - Flags - -#pragma pack(push, 1) - struct header_fixed_t - { - public: - uint32_t major_version; - uint32_t minor_version; - int32_t num_embedded_files; - - bool is_valid() const; - }; - - // netcoreapp3_compat_mode flag is set on a .net5 app, which chooses to build single-file apps in .netcore3.x compat mode, - // This indicates that: - // All published files are bundled into the app; some of them will be extracted to disk. - // AppContext.BaseDirectory is set to the extraction directory (and not the AppHost directory). - enum header_flags_t : uint64_t - { - none = 0, - netcoreapp3_compat_mode = 1 - }; - - struct location_t - { - public: - int64_t offset; - int64_t size; - - bool is_valid() const { return offset != 0; } - }; - - // header_fixed_v2_t is available in single-file apps targetting .net5+ frameworks. - // It stores information that facilitates the host to process contents of the bundle without extraction. - // - // The location of deps.json and runtimeconfig.json is already available in the Bundle manifest. - // However, the data is cached here in order to simplify the bundle-processing performed by hostfxr. - struct header_fixed_v2_t - { - public: - location_t deps_json_location; - location_t runtimeconfig_json_location; - header_flags_t flags; - - bool is_netcoreapp3_compat_mode() const { return (flags & header_flags_t::netcoreapp3_compat_mode) != 0; } - }; -#pragma pack(pop) - - struct header_t - { - public: - header_t(int32_t num_embedded_files = 0) - : m_num_embedded_files(num_embedded_files) - , m_bundle_id() - , m_v2_header() - { - } - - static header_t read(reader_t& reader); - const pal::string_t& bundle_id() const { return m_bundle_id; } - int32_t num_embedded_files() const { return m_num_embedded_files; } - - const location_t& deps_json_location() const { return m_v2_header.deps_json_location; } - const location_t& runtimeconfig_json_location() const { return m_v2_header.runtimeconfig_json_location; } - bool is_netcoreapp3_compat_mode() const { return m_v2_header.is_netcoreapp3_compat_mode(); } - - static const uint32_t major_version = 2; - static const uint32_t minor_version = 0; - - private: - int32_t m_num_embedded_files; - pal::string_t m_bundle_id; - header_fixed_v2_t m_v2_header; - }; -} -#endif // __HEADER_H__ diff --git a/src/installer/corehost/cli/bundle/info.cpp b/src/installer/corehost/cli/bundle/info.cpp deleted file mode 100644 index 3fef4220890..00000000000 --- a/src/installer/corehost/cli/bundle/info.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// 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. - -#include "trace.h" -#include "info.h" -#include "utils.h" - -using namespace bundle; - -// Global single-file bundle information, if any -const info_t* info_t::the_app = nullptr; - -info_t::info_t(const pal::char_t* bundle_path, - const pal::char_t* app_path, - int64_t header_offset) - : m_bundle_path(bundle_path) - , m_bundle_size(0) - , m_header_offset(header_offset) -{ - m_base_path = get_directory(m_bundle_path); - - // Single-file bundles currently only support deps/runtime config json files - // named based on the app.dll. Any other name for these configuration files - // mentioned via the command line are assumed to be actual files on disk. - // - // Supporting custom names for these config files is straightforward (with associated changes in bundler and SDK). - // There is no known use-case for it yet, and the facility is TBD. - - m_deps_json = config_t(get_deps_from_app_binary(m_base_path, app_path)); - m_runtimeconfig_json = config_t(get_runtime_config_path(m_base_path, get_filename_without_ext(app_path))); -} - -StatusCode info_t::process_bundle(const pal::char_t* bundle_path, const pal::char_t* app_path, int64_t header_offset) -{ - if (header_offset == 0) - { - // Not a single-file bundle. - return StatusCode::Success; - } - - static info_t info(bundle_path, app_path, header_offset); - StatusCode status = info.process_header(); - - if (status != StatusCode::Success) - { - return status; - } - - trace::info(_X("Single-File bundle details:")); - trace::info(_X("DepsJson Offset:[%lx] Size[%lx]"), info.m_header.deps_json_location().offset, info.m_header.deps_json_location().size); - trace::info(_X("RuntimeConfigJson Offset:[%lx] Size[%lx]"), info.m_header.runtimeconfig_json_location().offset, info.m_header.runtimeconfig_json_location().size); - trace::info(_X(".net core 3 compatibility mode: [%s]"), info.m_header.is_netcoreapp3_compat_mode() ? _X("Yes") : _X("No")); - - the_app = &info; - - return StatusCode::Success; -} - -StatusCode info_t::process_header() -{ - try - { - const char* addr = map_bundle(); - - reader_t reader(addr, m_bundle_size, m_header_offset); - - m_header = header_t::read(reader); - m_deps_json.set_location(&m_header.deps_json_location()); - m_runtimeconfig_json.set_location(&m_header.runtimeconfig_json_location()); - - unmap_bundle(addr); - - return StatusCode::Success; - } - catch (StatusCode e) - { - return e; - } -} - -char* info_t::config_t::map(const pal::string_t& path, const location_t* &location) -{ - assert(is_single_file_bundle()); - - const bundle::info_t* app = bundle::info_t::the_app; - if (app->m_deps_json.matches(path)) - { - location = app->m_deps_json.m_location; - } - else if (app->m_runtimeconfig_json.matches(path)) - { - location = app->m_runtimeconfig_json.m_location; - } - else - { - return nullptr; - } - - // When necessary to map the deps.json or runtimeconfig.json files, we map the whole single-file bundle, - // and return the address at the appropriate offset. - // This is because: - // * The host is the only code that is currently running and trying to map the bundle. - // * Files can only be memory mapped at page-aligned offsets, and in whole page units. - // Therefore, mapping only portions of the bundle will involve align-down/round-up calculations, and associated offset adjustments. - // We choose the simpler approach of rounding to the whole file - // * There is no performance limitation due to a larger sized mapping, since we actually only read the pages with relevant contents. - // * Files that are too large to be mapped (ex: that exhaust 32-bit virtual address space) are not supported. - - char* addr = (char*)pal::mmap_copy_on_write(app->m_bundle_path); - if (addr == nullptr) - { - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Failed to map bundle file [%s]"), path.c_str()); - } - - trace::info(_X("Mapped bundle for [%s]"), path.c_str()); - - return addr + location->offset; -} - -void info_t::config_t::unmap(const char* addr, const location_t* location) -{ - // Adjust to the beginning of the bundle. - addr -= location->offset; - bundle::info_t::the_app->unmap_bundle(addr); -} - -const char* info_t::map_bundle() -{ - const void *addr = pal::mmap_read(m_bundle_path, &m_bundle_size); - - if (addr == nullptr) - { - trace::error(_X("Failure processing application bundle.")); - trace::error(_X("Couldn't memory map the bundle file for reading.")); - throw StatusCode::BundleExtractionIOError; - } - - trace::info(_X("Mapped application bundle")); - - return (const char *)addr; -} - -void info_t::unmap_bundle(const char* addr) const -{ - if (!pal::munmap((void*)addr, m_bundle_size)) - { - trace::warning(_X("Failed to unmap bundle after extraction.")); - } - else - { - trace::info(_X("Unmapped application bundle")); - } -} - - diff --git a/src/installer/corehost/cli/bundle/info.h b/src/installer/corehost/cli/bundle/info.h deleted file mode 100644 index 0f3db559fc2..00000000000 --- a/src/installer/corehost/cli/bundle/info.h +++ /dev/null @@ -1,91 +0,0 @@ -// 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. - -#ifndef __INFO_H_ -#define __INFO_H_ - -#include "error_codes.h" -#include "header.h" - -// bundle::info supports: -// * API for identification of a single-file app bundle, and -// * Minimal probing and mapping functionality only for the app.runtimeconfig.json and app.deps.json files. -// bundle::info is used by HostFxr to read the above config files. - -namespace bundle -{ - struct info_t - { - struct config_t - { - config_t() - : m_location(nullptr) {} - - config_t(const config_t& config) - { - m_path = config.m_path; - m_location = config.m_location; - } - - config_t(const pal::string_t& path, const location_t *location=nullptr) - { - m_path = path; - m_location = location; - } - - bool matches(const pal::string_t& path) const - { - return m_location->is_valid() && path.compare(m_path) == 0; - } - - static bool probe(const pal::string_t& path) - { - return is_single_file_bundle() && - (the_app->m_deps_json.matches(path) || the_app->m_runtimeconfig_json.matches(path)); - } - - void set_location(const location_t* location) - { - m_location = location; - } - - static char* map(const pal::string_t& path, const location_t* &location); - static void unmap(const char* addr, const location_t* location); - - private: - pal::string_t m_path; - const location_t *m_location; - }; - - static StatusCode process_bundle(const pal::char_t* bundle_path, const pal::char_t *app_path, int64_t header_offset); - static bool is_single_file_bundle() { return the_app != nullptr; } - - bool is_netcoreapp3_compat_mode() const { return m_header.is_netcoreapp3_compat_mode(); } - const pal::string_t& base_path() const { return m_base_path; } - int64_t header_offset() const { return m_header_offset; } - - // Global single-file info object - static const info_t* the_app; - - protected: - info_t(const pal::char_t* bundle_path, - const pal::char_t* app_path, - int64_t header_offset); - - const char* map_bundle(); - void unmap_bundle(const char* addr) const; - - pal::string_t m_bundle_path; - pal::string_t m_base_path; - size_t m_bundle_size; - int64_t m_header_offset; - header_t m_header; - config_t m_deps_json; - config_t m_runtimeconfig_json; - - private: - StatusCode process_header(); - }; -} -#endif // __INFO_H_ diff --git a/src/installer/corehost/cli/bundle/manifest.cpp b/src/installer/corehost/cli/bundle/manifest.cpp deleted file mode 100644 index 55f1ff1e639..00000000000 --- a/src/installer/corehost/cli/bundle/manifest.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// 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. - -#include "manifest.h" - -using namespace bundle; - -manifest_t manifest_t::read(reader_t& reader, int32_t num_files) -{ - manifest_t manifest; - - for (int32_t i = 0; i < num_files; i++) - { - file_entry_t entry = file_entry_t::read(reader); - manifest.files.push_back(std::move(entry)); - manifest.m_need_extraction |= entry.needs_extraction(); - } - - return manifest; -} diff --git a/src/installer/corehost/cli/bundle/manifest.h b/src/installer/corehost/cli/bundle/manifest.h deleted file mode 100644 index af1f9fa9198..00000000000 --- a/src/installer/corehost/cli/bundle/manifest.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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. - -#ifndef __MANIFEST_H__ -#define __MANIFEST_H__ - -#include -#include "file_entry.h" - -namespace bundle -{ - // Bundle Manifest contains: - // Series of file entries (for each embedded file) - - class manifest_t - { - public: - manifest_t() - : m_need_extraction(false) {} - - std::vector files; - - static manifest_t read(reader_t &reader, int32_t num_files); - - bool files_need_extraction() { return m_need_extraction; } - - private: - bool m_need_extraction; - }; -} -#endif // __MANIFEST_H__ diff --git a/src/installer/corehost/cli/bundle/reader.cpp b/src/installer/corehost/cli/bundle/reader.cpp deleted file mode 100644 index 2e5135da564..00000000000 --- a/src/installer/corehost/cli/bundle/reader.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// 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. - -#include "reader.h" -#include "error_codes.h" -#include "trace.h" - -using namespace bundle; - -const char* reader_t::add_without_overflow(const char* ptr, int64_t len) -{ - const char* new_ptr = ptr + len; - - // The following check will fail in case len < 0 (which is also an error while reading) - // even if the actual arthmetic didn't overflow. - if (new_ptr < ptr) - { - trace::error(_X("Failure processing application bundle; possible file corruption.")); - trace::error(_X("Arithmetic overflow computing bundle-bounds.")); - throw StatusCode::BundleExtractionFailure; - } - - return new_ptr; -} - -void reader_t::set_offset(int64_t offset) -{ - if (offset < 0 || offset >= m_bound) - { - trace::error(_X("Failure processing application bundle; possible file corruption.")); - trace::error(_X("Arithmetic overflow while reading bundle.")); - throw StatusCode::BundleExtractionFailure; - } - - m_ptr = m_base_ptr + offset; -} - -void reader_t::bounds_check(int64_t len) -{ - const char* post_read_ptr = add_without_overflow(m_ptr, len); - - // It is legal for post_read_ptr == m_bound_ptr after reading the last byte. - if (m_ptr < m_base_ptr || post_read_ptr > m_bound_ptr) - { - trace::error(_X("Failure processing application bundle; possible file corruption.")); - trace::error(_X("Bounds check failed while reading the bundle.")); - throw StatusCode::BundleExtractionFailure; - } -} - -// Handle the relatively uncommon scenario where the bundle ID or -// the relative-path of a file within the bundle is longer than 127 bytes -size_t reader_t::read_path_length() -{ - size_t length = 0; - - int8_t first_byte = read(); - - // If the high bit is set, it means there are more bytes to read. - if ((first_byte & 0x80) == 0) - { - length = first_byte; - } - else - { - int8_t second_byte = read(); - - if (second_byte & 0x80) - { - // There can be no more than two bytes in path_length - trace::error(_X("Failure processing application bundle; possible file corruption.")); - trace::error(_X("Path length encoding read beyond two bytes.")); - - throw StatusCode::BundleExtractionFailure; - } - - length = (second_byte << 7) | (first_byte & 0x7f); - } - - if (length <= 0 || length > PATH_MAX) - { - trace::error(_X("Failure processing application bundle; possible file corruption.")); - trace::error(_X("Path length is zero or too long.")); - throw StatusCode::BundleExtractionFailure; - } - - return length; -} - -size_t reader_t::read_path_string(pal::string_t &str) -{ - const char* start_ptr = m_ptr; - size_t size = read_path_length(); - std::unique_ptr buffer{ new uint8_t[size + 1] }; - read(buffer.get(), size); - buffer[size] = 0; // null-terminator - pal::clr_palstring(reinterpret_cast(buffer.get()), &str); - - return m_ptr - start_ptr; // This subtraction can't overflow because addition above is bounds_checked -} diff --git a/src/installer/corehost/cli/bundle/reader.h b/src/installer/corehost/cli/bundle/reader.h deleted file mode 100644 index 8a1212d94b8..00000000000 --- a/src/installer/corehost/cli/bundle/reader.h +++ /dev/null @@ -1,73 +0,0 @@ -// 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. - -#ifndef __READER_H__ -#define __READER_H__ - -#include -#include "pal.h" - -namespace bundle -{ - // Helper class for reading sequentially from the memory-mapped bundle file. - struct reader_t - { - reader_t(const char* base_ptr, int64_t bound, int64_t start_offset = 0) - : m_base_ptr(base_ptr) - , m_ptr(base_ptr) - , m_bound(bound) - , m_bound_ptr(add_without_overflow(base_ptr, bound)) - { - set_offset(start_offset); - } - - public: - - void set_offset(int64_t offset); - - operator const char*() const - { - return m_ptr; - } - - int8_t read() - { - bounds_check(); - return *m_ptr++; - } - - // Copy len bytes from m_ptr to dest - void read(void* dest, int64_t len) - { - bounds_check(len); - memcpy(dest, m_ptr, len); - m_ptr += len; - } - - // Return a pointer to the requested bytes within the memory-mapped file. - // Skip over len bytes. - const char* read_direct(int64_t len) - { - bounds_check(len); - const char *ptr = m_ptr; - m_ptr += len; - return ptr; - } - - size_t read_path_length(); - size_t read_path_string(pal::string_t &str); - - private: - - void bounds_check(int64_t len = 1); - static const char* add_without_overflow(const char* ptr, int64_t len); - - const char* const m_base_ptr; - const char* m_ptr; - const int64_t m_bound; - const char* const m_bound_ptr; - }; -} - -#endif // __READER_H__ diff --git a/src/installer/corehost/cli/bundle/runner.cpp b/src/installer/corehost/cli/bundle/runner.cpp deleted file mode 100644 index 58d10a81785..00000000000 --- a/src/installer/corehost/cli/bundle/runner.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// 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. - -#include -#include "extractor.h" -#include "runner.h" -#include "trace.h" -#include "header.h" -#include "manifest.h" -#include "utils.h" - -using namespace bundle; - -// This method processes the bundle manifest. -// It also implements the extraction of files that cannot be directly processed from the bundle. -StatusCode runner_t::extract() -{ - try - { - const char* addr = map_bundle(); - - // Set the Reader at header_offset - reader_t reader(addr, m_bundle_size, m_header_offset); - - // Read the bundle header - m_header = header_t::read(reader); - m_deps_json.set_location(&m_header.deps_json_location()); - m_runtimeconfig_json.set_location(&m_header.runtimeconfig_json_location()); - - // Read the bundle manifest - m_manifest = manifest_t::read(reader, m_header.num_embedded_files()); - - // Extract the files if necessary - if (m_manifest.files_need_extraction()) - { - extractor_t extractor(m_header.bundle_id(), m_bundle_path, m_manifest); - m_extraction_path = extractor.extract(reader); - } - - unmap_bundle(addr); - - return StatusCode::Success; - } - catch (StatusCode e) - { - return e; - } -} - -const file_entry_t* runner_t::probe(const pal::string_t& path) const -{ - for (const file_entry_t& entry : m_manifest.files) - { - if (entry.relative_path() == path) - { - return &entry; - } - } - - return nullptr; -} - -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); - - if (entry == nullptr) - { - full_path.clear(); - return false; - } - - // Currently, all files except deps.json and runtimeconfig.json are extracted to disk. - // The json files are not queried by the host using this method. - assert(entry->needs_extraction()); - - full_path.assign(app->extraction_path()); - append_path(&full_path, relative_path.c_str()); - - return true; -} diff --git a/src/installer/corehost/cli/bundle/runner.h b/src/installer/corehost/cli/bundle/runner.h deleted file mode 100644 index fb4d74bf877..00000000000 --- a/src/installer/corehost/cli/bundle/runner.h +++ /dev/null @@ -1,49 +0,0 @@ -// 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. - -#ifndef __RUNNER_H__ -#define __RUNNER_H__ - -#include "error_codes.h" -#include "header.h" -#include "manifest.h" -#include "info.h" - -// bundle::runner extends bundle::info to supports: -// * Reading the bundle manifest and identifying file locations for the runtime -// * Extracting bundled files to disk when necessary -// bundle::runner is used by HostPolicy. - -namespace bundle -{ - class runner_t : public info_t - { - public: - runner_t(const pal::char_t* bundle_path, - const pal::char_t *app_path, - int64_t header_offset) - : info_t(bundle_path, app_path, header_offset) {} - - const pal::string_t& extraction_path() const { return m_extraction_path; } - - const file_entry_t *probe(const pal::string_t& path) const; - bool locate(const pal::string_t& relative_path, pal::string_t& full_path) const; - - static StatusCode process_manifest_and_extract() - { - return ((runner_t*) the_app)->extract(); - } - - static const runner_t* app() { return (const runner_t*)the_app; } - - private: - - StatusCode extract(); - - manifest_t m_manifest; - pal::string_t m_extraction_path; - }; -} - -#endif // __RUNNER_H__ diff --git a/src/installer/corehost/cli/deps_entry.cpp b/src/installer/corehost/cli/deps_entry.cpp index 449403fa038..08d196c1741 100644 --- a/src/installer/corehost/cli/deps_entry.cpp +++ b/src/installer/corehost/cli/deps_entry.cpp @@ -6,39 +6,9 @@ #include "utils.h" #include "deps_entry.h" #include "trace.h" -#include "bundle/runner.h" -static pal::string_t normalize_dir_separator(const pal::string_t& path) -{ - // Entry relative path contains '/' separator, sanitize it to use - // platform separator. Perf: avoid extra copy if it matters. - pal::string_t normalized_path = path; - if (_X('/') != DIR_SEPARATOR) - { - replace_char(&normalized_path, _X('/'), DIR_SEPARATOR); - } - - return normalized_path; -} -// ----------------------------------------------------------------------------- -// Given a "base" directory, determine the resolved path for this file. -// -// * If this file exists within the single-file bundle candidate is -// the full-path to the extracted file. -// * Otherwise, candidate is the full local path of the file. -// -// Parameters: -// base - The base directory to look for the relative path of this entry -// ietf_dir - If this is a resource asset, the IETF intermediate directory -// look_in_base - Whether to search as a relative path -// look_in_bundle - Whether to look within the single-file bundle -// str - If the method returns true, contains the file path for this deps entry -// -// Returns: -// If the file exists in the path relative to the "base" directory within the -// single-file or on disk. -bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_dir, bool look_in_base, bool look_in_bundle, pal::string_t* str) const +bool deps_entry_t::to_path(const pal::string_t& base, bool look_in_base, pal::string_t* str) const { pal::string_t& candidate = *str; @@ -50,41 +20,20 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_ return false; } - pal::string_t normalized_path = normalize_dir_separator(asset.relative_path); - - // Reserve space for the path below - candidate.reserve(base.length() + ietf_dir.length() + normalized_path.length() + 3); - - pal::string_t file_path = look_in_base ? get_filename(normalized_path) : normalized_path; - pal::string_t sub_path = ietf_dir; - append_path(&sub_path, file_path.c_str()); - - if (look_in_bundle && bundle::info_t::is_single_file_bundle()) + // Entry relative path contains '/' separator, sanitize it to use + // platform separator. Perf: avoid extra copy if it matters. + pal::string_t pal_relative_path = asset.relative_path; + if (_X('/') != DIR_SEPARATOR) { - const bundle::runner_t* app = bundle::runner_t::app(); - - if (base.compare(app->base_path()) == 0) - { - // If sub_path is found in the single-file bundle, - // app::locate() will set candidate to the full-path to the assembly extracted out to disk. - if (app->locate(sub_path, candidate)) - { - trace::verbose(_X(" %s found in bundle [%s]"), sub_path.c_str(), candidate.c_str()); - return true; - } - else - { - trace::verbose(_X(" %s not found in bundle"), sub_path.c_str()); - } - } - else - { - trace::verbose(_X(" %s not searched in bundle base path %s doesn't match bundle base %s."), - sub_path.c_str(), base.c_str(), app->base_path().c_str()); - } + replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR); } + // Reserve space for the path below + candidate.reserve(base.length() + + pal_relative_path.length() + 3); + candidate.assign(base); + pal::string_t sub_path = look_in_base ? get_filename(pal_relative_path) : pal_relative_path; append_path(&candidate, sub_path.c_str()); bool exists = pal::file_exists(candidate); @@ -98,7 +47,6 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_ { trace::verbose(_X(" %s path query exists %s"), query_type, candidate.c_str()); } - return exists; } @@ -107,50 +55,55 @@ bool deps_entry_t::to_path(const pal::string_t& base, const pal::string_t& ietf_ // // 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 +// 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_dir_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const +bool deps_entry_t::to_dir_path(const pal::string_t& base, pal::string_t* str) const { - pal::string_t ietf_dir; - if (asset_type == asset_types::resources) { - pal::string_t pal_relative_path = normalize_dir_separator(asset.relative_path); + pal::string_t pal_relative_path = asset.relative_path; + if (_X('/') != DIR_SEPARATOR) + { + replace_char(&pal_relative_path, _X('/'), DIR_SEPARATOR); + } // Resources are represented as "lib///" in the deps.json. // The is the "directory" in the pal_relative_path below, so extract it. - ietf_dir = get_directory(pal_relative_path); + pal::string_t ietf_dir = get_directory(pal_relative_path); + pal::string_t ietf = ietf_dir; // get_directory returns with DIR_SEPARATOR appended that we need to remove. - remove_trailing_dir_seperator(&ietf_dir); + remove_trailing_dir_seperator(&ietf); // Extract IETF code from "lib//" - ietf_dir = get_filename(ietf_dir); - - trace::verbose(_X("Detected a resource asset, will query dir/ietf-tag/resource base: %s ietf: %s asset: %s"), - base.c_str(), ietf_dir.c_str(), asset.name.c_str()); + ietf = get_filename(ietf); + + pal::string_t base_ietf_dir = base; + append_path(&base_ietf_dir, ietf.c_str()); + trace::verbose(_X("Detected a resource asset, will query dir/ietf-tag/resource base: %s asset: %s"), base_ietf_dir.c_str(), asset.name.c_str()); + return to_path(base_ietf_dir, true, str); } - - return to_path(base, ietf_dir, true, look_in_bundle, str); + return to_path(base, true, str); } - // ----------------------------------------------------------------------------- // 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 +// 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_rel_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const +bool deps_entry_t::to_rel_path(const pal::string_t& base, pal::string_t* str) const { - return to_path(base, _X(""), false, look_in_bundle, str); + return to_path(base, false, str); } // ----------------------------------------------------------------------------- @@ -159,7 +112,8 @@ bool deps_entry_t::to_rel_path(const pal::string_t& base, bool look_in_bundle, p // // 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 +// 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. @@ -186,5 +140,5 @@ bool deps_entry_t::to_full_path(const pal::string_t& base, pal::string_t* str) c append_path(&new_base, library_path.c_str()); } - return to_rel_path(new_base, false, str); + return to_rel_path(new_base, str); } diff --git a/src/installer/corehost/cli/deps_entry.h b/src/installer/corehost/cli/deps_entry.h index 2c66b1f0552..d06e2988aa3 100644 --- a/src/installer/corehost/cli/deps_entry.h +++ b/src/installer/corehost/cli/deps_entry.h @@ -52,19 +52,17 @@ struct deps_entry_t bool is_serviceable; bool is_rid_specific; - // Given a "base" dir, yield the file path within this directory or single-file bundle. - bool to_dir_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const; + // Given a "base" dir, yield the filepath within this directory or relative to this directory based on "look_in_base" + bool to_path(const pal::string_t& base, bool look_in_base, pal::string_t* str) const; + + // Given a "base" dir, yield the file path within this directory. + bool to_dir_path(const pal::string_t& base, pal::string_t* str) const; // Given a "base" dir, yield the relative path in the package layout. - bool to_rel_path(const pal::string_t& base, bool look_in_bundle, pal::string_t* str) const; + bool to_rel_path(const pal::string_t& base, pal::string_t* str) const; // Given a "base" dir, yield the relative path with package name, version in the package layout. bool to_full_path(const pal::string_t& root, pal::string_t* str) const; - -private: - // Given a "base" dir, yield the filepath within this directory or relative to this directory based on "look_in_base" - // Returns a path within the single-file bundle, or a file on disk, - bool to_path(const pal::string_t& base, const pal::string_t& ietf_code, bool look_in_base, bool look_in_bundle, pal::string_t* str) const; }; #endif // __DEPS_ENTRY_H_ diff --git a/src/installer/corehost/cli/deps_format.cpp b/src/installer/corehost/cli/deps_format.cpp index 873dd815c7a..b098d953025 100644 --- a/src/installer/corehost/cli/deps_format.cpp +++ b/src/installer/corehost/cli/deps_format.cpp @@ -6,7 +6,6 @@ #include "deps_format.h" #include "utils.h" #include "trace.h" -#include "bundle/info.h" #include #include #include @@ -427,16 +426,16 @@ bool deps_json_t::has_package(const pal::string_t& name, const pal::string_t& ve bool deps_json_t::load(bool is_framework_dependent, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph) { m_deps_file = deps_path; - m_file_exists = bundle::info_t::config_t::probe(deps_path) || pal::file_exists(deps_path); + m_file_exists = pal::file_exists(deps_path); - json_parser_t json; + // If file doesn't exist, then assume parsed. if (!m_file_exists) { - // If file doesn't exist, then assume parsed. trace::verbose(_X("Could not locate the dependencies manifest file [%s]. Some libraries may fail to resolve."), deps_path.c_str()); return true; } + json_parser_t json; if (!json.parse_file(deps_path)) { return false; diff --git a/src/installer/corehost/cli/fxr/command_line.cpp b/src/installer/corehost/cli/fxr/command_line.cpp index 045f7700e74..e3563947c3c 100644 --- a/src/installer/corehost/cli/fxr/command_line.cpp +++ b/src/installer/corehost/cli/fxr/command_line.cpp @@ -9,7 +9,6 @@ #include "sdk_info.h" #include #include -#include "bundle/info.h" namespace { @@ -145,7 +144,7 @@ namespace if (mode == host_mode_t::apphost) { app_candidate = host_info.app_path; - doesAppExist = bundle::info_t::is_single_file_bundle() || pal::realpath(&app_candidate); + doesAppExist = pal::realpath(&app_candidate); } else { diff --git a/src/installer/corehost/cli/fxr/corehost_init.cpp b/src/installer/corehost/cli/fxr/corehost_init.cpp index d8a5f249829..22a3921f8e9 100644 --- a/src/installer/corehost/cli/fxr/corehost_init.cpp +++ b/src/installer/corehost/cli/fxr/corehost_init.cpp @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. #include "corehost_init.h" -#include "bundle/info.h" void make_cstr_arr(const std::vector& arr, std::vector* out) { @@ -133,8 +132,6 @@ const host_interface_t& corehost_init_t::get_host_init_data() hi.host_info_dotnet_root = m_host_info_dotnet_root.c_str(); hi.host_info_app_path = m_host_info_app_path.c_str(); - hi.single_file_bundle_header_offset = bundle::info_t::is_single_file_bundle() ? bundle::info_t::the_app->header_offset() : 0; - return hi; } diff --git a/src/installer/corehost/cli/fxr/corehost_init.h b/src/installer/corehost/cli/fxr/corehost_init.h index 0d7f87ae92f..eedc2e70d22 100644 --- a/src/installer/corehost/cli/fxr/corehost_init.h +++ b/src/installer/corehost/cli/fxr/corehost_init.h @@ -8,7 +8,7 @@ #include "host_interface.h" #include "host_startup_info.h" #include "fx_definition.h" - + class corehost_init_t { private: @@ -37,7 +37,6 @@ private: const pal::string_t m_host_info_host_path; const pal::string_t m_host_info_dotnet_root; const pal::string_t m_host_info_app_path; - public: corehost_init_t( const pal::string_t& host_command, diff --git a/src/installer/corehost/cli/fxr/fx_muxer.cpp b/src/installer/corehost/cli/fxr/fx_muxer.cpp index 31c8c177978..00584e4a91e 100644 --- a/src/installer/corehost/cli/fxr/fx_muxer.cpp +++ b/src/installer/corehost/cli/fxr/fx_muxer.cpp @@ -27,7 +27,6 @@ #include "sdk_info.h" #include "sdk_resolver.h" #include "roll_fwd_on_no_candidate_fx_option.h" -#include "bundle/info.h" namespace { @@ -263,7 +262,6 @@ namespace pal::string_t& runtime_config, const runtime_config_t::settings_t& override_settings) { - // Check for the runtimeconfig.json file specified at the command line if (!runtime_config.empty() && !pal::realpath(&runtime_config)) { trace::error(_X("The specified runtimeconfig.json [%s] does not exist"), runtime_config.c_str()); @@ -295,11 +293,6 @@ namespace host_mode_t detect_operating_mode(const host_startup_info_t& host_info) { - if (bundle::info_t::is_single_file_bundle()) - { - return host_mode_t::apphost; - } - if (coreclr_exists_in_dir(host_info.dotnet_root)) { // Detect between standalone apphost or legacy split mode (specifying --depsfile and --runtimeconfig) @@ -364,7 +357,6 @@ namespace { pal::string_t runtime_config = command_line::get_option_value(opts, known_options::runtime_config, _X("")); - // This check is for --depsfile option, which must be an actual file. pal::string_t deps_file = command_line::get_option_value(opts, known_options::deps_file, _X("")); if (!deps_file.empty() && !pal::realpath(&deps_file)) { diff --git a/src/installer/corehost/cli/fxr/hostfxr.cpp b/src/installer/corehost/cli/fxr/hostfxr.cpp index 1fdfa210489..c9a346b799b 100644 --- a/src/installer/corehost/cli/fxr/hostfxr.cpp +++ b/src/installer/corehost/cli/fxr/hostfxr.cpp @@ -14,7 +14,6 @@ #include "sdk_resolver.h" #include "hostfxr.h" #include "host_context.h" -#include "bundle/info.h" namespace { @@ -25,23 +24,6 @@ namespace } } -SHARED_API int HOSTFXR_CALLTYPE hostfxr_main_bundle_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path, int64_t bundle_header_offset) -{ - trace_hostfxr_entry_point(_X("hostfxr_main_bundle_startupinfo")); - - StatusCode bundleStatus = bundle::info_t::process_bundle(host_path, app_path, bundle_header_offset); - if (bundleStatus != StatusCode::Success) - { - trace::error(_X("A fatal error occured while processing application bundle")); - return bundleStatus; - } - - host_startup_info_t startup_info(host_path, dotnet_root, app_path); - - return fx_muxer_t::execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr); -} - - SHARED_API int HOSTFXR_CALLTYPE hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path) { trace_hostfxr_entry_point(_X("hostfxr_main_startupinfo")); diff --git a/src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp b/src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp index de3291fecc5..049d61a62a5 100644 --- a/src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp +++ b/src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp @@ -29,6 +29,7 @@ namespace trace::verbose(_X("--- Resolving %s version from deps json [%s]"), LIBHOSTPOLICY_NAME, deps_json.c_str()); pal::string_t retval; + json_parser_t json; if (!json.parse_file(deps_json)) { diff --git a/src/installer/corehost/cli/host_interface.h b/src/installer/corehost/cli/host_interface.h index 72295aee99e..c7148e0ddc6 100644 --- a/src/installer/corehost/cli/host_interface.h +++ b/src/installer/corehost/cli/host_interface.h @@ -7,7 +7,6 @@ #include #include "pal.h" -#include "bundle/info.h" enum host_mode_t { @@ -59,7 +58,6 @@ struct host_interface_t const pal::char_t* host_info_host_path; const pal::char_t* host_info_dotnet_root; const pal::char_t* host_info_app_path; - size_t single_file_bundle_header_offset; // !! WARNING / WARNING / WARNING / WARNING / WARNING / WARNING / WARNING / WARNING / WARNING // !! 1. Only append to this structure to maintain compat. // !! 2. Any nested structs should not use compiler specific padding (pack with _HOST_INTERFACE_PACK) @@ -93,8 +91,7 @@ static_assert(offsetof(host_interface_t, host_command) == 26 * sizeof(size_t), " static_assert(offsetof(host_interface_t, host_info_host_path) == 27 * sizeof(size_t), "Struct offset breaks backwards compatibility"); static_assert(offsetof(host_interface_t, host_info_dotnet_root) == 28 * sizeof(size_t), "Struct offset breaks backwards compatibility"); static_assert(offsetof(host_interface_t, host_info_app_path) == 29 * sizeof(size_t), "Struct offset breaks backwards compatibility"); -static_assert(offsetof(host_interface_t, single_file_bundle_header_offset) == 30 * sizeof(size_t), "Struct offset breaks backwards compatibility"); -static_assert(sizeof(host_interface_t) == 31 * sizeof(size_t), "Did you add static asserts for the newly added fields?"); +static_assert(sizeof(host_interface_t) == 30 * sizeof(size_t), "Did you add static asserts for the newly added fields?"); #define HOST_INTERFACE_LAYOUT_VERSION_HI 0x16041101 // YYMMDD:nn always increases when layout breaks compat. #define HOST_INTERFACE_LAYOUT_VERSION_LO sizeof(host_interface_t) diff --git a/src/installer/corehost/cli/host_startup_info.h b/src/installer/corehost/cli/host_startup_info.h index cdc2746021a..4cb4b3e92b9 100644 --- a/src/installer/corehost/cli/host_startup_info.h +++ b/src/installer/corehost/cli/host_startup_info.h @@ -11,7 +11,6 @@ struct host_startup_info_t { host_startup_info_t() {} - host_startup_info_t( const pal::char_t* host_path_value, const pal::char_t* dotnet_root_value, diff --git a/src/installer/corehost/cli/hostcommon/CMakeLists.txt b/src/installer/corehost/cli/hostcommon/CMakeLists.txt index 8ad0c8d09f4..deac26114aa 100644 --- a/src/installer/corehost/cli/hostcommon/CMakeLists.txt +++ b/src/installer/corehost/cli/hostcommon/CMakeLists.txt @@ -23,9 +23,6 @@ set(SOURCES ../version.cpp ../version_compatibility_range.cpp ../runtime_config.cpp - ../bundle/info.cpp - ../bundle/reader.cpp - ../bundle/header.cpp ) set(HEADERS @@ -40,9 +37,6 @@ set(HEADERS ../version.h ../version_compatibility_range.h ../runtime_config.h - ../bundle/info.h - ../bundle/reader.h - ../bundle/header.h ) set(SKIP_VERSIONING 1) diff --git a/src/installer/corehost/cli/hostfxr.h b/src/installer/corehost/cli/hostfxr.h index f0bc0a53e22..040b7c78d87 100644 --- a/src/installer/corehost/cli/hostfxr.h +++ b/src/installer/corehost/cli/hostfxr.h @@ -37,13 +37,6 @@ typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_startupinfo_fn)( const char_t *host_path, const char_t *dotnet_root, const char_t *app_path); -typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_bundle_startupinfo_fn)( - const int argc, - const char_t** argv, - const char_t* host_path, - const char_t* dotnet_root, - const char_t* app_path, - int64_t bundle_header_offset); typedef void(HOSTFXR_CALLTYPE *hostfxr_error_writer_fn)(const char_t *message); typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE *hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer); diff --git a/src/installer/corehost/cli/hostmisc/pal.h b/src/installer/corehost/cli/hostmisc/pal.h index 10cb0e2c302..432756a3f37 100644 --- a/src/installer/corehost/cli/hostmisc/pal.h +++ b/src/installer/corehost/cli/hostmisc/pal.h @@ -165,7 +165,7 @@ namespace pal inline bool rmdir (const char_t* path) { return RemoveDirectoryW(path) != 0; } inline int rename(const char_t* old_name, const char_t* new_name) { return ::_wrename(old_name, new_name); } inline int remove(const char_t* path) { return ::_wremove(path); } - inline bool munmap(void* addr, size_t length) { return UnmapViewOfFile(addr) != 0; } + inline bool unmap_file(void* addr, size_t length) { return UnmapViewOfFile(addr) != 0; } inline int get_pid() { return GetCurrentProcessId(); } inline void sleep(uint32_t milliseconds) { Sleep(milliseconds); } #else @@ -222,7 +222,7 @@ namespace pal inline bool rmdir(const char_t* path) { return ::rmdir(path) == 0; } inline int rename(const char_t* old_name, const char_t* new_name) { return ::rename(old_name, new_name); } inline int remove(const char_t* path) { return ::remove(path); } - inline bool munmap(void* addr, size_t length) { return ::munmap(addr, length) == 0; } + inline bool unmap_file(void* addr, size_t length) { return munmap(addr, length) == 0; } inline int get_pid() { return getpid(); } inline void sleep(uint32_t milliseconds) { usleep(milliseconds * 1000); } @@ -257,9 +257,7 @@ namespace pal return fallbackRid; } - const void* mmap_read(const string_t& path, size_t* length = nullptr); - void* mmap_copy_on_write(const string_t& path, size_t* length = nullptr); - + void* map_file_readonly(const string_t& path, size_t& length); bool touch_file(const string_t& path); bool realpath(string_t* path, bool skip_error_logging = false); bool file_exists(const string_t& path); diff --git a/src/installer/corehost/cli/hostmisc/pal.unix.cpp b/src/installer/corehost/cli/hostmisc/pal.unix.cpp index 3626761b0ec..2ca39c3793a 100644 --- a/src/installer/corehost/cli/hostmisc/pal.unix.cpp +++ b/src/installer/corehost/cli/hostmisc/pal.unix.cpp @@ -70,7 +70,7 @@ bool pal::touch_file(const pal::string_t& path) return true; } -static void* map_file(const pal::string_t& path, size_t* length, int prot, int flags) +void* pal::map_file_readonly(const pal::string_t& path, size_t& length) { int fd = open(path.c_str(), O_RDONLY); if (fd == -1) @@ -86,35 +86,21 @@ static void* map_file(const pal::string_t& path, size_t* length, int prot, int f close(fd); return nullptr; } - size_t size = buf.st_size; - if (length != nullptr) - { - *length = size; - } - - void* address = mmap(nullptr, size, prot, flags, fd, 0); + length = buf.st_size; + void* address = mmap(nullptr, length, PROT_READ, MAP_SHARED, fd, 0); - if (address == MAP_FAILED) + if(address == nullptr) { trace::error(_X("Failed to map file. mmap(%s) failed with error %d"), path.c_str(), errno); - address = nullptr; + close(fd); + return nullptr; } close(fd); return address; } -const void* pal::mmap_read(const string_t& path, size_t* length) -{ - return map_file(path, length, PROT_READ, MAP_SHARED); -} - -void* pal::mmap_copy_on_write(const string_t& path, size_t* length) -{ - return map_file(path, length, PROT_READ | PROT_WRITE, MAP_PRIVATE); -} - bool pal::getcwd(pal::string_t* recv) { recv->clear(); @@ -504,10 +490,10 @@ bool pal::get_default_installation_dir(pal::string_t* recv) pal::string_t trim_quotes(pal::string_t stringToCleanup) { pal::char_t quote_array[2] = {'\"', '\''}; - for (size_t index = 0; index < sizeof(quote_array)/sizeof(quote_array[0]); index++) + for(size_t index = 0; index < sizeof(quote_array)/sizeof(quote_array[0]); index++) { size_t pos = stringToCleanup.find(quote_array[index]); - while (pos != std::string::npos) + while(pos != std::string::npos) { stringToCleanup = stringToCleanup.erase(pos, 1); pos = stringToCleanup.find(quote_array[index]); diff --git a/src/installer/corehost/cli/hostmisc/pal.windows.cpp b/src/installer/corehost/cli/hostmisc/pal.windows.cpp index f8348a05d16..0cb6d2f91d9 100644 --- a/src/installer/corehost/cli/hostmisc/pal.windows.cpp +++ b/src/installer/corehost/cli/hostmisc/pal.windows.cpp @@ -76,7 +76,7 @@ bool pal::touch_file(const pal::string_t& path) return true; } -static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_protect, DWORD view_desired_access) +void* pal::map_file_readonly(const pal::string_t& path, size_t &length) { HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -86,19 +86,16 @@ static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_p return nullptr; } - if (length != nullptr) + LARGE_INTEGER fileSize; + if (GetFileSizeEx(file, &fileSize) == 0) { - LARGE_INTEGER fileSize; - if (GetFileSizeEx(file, &fileSize) == 0) - { - trace::error(_X("Failed to map file. GetFileSizeEx(%s) failed with error %d"), path.c_str(), GetLastError()); - CloseHandle(file); - return nullptr; - } - *length = (size_t)fileSize.QuadPart; + trace::error(_X("Failed to map file. GetFileSizeEx(%s) failed with error %d"), path.c_str(), GetLastError()); + CloseHandle(file); + return nullptr; } + length = (size_t)fileSize.QuadPart; - HANDLE map = CreateFileMappingW(file, NULL, mapping_protect, 0, 0, NULL); + HANDLE map = CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, NULL); if (map == NULL) { @@ -107,7 +104,7 @@ static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_p return nullptr; } - void *address = MapViewOfFile(map, view_desired_access, 0, 0, 0); + void *address = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0); if (address == NULL) { @@ -123,16 +120,6 @@ static void* map_file(const pal::string_t& path, size_t *length, DWORD mapping_p return address; } -const void* pal::mmap_read(const string_t& path, size_t* length) -{ - return map_file(path, length, PAGE_READONLY, FILE_MAP_READ); -} - -void* pal::mmap_copy_on_write(const string_t& path, size_t* length) -{ - return map_file(path, length, PAGE_WRITECOPY, FILE_MAP_READ | FILE_MAP_COPY); -} - bool pal::getcwd(pal::string_t* recv) { recv->clear(); diff --git a/src/installer/corehost/cli/hostmisc/utils.cpp b/src/installer/corehost/cli/hostmisc/utils.cpp index 4a3208af237..4e0d8ab3170 100644 --- a/src/installer/corehost/cli/hostmisc/utils.cpp +++ b/src/installer/corehost/cli/hostmisc/utils.cpp @@ -4,7 +4,6 @@ #include "utils.h" #include "trace.h" -#include "bundle/info.h" bool library_exists_in_dir(const pal::string_t& lib_dir, const pal::string_t& lib_name, pal::string_t* p_lib_path) { @@ -366,7 +365,6 @@ pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal: { pal::string_t deps_file; auto app_name = get_filename(app); - deps_file.reserve(app_base.length() + 1 + app_name.length() + 5); deps_file.append(app_base); @@ -379,28 +377,19 @@ pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal: return deps_file; } -pal::string_t get_runtime_config_path(const pal::string_t& path, const pal::string_t& name) +void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg) { auto json_path = path; auto json_name = name + _X(".runtimeconfig.json"); append_path(&json_path, json_name.c_str()); - return json_path; -} + cfg->assign(json_path); -pal::string_t get_runtime_config_dev_path(const pal::string_t& path, const pal::string_t& name) -{ auto dev_json_path = path; auto dev_json_name = name + _X(".runtimeconfig.dev.json"); append_path(&dev_json_path, dev_json_name.c_str()); - return dev_json_path; -} - -void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg) -{ - cfg->assign(get_runtime_config_path(path, name)); - dev_cfg->assign(get_runtime_config_dev_path(path, name)); + dev_cfg->assign(dev_json_path); - trace::verbose(_X("Runtime config is cfg=%s dev=%s"), cfg->c_str(), dev_cfg->c_str()); + trace::verbose(_X("Runtime config is cfg=%s dev=%s"), json_path.c_str(), dev_json_path.c_str()); } pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path) diff --git a/src/installer/corehost/cli/hostmisc/utils.h b/src/installer/corehost/cli/hostmisc/utils.h index 7de346c7bce..2f13193e608 100644 --- a/src/installer/corehost/cli/hostmisc/utils.h +++ b/src/installer/corehost/cli/hostmisc/utils.h @@ -45,8 +45,6 @@ size_t index_of_non_numeric(const pal::string_t& str, unsigned i); bool try_stou(const pal::string_t& str, unsigned* num); pal::string_t get_dotnet_root_env_var_name(); pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal::string_t& app); -pal::string_t get_runtime_config_path(const pal::string_t& path, const pal::string_t& name); -pal::string_t get_runtime_config_dev_path(const pal::string_t& path, const pal::string_t& name); void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg); pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path); diff --git a/src/installer/corehost/cli/hostpolicy/CMakeLists.txt b/src/installer/corehost/cli/hostpolicy/CMakeLists.txt index ca2c78fa279..c05c876e1d9 100644 --- a/src/installer/corehost/cli/hostpolicy/CMakeLists.txt +++ b/src/installer/corehost/cli/hostpolicy/CMakeLists.txt @@ -19,11 +19,6 @@ set(SOURCES ./hostpolicy_context.cpp ./hostpolicy.cpp ./hostpolicy_init.cpp - ../bundle/dir_utils.cpp - ../bundle/extractor.cpp - ../bundle/file_entry.cpp - ../bundle/manifest.cpp - ../bundle/runner.cpp ) set(HEADERS @@ -35,11 +30,6 @@ set(HEADERS ./hostpolicy_context.h ../hostpolicy.h ./hostpolicy_init.h - ../bundle/dir_utils.h - ../bundle/extractor.h - ../bundle/file_entry.h - ../bundle/manifest.h - ../bundle/runner.h ) include(../lib.cmake) diff --git a/src/installer/corehost/cli/hostpolicy/args.cpp b/src/installer/corehost/cli/hostpolicy/args.cpp index f022d23ae5f..562f70baea8 100644 --- a/src/installer/corehost/cli/hostpolicy/args.cpp +++ b/src/installer/corehost/cli/hostpolicy/args.cpp @@ -4,7 +4,6 @@ #include "args.h" #include -#include "bundle/runner.h" arguments_t::arguments_t() : host_mode(host_mode_t::invalid) @@ -102,47 +101,6 @@ bool parse_arguments( args); } -bool set_root_from_app(const pal::string_t& managed_application_path, - arguments_t& args) -{ - args.managed_application = managed_application_path; - - if (args.managed_application.empty()) - { - // Managed app being empty by itself is not a failure. Host may be initialized from a config file. - assert(args.host_mode != host_mode_t::apphost); - return true; - } - - if (bundle::info_t::is_single_file_bundle()) - { - const bundle::runner_t* app = bundle::runner_t::app(); - args.app_root = app->base_path(); - - // Check for the main app within the bundle. - // locate() sets args.managed_application to the full path of the app extracted to disk. - pal::string_t managed_application_name = get_filename(managed_application_path); - if (app->locate(managed_application_name, args.managed_application)) - { - return true; - } - - trace::info(_X("Managed application [%s] not found in single-file bundle"), managed_application_name.c_str()); - - // If the main assembly is not found in the bundle, continue checking on disk - // for very unlikely case where the main app.dll was itself excluded from the app bundle. - return pal::realpath(&args.managed_application); - } - - if (pal::realpath(&args.managed_application)) - { - args.app_root = get_directory(args.managed_application); - return true; - } - - return false; -} - bool init_arguments( const pal::string_t& managed_application_path, const host_startup_info_t& host_info, @@ -157,11 +115,13 @@ bool init_arguments( args.host_path = host_info.host_path; args.additional_deps_serialized = additional_deps_serialized; - if (!set_root_from_app(managed_application_path, args)) + args.managed_application = managed_application_path; + if (!args.managed_application.empty() && !pal::realpath(&args.managed_application)) { trace::error(_X("Failed to locate managed application [%s]"), args.managed_application.c_str()); return false; } + args.app_root = get_directory(args.managed_application); if (!deps_file.empty()) { diff --git a/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp b/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp index 06ededa7e60..2e245b56dee 100644 --- a/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp +++ b/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp @@ -316,7 +316,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str // If the deps json has the package name and version, then someone has already done rid selection and // put the right asset in the dir. So checking just package name and version would suffice. // No need to check further for the exact asset relative sub path. - if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, false, candidate)) + if (config.probe_deps_json->has_package(entry.library_name, entry.library_version) && entry.to_dir_path(probe_dir, candidate)) { trace::verbose(_X(" Probed deps json and matched '%s'"), candidate->c_str()); return true; @@ -334,7 +334,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str { if (entry.is_rid_specific) { - if (entry.to_rel_path(deps_dir, true, candidate)) + if (entry.to_rel_path(deps_dir, candidate)) { trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str()); return true; @@ -343,7 +343,7 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str else { // Non-rid assets, lookup in the published dir. - if (entry.to_dir_path(deps_dir, true, candidate)) + if (entry.to_dir_path(deps_dir, candidate)) { trace::verbose(_X(" Probed deps dir and matched '%s'"), candidate->c_str()); return true; diff --git a/src/installer/corehost/cli/hostpolicy/deps_resolver.h b/src/installer/corehost/cli/hostpolicy/deps_resolver.h index f2b27d4620a..d61a78c3179 100644 --- a/src/installer/corehost/cli/hostpolicy/deps_resolver.h +++ b/src/installer/corehost/cli/hostpolicy/deps_resolver.h @@ -14,7 +14,6 @@ #include "deps_format.h" #include "deps_entry.h" #include "runtime_config.h" -#include "bundle/runner.h" // Probe paths to be resolved for ordering struct probe_paths_t @@ -186,17 +185,6 @@ public: static const pal::string_t s_empty; return s_empty; } - if (m_host_mode == host_mode_t::apphost) - { - if (bundle::info_t::is_single_file_bundle()) - { - const bundle::runner_t* app = bundle::runner_t::app(); - if (app->is_netcoreapp3_compat_mode()) - { - return app->extraction_path(); - } - } - } return m_app_dir; } diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp b/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp index c2a933ec364..5c7d766a87d 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp @@ -18,7 +18,6 @@ #include #include #include "hostpolicy_context.h" -#include "bundle/runner.h" namespace { @@ -355,7 +354,7 @@ int corehost_init( } int corehost_main_init( - hostpolicy_init_t& hostpolicy_init, + hostpolicy_init_t &hostpolicy_init, const int argc, const pal::char_t* argv[], const pal::string_t& location, @@ -368,15 +367,6 @@ int corehost_main_init( hostpolicy_init.host_info.parse(argc, argv); } - if (bundle::info_t::is_single_file_bundle()) - { - StatusCode status = bundle::runner_t::process_manifest_and_extract(); - if (status != StatusCode::Success) - { - return status; - } - } - return corehost_init(hostpolicy_init, argc, argv, location, args); } @@ -444,9 +434,6 @@ int corehost_libhost_init(const hostpolicy_init_t &hostpolicy_init, const pal::s // Host info should always be valid in the delegate scenario assert(hostpolicy_init.host_info.is_valid(host_mode_t::libhost)); - // Single-file bundle is only expected in apphost mode. - assert(!bundle::info_t::is_single_file_bundle()); - return corehost_init(hostpolicy_init, 0, nullptr, location, args); } diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp b/src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp index 3e0d02a17c5..c6103f7af91 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy_init.cpp @@ -4,7 +4,6 @@ #include "hostpolicy_init.h" #include -#include "bundle/runner.h" void make_palstr_arr(int argc, const pal::char_t** argv, std::vector* out) { @@ -127,15 +126,6 @@ bool hostpolicy_init_t::init(host_interface_t* input, hostpolicy_init_t* init) // For the backwards compat case, this will be later initialized with argv[0] } - if (input->version_lo >= offsetof(host_interface_t, single_file_bundle_header_offset) + sizeof(input->single_file_bundle_header_offset)) - { - if (input->single_file_bundle_header_offset != 0) - { - static bundle::runner_t bundle_runner(input->host_info_host_path, input->host_info_app_path, input->single_file_bundle_header_offset); - bundle::info_t::the_app = &bundle_runner; - } - } - return true; } diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy_init.h b/src/installer/corehost/cli/hostpolicy/hostpolicy_init.h index 8d2ea616fcc..18af97c9c25 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy_init.h +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy_init.h @@ -8,7 +8,6 @@ #include "host_interface.h" #include "host_startup_info.h" #include "fx_definition.h" -#include "bundle/info.h" struct hostpolicy_init_t { diff --git a/src/installer/corehost/cli/ijwhost/ijwthunk.cpp b/src/installer/corehost/cli/ijwhost/ijwthunk.cpp index 2b05bcfe708..b2329112162 100644 --- a/src/installer/corehost/cli/ijwhost/ijwthunk.cpp +++ b/src/installer/corehost/cli/ijwhost/ijwthunk.cpp @@ -73,7 +73,7 @@ bool patch_vtable_entries(PEDecoder& pe) error_writer_scope_t writer_scope(swallow_trace); size_t currentThunk = 0; - for (size_t i = 0; i < numFixupRecords; ++i) + for(size_t i = 0; i < numFixupRecords; ++i) { if (pFixupTable[i].Type & COR_VTABLE_PTRSIZED) { @@ -81,7 +81,7 @@ bool patch_vtable_entries(PEDecoder& pe) #ifdef _WIN64 DWORD oldProtect; - if (!VirtualProtect(pointers, (sizeof(BYTE*) * pFixupTable[i].Count), PAGE_READWRITE, &oldProtect)) + if(!VirtualProtect(pointers, (sizeof(BYTE*) * pFixupTable[i].Count), PAGE_READWRITE, &oldProtect)) { trace::error(_X("Failed to change the vtfixup table from RO to R/W failed.\n")); return false; @@ -101,7 +101,7 @@ bool patch_vtable_entries(PEDecoder& pe) #ifdef _WIN64 DWORD _; - if (!VirtualProtect(pointers, (sizeof(BYTE*) * pFixupTable[i].Count), oldProtect, &_)) + if(!VirtualProtect(pointers, (sizeof(BYTE*) * pFixupTable[i].Count), oldProtect, &_)) { trace::warning(_X("Failed to change the vtfixup table from R/W back to RO failed.\n")); } diff --git a/src/installer/corehost/cli/json/rapidjson/document.h b/src/installer/corehost/cli/json/rapidjson/document.h index 74666e3423e..9783fe4acc9 100644 --- a/src/installer/corehost/cli/json/rapidjson/document.h +++ b/src/installer/corehost/cli/json/rapidjson/document.h @@ -2094,11 +2094,11 @@ private: const SizeType len1 = GetStringLength(); const SizeType len2 = rhs.GetStringLength(); - if (len1 != len2) { return false; } + if(len1 != len2) { return false; } const Ch* const str1 = GetString(); const Ch* const str2 = rhs.GetString(); - if (str1 == str2) { return true; } // fast path for constant string + if(str1 == str2) { return true; } // fast path for constant string return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); } diff --git a/src/installer/corehost/cli/json_parser.cpp b/src/installer/corehost/cli/json_parser.cpp index c6f0cbbf0c9..9814f3215d6 100644 --- a/src/installer/corehost/cli/json_parser.cpp +++ b/src/installer/corehost/cli/json_parser.cpp @@ -42,9 +42,9 @@ std::streampos get_utf8_bom_length(pal::istream_t& stream) return 3; } -void get_line_column_from_offset(const char* data, uint64_t size, size_t offset, int *line, int *column) +void get_line_column_from_offset(const std::vector& json, size_t offset, int *line, int *column) { - assert(offset < size); + assert(offset < json.size()); *line = *column = 1; @@ -52,12 +52,12 @@ void get_line_column_from_offset(const char* data, uint64_t size, size_t offset, { (*column)++; - if (data[i] == '\n') + if (json[i] == '\n') { (*line)++; *column = 1; } - else if (data[i] == '\r' && data[i + 1] == '\n') + else if (json[i] == '\r' && json[i + 1] == '\n') { (*line)++; *column = 1; @@ -75,30 +75,32 @@ void json_parser_t::realloc_buffer(size_t size) m_json[size] = '\0'; } -bool json_parser_t::parse_json(char* data, int64_t size, const pal::string_t& context) +bool json_parser_t::parse_json(const pal::string_t& context) { + assert(!m_json.empty()); + #ifdef _WIN32 // Can't use in-situ parsing on Windows, as JSON data is encoded in // UTF-8 and the host expects wide strings. m_document will store // data in UTF-16 (with pal::char_t as the character type), but it // has to know that data is encoded in UTF-8 to convert during parsing. constexpr auto flags = rapidjson::ParseFlag::kParseStopWhenDoneFlag - | rapidjson::ParseFlag::kParseCommentsFlag; - m_document.Parse>(data); -#else // _WIN32 - m_document.ParseInsitu(data); -#endif // _WIN32 + | rapidjson::ParseFlag::kParseCommentsFlag; + m_document.Parse>(m_json.data()); +#else + m_document.ParseInsitu(m_json.data()); +#endif if (m_document.HasParseError()) { int line, column; size_t offset = m_document.GetErrorOffset(); - get_line_column_from_offset(data, size, offset, &line, &column); + get_line_column_from_offset(m_json, offset, &line, &column); trace::error(_X("A JSON parsing exception occurred in [%s], offset %zu (line %d, column %d): %s"), - context.c_str(), offset, line, column, - rapidjson::GetParseError_En(m_document.GetParseError())); + context.c_str(), offset, line, column, + rapidjson::GetParseError_En(m_document.GetParseError())); return false; } @@ -128,37 +130,5 @@ bool json_parser_t::parse_stream(pal::istream_t& stream, realloc_buffer(stream_size - current_pos); stream.read(m_json.data(), stream_size - current_pos); - return parse_json(m_json.data(), m_json.size(), context); -} - -bool json_parser_t::parse_file(const pal::string_t& path) -{ - // This code assumes that the caller has checked that the file `path` exists - // either within the bundle, or as a real file on disk. - assert(m_bundle_data == nullptr); - assert(m_bundle_location == nullptr); - - if (bundle::info_t::is_single_file_bundle()) - { - m_bundle_data = bundle::info_t::config_t::map(path, m_bundle_location); - // The mapping will be unmapped by the json_parser destructor. - // The mapping cannot be immediately released due to in-situ parsing on Linux. - - if (m_bundle_data != nullptr) - { - bool result = parse_json(m_bundle_data, m_bundle_location->size, path); - return result; - } - } - - pal::ifstream_t file{ path }; - return parse_stream(file, path); -} - -json_parser_t::~json_parser_t() -{ - if (m_bundle_data != nullptr) - { - bundle::info_t::config_t::unmap(m_bundle_data, m_bundle_location); - } + return parse_json(context); } diff --git a/src/installer/corehost/cli/json_parser.h b/src/installer/corehost/cli/json_parser.h index 16ed21bd033..e5c477b1171 100644 --- a/src/installer/corehost/cli/json_parser.h +++ b/src/installer/corehost/cli/json_parser.h @@ -9,7 +9,6 @@ #include "rapidjson/document.h" #include "rapidjson/fwd.h" #include -#include "bundle/info.h" class json_parser_t { public: @@ -22,15 +21,12 @@ class json_parser_t { using document_t = rapidjson::GenericDocument; const document_t& document() const { return m_document; } - bool parse_stream(pal::istream_t& stream, const pal::string_t& context); - bool parse_file(const pal::string_t& path); - - json_parser_t() - : m_bundle_data(nullptr) - , m_bundle_location(nullptr) {} - - ~json_parser_t(); + bool parse_file(const pal::string_t& path) + { + pal::ifstream_t file{path}; + return parse_stream(file, path); + } private: // This is a vector of char and not pal::char_t because JSON data @@ -40,12 +36,8 @@ class json_parser_t { std::vector m_json; document_t m_document; - // If a json file is parsed from a single-file bundle, the following two fields represent: - char* m_bundle_data; // The memory mapped bytes of the application bundle. - const bundle::location_t* m_bundle_location; // Location of this json file within the bundle. - void realloc_buffer(size_t size); - bool parse_json(char* data, int64_t size, const pal::string_t& context); + bool parse_json(const pal::string_t& context); }; #endif // __JSON_PARSER_H__ diff --git a/src/installer/corehost/cli/runtime_config.cpp b/src/installer/corehost/cli/runtime_config.cpp index d41aa2a0573..2026da52d81 100644 --- a/src/installer/corehost/cli/runtime_config.cpp +++ b/src/installer/corehost/cli/runtime_config.cpp @@ -9,7 +9,6 @@ #include "runtime_config.h" #include "trace.h" #include "utils.h" -#include "bundle/info.h" #include // The semantics of applying the runtimeconfig.json values follows, in the following steps from @@ -339,8 +338,6 @@ bool runtime_config_t::ensure_dev_config_parsed() return true; } - // runtimeconfig.dev.json is never bundled into the single-file app. - // So, only a file on disk is processed. json_parser_t json; if (!json.parse_file(m_dev_path)) { @@ -401,7 +398,7 @@ bool runtime_config_t::ensure_parsed() trace::verbose(_X("Did not successfully parse the runtimeconfig.dev.json")); } - if (!bundle::info_t::config_t::probe(m_path) && !pal::file_exists(m_path)) + if (!pal::file_exists(m_path)) { // Not existing is not an error. return true; diff --git a/src/installer/corehost/cli/test/mockhostpolicy/mockhostpolicy.cpp b/src/installer/corehost/cli/test/mockhostpolicy/mockhostpolicy.cpp index 2130fe7b2f9..4a0567f3f35 100644 --- a/src/installer/corehost/cli/test/mockhostpolicy/mockhostpolicy.cpp +++ b/src/installer/corehost/cli/test/mockhostpolicy/mockhostpolicy.cpp @@ -66,7 +66,6 @@ SHARED_API int HOSTPOLICY_CALLTYPE corehost_load(host_interface_t* init) std::cout << "mock host_info_host_path:" << tostr(init->host_info_host_path).data() << std::endl; std::cout << "mock host_info_dotnet_root:" << tostr(init->host_info_dotnet_root).data() << std::endl; std::cout << "mock host_info_app_path:" << tostr(init->host_info_app_path).data() << std::endl; - std::cout << "mock single_file_bundle_header_offset:" << std::hex << init->single_file_bundle_header_offset << std::endl; if (init->fx_names.len == 0) { diff --git a/src/installer/corehost/corehost.cpp b/src/installer/corehost/corehost.cpp index 55b8ed62982..bb6b4ce60e5 100644 --- a/src/installer/corehost/corehost.cpp +++ b/src/installer/corehost/corehost.cpp @@ -11,7 +11,8 @@ #include "utils.h" #if defined(FEATURE_APPHOST) -#include "bundle_marker.h" +#include "cli/apphost/bundle/marker.h" +#include "cli/apphost/bundle/runner.h" #if defined(_WIN32) #include "cli/apphost/apphost.windows.h" @@ -83,14 +84,6 @@ bool is_exe_enabled_for_execution(pal::string_t* app_dll) #define CURHOST_EXE #endif -void need_newer_framework_error() -{ - pal::string_t url = get_download_url(); - trace::error(_X(" _ To run this application, you need to install a newer version of .NET Core.")); - trace::error(_X("")); - trace::error(_X(" - %s&apphost_version=%s"), url.c_str(), _STRINGIFY(COMMON_HOST_PKG_VER)); -} - #if defined(CURHOST_EXE) int exe_start(const int argc, const pal::char_t* argv[]) @@ -104,8 +97,8 @@ int exe_start(const int argc, const pal::char_t* argv[]) pal::string_t app_path; pal::string_t app_root; - bool requires_hostfxr_startupinfo_interface = false; - + bool requires_v2_hostfxr_interface = false; + #if defined(FEATURE_APPHOST) pal::string_t embedded_app_name; if (!is_exe_enabled_for_execution(&embedded_app_name)) @@ -122,17 +115,29 @@ int exe_start(const int argc, const pal::char_t* argv[]) auto pos_path_char = embedded_app_name.find(DIR_SEPARATOR); if (pos_path_char != pal::string_t::npos) { - requires_hostfxr_startupinfo_interface = true; + requires_v2_hostfxr_interface = true; } - app_path.assign(get_directory(host_path)); - append_path(&app_path, embedded_app_name.c_str()); + if (bundle::marker_t::is_bundle()) + { + bundle::runner_t bundle_runner(host_path); + StatusCode bundle_status = bundle_runner.extract(); + + if (bundle_status != StatusCode::Success) + { + trace::error(_X("A fatal error was encountered. Could not extract contents of the bundle")); + return bundle_status; + } - if (bundle_marker_t::is_bundle()) + app_path.assign(bundle_runner.extraction_dir()); + } + else { - trace::info(_X("Detected Single-File app bundle")); + app_path.assign(get_directory(host_path)); } - else if (!pal::realpath(&app_path)) + + append_path(&app_path, embedded_app_name.c_str()); + if (!pal::realpath(&app_path)) { trace::error(_X("The application to execute does not exist: '%s'."), app_path.c_str()); return StatusCode::LibHostAppRootFindFailure; @@ -195,88 +200,58 @@ int exe_start(const int argc, const pal::char_t* argv[]) // Obtain the entrypoints. int rc; -#if defined(FEATURE_APPHOST) - if (bundle_marker_t::is_bundle()) - { - hostfxr_main_bundle_startupinfo_fn hostfxr_main_bundle_startupinfo = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main_bundle_startupinfo")); - if (hostfxr_main_bundle_startupinfo != nullptr) - { - const pal::char_t* host_path_cstr = host_path.c_str(); - const pal::char_t* dotnet_root_cstr = dotnet_root.empty() ? nullptr : dotnet_root.c_str(); - const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str(); - int64_t bundle_header_offset = bundle_marker_t::header_offset(); - - trace::info(_X("Invoking fx resolver [%s] hostfxr_main_bundle_startupinfo"), fxr_path.c_str()); - trace::info(_X("Host path: [%s]"), host_path.c_str()); - trace::info(_X("Dotnet path: [%s]"), dotnet_root.c_str()); - trace::info(_X("App path: [%s]"), app_path.c_str()); - trace::info(_X("Bundle Header Offset: [%lx]"), bundle_header_offset); - - hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); - propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn); - rc = hostfxr_main_bundle_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr, bundle_header_offset); - } - else - { - // The host components will be statically linked with the app-host: https://github.com/dotnet/runtime/issues/32823 - // Once this work is completed, an outdated hostfxr can only be found for framework-related apps. - trace::error(_X("The required library %s does not support single-file apps."), fxr_path.c_str()); - need_newer_framework_error(); - rc = StatusCode::FrameworkMissingFailure; - } - } - else -#endif // defined(FEATURE_APPHOST) + hostfxr_main_startupinfo_fn main_fn_v2 = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main_startupinfo")); + if (main_fn_v2 != nullptr) { - hostfxr_main_startupinfo_fn hostfxr_main_startupinfo = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main_startupinfo")); - if (hostfxr_main_startupinfo != nullptr) - { - const pal::char_t* host_path_cstr = host_path.c_str(); - const pal::char_t* dotnet_root_cstr = dotnet_root.empty() ? nullptr : dotnet_root.c_str(); - const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str(); + const pal::char_t* host_path_cstr = host_path.c_str(); + const pal::char_t* dotnet_root_cstr = dotnet_root.empty() ? nullptr : dotnet_root.c_str(); + const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str(); - trace::info(_X("Invoking fx resolver [%s] hostfxr_main_startupinfo"), fxr_path.c_str()); - trace::info(_X("Host path: [%s]"), host_path.c_str()); - trace::info(_X("Dotnet path: [%s]"), dotnet_root.c_str()); - trace::info(_X("App path: [%s]"), app_path.c_str()); + trace::info(_X("Invoking fx resolver [%s] v2"), fxr_path.c_str()); + trace::info(_X("Host path: [%s]"), host_path.c_str()); + trace::info(_X("Dotnet path: [%s]"), dotnet_root.c_str()); + trace::info(_X("App path: [%s]"), app_path.c_str()); - hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); + hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); + { propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn); - rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr); + rc = main_fn_v2(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr); - // This check exists to provide an error message for UI apps when running 3.0 apps on 2.0 only hostfxr, which doesn't support error writer redirection. if (trace::get_error_writer() != nullptr && rc == static_cast(StatusCode::FrameworkMissingFailure) && !set_error_writer_fn) { - need_newer_framework_error(); + pal::string_t url = get_download_url(); + trace::error(_X(" _ To run this application, you need to install a newer version of .NET.")); + trace::error(_X("")); + trace::error(_X(" - %s"), url.c_str()); } } + } + else + { + if (requires_v2_hostfxr_interface) + { + trace::error(_X("The required library %s does not support relative app dll paths."), fxr_path.c_str()); + rc = StatusCode::CoreHostEntryPointFailure; + } else { - if (requires_hostfxr_startupinfo_interface) + trace::info(_X("Invoking fx resolver [%s] v1"), fxr_path.c_str()); + + // Previous corehost trace messages must be printed before calling trace::setup in hostfxr + trace::flush(); + + // For compat, use the v1 interface. This requires additional file I\O to re-parse parameters and + // for apphost, does not support DOTNET_ROOT or dll with different name for exe. + hostfxr_main_fn main_fn_v1 = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main")); + if (main_fn_v1 != nullptr) { - trace::error(_X("The required library %s does not support relative app dll paths."), fxr_path.c_str()); - rc = StatusCode::CoreHostEntryPointFailure; + rc = main_fn_v1(argc, argv); } else { - trace::info(_X("Invoking fx resolver [%s] v1"), fxr_path.c_str()); - - // Previous corehost trace messages must be printed before calling trace::setup in hostfxr - trace::flush(); - - // For compat, use the v1 interface. This requires additional file I\O to re-parse parameters and - // for apphost, does not support DOTNET_ROOT or dll with different name for exe. - hostfxr_main_fn main_fn_v1 = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_main")); - if (main_fn_v1 != nullptr) - { - rc = main_fn_v1(argc, argv); - } - else - { - trace::error(_X("The required library %s does not contain the expected entry point."), fxr_path.c_str()); - rc = StatusCode::CoreHostEntryPointFailure; - } + trace::error(_X("The required library %s does not contain the expected entry point."), fxr_path.c_str()); + rc = StatusCode::CoreHostEntryPointFailure; } } } diff --git a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs index 29e9635d4fd..320969c101d 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs @@ -90,7 +90,7 @@ namespace Microsoft.NET.HostModel.Bundle return fileRelativePath.Equals(RuntimeConfigDevJson); } - bool ShouldExclude(FileType type, string relativePath) + bool ShouldExclude(FileType type) { switch (type) { @@ -100,7 +100,7 @@ namespace Microsoft.NET.HostModel.Bundle return false; case FileType.NativeBinary: - return !Options.HasFlag(BundleOptions.BundleNativeBinaries) || Target.ShouldExclude(relativePath); + return !Options.HasFlag(BundleOptions.BundleNativeBinaries); case FileType.Symbols: return !Options.HasFlag(BundleOptions.BundleSymbolFiles); @@ -229,24 +229,22 @@ namespace Microsoft.NET.HostModel.Bundle foreach (var fileSpec in fileSpecs) { - string relativePath = fileSpec.BundleRelativePath; - - if (IsHost(relativePath)) + if (IsHost(fileSpec.BundleRelativePath)) { continue; } - if (ShouldIgnore(relativePath)) + if (ShouldIgnore(fileSpec.BundleRelativePath)) { - Tracer.Log($"Ignore: {relativePath}"); + Tracer.Log($"Ignore: {fileSpec.BundleRelativePath}"); continue; } FileType type = InferType(fileSpec); - if (ShouldExclude(type, relativePath)) + if (ShouldExclude(type)) { - Tracer.Log($"Exclude [{type}]: {relativePath}"); + Tracer.Log($"Exclude [{type}]: {fileSpec.BundleRelativePath}"); fileSpec.Excluded = true; continue; } @@ -255,7 +253,7 @@ namespace Microsoft.NET.HostModel.Bundle { FileType targetType = Target.TargetSpecificFileType(type); long startOffset = AddToBundle(bundle, file, targetType); - FileEntry entry = BundleManifest.AddEntry(targetType, relativePath, startOffset, file.Length); + FileEntry entry = BundleManifest.AddEntry(targetType, fileSpec.BundleRelativePath, startOffset, file.Length); Tracer.Log($"Embed: {entry}"); } } diff --git a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs index 9c171ae2da7..bd2b340971e 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs @@ -59,7 +59,7 @@ namespace Microsoft.NET.HostModel.Bundle enum HeaderFlags : ulong { None = 0, - NetcoreApp3CompatMode = 1 + NetcoreApp3CompatMode = 2 } // Bundle ID is a string that is used to uniquely diff --git a/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs b/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs index 87b1c65202e..1f6f61a7eee 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs @@ -27,6 +27,8 @@ namespace Microsoft.NET.HostModel.Bundle public TargetInfo(OSPlatform? os, Version targetFrameworkVersion) { + Version net50 = new Version(5, 0); + OS = os ?? HostOS; FrameworkVersion = targetFrameworkVersion ?? net50; @@ -69,21 +71,6 @@ namespace Microsoft.NET.HostModel.Bundle // The .net core 3 apphost doesn't care about semantics of FileType -- all files are extracted at startup. // However, the apphost checks that the FileType value is within expected bounds, so set it to the first enumeration. public FileType TargetSpecificFileType(FileType fileType) => (BundleVersion == 1) ? FileType.Unknown : fileType; - - // In .net core 3.x, bundle processing happens within the AppHost. - // Therefore HostFxr and HostPolicy can be bundled within the single-file app. - // In .net 5, bundle processing happens in HostFxr and HostPolicy libraries. - // Therefore, these libraries themselves cannot be bundled into the single-file app. - // This problem is mitigated by statically linking these host components with the AppHost. - // https://github.com/dotnet/runtime/issues/32823 - public bool ShouldExclude(string relativePath) => - (FrameworkVersion.Major != 3) && (relativePath.Equals(HostFxr) || relativePath.Equals(HostPolicy)); - - readonly Version net50 = new Version(5, 0); - string HostFxr => IsWindows ? "hostfxr.dll" : IsLinux ? "libhostfxr.so" : "libhostfxr.dylib"; - string HostPolicy => IsWindows ? "hostpolicy.dll" : IsLinux ? "libhostpolicy.so" : "libhostpolicy.dylib"; - - } } diff --git a/src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs b/src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs index 5b51c37991b..20c24839186 100644 --- a/src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs +++ b/src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs @@ -4,6 +4,7 @@ using System; using System.IO; +using System.Reflection; namespace AppWithSubDirs { @@ -11,7 +12,10 @@ namespace AppWithSubDirs { public static void Main(string[] args) { - string baseDir = Path.Combine(AppContext.BaseDirectory, "Sentence"); + string baseDir = + Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "Sentence"); string Part(string dir="", string subdir="", string subsubdir="") { diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs index 26dbc3a00ef..5f35a406c91 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs @@ -7,7 +7,9 @@ using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; using Microsoft.NET.HostModel.Bundle; using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using Xunit; @@ -27,19 +29,24 @@ namespace AppHost.Bundle.Tests { var fixture = sharedTestState.TestFixture.Copy(); var hostName = BundleHelper.GetHostName(fixture); + var appName = Path.GetFileNameWithoutExtension(hostName); + string publishPath = BundleHelper.GetPublishPath(fixture); // Publish the bundle - string singleFile; - Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile); + var bundleDir = BundleHelper.GetBundleDir(fixture); + var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); + string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); + + // Compute bundled files + var bundledFiles = bundler.BundleManifest.Files.Select(file => file.RelativePath).ToList(); // Verify expected files in the bundle directory - var bundleDir = BundleHelper.GetBundleDir(fixture); bundleDir.Should().HaveFile(hostName); - bundleDir.Should().NotHaveFiles(BundleHelper.GetBundledFiles(fixture)); + bundleDir.Should().NotHaveFiles(bundledFiles); // Create a directory for extraction. - var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture); - extractBaseDir.Should().NotHaveDirectory(BundleHelper.GetAppBaseName(fixture)); + var extractBaseDir = BundleHelper.GetExtractDir(fixture); + extractBaseDir.Should().NotHaveDirectory(appName); // Run the bundled app for the first time, and extract files to // $DOTNET_BUNDLE_EXTRACT_BASE_DIR//bundle-id @@ -53,22 +60,27 @@ namespace AppHost.Bundle.Tests .And .HaveStdOutContaining("Hello World"); - var extractDir = BundleHelper.GetExtractionDir(fixture, bundler); - extractDir.Should().HaveFiles(BundleHelper.GetExtractedFiles(fixture)); - extractDir.Should().NotHaveFiles(BundleHelper.GetFilesNeverExtracted(fixture)); + string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID); + var extractDir = new DirectoryInfo(extractPath); + extractDir.Should().OnlyHaveFiles(bundledFiles); + extractDir.Should().NotHaveFile(hostName); } [Fact] private void Bundle_extraction_is_reused() { var fixture = sharedTestState.TestFixture.Copy(); + var hostName = BundleHelper.GetHostName(fixture); + var appName = Path.GetFileNameWithoutExtension(hostName); + string publishPath = BundleHelper.GetPublishPath(fixture); // Publish the bundle - string singleFile; - Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleNativeBinaries); + var bundleDir = BundleHelper.GetBundleDir(fixture); + var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); + string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); // Create a directory for extraction. - var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture); + var extractBaseDir = BundleHelper.GetExtractDir(fixture); // Run the bunded app for the first time, and extract files to // $DOTNET_BUNDLE_EXTRACT_BASE_DIR//bundle-id @@ -82,8 +94,8 @@ namespace AppHost.Bundle.Tests .And .HaveStdOutContaining("Hello World"); - var appBaseName = BundleHelper.GetAppBaseName(fixture); - var extractDir = BundleHelper.GetExtractionDir(fixture, bundler); + string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID); + var extractDir = new DirectoryInfo(extractPath); extractDir.Refresh(); DateTime firstWriteTime = extractDir.LastWriteTimeUtc; @@ -113,14 +125,20 @@ namespace AppHost.Bundle.Tests var fixture = sharedTestState.TestFixture.Copy(); var hostName = BundleHelper.GetHostName(fixture); var appName = Path.GetFileNameWithoutExtension(hostName); + string publishPath = BundleHelper.GetPublishPath(fixture); // Publish the bundle - string singleFile; - Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleNativeBinaries); + var bundleDir = BundleHelper.GetBundleDir(fixture); + var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); + string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); + + // Compute bundled files + List bundledFiles = bundler.BundleManifest.Files.Select(file => file.RelativePath).ToList(); // Create a directory for extraction. - var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture); - + var extractBaseDir = BundleHelper.GetExtractDir(fixture); + string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID); + var extractDir = new DirectoryInfo(extractPath); // Run the bunded app for the first time, and extract files to // $DOTNET_BUNDLE_EXTRACT_BASE_DIR//bundle-id @@ -134,14 +152,10 @@ namespace AppHost.Bundle.Tests .And .HaveStdOutContaining("Hello World"); - // Remove the extracted files, but keep the extraction directory - var extractDir = BundleHelper.GetExtractionDir(fixture, bundler); - var extractedFiles = BundleHelper.GetExtractedFiles(fixture); - - Array.ForEach(extractedFiles, file => File.Delete(Path.Combine(extractDir.FullName, file))); + bundledFiles.ForEach(file => File.Delete(Path.Combine(extractPath, file))); extractDir.Should().Exist(); - extractDir.Should().NotHaveFiles(extractedFiles); + extractDir.Should().NotHaveFiles(bundledFiles); // Run the bundled app again (recover deleted files) Command.Create(singleFile) @@ -154,7 +168,7 @@ namespace AppHost.Bundle.Tests .And .HaveStdOutContaining("Hello World"); - extractDir.Should().HaveFiles(extractedFiles); + extractDir.Should().HaveFiles(bundledFiles); } diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs index 581a1155c98..5466743fc5d 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs @@ -51,13 +51,11 @@ namespace AppHost.Bundle.Tests .CaptureStdOut() .Start(); - while (!File.Exists(waitFile) && !singleExe.Process.HasExited) + while (!File.Exists(waitFile)) { Thread.Sleep(100); } - Assert.True(File.Exists(waitFile)); - File.Move(singleFile, renameFile); File.Create(resumeFile).Close(); diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs index 33e05b5c050..59360c09f99 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using BundleTests.Helpers; -using Microsoft.DotNet.Cli.Build.Framework; -using Microsoft.NET.HostModel.Bundle; -using Microsoft.DotNet.CoreSetup.Test; using System; using Xunit; +using Microsoft.DotNet.Cli.Build.Framework; +using BundleTests.Helpers; +using Microsoft.DotNet.CoreSetup.Test; namespace AppHost.Bundle.Tests { @@ -32,16 +31,11 @@ namespace AppHost.Bundle.Tests .HaveStdOutContaining("Wow! We now say hello to the big world and you."); } - // BundleOptions.BundleNativeBinaries: Test when the payload data files are unbundled, and beside the single-file app. - // BundleOptions.BundleAllContent: Test when the payload data files are bundled and extracted to temporary directory. - // Once the runtime can load assemblies from the bundle, BundleOptions.None can be used in place of BundleOptions.BundleNativeBinaries. - [InlineData(BundleOptions.BundleNativeBinaries)] - [InlineData(BundleOptions.BundleAllContent)] - [Theory] - public void Bundled_Framework_dependent_App_Run_Succeeds(BundleOptions options) + [Fact] + public void Bundled_Framework_dependent_App_Run_Succeeds() { var fixture = sharedTestState.TestFrameworkDependentFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture, options); + var singleFile = BundleHelper.BundleApp(fixture); // Run the bundled app (extract files) RunTheApp(singleFile); @@ -50,13 +44,11 @@ namespace AppHost.Bundle.Tests RunTheApp(singleFile); } - [InlineData(BundleOptions.BundleNativeBinaries)] - [InlineData(BundleOptions.BundleAllContent)] - [Theory] - public void Bundled_Self_Contained_App_Run_Succeeds(BundleOptions options) + [Fact] + public void Bundled_Self_Contained_App_Run_Succeeds() { var fixture = sharedTestState.TestSelfContainedFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture, options); + var singleFile = BundleHelper.BundleApp(fixture); // Run the bundled app (extract files) RunTheApp(singleFile); @@ -65,13 +57,11 @@ namespace AppHost.Bundle.Tests RunTheApp(singleFile); } - [InlineData(BundleOptions.BundleNativeBinaries)] - [InlineData(BundleOptions.BundleAllContent)] - [Theory] - public void Bundled_With_Empty_File_Succeeds(BundleOptions options) + [Fact] + public void Bundled_With_Empty_File_Succeeds() { var fixture = sharedTestState.TestAppWithEmptyFileFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture, options); + var singleFile = BundleHelper.BundleApp(fixture); // Run the app RunTheApp(singleFile); diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs index c5f77179436..e941becc236 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs @@ -40,32 +40,6 @@ namespace BundleTests.Helpers return Path.GetFileName(fixture.TestProject.AppDll); } - public static string GetAppBaseName(TestProjectFixture fixture) - { - return Path.GetFileNameWithoutExtension(GetAppName(fixture)); - } - - public static string[] GetBundledFiles(TestProjectFixture fixture) - { - string appBaseName = GetAppBaseName(fixture); - return new string[] { $"{appBaseName}.dll", $"{appBaseName}.deps.json", $"{appBaseName}.runtimeconfig.json" }; - } - - public static string[] GetExtractedFiles(TestProjectFixture fixture) - { - string appBaseName = GetAppBaseName(fixture); - return new string[] { $"{appBaseName}.dll" }; - } - - public static string[] GetFilesNeverExtracted(TestProjectFixture fixture) - { - string appBaseName = GetAppBaseName(fixture); - return new string[] { $"{appBaseName}.deps.json", - $"{appBaseName}.runtimeconfig.json", - Path.GetFileName(fixture.TestProject.HostFxrDll), - Path.GetFileName(fixture.TestProject.HostPolicyDll) }; - } - public static string GetPublishPath(TestProjectFixture fixture) { return Path.Combine(fixture.TestProject.ProjectDirectory, "publish"); @@ -76,28 +50,13 @@ namespace BundleTests.Helpers return Directory.CreateDirectory(Path.Combine(fixture.TestProject.ProjectDirectory, "bundle")); } - public static string GetExtractionRootPath(TestProjectFixture fixture) - { - return Path.Combine(fixture.TestProject.ProjectDirectory, "extract"); - } - - public static DirectoryInfo GetExtractionRootDir(TestProjectFixture fixture) + public static DirectoryInfo GetExtractDir(TestProjectFixture fixture) { - return Directory.CreateDirectory(GetExtractionRootPath(fixture)); - } - - public static string GetExtractionPath(TestProjectFixture fixture, Bundler bundler) - { - return Path.Combine(GetExtractionRootPath(fixture), GetAppBaseName(fixture), bundler.BundleManifest.BundleID); - - } - public static DirectoryInfo GetExtractionDir(TestProjectFixture fixture, Bundler bundler) - { - return new DirectoryInfo(GetExtractionPath(fixture, bundler)); + return Directory.CreateDirectory(Path.Combine(fixture.TestProject.ProjectDirectory, "extract")); } /// Generate a bundle containind the (embeddable) files in sourceDir - public static string GenerateBundle(Bundler bundler, string sourceDir, string outputDir, bool copyExludedFiles=true) + public static string GenerateBundle(Bundler bundler, string sourceDir) { // Convert sourceDir to absolute path sourceDir = Path.GetFullPath(sourceDir); @@ -114,22 +73,7 @@ namespace BundleTests.Helpers fileSpecs.Add(new FileSpec(file, Path.GetRelativePath(sourceDir, file))); } - var singleFile = bundler.GenerateBundle(fileSpecs); - - if (copyExludedFiles) - { - foreach (var spec in fileSpecs) - { - if (spec.Excluded) - { - var outputFilePath = Path.Combine(outputDir, spec.BundleRelativePath); - Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath)); - File.Copy(spec.SourcePath, outputFilePath, true); - } - } - } - - return singleFile; + return bundler.GenerateBundle(fileSpecs); } // Bundle to a single-file @@ -137,40 +81,23 @@ namespace BundleTests.Helpers // instead of the SDK via /p:PublishSingleFile=true. // This is necessary when the test needs the latest changes in the AppHost, // which may not (yet) be available in the SDK. - public static Bundler BundleApp(TestProjectFixture fixture, - out string singleFile, - BundleOptions options = BundleOptions.BundleNativeBinaries, - Version targetFrameworkVersion = null, - bool copyExcludedFiles = true) + // + // Currently, AppHost can only handle bundles if all content is extracted to disk on startup. + // Therefore, the BundleOption is BundleAllContent by default. + // The default should be BundleOptions.None once host/runtime no longer requires full-extraction. + public static string BundleApp(TestProjectFixture fixture, + BundleOptions options = BundleOptions.BundleAllContent, + Version targetFrameworkVersion = null) { var hostName = GetHostName(fixture); string publishPath = GetPublishPath(fixture); var bundleDir = GetBundleDir(fixture); var bundler = new Bundler(hostName, bundleDir.FullName, options, targetFrameworkVersion: targetFrameworkVersion); - singleFile = GenerateBundle(bundler, publishPath, bundleDir.FullName, copyExcludedFiles); - - return bundler; - } - - // The defaut option for Bundling apps is BundleOptions.BundleNativeBinaries - // Until CoreCLR runtime can learn how to process assemblies from the bundle. - // This is because CoreCLR expects System.Private.Corelib.dll to be beside CoreCLR.dll - public static string BundleApp(TestProjectFixture fixture, - BundleOptions options = BundleOptions.BundleNativeBinaries, - Version targetFrameworkVersion = null) - { - string singleFile; - BundleApp(fixture, out singleFile, options, targetFrameworkVersion); + string singleFile = GenerateBundle(bundler, publishPath); return singleFile; } - public static Bundler Bundle(TestProjectFixture fixture, BundleOptions options = BundleOptions.None) - { - string singleFile; - return BundleApp(fixture, out singleFile, options, copyExcludedFiles:false); - } - public static void AddLongNameContentToAppWithSubDirs(TestProjectFixture fixture) { // For tests using the AppWithSubDirs, One of the sub-directories with a really long name diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs index e4949b47750..3199b1559ff 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs @@ -7,6 +7,7 @@ using System.IO; using Xunit; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; +using Microsoft.NET.HostModel.Bundle; using BundleTests.Helpers; namespace Microsoft.NET.HostModel.Tests @@ -40,7 +41,9 @@ namespace Microsoft.NET.HostModel.Tests RunTheApp(Path.Combine(publishPath, hostName)); // Bundle to a single-file - string singleFile = BundleHelper.BundleApp(fixture); + // Bundle all content, until the host can handle other scenarios. + Bundler bundler = new Bundler(hostName, singleFileDir, BundleOptions.BundleAllContent); + string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); // Run the extracted app RunTheApp(singleFile); diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs index 7b9d6863607..a9c9b700064 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleLegacy.cs @@ -3,6 +3,7 @@ // 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; diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs index 00f3b1cc843..efa13283bd1 100644 --- a/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs +++ b/src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs @@ -92,10 +92,18 @@ namespace Microsoft.NET.HostModel.Tests public void TestFilesAlwaysBundled(BundleOptions options) { var fixture = sharedTestState.TestFixture.Copy(); - var bundler = BundleHelper.Bundle(fixture, options); - var bundledFiles = BundleHelper.GetBundledFiles(fixture); - Array.ForEach(bundledFiles, file => bundler.BundleManifest.Contains(file).Should().BeTrue()); + var hostName = BundleHelper.GetHostName(fixture); + var appName = Path.GetFileNameWithoutExtension(hostName); + string publishPath = BundleHelper.GetPublishPath(fixture); + var bundleDir = BundleHelper.GetBundleDir(fixture); + + var bundler = new Bundler(hostName, bundleDir.FullName, options); + BundleHelper.GenerateBundle(bundler, publishPath); + + bundler.BundleManifest.Contains($"{appName}.dll").Should().BeTrue(); + bundler.BundleManifest.Contains($"{appName}.deps.json").Should().BeTrue(); + bundler.BundleManifest.Contains($"{appName}.runtimeconfig.json").Should().BeTrue(); } [InlineData(BundleOptions.None)] @@ -107,16 +115,20 @@ namespace Microsoft.NET.HostModel.Tests public void TestFilesNeverBundled(BundleOptions options) { var fixture = sharedTestState.TestFixture.Copy(); - var appBaseName = BundleHelper.GetAppBaseName(fixture); + + var hostName = BundleHelper.GetHostName(fixture); + var appName = Path.GetFileNameWithoutExtension(hostName); string publishPath = BundleHelper.GetPublishPath(fixture); + var bundleDir = BundleHelper.GetBundleDir(fixture); // Make up a app.runtimeconfig.dev.json file in the publish directory. - File.Copy(Path.Combine(publishPath, $"{appBaseName}.runtimeconfig.json"), - Path.Combine(publishPath, $"{appBaseName}.runtimeconfig.dev.json")); + File.Copy(Path.Combine(publishPath, $"{appName}.runtimeconfig.json"), + Path.Combine(publishPath, $"{appName}.runtimeconfig.dev.json")); - var bundler = BundleHelper.Bundle(fixture, options); + var bundler = new Bundler(hostName, bundleDir.FullName, options); + BundleHelper.GenerateBundle(bundler, publishPath); - bundler.BundleManifest.Contains($"{appBaseName}.runtimeconfig.dev.json").Should().BeFalse(); + bundler.BundleManifest.Contains($"{appName}.runtimeconfig.dev.json").Should().BeFalse(); } [InlineData(BundleOptions.None)] @@ -125,10 +137,16 @@ namespace Microsoft.NET.HostModel.Tests public void TestBundlingSymbols(BundleOptions options) { var fixture = sharedTestState.TestFixture.Copy(); - var appBaseName = BundleHelper.GetAppBaseName(fixture); - var bundler = BundleHelper.Bundle(fixture, options); - bundler.BundleManifest.Contains($"{appBaseName}.pdb").Should().Be(options.HasFlag(BundleOptions.BundleSymbolFiles)); + var hostName = BundleHelper.GetHostName(fixture); + var appName = Path.GetFileNameWithoutExtension(hostName); + string publishPath = BundleHelper.GetPublishPath(fixture); + var bundleDir = BundleHelper.GetBundleDir(fixture); + + var bundler = new Bundler(hostName, bundleDir.FullName, options); + BundleHelper.GenerateBundle(bundler, publishPath); + + bundler.BundleManifest.Contains($"{appName}.pdb").Should().Be(options.HasFlag(BundleOptions.BundleSymbolFiles)); } [InlineData(BundleOptions.None)] @@ -137,28 +155,30 @@ namespace Microsoft.NET.HostModel.Tests public void TestBundlingNativeBinaries(BundleOptions options) { var fixture = sharedTestState.TestFixture.Copy(); - var coreclr = Path.GetFileName(fixture.TestProject.CoreClrDll); - var bundler = BundleHelper.Bundle(fixture, options); - bundler.BundleManifest.Contains($"{coreclr}").Should().Be(options.HasFlag(BundleOptions.BundleNativeBinaries)); - } + var hostName = BundleHelper.GetHostName(fixture); + var hostfxr = Path.GetFileName(fixture.TestProject.HostFxrDll); + string publishPath = BundleHelper.GetPublishPath(fixture); + var bundleDir = BundleHelper.GetBundleDir(fixture); - [Fact] - public void TestFileSizes() - { - var fixture = sharedTestState.TestFixture.Copy(); - var bundler = BundleHelper.Bundle(fixture); - var publishPath = BundleHelper.GetPublishPath(fixture); + var bundler = new Bundler(hostName, bundleDir.FullName, options); + BundleHelper.GenerateBundle(bundler, publishPath); - bundler.BundleManifest.Files.ForEach(file => - Assert.True(file.Size == new FileInfo(Path.Combine(publishPath, file.RelativePath)).Length)); + bundler.BundleManifest.Contains($"{hostfxr}").Should().Be(options.HasFlag(BundleOptions.BundleNativeBinaries)); } [Fact] public void TestAssemblyAlignment() { var fixture = sharedTestState.TestFixture.Copy(); - var bundler = BundleHelper.Bundle(fixture); + + var hostName = BundleHelper.GetHostName(fixture); + var appName = Path.GetFileNameWithoutExtension(hostName); + string publishPath = BundleHelper.GetPublishPath(fixture); + var bundleDir = BundleHelper.GetBundleDir(fixture); + + var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); + BundleHelper.GenerateBundle(bundler, publishPath); bundler.BundleManifest.Files.ForEach(file => Assert.True((file.Type != FileType.Assembly) || (file.Offset % Bundler.AssemblyAlignment == 0))); @@ -168,7 +188,13 @@ namespace Microsoft.NET.HostModel.Tests public void TestWithAdditionalContentAfterBundleMetadata() { var fixture = sharedTestState.TestFixture.Copy(); - string singleFile = BundleHelper.BundleApp(fixture); + + var hostName = BundleHelper.GetHostName(fixture); + var bundleDir = BundleHelper.GetBundleDir(fixture); + string publishPath = BundleHelper.GetPublishPath(fixture); + + var bundler = new Bundler(hostName, bundleDir.FullName, BundleOptions.BundleAllContent); + string singleFile = BundleHelper.GenerateBundle(bundler, publishPath); using (var file = File.OpenWrite(singleFile)) { diff --git a/src/installer/test/TestUtils/TestApp.cs b/src/installer/test/TestUtils/TestApp.cs index a1ff67d95c8..3e4e72f1fe7 100644 --- a/src/installer/test/TestUtils/TestApp.cs +++ b/src/installer/test/TestUtils/TestApp.cs @@ -16,7 +16,6 @@ namespace Microsoft.DotNet.CoreSetup.Test public string RuntimeDevConfigJson { get; private set; } public string HostPolicyDll { get; private set; } public string HostFxrDll { get; private set; } - public string CoreClrDll { get; private set; } public string AssemblyName { get; } @@ -56,7 +55,6 @@ namespace Microsoft.DotNet.CoreSetup.Test RuntimeDevConfigJson = Path.Combine(Location, $"{AssemblyName}.runtimeconfig.dev.json"); HostPolicyDll = Path.Combine(Location, RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostpolicy")); HostFxrDll = Path.Combine(Location, RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr")); - CoreClrDll = Path.Combine(Location, RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("coreclr")); } } } diff --git a/src/installer/test/TestUtils/TestProject.cs b/src/installer/test/TestUtils/TestProject.cs index b512f4c1e6b..5fd9135beec 100644 --- a/src/installer/test/TestUtils/TestProject.cs +++ b/src/installer/test/TestUtils/TestProject.cs @@ -22,7 +22,6 @@ namespace Microsoft.DotNet.CoreSetup.Test public string AppExe { get => BuiltApp?.AppExe; } public string HostPolicyDll { get => BuiltApp?.HostPolicyDll; } public string HostFxrDll { get => BuiltApp?.HostFxrDll; } - public string CoreClrDll { get => BuiltApp?.CoreClrDll; } public TestApp BuiltApp { get; private set; } -- cgit v1.2.3