Single-File: MemoryMap the bundle file (dotnet/core-setup#6576)
authorSwaroop Sridhar <Swaroop.Sridhar@microsoft.com>
Mon, 19 Aug 2019 18:52:03 +0000 (11:52 -0700)
committerGitHub <noreply@github.com>
Mon, 19 Aug 2019 18:52:03 +0000 (11:52 -0700)
* Single-File: MemoryMap the bundle file

This changes makes a few changes in preparation for
loading IL files from the single-file bundle.

**Memory Mapping**

At startup, the entire bundle-file is memory mapped.
The individual embedded files are then extracted out to actual files on disk on startup.
However, this change will facilitate later checkins to:
* Loading files directly from bundle (ex: IL files using PELoader, config files using memory-streams, etc ).
* Extracting out files to disk lazily
Further details are available in [this document](https://github.com/dotnet/designs/blob/master/accepted/single-file/design.md#the-host)

**Refactoring**

* Static bundle_runner: The bundle_runner is made a static class that holds a global pointer
  to the memory-mapped application bundle, and meta-data about the contents.
  This facilitates the implementation of call-backs for accessing content directly from the bundle,
  as explained in [this document](https://github.com/dotnet/designs/blob/master/accepted/single-file/direct.md)

* A few new classes are created by factoring out functionality from bundle_runner
   * Extractor: Functionality for extracting contents of a bundle to files on disk.
   * Reader: This is a helper class that implements sequentional reading on memory-mapped bundle file.
   * dir-Util: Extraction directory handling utilities that assist in processing the bundle.

* Naming: bundle::bundle_runner is renamed as bundle::runner to remove the redundancy.
  Similarly, the newly refactored classes are named bundle::extractor and bundle::reader.

Commit migrated from https://github.com/dotnet/core-setup/commit/9bb119b2a4b82d54e73669928a3b831cc07a1291

21 files changed:
src/installer/corehost/cli/apphost/CMakeLists.txt
src/installer/corehost/cli/apphost/bundle/bundle_runner.cpp [deleted file]
src/installer/corehost/cli/apphost/bundle/bundle_runner.h [deleted file]
src/installer/corehost/cli/apphost/bundle/dir_utils.cpp [new file with mode: 0644]
src/installer/corehost/cli/apphost/bundle/dir_utils.h [new file with mode: 0644]
src/installer/corehost/cli/apphost/bundle/extractor.cpp [new file with mode: 0644]
src/installer/corehost/cli/apphost/bundle/extractor.h [new file with mode: 0644]
src/installer/corehost/cli/apphost/bundle/file_entry.cpp
src/installer/corehost/cli/apphost/bundle/file_entry.h
src/installer/corehost/cli/apphost/bundle/header.cpp
src/installer/corehost/cli/apphost/bundle/header.h
src/installer/corehost/cli/apphost/bundle/manifest.cpp
src/installer/corehost/cli/apphost/bundle/manifest.h
src/installer/corehost/cli/apphost/bundle/reader.cpp [new file with mode: 0644]
src/installer/corehost/cli/apphost/bundle/reader.h [new file with mode: 0644]
src/installer/corehost/cli/apphost/bundle/runner.cpp [new file with mode: 0644]
src/installer/corehost/cli/apphost/bundle/runner.h [new file with mode: 0644]
src/installer/corehost/common/pal.h
src/installer/corehost/common/pal.unix.cpp
src/installer/corehost/common/pal.windows.cpp
src/installer/corehost/corehost.cpp

index 8d755e3..0c034a4 100644 (file)
@@ -23,8 +23,11 @@ set(SOURCES
     ./bundle/file_entry.cpp
     ./bundle/manifest.cpp
     ./bundle/header.cpp
-    ./bundle/bundle_runner.cpp
     ./bundle/marker.cpp
+    ./bundle/reader.cpp
+    ./bundle/extractor.cpp
+    ./bundle/runner.cpp
+    ./bundle/dir_utils.cpp
 )
 
 set(HEADERS
@@ -33,8 +36,11 @@ set(HEADERS
     ./bundle/file_entry.h
     ./bundle/manifest.h
     ./bundle/header.h
-    ./bundle/bundle_runner.h
     ./bundle/marker.h
+    ./bundle/reader.h
+    ./bundle/extractor.h
+    ./bundle/runner.h
+    ./bundle/dir_utils.h
 )
 
 include(../exe.cmake)
diff --git a/src/installer/corehost/cli/apphost/bundle/bundle_runner.cpp b/src/installer/corehost/cli/apphost/bundle/bundle_runner.cpp
deleted file mode 100644 (file)
index 3aa3e27..0000000
+++ /dev/null
@@ -1,357 +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_runner.h"
-#include "pal.h"
-#include "trace.h"
-#include "utils.h"
-
-using namespace bundle;
-
-void bundle_runner_t::seek(FILE* stream, long offset, int origin)
-{
-    if (fseek(stream, offset, origin) != 0)
-    {
-        trace::error(_X("Failure processing application bundle; possible file corruption."));
-        trace::error(_X("I/O seek failure within the bundle."));
-        throw StatusCode::BundleExtractionIOError;
-    }
-}
-
-void bundle_runner_t::write(const void* buf, size_t size, FILE *stream)
-{
-    if (fwrite(buf, 1, size, stream) != size)
-    {
-        trace::error(_X("Failure extracting contents of the application bundle."));
-        trace::error(_X("I/O failure when writing extracted files."));
-        throw StatusCode::BundleExtractionIOError;
-    }
-}
-
-void bundle_runner_t::read(void* buf, size_t size, FILE* stream)
-{
-    if (fread(buf, 1, size, stream) != size)
-    {
-        trace::error(_X("Failure processing application bundle; possible file corruption."));
-        trace::error(_X("I/O failure reading contents of the bundle."));
-        throw StatusCode::BundleExtractionIOError;
-    }
-}
-
-// 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 bundle_runner_t::get_path_length(int8_t first_byte, FILE* stream)
-{
-    size_t length = 0;
-
-    // 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 = 0;
-        read(&second_byte, 1, stream);
-
-        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;
-}
-
-// Read a non-null terminated fixed length UTF8 string from a byte-stream
-// and transform it to pal::string_t
-void bundle_runner_t::read_string(pal::string_t &str, size_t size, FILE* stream)
-{
-    std::unique_ptr<uint8_t[]> buffer{new uint8_t[size + 1]};
-    read(buffer.get(), size, stream);
-    buffer[size] = 0; // null-terminator
-    pal::clr_palstring(reinterpret_cast<const char*>(buffer.get()), &str);
-}
-
-static bool has_dirs_in_path(const pal::string_t& path)
-{
-    return path.find_last_of(DIR_SEPARATOR) != pal::string_t::npos;
-}
-
-static void 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;
-    }
-}
-
-static void remove_directory_tree(const pal::string_t& path)
-{
-    if (path.empty())
-    {
-        return;
-    }
-
-    std::vector<pal::string_t> dirs;
-    pal::readdir_onlydirectories(path, &dirs);
-
-    for (const pal::string_t &dir : dirs)
-    {
-        remove_directory_tree(dir);
-    }
-
-    std::vector<pal::string_t> files;
-    pal::readdir(path, &files);
-
-    for (const pal::string_t &file : files)
-    {
-        if (!pal::remove(file.c_str()))
-        {
-            trace::warning(_X("Failed to remove temporary file [%s]."), file.c_str());
-        }
-    }
-
-    if (!pal::rmdir(path.c_str()))
-    {
-        trace::warning(_X("Failed to remove temporary directory [%s]."), path.c_str());
-    }
-}
-
-void bundle_runner_t::reopen_host_for_reading()
-{
-    m_bundle_stream = pal::file_open(m_bundle_path, _X("rb"));
-    if (m_bundle_stream == nullptr)
-    {
-        trace::error(_X("Failure processing application bundle."));
-        trace::error(_X("Couldn't open host binary for reading contents"));
-        throw StatusCode::BundleExtractionIOError;
-    }
-}
-
-// Compute the final extraction location as:
-// m_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<id>/...
-//
-// If DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set in the environment, the 
-// base directory defaults to $TMPDIR/.net
-void bundle_runner_t::determine_extraction_dir()
-{
-    if (!pal::getenv(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR"), &m_extraction_dir))
-    {
-        if (!pal::get_temp_directory(m_extraction_dir))
-        {
-            trace::error(_X("Failure processing application bundle."));
-            trace::error(_X("Failed to determine location for extracting embedded files"));
-            throw StatusCode::BundleExtractionFailure;
-        }
-
-        append_path(&m_extraction_dir, _X(".net"));
-    }
-
-    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, bundle_id().c_str());
-
-    trace::info(_X("Files embedded within the bundled will be extracted to [%s] directory"), m_extraction_dir.c_str());
-}
-
-// Compute the worker extraction location for this process, before the 
-// extracted files are committed to the final location
-// m_working_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<proc-id-hex>
-void bundle_runner_t::create_working_extraction_dir()
-{
-    // Set the working extraction path
-    m_working_extraction_dir = get_directory(m_extraction_dir);
-    pal::char_t pid[32];
-    pal::snwprintf(pid, 32, _X("%x"), pal::get_pid());
-    append_path(&m_working_extraction_dir, pid);
-
-    create_directory_tree(m_working_extraction_dir);
-
-    trace::info(_X("Temporary directory used to extract bundled files is [%s]"), m_working_extraction_dir.c_str());
-}
-
-// Create a file to be extracted out on disk, including any intermediate sub-directories.
-FILE* bundle_runner_t::create_extraction_file(const pal::string_t& relative_path)
-{
-    pal::string_t file_path = m_working_extraction_dir;
-    append_path(&file_path, relative_path.c_str());
-
-    // m_working_extraction_dir is assumed to exist, 
-    // so we only create sub-directories if relative_path contains directories
-    if (has_dirs_in_path(relative_path))
-    {
-        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 bundle_runner_t::extract_file(const file_entry_t& entry)
-{
-    FILE* file = create_extraction_file(entry.relative_path());
-    const int64_t buffer_size = 8 * 1024; // Copy the file in 8KB chunks
-    uint8_t buffer[buffer_size];
-    int64_t file_size = entry.size();
-
-    seek(m_bundle_stream, entry.offset(), SEEK_SET);
-    do {
-        int64_t copy_size = (file_size <= buffer_size) ? file_size : buffer_size;
-        read(buffer, copy_size, m_bundle_stream);
-        write(buffer, copy_size, file);
-        file_size -= copy_size;
-    } while (file_size > 0);
-
-    fclose(file);
-}
-
-bool bundle_runner_t::can_reuse_extraction()
-{
-    // In this version, the extracted files are assumed to be 
-    // correct by construction.
-    // 
-    // Files embedded in the bundle are first extracted to m_working_extraction_dir
-    // Once all files are successfully extracted, the extraction location is 
-    // committed (renamed) to m_extraction_dir. Therefore, the presence of 
-    // m_extraction_dir means that the files are pre-extracted. 
-
-
-    return pal::directory_exists(m_extraction_dir);
-}
-
-// 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 bundle_runner_t::extract()
-{
-    try
-    {
-        reopen_host_for_reading();
-
-        // Read the bundle header
-        seek(m_bundle_stream, marker_t::header_offset(), SEEK_SET);
-        m_header = header_t::read(m_bundle_stream);
-
-        // Determine if embedded files are already extracted, and available for reuse
-        determine_extraction_dir();
-        if (can_reuse_extraction())
-        {
-            return StatusCode::Success;
-        }
-
-        // Extract files to temporary working directory
-        //
-        // 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 (m_working_extraction_dir)
-        // 2) Upon successful extraction, m_working_extraction_dir is renamed to the actual
-        //    extraction location (m_extraction_dir)
-        //    
-        // This effectively creates a file-lock to protect against races and failed extractions.
-        
-        create_working_extraction_dir();
-
-        m_manifest = manifest_t::read(m_bundle_stream, num_embedded_files());
-
-        for (const file_entry_t & entry : m_manifest.files) {
-            extract_file(entry);
-        }
-
-        // Commit files 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.
-        int retry_count = 500;
-        while (true)
-        {
-            if (pal::rename(m_working_extraction_dir.c_str(), m_extraction_dir.c_str()) == 0)
-                break;
-
-            bool should_retry = errno == EACCES;
-            if (can_reuse_extraction())
-            {
-                // Another process successfully extracted the dependencies
-                trace::info(_X("Extraction completed by another process, aborting current extraction."));
-
-                remove_directory_tree(m_working_extraction_dir);
-                break;
-            }
-
-            if (should_retry && (retry_count--) > 0)
-            {
-                trace::info(_X("Retrying extraction due to EACCES trying to rename the extraction folder to [%s]."), m_extraction_dir.c_str());
-                pal::sleep(100);
-                continue;
-            }
-            else
-            {
-                trace::error(_X("Failure processing application bundle."));
-                trace::error(_X("Failed to commit extracted files to directory [%s]"), m_extraction_dir.c_str());
-                throw StatusCode::BundleExtractionFailure;
-            }
-        }
-
-        fclose(m_bundle_stream);
-        return StatusCode::Success;
-    }
-    catch (StatusCode e)
-    {
-        fclose(m_bundle_stream);
-        return e;
-    }
-}
diff --git a/src/installer/corehost/cli/apphost/bundle/bundle_runner.h b/src/installer/corehost/cli/apphost/bundle/bundle_runner.h
deleted file mode 100644 (file)
index 33e7c3a..0000000
+++ /dev/null
@@ -1,63 +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_RUNNER_H__
-#define __BUNDLE_RUNNER_H__
-
-
-#include <cstdint>
-#include <memory>
-#include "header.h"
-#include "manifest.h"
-#include "marker.h"
-#include "error_codes.h"
-
-namespace bundle
-{
-    class bundle_runner_t
-    {
-    public:
-        bundle_runner_t(const pal::string_t& bundle_path)
-            : m_bundle_stream(nullptr)
-            , m_bundle_path(bundle_path)
-        {
-        }
-
-        pal::string_t get_extraction_dir()
-        {
-            return m_extraction_dir;
-        }
-
-        StatusCode extract();
-
-        static void read(void* buf, size_t size, FILE* stream);
-        static void write(const void* buf, size_t size, FILE* stream);
-        static size_t get_path_length(int8_t first_byte, FILE* stream);
-        static void read_string(pal::string_t& str, size_t size, FILE* stream);
-
-    private:
-        void reopen_host_for_reading();
-        static void seek(FILE* stream, long offset, int origin);
-
-        int32_t num_embedded_files() { return m_header.num_embedded_files(); }
-        const pal::string_t& bundle_id() { return m_header.bundle_id(); }
-
-        void determine_extraction_dir();
-        void create_working_extraction_dir();
-        bool can_reuse_extraction();
-
-        FILE* create_extraction_file(const pal::string_t& relative_path);
-        void extract_file(const file_entry_t& entry);
-
-        FILE* m_bundle_stream;
-        header_t m_header;
-        manifest_t m_manifest;
-        pal::string_t m_bundle_path;
-        pal::string_t m_extraction_dir;
-        pal::string_t m_working_extraction_dir;
-    };
-
-}
-
-#endif // __BUNDLE_RUNNER_H__
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 (file)
index 0000000..2f067b7
--- /dev/null
@@ -0,0 +1,93 @@
+// 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<pal::string_t> dirs;
+    pal::readdir_onlydirectories(path, &dirs);
+
+    for (const pal::string_t &dir : dirs)
+    {
+        remove_directory_tree(dir);
+    }
+
+    std::vector<pal::string_t> files;
+    pal::readdir(path, &files);
+
+    for (const pal::string_t &file : files)
+    {
+        if (!pal::remove(file.c_str()))
+        {
+            trace::warning(_X("Failed to remove temporary file [%s]."), file.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;
+        }
+    }
+}
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 (file)
index 0000000..8824153
--- /dev/null
@@ -0,0 +1,23 @@
+// 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 <cstdint>
+#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);
+    };
+}
+
+#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 (file)
index 0000000..2f47e86
--- /dev/null
@@ -0,0 +1,184 @@
+// 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;
+
+// Compute the final extraction location as:
+// m_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<id>/...
+//
+// If DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set in the environment, the 
+// base directory defaults to $TMPDIR/.net
+void extractor_t::determine_extraction_dir()
+{
+    if (!pal::getenv(_X("DOTNET_BUNDLE_EXTRACT_BASE_DIR"), &m_extraction_dir))
+    {
+        if (!pal::get_temp_directory(m_extraction_dir))
+        {
+            trace::error(_X("Failure processing application bundle."));
+            trace::error(_X("Failed to determine location for extracting embedded files."));
+            throw StatusCode::BundleExtractionFailure;
+        }
+
+        append_path(&m_extraction_dir, _X(".net"));
+    }
+
+    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());
+}
+
+// Compute the working extraction location for this process, before the 
+// extracted files are committed to the final location
+// m_working_extraction_dir = $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/<proc-id-hex>
+void extractor_t::determine_working_extraction_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);
+
+    dir_utils_t::create_directory_tree(m_working_extraction_dir);
+
+    trace::info(_X("Temporary directory used to extract bundled files is [%s]."), m_working_extraction_dir.c_str());
+}
+
+// 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 = m_working_extraction_dir;
+    append_path(&file_path, relative_path.c_str());
+
+    // m_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);
+}
+
+pal::string_t& extractor_t::extraction_dir()
+{
+    if (m_extraction_dir.empty())
+    {
+        determine_extraction_dir();
+    }
+
+    return m_extraction_dir;
+}
+
+bool extractor_t::can_reuse_extraction()
+{
+    // In this version, the extracted files are assumed to be 
+    // correct by construction.
+    // 
+    // Files embedded in the bundle are first extracted to m_working_extraction_dir
+    // Once all files are successfully extracted, the extraction location is 
+    // committed (renamed) to m_extraction_dir. Therefore, the presence of 
+    // m_extraction_dir means that the files are pre-extracted. 
+
+    return pal::directory_exists(extraction_dir());
+}
+
+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 (m_working_extraction_dir)
+    // 2) Upon successful extraction, m_working_extraction_dir is renamed to the actual
+    //    extraction location (m_extraction_dir)
+    //    
+    // This effectively creates a file-lock to protect against races and failed extractions.
+
+    determine_working_extraction_dir();
+}
+
+void extractor_t::commit()
+{
+    // Commit files 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.
+    int retry_count = 500;
+    while (true)
+    {
+        if (pal::rename(m_working_extraction_dir.c_str(), m_extraction_dir.c_str()) == 0)
+            break;
+
+        bool should_retry = errno == EACCES;
+        if (can_reuse_extraction())
+        {
+            // Another process successfully extracted the dependencies
+            trace::info(_X("Extraction completed by another process, aborting current extraction."));
+
+            dir_utils_t::remove_directory_tree(m_working_extraction_dir);
+            break;
+        }
+
+        if (should_retry && (retry_count--) > 0)
+        {
+            trace::info(_X("Retrying extraction due to EACCES trying to rename the extraction folder to [%s]."), m_extraction_dir.c_str());
+            pal::sleep(100);
+            continue;
+        }
+        else
+        {
+            trace::error(_X("Failure processing application bundle."));
+            trace::error(_X("Failed to commit extracted files to directory [%s]."), m_extraction_dir.c_str());
+            throw StatusCode::BundleExtractionFailure;
+        }
+    }
+}
+
+void extractor_t::extract(const manifest_t& manifest, reader_t& reader)
+{
+    begin();
+    for (const file_entry_t& entry : manifest.files) {
+        extract(entry, reader);
+    }
+    commit();
+}
diff --git a/src/installer/corehost/cli/apphost/bundle/extractor.h b/src/installer/corehost/cli/apphost/bundle/extractor.h
new file mode 100644 (file)
index 0000000..eb45e2c
--- /dev/null
@@ -0,0 +1,45 @@
+// 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)
+            :m_extraction_dir(), m_working_extraction_dir()
+        {
+            m_bundle_id = bundle_id;
+            m_bundle_path = bundle_path;
+        }
+
+        pal::string_t& extraction_dir();
+        bool can_reuse_extraction();
+
+        void extract(const manifest_t &manifest, reader_t& reader);
+
+    private:
+        void determine_extraction_dir();
+        void determine_working_extraction_dir();
+
+        FILE* create_extraction_file(const pal::string_t& relative_path);
+
+        void begin();
+        void extract(const file_entry_t& entry, reader_t& reader);
+        void commit();
+
+        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;
+    };
+}
+
+#endif // __EXTRACTOR_H__
index 584c233..dec8fec 100644 (file)
@@ -2,26 +2,25 @@
 // 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_runner.h"
-#include "pal.h"
-#include "error_codes.h"
+#include "file_entry.h"
 #include "trace.h"
-#include "utils.h"
+#include "dir_utils.h"
+#include "error_codes.h"
 
 using namespace bundle;
 
-bool file_entry_t::is_valid()
+bool file_entry_t::is_valid() const
 {
-    return m_data.offset > 0 && m_data.size > 0 &&
-        static_cast<file_type_t>(m_data.type) < file_type_t::__last;
+    return m_offset > 0 && m_size > 0 &&
+        static_cast<file_type_t>(m_type) < file_type_t::__last;
 }
 
-file_entry_t file_entry_t::read(FILE* stream)
+file_entry_t file_entry_t::read(reader_t &reader)
 {
-    file_entry_t entry;
-
     // First read the fixed-sized portion of file-entry
-    bundle_runner_t::read(&entry.m_data, sizeof(entry.m_data), stream);
+    const file_entry_fixed_t* fixed_data = reinterpret_cast<const file_entry_fixed_t*>(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."));
@@ -29,23 +28,8 @@ file_entry_t file_entry_t::read(FILE* stream)
         throw StatusCode::BundleExtractionFailure;
     }
 
-    size_t path_length =
-        bundle_runner_t::get_path_length(entry.m_data.path_length_byte_1, stream);
-
-    // Read the relative-path, given its length 
-    pal::string_t& path = entry.m_relative_path;
-    bundle_runner_t::read_string(path, path_length, stream);
-
-    // Fixup the relative-path to have current platform's directory 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;
-        }
-    }
+    reader.read_path_string(entry.m_relative_path);
+    dir_utils_t::fixup_path_separator(entry.m_relative_path);
 
     return entry;
 }
index 8b75588..9709662 100644 (file)
@@ -5,54 +5,68 @@
 #ifndef __FILE_ENTRY_H__
 #define __FILE_ENTRY_H__
 
-#include <cstdint>
 #include "file_type.h"
-#include "pal.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 (m_data)
+    // Fixed size portion (file_entry_fixed_t)
     //   - Offset     
     //   - Size       
     //   - File Entry Type       
-    //   - path-length  (7-bit extension encoding, 1 Byte due to MAX_PATH)
     // Variable Size portion
-    //   - relative path  ("path-length" Bytes)
+    //   - 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
     {
-    private:
+    public:
+        file_entry_t()
+            : m_offset(0)
+            , m_size(0)
+            , m_type(bundle::file_type_t::__last)
+            , m_relative_path()
+        {
+        }
 
-        // The inner structure represents the fields that can be 
-        // read contiguously for every file_entry. 
-#pragma pack(push, 1)
-        struct
+        file_entry_t(const file_entry_fixed_t *fixed_data)
+            :m_relative_path()
         {
-            int64_t offset;
-            int64_t size;
-            file_type_t type;
-            int8_t path_length_byte_1;
-        } m_data;
-#pragma pack(pop)
+            // 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.
 
-        pal::string_t m_relative_path; // Path of an embedded file, relative to the extraction directory.
+            m_offset = fixed_data->offset;
+            m_size = fixed_data->size;
+            m_type = fixed_data->type;
+        }
 
-    public:
-        const pal::string_t& relative_path() const { return m_relative_path; }
-        int64_t offset() const { return m_data.offset; }
-        int64_t size() const { return m_data.size; }
-        file_type_t type() const { return m_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(FILE* stream);
+        static file_entry_t read(reader_t &reader);
 
     private:
-        static const pal::char_t bundle_dir_separator = '/';
-        bool is_valid();
+        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__
index f105a4d..d215e0f 100644 (file)
@@ -2,40 +2,36 @@
 // 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_runner.h"
+#include "header.h"
+#include "reader.h"
 #include "error_codes.h"
 #include "trace.h"
-#include "utils.h"
 
 using namespace bundle;
 
-bool header_t::is_valid()
+bool header_fixed_t::is_valid() const
 {
-    return m_data.num_embedded_files > 0 &&
-           ((m_data.major_version < current_major_version) ||
-            (m_data.major_version == current_major_version && m_data.minor_version <= current_minor_version));
+    return num_embedded_files > 0 &&
+           ((major_version < header_t::major_version) ||
+            (major_version == header_t::major_version && minor_version <= header_t::minor_version));
 }
 
-header_t header_t::read(FILE* stream)
+header_t header_t::read(reader_t& reader)
 {
-    header_t header;
+    const header_fixed_t* fixed_header = reinterpret_cast<const header_fixed_t*>(reader.read_direct(sizeof(header_fixed_t)));
 
-    // First read the fixed size portion of the header
-    bundle_runner_t::read(&header.m_data, sizeof(header.m_data), stream);
-    if (!header.is_valid())
+    if (!fixed_header->is_valid())
     {
         trace::error(_X("Failure processing application bundle."));
-        trace::error(_X("Bundle header version compatibility check failed"));
+        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
-    size_t bundle_id_length = 
-        bundle_runner_t::get_path_length(header.m_data.bundle_id_length_byte_1, stream);
-     
-    // Next read the bundle-ID string, given its length
-    bundle_runner_t::read_string(header.m_bundle_id, bundle_id_length, stream);
+    reader.read_path_string(header.m_bundle_id);
 
     return header;
 }
index 96b621a..807fc47 100644 (file)
@@ -7,40 +7,50 @@
 
 #include <cstdint>
 #include "pal.h"
+#include "reader.h"
 
 namespace bundle
 {
     // The Bundle Header contains:
-    // Fixed size thunk (m_data)
+    // Fixed size thunk (header_fixed_t)
     //   - Major Version     
     //   - Minor Version     
     //   - Number of embedded files
-    //   - Bundle ID length 
     // Variable size portion:
-    //   - Bundle ID ("Bundle ID length" bytes)
+    //   - Bundle ID (7-bit extension encoded length prefixed string)
+
+#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;
+    };
+#pragma pack(pop)
 
     struct header_t
     {
     public:
-        bool is_valid();
-        static header_t read(FILE* stream);
+        header_t(int32_t num_embedded_files = 0)
+            : m_num_embedded_files(num_embedded_files)
+            , m_bundle_id()
+        {
+        }
+
+        static header_t read(reader_t& reader);
         const pal::string_t& bundle_id() { return m_bundle_id; }
-        int32_t num_embedded_files() { return m_data.num_embedded_files;  }
+        int32_t num_embedded_files() { return m_num_embedded_files;  }
+
+        static const uint32_t major_version = 1;
+        static const uint32_t minor_version = 0;
 
     private:
-#pragma pack(push, 1)
-        struct
-        {
-            uint32_t major_version;
-            uint32_t minor_version;
-            int32_t num_embedded_files;
-            int8_t bundle_id_length_byte_1;
-        } m_data;
-#pragma pack(pop)
+        int32_t m_num_embedded_files;
         pal::string_t m_bundle_id;
 
-        static const uint32_t current_major_version = 1;
-        static const uint32_t current_minor_version = 0;
     };
 }
 #endif // __HEADER_H__
index 490e85a..6de65d3 100644 (file)
@@ -2,21 +2,17 @@
 // 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_runner.h"
-#include "pal.h"
-#include "error_codes.h"
-#include "trace.h"
-#include "utils.h"
+#include "manifest.h"
 
 using namespace bundle;
 
-manifest_t manifest_t::read(FILE* stream, int32_t num_files)
+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(stream));
+        manifest.files.emplace_back(file_entry_t::read(reader));
     }
 
     return manifest;
index 9d8c352..ee8cd1e 100644 (file)
@@ -5,7 +5,6 @@
 #ifndef __MANIFEST_H__
 #define __MANIFEST_H__
 
-#include <cstdint>
 #include <list>
 #include "file_entry.h"
 
@@ -19,7 +18,7 @@ namespace bundle
     public:
         std::vector<file_entry_t> files;
 
-        static manifest_t read(FILE* host, int32_t num_files);
+        static manifest_t read(reader_t &reader, int32_t num_files);
     };
 }
 #endif // __MANIFEST_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 (file)
index 0000000..e2fa9b6
--- /dev/null
@@ -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<uint8_t[]> buffer{ new uint8_t[size + 1] };
+    read(buffer.get(), size);
+    buffer[size] = 0; // null-terminator
+    pal::clr_palstring(reinterpret_cast<const char*>(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 (file)
index 0000000..1824ece
--- /dev/null
@@ -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 <cstdint>
+#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 (file)
index 0000000..ccae5ed
--- /dev/null
@@ -0,0 +1,71 @@
+// 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 <memory>
+#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);
+
+        extractor_t extractor(header.bundle_id(), m_bundle_path);
+        m_extraction_dir = extractor.extraction_dir();
+
+        // Determine if embedded files are already extracted, and available for reuse
+        if (extractor.can_reuse_extraction())
+        {
+            return StatusCode::Success;
+        }
+
+        manifest_t manifest = manifest_t::read(reader, header.num_embedded_files());
+
+        extractor.extract(manifest, 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 (file)
index 0000000..07daded
--- /dev/null
@@ -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__
index 7c8e58c..8636ce3 100644 (file)
@@ -40,6 +40,7 @@
 #include <mutex>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/mman.h>
 
 #define xerr std::cerr
 #define xout std::cout
@@ -112,7 +113,7 @@ namespace pal
     // temporarily wchar for Windows and char for Unix. Current implementation
     // implicitly expects the contents on both Windows and Unix as char and
     // converts them to wchar in code for Windows. This line should become:
-    // typedef std::basic_ifstream<pal::char_t> ifstream_t.
+    // typedef std::basic_ifstream<char_t> ifstream_t.
     typedef std::basic_ifstream<char> ifstream_t;
     typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
     typedef std::basic_istream<char> istream_t;
@@ -146,24 +147,25 @@ namespace pal
     inline int strncasecmp(const char_t* str1, const char_t* str2, int len) { return ::_wcsnicmp(str1, str2, len); }
 
     inline size_t strlen(const char_t* str) { return ::wcslen(str); }
-    inline FILE * file_open(const pal::string_t& path, const char_t* mode) { return ::_wfopen(path.c_str(), mode); }
+    inline FILE * file_open(const string_t& path, const char_t* mode) { return ::_wfopen(path.c_str(), mode); }
+
     inline void file_vprintf(FILE* f, const char_t* format, va_list vl) { ::vfwprintf(f, format, vl); ::fputwc(_X('\n'), f); }
     inline void err_fputs(const char_t* message) { ::fputws(message, stderr); ::fputwc(_X('\n'), stderr); }
     inline void out_vprintf(const char_t* format, va_list vl) { ::vfwprintf(stdout, format, vl); ::fputwc(_X('\n'), stdout); }
     inline int str_vprintf(char_t* buffer, size_t count, const char_t* format, va_list vl) { return ::_vsnwprintf(buffer, count, format, vl); }
 
-    bool pal_utf8string(const pal::string_t& str, std::vector<char>* out);
-    bool utf8_palstring(const std::string& str, pal::string_t* out);
-    bool pal_clrstring(const pal::string_t& str, std::vector<char>* out);
-    bool clr_palstring(const char* cstr, pal::string_t* out);
+    bool pal_utf8string(const string_t& str, std::vector<char>* out);
+    bool utf8_palstring(const std::string& str, string_t* out);
+    bool pal_clrstring(const string_t& str, std::vector<char>* out);
+    bool clr_palstring(const char* cstr, string_t* out);
 
-    inline bool mkdir(const pal::char_t* dir, int mode) { return CreateDirectoryW(dir, NULL) != 0; }
-    inline bool rmdir (const pal::char_t* path) { return RemoveDirectoryW(path) != 0; }
-    inline int rename(const pal::char_t* old_name, const pal::char_t* new_name) { return ::_wrename(old_name, new_name); }
-    inline int remove(const pal::char_t* path) { return ::_wremove(path); }
+    inline bool mkdir(const char_t* dir, int mode) { return CreateDirectoryW(dir, NULL) != 0; }
+    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 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
     #ifdef EXPORT_SHARED_API
         #define SHARED_API extern "C" __attribute__((__visibility__("default")))
@@ -202,21 +204,22 @@ namespace pal
     inline int strncasecmp(const char_t* str1, const char_t* str2, int len) { return ::strncasecmp(str1, str2, len); }
 
     inline size_t strlen(const char_t* str) { return ::strlen(str); }
-    inline FILE * file_open(const pal::string_t& path, const char_t* mode) { return fopen(path.c_str(), mode); }
+    inline FILE * file_open(const string_t& path, const char_t* mode) { return fopen(path.c_str(), mode); }
     inline void file_vprintf(FILE* f, const char_t* format, va_list vl) { ::vfprintf(f, format, vl); ::fputc('\n', f); }
     inline void err_fputs(const char_t* message) { ::fputs(message, stderr); ::fputc(_X('\n'), stderr); }
     inline void out_vprintf(const char_t* format, va_list vl) { ::vfprintf(stdout, format, vl); ::fputc('\n', stdout); }
     inline int str_vprintf(char_t* str, size_t size, const char_t* format, va_list vl) { return ::vsnprintf(str, size, format, vl); }
 
-    inline bool pal_utf8string(const pal::string_t& str, std::vector<char>* out) { out->assign(str.begin(), str.end()); out->push_back('\0'); return true; }
-    inline bool utf8_palstring(const std::string& str, pal::string_t* out) { out->assign(str); return true; }
-    inline bool pal_clrstring(const pal::string_t& str, std::vector<char>* out) { return pal_utf8string(str, out); }
-    inline bool clr_palstring(const char* cstr, pal::string_t* out) { out->assign(cstr); return true; }
+    inline bool pal_utf8string(const string_t& str, std::vector<char>* out) { out->assign(str.begin(), str.end()); out->push_back('\0'); return true; }
+    inline bool utf8_palstring(const std::string& str, string_t* out) { out->assign(str); return true; }
+    inline bool pal_clrstring(const string_t& str, std::vector<char>* out) { return pal_utf8string(str, out); }
+    inline bool clr_palstring(const char* cstr, string_t* out) { out->assign(cstr); return true; }
 
-    inline bool mkdir(const pal::char_t* dir, int mode) { return ::mkdir(dir, mode) == 0; }
-    inline bool rmdir(const pal::char_t* path) { return ::rmdir(path) == 0; }
-    inline int rename(const pal::char_t* old_name, const pal::char_t* new_name) { return ::rename(old_name, new_name); }
-    inline int remove(const pal::char_t* path) { return ::remove(path); }
+    inline bool mkdir(const char_t* dir, int mode) { return ::mkdir(dir, mode) == 0; }
+    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 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); }
 
@@ -231,11 +234,11 @@ namespace pal
         return ret;
     }
 
-    pal::string_t to_string(int value);
-    pal::string_t get_timestamp();
+    string_t to_string(int value);
+    string_t get_timestamp();
 
-    bool getcwd(pal::string_t* recv);
-    pal::string_t to_lower(const pal::string_t& in);
+    bool getcwd(string_t* recv);
+    string_t to_lower(const string_t& in);
 
 
     inline void file_flush(FILE *f) { std::fflush(f); }
@@ -243,22 +246,23 @@ namespace pal
     inline void out_flush() { std::fflush(stdout); }
 
     // Based upon https://github.com/dotnet/core-setup/blob/master/src/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs
-    pal::string_t get_current_os_rid_platform();
-    inline pal::string_t get_current_os_fallback_rid()
+    string_t get_current_os_rid_platform();
+    inline string_t get_current_os_fallback_rid()
     {
-        pal::string_t fallbackRid(FALLBACK_HOST_RID);
+        string_t fallbackRid(FALLBACK_HOST_RID);
 
         return fallbackRid;
     }
 
-    bool touch_file(const pal::string_t& path);
+    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);
     inline bool directory_exists(const string_t& path) { return file_exists(path); }
-    void readdir(const string_t& path, const string_t& pattern, std::vector<pal::string_t>* list);
-    void readdir(const string_t& path, std::vector<pal::string_t>* list);
-    void readdir_onlydirectories(const string_t& path, const string_t& pattern, std::vector<pal::string_t>* list);
-    void readdir_onlydirectories(const string_t& path, std::vector<pal::string_t>* list);
+    void readdir(const string_t& path, const string_t& pattern, std::vector<string_t>* list);
+    void readdir(const string_t& path, std::vector<string_t>* list);
+    void readdir_onlydirectories(const string_t& path, const string_t& pattern, std::vector<string_t>* list);
+    void readdir_onlydirectories(const string_t& path, std::vector<string_t>* list);
 
     bool get_own_executable_path(string_t* recv);
     bool get_own_module_path(string_t* recv);
@@ -268,24 +272,24 @@ namespace pal
     bool get_default_servicing_directory(string_t* recv);
 
     // Returns the globally registered install location (if any)
-    bool get_dotnet_self_registered_dir(pal::string_t* recv);
+    bool get_dotnet_self_registered_dir(string_t* recv);
     // Returns name of the global registry location (for error messages)
-    bool get_dotnet_self_registered_config_location(pal::string_t* recv);
+    bool get_dotnet_self_registered_config_location(string_t* recv);
 
     // Returns the default install location for a given platform
-    bool get_default_installation_dir(pal::string_t* recv);
+    bool get_default_installation_dir(string_t* recv);
 
     // Returns the global locations to search for SDK/Frameworks - used when multi-level lookup is enabled
-    bool get_global_dotnet_dirs(std::vector<pal::string_t>* recv);
+    bool get_global_dotnet_dirs(std::vector<string_t>* recv);
 
     bool get_default_breadcrumb_store(string_t* recv);
     bool is_path_rooted(const string_t& path);
 
-    bool get_temp_directory(pal::string_t& tmp_dir);
+    bool get_temp_directory(string_t& tmp_dir);
 
     int xtoi(const char_t* input);
 
-    bool get_loaded_library(const char_t *library_name, const char *symbol_name, /*out*/ dll_t *dll, /*out*/ pal::string_t *path);
+    bool get_loaded_library(const char_t *library_name, const char *symbol_name, /*out*/ dll_t *dll, /*out*/ string_t *path);
     bool load_library(const string_t* path, dll_t* dll);
     proc_t get_symbol(dll_t library, const char* name);
     void unload_library(dll_t library);
index c98f562..2fae9b5 100644 (file)
@@ -57,6 +57,37 @@ bool pal::touch_file(const pal::string_t& path)
     return true;
 }
 
+void* pal::map_file_readonly(const pal::string_t& path, size_t& length)
+{
+    int fd = open(path.c_str(), O_RDONLY, (S_IRUSR | S_IRGRP | S_IROTH));
+    if (fd == -1)
+    {
+        trace::warning(_X("Failed to map file. open(%s) failed with error %d"), path.c_str(), errno);
+        return nullptr;
+    }
+
+    struct stat buf;
+    if (fstat(fd, &buf) != 0)
+    {
+        trace::warning(_X("Failed to map file. fstat(%s) failed with error %d"), path.c_str(), errno);
+        close(fd);
+        return nullptr;
+    }
+
+    length = buf.st_size;
+    void* address = mmap(nullptr, length, PROT_READ, MAP_SHARED, fd, 0);
+
+    if(address == nullptr)
+    {
+        trace::warning(_X("Failed to map file. mmap(%s) failed with error %d"), path.c_str(), errno);
+        close(fd);
+        return nullptr;
+    }
+
+    close(fd);
+    return address;
+}
+
 bool pal::getcwd(pal::string_t* recv)
 {
     recv->clear();
index 0b65d3d..c32a844 100644 (file)
@@ -76,6 +76,46 @@ bool pal::touch_file(const pal::string_t& path)
     return true;
 }
 
+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);
+
+    if (file == INVALID_HANDLE_VALUE)
+    {
+        trace::warning(_X("Failed to map file. CreateFileW(%s) failed with error %d"), path.c_str(), GetLastError());
+        return nullptr;
+    }
+
+    LARGE_INTEGER fileSize;
+    if (GetFileSizeEx(file, &fileSize) == 0)
+    {
+        trace::warning(_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, PAGE_READONLY, 0, 0, NULL);
+
+    if (map == NULL)
+    {
+        trace::warning(_X("Failed to map file. CreateFileMappingW(%s) failed with error %d"), path.c_str(), GetLastError());
+        CloseHandle(file);
+        return nullptr;
+    }
+
+    void *address = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
+
+    if (map == NULL)
+    {
+        trace::warning(_X("Failed to map file. MapViewOfFile(%s) failed with error %d"), path.c_str(), GetLastError());
+        CloseHandle(file);
+        return nullptr;
+    }
+
+    return address;
+}
+
 bool pal::getcwd(pal::string_t* recv)
 {
     recv->clear();
index 8453033..44fdc7f 100644 (file)
@@ -11,8 +11,8 @@
 #include "utils.h"
 
 #if defined(FEATURE_APPHOST)
-#include "cli/apphost/bundle/bundle_runner.h"
 #include "cli/apphost/bundle/marker.h"
+#include "cli/apphost/bundle/runner.h"
 
 #define CURHOST_TYPE    _X("apphost")
 #define CUREXE_PKG_VER  COMMON_HOST_PKG_VER
@@ -116,8 +116,8 @@ int exe_start(const int argc, const pal::char_t* argv[])
 
     if (bundle::marker_t::is_bundle())
     {
-        bundle::bundle_runner_t extractor(host_path);
-        StatusCode bundle_status = extractor.extract();
+        bundle::runner_t bundle_runner(host_path);
+        StatusCode bundle_status = bundle_runner.extract();
 
         if (bundle_status != StatusCode::Success)
         {
@@ -125,7 +125,7 @@ int exe_start(const int argc, const pal::char_t* argv[])
             return bundle_status;
         }
 
-        app_path.assign(extractor.get_extraction_dir());
+        app_path.assign(bundle_runner.extraction_dir());
     }
     else
     {