// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "bin/snapshot_utils.h"

#include <cerrno>
#include <memory>

#include "bin/dartutils.h"
#include "bin/dfe.h"
#include "bin/elf_loader.h"
#include "bin/error_exit.h"
#include "bin/exe_utils.h"
#include "bin/file.h"
#include "bin/macho_loader.h"
#include "bin/platform.h"
#include "include/dart_api.h"
#if defined(DART_TARGET_OS_MACOS)
#include <platform/mach_o.h>
#endif
#if defined(DART_TARGET_OS_WINDOWS)
#include <platform/pe.h>
#endif
#include "platform/utils.h"

#define LOG_SECTION_BOUNDARIES false

#if !defined(DART_INCLUDE_SIMULATOR)
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID) ||            \
    defined(DART_HOST_OS_FUCHSIA)
#define NATIVE_SHARED_OBJECT_FORMAT_ELF 1
#elif defined(DART_HOST_OS_MACOS)
#define NATIVE_SHARED_OBJECT_FORMAT_MACHO 1
#endif
#endif  // !defined(DART_INCLUDE_SIMULATOR)

namespace dart {
namespace bin {

static constexpr int64_t kAppSnapshotHeaderSize = 2 * kInt64Size;
// The largest possible page size among the platforms we support (Linux ARM64).
static constexpr int64_t kAppSnapshotPageSize = 64 * KB;

static const char kMachOAppSnapshotNoteName[] DART_UNUSED = "__dart_app_snap";

#if !defined(DART_PRECOMPILED_RUNTIME)
class DummySnapshot : public AppSnapshot {
 public:
  explicit DummySnapshot(DartUtils::MagicNumber num) : AppSnapshot(num) {}

  ~DummySnapshot() {}

  void SetBuffers(const uint8_t** snapshot_data_buffer,
                  const uint8_t** snapshot_text_buffer) {
    UNREACHABLE();
  }

 private:
};

class MappedAppSnapshot : public AppSnapshot {
 public:
  MappedAppSnapshot(MappedMemory* snapshot_data, MappedMemory* snapshot_text)
      : AppSnapshot(DartUtils::kAppJITMagicNumber),
        data_mapping_(snapshot_data),
        text_mapping_(snapshot_text) {}

  ~MappedAppSnapshot() {
    delete data_mapping_;
    delete text_mapping_;
  }

  void SetBuffers(const uint8_t** data_buffer, const uint8_t** text_buffer) {
    if (data_mapping_ != nullptr) {
      *data_buffer = reinterpret_cast<const uint8_t*>(data_mapping_->address());
    }
    if (text_mapping_ != nullptr) {
      *text_buffer = reinterpret_cast<const uint8_t*>(text_mapping_->address());
    }
  }

 private:
  MappedMemory* data_mapping_;
  MappedMemory* text_mapping_;
};

static AppSnapshot* TryReadAppSnapshotBlobs(const char* script_name,
                                            File* file) {
  if ((file->Length() - file->Position()) < kAppSnapshotHeaderSize) {
    return nullptr;
  }

  int64_t header[2];
  ASSERT(sizeof(header) == kAppSnapshotHeaderSize);
  if (!file->ReadFully(&header, kAppSnapshotHeaderSize)) {
    return nullptr;
  }
  int64_t isolate_data_size = header[0];
  int64_t isolate_data_position =
      Utils::RoundUp(file->Position(), kAppSnapshotPageSize);
  int64_t isolate_instructions_size = header[1];
  int64_t isolate_instructions_position =
      isolate_data_position + isolate_data_size;
  if (isolate_instructions_size != 0) {
    isolate_instructions_position =
        Utils::RoundUp(isolate_instructions_position, kAppSnapshotPageSize);
  }

  MappedMemory* isolate_data_mapping = nullptr;
  if (isolate_data_size != 0) {
    isolate_data_mapping =
        file->Map(File::kReadOnly, isolate_data_position, isolate_data_size);
    if (isolate_data_mapping == nullptr) {
      FATAL("Failed to memory map snapshot: %s\n", script_name);
    }
  }

  MappedMemory* isolate_instr_mapping = nullptr;
  if (isolate_instructions_size != 0) {
    isolate_instr_mapping =
        file->Map(File::kReadExecute, isolate_instructions_position,
                  isolate_instructions_size);
    if (isolate_instr_mapping == nullptr) {
      FATAL("Failed to memory map snapshot: %s\n", script_name);
    }
  }

  auto app_snapshot =
      new MappedAppSnapshot(isolate_data_mapping, isolate_instr_mapping);
  return app_snapshot;
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

static DartUtils::MagicNumber ReadMagicNumberAt(File& file, int64_t offset) {
  // Attempt to read a magic number from the specified offset, even if there
  // are less than kMaxMagicNumberSize bytes available.
  const int64_t remaining = file.Length() - offset;
  if (remaining <= 0) {
    Syslog::PrintErr("File truncated before or at offset 0x%" Px64 ".\n",
                     offset);
    return DartUtils::kUnknownMagicNumber;
  }
  if (!file.SetPosition(offset)) {
    return DartUtils::kUnknownMagicNumber;
  }
  uint8_t header[DartUtils::kMaxMagicNumberSize];
  auto const read_size = Utils::Minimum<int64_t>(remaining, sizeof(header));
  if (!file.ReadFully(&header, read_size)) {
    return DartUtils::kUnknownMagicNumber;
  }
  return DartUtils::SniffForMagicNumber(header, read_size);
}

class DylibAppSnapshot : public AppSnapshot {
 public:
  DylibAppSnapshot(DartUtils::MagicNumber magic_number,
                   void* library,
                   const uint8_t* snapshot_data,
                   const uint8_t* snapshot_text)
      : AppSnapshot(magic_number),
        library_(library),
        snapshot_data_(snapshot_data),
        snapshot_text_(snapshot_text) {}

  ~DylibAppSnapshot() { Utils::UnloadDynamicLibrary(library_); }

  void SetBuffers(const uint8_t** data_buffer, const uint8_t** text_buffer) {
    *data_buffer = snapshot_data_;
    *text_buffer = snapshot_text_;
  }

 private:
  void* library_;
  const uint8_t* snapshot_data_;
  const uint8_t* snapshot_text_;
};

static AppSnapshot* TryReadAppSnapshotDynamicLibrary(
    DartUtils::MagicNumber magic_number,
    const char* script_name,
    char** error) {
#if defined(DART_INCLUDE_SIMULATOR)
  *error = Utils::StrDup("running on a simulated architecture");
  return nullptr;
#else
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS)
  // On Linux and OSX, resolve the script path before passing into dlopen()
  // since dlopen will not search the filesystem for paths like 'libtest.so'.
  const size_t kPathBufSize = PATH_MAX + 1;
  char canon_path[kPathBufSize];
  script_name =
      File::GetCanonicalPath(nullptr, script_name, canon_path, kPathBufSize);
  if (script_name == nullptr) {
    const intptr_t err = errno;
    const int kBufferSize = 1024;
    char error_buf[kBufferSize];
    Utils::StrError(err, error_buf, kBufferSize);
    *error = Utils::SCreate("could not resolve path: %s", error_buf);
    return nullptr;
  }
#endif
  void* library = Utils::LoadDynamicLibrary(script_name, error);
  if (library == nullptr) {
#if defined(NATIVE_SHARED_OBJECT_FORMAT_ELF)
    if (*error == nullptr && magic_number != DartUtils::kAotELFMagicNumber) {
      *error = Utils::StrDup("not an ELF shared object");
    }
#elif defined(NATIVE_SHARED_OBJECT_FORMAT_MACHO)
    if (*error == nullptr &&
        magic_number != DartUtils::kAotMachO32MagicNumber &&
        magic_number != DartUtils::kAotMachO64MagicNumber) {
      *error = Utils::StrDup("not a Mach-O shared object");
    }
#endif
    if (*error == nullptr) {
      *error = Utils::StrDup(
          "unknown failure loading dynamic library (wrong format?)");
    }
    return nullptr;
  }

  const uint8_t* snapshot_data_buffer = reinterpret_cast<const uint8_t*>(
      Utils::ResolveSymbolInDynamicLibrary(library, kSnapshotDataCSymbol));
  if (snapshot_data_buffer == nullptr) {
    FATAL("Failed to resolve symbol '%s'\n", kSnapshotDataCSymbol);
  }

  const uint8_t* snapshot_text_buffer = reinterpret_cast<const uint8_t*>(
      Utils::ResolveSymbolInDynamicLibrary(library, kSnapshotTextCSymbol));
  if (snapshot_text_buffer == nullptr) {
    FATAL("Failed to resolve symbol '%s'\n", kSnapshotTextCSymbol);
  }

  return new DylibAppSnapshot(magic_number, library, snapshot_data_buffer,
                              snapshot_text_buffer);
#endif  // defined(DART_INCLUDE_SIMULATOR)
}

#if defined(DART_PRECOMPILED_RUNTIME)
class ElfAppSnapshot : public AppSnapshot {
 public:
  ElfAppSnapshot(Dart_LoadedElf* elf,
                 const uint8_t* snapshot_data,
                 const uint8_t* snapshot_text)
      : AppSnapshot{DartUtils::kAotELFMagicNumber},
        elf_(elf),
        snapshot_data_(snapshot_data),
        snapshot_text_(snapshot_text) {}

  virtual ~ElfAppSnapshot() { Dart_UnloadELF(elf_); }

  void SetBuffers(const uint8_t** data_buffer, const uint8_t** text_buffer) {
    *data_buffer = snapshot_data_;
    *text_buffer = snapshot_text_;
  }

 private:
  Dart_LoadedElf* elf_;
  const uint8_t* snapshot_data_;
  const uint8_t* snapshot_text_;
};

static AppSnapshot* TryReadAppSnapshotElf(const char* script_name,
                                          uint64_t file_offset,
                                          bool force_load_from_memory) {
#if defined(NATIVE_SHARED_OBJECT_FORMAT_ELF)
  if (file_offset == 0 && !force_load_from_memory) {
    // The load as a dynamic library should succeed, since this is a platform
    // that natively understands ELF.
    char* error = nullptr;
    if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary(
            DartUtils::kAotELFMagicNumber, script_name, &error)) {
      return snapshot;
    }
    Syslog::PrintErr("Loading dynamic library failed: %s\n", error);
    free(error);
    return nullptr;
  }
#endif
  const uint8_t* snapshot_data_buffer = nullptr;
  const uint8_t* snapshot_text_buffer = nullptr;
  Dart_LoadedElf* handle = nullptr;
  const char* error = nullptr;
  if (force_load_from_memory) {
    File* const file =
        File::Open(/*namespc=*/nullptr, script_name, File::kRead);
    if (file == nullptr) return nullptr;
    MappedMemory* memory = file->Map(File::kReadOnly, /*position=*/0,
                                     /*length=*/file->Length());
    if (memory == nullptr) return nullptr;
    const uint8_t* address =
        reinterpret_cast<const uint8_t*>(memory->address());
    handle = Dart_LoadELF_Memory(address + file_offset, file->Length(), &error,
                                 &snapshot_data_buffer, &snapshot_text_buffer);
    delete memory;
    file->Release();
  } else {
    handle = Dart_LoadELF(script_name, file_offset, &error,
                          &snapshot_data_buffer, &snapshot_text_buffer);
  }
  if (handle == nullptr) {
    Syslog::PrintErr("Loading failed: %s\n", error);
    return nullptr;
  }
  return new ElfAppSnapshot(handle, snapshot_data_buffer, snapshot_text_buffer);
}

class MachODylibAppSnapshot : public AppSnapshot {
 public:
  MachODylibAppSnapshot(DartUtils::MagicNumber magic_number,
                        Dart_LoadedMachODylib* macho,
                        const uint8_t* snapshot_data,
                        const uint8_t* snapshot_text)
      : AppSnapshot{magic_number},
        macho_(macho),
        snapshot_data_(snapshot_data),
        snapshot_text_(snapshot_text) {}

  virtual ~MachODylibAppSnapshot() { Dart_UnloadMachODylib(macho_); }

  void SetBuffers(const uint8_t** data_buffer, const uint8_t** text_buffer) {
    *data_buffer = snapshot_data_;
    *text_buffer = snapshot_text_;
  }

 private:
  Dart_LoadedMachODylib* macho_;
  const uint8_t* snapshot_data_;
  const uint8_t* snapshot_text_;
};

static AppSnapshot* TryReadAppSnapshotMachODylib(
    DartUtils::MagicNumber magic_number,
    const char* script_name,
    uint64_t file_offset,
    bool force_load_from_memory) {
#if defined(NATIVE_SHARED_OBJECT_FORMAT_MACHO)
  if (file_offset == 0 && !force_load_from_memory) {
    // The load as a dynamic library should succeed, since this is a platform
    // that natively understands Mach-O.
    char* error = nullptr;
    if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary(
            magic_number, script_name, &error)) {
      return snapshot;
    }
    Syslog::PrintErr("Loading dynamic library failed: %s\n", error);
    free(error);
    return nullptr;
  }
#endif
  const uint8_t* snapshot_data_buffer = nullptr;
  const uint8_t* snapshot_text_buffer = nullptr;
  Dart_LoadedMachODylib* handle = nullptr;
  const char* error = nullptr;
  if (force_load_from_memory) {
    File* const file =
        File::Open(/*namespc=*/nullptr, script_name, File::kRead);
    if (file == nullptr) return nullptr;
    MappedMemory* memory = file->Map(File::kReadOnly, /*position=*/0,
                                     /*length=*/file->Length());
    if (memory == nullptr) {
      Syslog::PrintErr("File mapping failed\n");
      return nullptr;
    }
    const uint8_t* address =
        reinterpret_cast<const uint8_t*>(memory->address());
    handle = Dart_LoadMachODylib_Memory(address + file_offset, file->Length(),
                                        &error, &snapshot_data_buffer,
                                        &snapshot_text_buffer);
    delete memory;
    file->Release();
  } else {
    handle = Dart_LoadMachODylib(script_name, file_offset, &error,
                                 &snapshot_data_buffer, &snapshot_text_buffer);
  }
  if (handle == nullptr) {
    Syslog::PrintErr("Loading failed: %s\n", error);
    return nullptr;
  }
  return new MachODylibAppSnapshot(magic_number, handle, snapshot_data_buffer,
                                   snapshot_text_buffer);
}

static AppSnapshot* TryReadAppSnapshotAt(const char* script_name,
                                         File& file,
                                         int64_t file_offset,
                                         bool force_load_from_memory = false) {
  auto const magic_number = ReadMagicNumberAt(file, file_offset);
  if (magic_number == DartUtils::kAotELFMagicNumber) {
    return TryReadAppSnapshotElf(script_name, file_offset,
                                 force_load_from_memory);
  }

  if (magic_number == DartUtils::kAotMachO32MagicNumber ||
      magic_number == DartUtils::kAotMachO64MagicNumber) {
    return TryReadAppSnapshotMachODylib(magic_number, script_name, file_offset,
                                        force_load_from_memory);
  }

  if (file_offset == 0) {
    // This is a non-appended snapshot which is not handled by any of the
    // non-native loaders, so attempt to load it as a native dynamic library.
    char* error = nullptr;
    if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary(
            magic_number, script_name, &error)) {
      return snapshot;
    }
    Syslog::PrintErr("Loading dynamic library failed: %s\n", error);
    free(error);
  }

  return nullptr;
}

#if defined(DART_TARGET_OS_MACOS)
AppSnapshot* Snapshot::TryReadAppendedAppSnapshotFromMachO(
    const char* container_path) {
  // Ensure file is actually MachO-formatted.
  DartUtils::MagicNumber magic_number;
  if (!IsMachOFormattedBinary(container_path, &magic_number)) {
    Syslog::PrintErr("Expected a Mach-O binary.\n");
    return nullptr;
  }

  File* file = File::Open(nullptr, container_path, File::kRead);
  if (file == nullptr) {
    return nullptr;
  }
  RefCntReleaseScope<File> rs(file);

  // Read in the Mach-O header. Note that the 64-bit header is the same layout
  // as the 32-bit header, just with an extra field for alignment, so we can
  // safely load a 32-bit header to get all the information we need.
  mach_o::mach_header header;
  if (!file->ReadFully(&header, sizeof(header))) {
    Syslog::PrintErr("Could not read a complete Mach-O header.\n");
    return nullptr;
  }

  auto const bitsize = DartUtils::MagicNumberBitSize(magic_number);
  if (bitsize == 64) {
    // The load commands start immediately after the full header.
    if (!file->SetPosition(sizeof(mach_o::mach_header_64))) {
      Syslog::PrintErr("Could not read a complete Mach-O 64-bit header.\n");
    }
  } else {
    ASSERT_EQUAL(bitsize, 32);
  }

  // Now we search through the load commands to find our snapshot note, which
  // has a data_owner field of kMachOAppSnapshotNoteName.
  for (uint32_t i = 0; i < header.ncmds; ++i) {
    mach_o::load_command command;
    file->ReadFully(&command, sizeof(mach_o::load_command));

    file->SetPosition(file->Position() - sizeof(command));
    if (command.cmd != mach_o::LC_NOTE) {
      file->SetPosition(file->Position() + command.cmdsize);
      continue;
    }

    mach_o::note_command note;
    file->ReadFully(&note, sizeof(note));

    if (strcmp(note.data_owner, kMachOAppSnapshotNoteName) != 0) {
      file->SetPosition(file->Position() + command.cmdsize);
      continue;
    }

    return TryReadAppSnapshotAt(container_path, *file, note.offset);
  }

  return nullptr;
}
#endif  // defined(DART_TARGET_OS_MACOS)

#if defined(DART_TARGET_OS_WINDOWS)
// Keep in sync with CoffSectionTable._snapshotSectionName from
// pkg/dart2native/lib/dart2native_pe.dart.
static const char kSnapshotSectionName[] = "snapshot";
// Ignore the null terminator, as it won't be present if the string length is
// exactly pe::kCoffSectionNameSize.
static_assert(sizeof(kSnapshotSectionName) - 1 <= pe::kCoffSectionNameSize,
              "Section name of snapshot too large");

AppSnapshot* Snapshot::TryReadAppendedAppSnapshotFromPE(
    const char* container_path) {
  File* const file = File::Open(nullptr, container_path, File::kRead);
  if (file == nullptr) {
    return nullptr;
  }
  RefCntReleaseScope<File> rs(file);

  // Ensure file is actually PE-formatted.
  if (!IsPEFormattedBinary(container_path)) {
    Syslog::PrintErr(
        "Attempted load target was not formatted as expected: "
        "expected PE32 or PE32+ image file.\n");
    return nullptr;
  }

  // Parse the offset into the PE contents (i.e., skipping the MS-DOS stub).
  uint32_t pe_offset;
  file->SetPosition(pe::kPEOffsetOffset);
  file->ReadFully(&pe_offset, sizeof(pe_offset));

  // Skip past the magic bytes to the COFF file header and COFF optional header.
  const intptr_t coff_offset = pe_offset + sizeof(pe::kPEMagic);
  file->SetPosition(coff_offset);
  pe::coff_file_header file_header;
  file->ReadFully(&file_header, sizeof(file_header));
  // The optional header follows directly after the file header.
  pe::coff_optional_header opt_header;
  file->ReadFully(&opt_header, sizeof(opt_header));

  // Skip to the section table.
  const intptr_t coff_symbol_table_offset =
      coff_offset + sizeof(file_header) + file_header.optional_header_size;
  file->SetPosition(coff_symbol_table_offset);
  for (intptr_t i = 0; i < file_header.num_sections; i++) {
    pe::coff_section_header section_header;
    file->ReadFully(&section_header, sizeof(section_header));
    if (strncmp(section_header.name, kSnapshotSectionName,
                pe::kCoffSectionNameSize) == 0) {
      // We have to do the loading manually even though currently the snapshot
      // data is at the end of the file because the file alignment for
      // PE sections can be less than the page size, and TryReadAppSnapshotElf
      // won't work if the file offset isn't page-aligned.
      const char* error = nullptr;
      const uint8_t* snapshot_data_buffer = nullptr;
      const uint8_t* snapshot_text_buffer = nullptr;

      const intptr_t offset = section_header.file_offset;
      const intptr_t size = section_header.file_size;

      auto const magic_number = ReadMagicNumberAt(*file, offset);

      std::unique_ptr<uint8_t[]> snapshot(new uint8_t[size]);
      file->SetPosition(offset);
      file->ReadFully(snapshot.get(), sizeof(uint8_t) * size);

      if (magic_number == DartUtils::kAotELFMagicNumber) {
        Dart_LoadedElf* const handle =
            Dart_LoadELF_Memory(snapshot.get(), size, &error,
                                &snapshot_data_buffer, &snapshot_text_buffer);

        if (handle == nullptr) {
          Syslog::PrintErr("Loading failed: %s\n", error);
          return nullptr;
        }

        return new ElfAppSnapshot(handle, snapshot_data_buffer,
                                  snapshot_text_buffer);
      }

      if (magic_number == DartUtils::kAotMachO32MagicNumber ||
          magic_number == DartUtils::kAotMachO64MagicNumber) {
        Dart_LoadedMachODylib* const handle = Dart_LoadMachODylib_Memory(
            snapshot.get(), size, &error, &snapshot_data_buffer,
            &snapshot_text_buffer);

        if (handle == nullptr) {
          Syslog::PrintErr("Loading failed: %s\n", error);
          return nullptr;
        }

        return new MachODylibAppSnapshot(
            magic_number, handle, snapshot_data_buffer, snapshot_text_buffer);
      }

      return nullptr;
    }
  }

  return nullptr;
}
#endif  // defined(DART_TARGET_OS_WINDOWS)

AppSnapshot* Snapshot::TryReadAppendedAppSnapshot(const char* container_path) {
#if defined(DART_TARGET_OS_MACOS)
  if (IsMachOFormattedBinary(container_path)) {
    return TryReadAppendedAppSnapshotFromMachO(container_path);
  }
#elif defined(DART_TARGET_OS_WINDOWS)
  if (IsPEFormattedBinary(container_path)) {
    return TryReadAppendedAppSnapshotFromPE(container_path);
  }
#endif

  File* file = File::Open(nullptr, container_path, File::kRead);
  if (file == nullptr) {
    return nullptr;
  }
  RefCntReleaseScope<File> rs(file);

  // For other appended snapshots, the header for the appended snapshot
  // information are two 64-bit integers at the end of the file:
  //    ...
  //    snapshot offset (length of snapshot is to appended header)
  //    DartUtils::kAppJITMagicNumber
  const int64_t magic_number_offset = file->Length() - kInt64Size;
  auto const magic_number = ReadMagicNumberAt(*file, magic_number_offset);
  if (magic_number != DartUtils::kAppJITMagicNumber) {
    return nullptr;
  }

  const int64_t snapshot_offset_offset = magic_number_offset - kInt64Size;
  int64_t snapshot_offset;
  if (!file->SetPosition(snapshot_offset_offset)) {
    return nullptr;
  }
  if (!file->ReadFully(&snapshot_offset, sizeof(snapshot_offset))) {
    return nullptr;
  }
  // The offset is always encoded as Little Endian.
  snapshot_offset = Utils::LittleEndianToHost64(snapshot_offset);
  if (snapshot_offset <= 0) {
    return nullptr;
  }

  return TryReadAppSnapshotAt(container_path, *file, snapshot_offset);
}
#endif  // defined(DART_PRECOMPILED_RUNTIME)

bool Snapshot::IsMachOFormattedBinary(const char* filename,
                                      DartUtils::MagicNumber* out) {
  File* file = File::Open(nullptr, filename, File::kRead);
  if (file == nullptr) {
    return false;
  }
  RefCntReleaseScope<File> rs(file);

  auto const magic_number = ReadMagicNumberAt(*file, /*offset=*/0);
  if (out != nullptr) {
    *out = magic_number;
  }
  return magic_number == DartUtils::kAotMachO32MagicNumber ||
         magic_number == DartUtils::kAotMachO64MagicNumber;
}

#if defined(DART_TARGET_OS_WINDOWS)
bool Snapshot::IsPEFormattedBinary(const char* filename) {
  File* file = File::Open(nullptr, filename, File::kRead);
  if (file == nullptr) {
    return false;
  }
  RefCntReleaseScope<File> rs(file);

  // Parse the PE offset.
  uint32_t pe_offset;
  // Ensure the file is long enough to contain the PE offset.
  if (file->Length() <
      static_cast<intptr_t>(pe::kPEOffsetOffset + sizeof(pe_offset))) {
    return false;
  }
  file->SetPosition(pe::kPEOffsetOffset);
  file->Read(&pe_offset, sizeof(pe_offset));

  // Ensure the file is long enough to contain the PE magic bytes.
  if (file->Length() <
      static_cast<intptr_t>(pe_offset + sizeof(pe::kPEMagic))) {
    return false;
  }
  // Check the magic bytes.
  file->SetPosition(pe_offset);
  for (size_t i = 0; i < sizeof(pe::kPEMagic); i++) {
    char c;
    file->Read(&c, sizeof(c));
    if (c != pe::kPEMagic[i]) {
      return false;
    }
  }

  // Check that there is a coff optional header.
  pe::coff_file_header file_header;
  pe::coff_optional_header opt_header;
  file->Read(&file_header, sizeof(file_header));
  if (file_header.optional_header_size < sizeof(opt_header)) {
    return false;
  }
  file->Read(&opt_header, sizeof(opt_header));
  // Check the magic bytes in the coff optional header.
  if (opt_header.magic != pe::kPE32Magic &&
      opt_header.magic != pe::kPE32PlusMagic) {
    return false;
  }

  return true;
}
#endif  // defined(DART_TARGET_OS_WINDOWS)

AppSnapshot* Snapshot::TryReadAppSnapshot(const char* script_uri,
                                          bool force_load_from_memory,
                                          bool decode_uri) {
  CStringUniquePtr decoded_path(nullptr);
  const char* script_name = nullptr;
  if (decode_uri) {
    decoded_path = File::UriToPath(script_uri);
    if (decoded_path == nullptr) {
      return nullptr;
    }
    script_name = decoded_path.get();
  } else {
    script_name = script_uri;
  }
  if (File::GetType(nullptr, script_name, true) != File::kIsFile) {
    // If 'script_name' refers to a pipe, don't read to check for an app
    // snapshot since we cannot rewind if it isn't (and couldn't mmap it in
    // anyway if it was).
    return nullptr;
  }
  File* file = File::Open(nullptr, script_name, File::kRead);
  if (file == nullptr) {
    return nullptr;
  }
  RefCntReleaseScope<File> rs(file);

  const intptr_t offset = 0;
#if defined(DART_PRECOMPILED_RUNTIME)
  return TryReadAppSnapshotAt(script_name, *file, offset,
                              force_load_from_memory);
#else
  auto const magic_number = ReadMagicNumberAt(*file, offset);
  if (magic_number == DartUtils::kAppJITMagicNumber) {
    // Return the JIT snapshot.
    return TryReadAppSnapshotBlobs(script_name, file);
  }
  if (magic_number == DartUtils::kAotMachO64MagicNumber) {
    // Read module snapshot.
    char* error = nullptr;
    if (auto* const snapshot = TryReadAppSnapshotDynamicLibrary(
            magic_number, script_name, &error)) {
      return snapshot;
    }
    Syslog::PrintErr("Loading dynamic library failed: %s\n", error);
    free(error);
    return nullptr;
  }
  // We create a dummy snapshot object just to remember the type which
  // has already been identified by sniffing the magic number.
  return new DummySnapshot(magic_number);
#endif  // defined(DART_PRECOMPILED_RUNTIME)

  return nullptr;
}

#if !defined(EXCLUDE_CFE_AND_KERNEL_PLATFORM) && !defined(TESTING)
static void WriteSnapshotFile(const char* filename,
                              const uint8_t* buffer,
                              const intptr_t size) {
  File* file = File::Open(nullptr, filename, File::kWriteTruncate);
  if (file == nullptr) {
    ErrorExit(kErrorExitCode, "Unable to open file %s for writing snapshot\n",
              filename);
  }

  if (!file->WriteFully(buffer, size)) {
    ErrorExit(kErrorExitCode, "Unable to write file %s for writing snapshot\n",
              filename);
  }
  file->Release();
}
#endif

// TODO(bkonyi): dedup
static bool CheckForInvalidPath(const char* path) {
  // TODO(zichangguo): "\\?\" is a prefix for paths on Windows.
  // Arguments passed are parsed as an URI. "\\?\" causes problems as a part
  // of URIs. This is a temporary workaround to prevent VM from crashing.
  // Issue: https://github.com/dart-lang/sdk/issues/42779
  if (strncmp(path, R"(\\?\)", 4) == 0) {
    Syslog::PrintErr(R"(\\?\ prefix is not supported)");
    return false;
  }
  return true;
}

std::pair<AppSnapshot*, CStringUniquePtr> Snapshot::TryReadSDKSnapshot(
    const char* snapshot_name) {
  auto try_resolve_path = [&](CStringUniquePtr dir_prefix) {
    // |dir_prefix| includes the last path separator.
    // First assume we're in dart-sdk/bin.
    char* snapshot_path =
        Utils::SCreate("%ssnapshots/%s", dir_prefix.get(), snapshot_name);
    if (File::Exists(nullptr, snapshot_path)) {
      return CStringUniquePtr(snapshot_path);
    }
    free(snapshot_path);

    // If we're not in dart-sdk/bin, we might be in one of the $SDK/out*/
    // directories, Try to use a snapshot from that directory.
    snapshot_path = Utils::SCreate("%s%s", dir_prefix.get(), snapshot_name);
    if (File::Exists(nullptr, snapshot_path)) {
      return CStringUniquePtr(snapshot_path);
    }
    free(snapshot_path);
    return CStringUniquePtr(nullptr);
  };

  auto script_path =
      try_resolve_path(EXEUtils::GetDirectoryPrefixFromResolvedExeName());
  if (script_path == nullptr) {
    script_path =
        try_resolve_path(EXEUtils::GetDirectoryPrefixFromUnresolvedExeName());
  }
  if (script_path == nullptr || !CheckForInvalidPath(script_path.get())) {
    Syslog::PrintErr("Unable to find snapshot: %s\n", snapshot_name);
    return std::make_pair(static_cast<AppSnapshot*>(nullptr),
                          std::move(script_path));
  }

  AppSnapshot* app_snapshot =
      TryReadAppSnapshot(script_path.get(), /*force_load_from_memory*/ false,
                         /*decode_uri*/ false);
  if (app_snapshot == nullptr) {
    Syslog::PrintErr("%s is not a valid snapshot\n", script_path.get());
    if (app_snapshot != nullptr) {
      delete app_snapshot;
    }
    return std::make_pair(static_cast<AppSnapshot*>(nullptr),
                          std::move(script_path));
  }
  return std::make_pair(app_snapshot, std::move(script_path));
}

static bool WriteInt64(File* file, int64_t size) {
  return file->WriteFully(&size, sizeof(size));
}

void Snapshot::WriteAppSnapshot(const char* filename,
                                uint8_t* isolate_data_buffer,
                                intptr_t isolate_data_size,
                                uint8_t* isolate_instructions_buffer,
                                intptr_t isolate_instructions_size) {
  File* file = File::Open(nullptr, filename, File::kWriteTruncate);
  if (file == nullptr) {
    ErrorExit(kErrorExitCode, "Unable to write snapshot file '%s'\n", filename);
  }

  file->WriteFully(appjit_magic_number.bytes, appjit_magic_number.length);
  WriteInt64(file, isolate_data_size);
  WriteInt64(file, isolate_instructions_size);
  ASSERT(file->Position() ==
         (kAppSnapshotHeaderSize + DartUtils::kMaxMagicNumberSize));

  file->SetPosition(Utils::RoundUp(file->Position(), kAppSnapshotPageSize));
  if (LOG_SECTION_BOUNDARIES) {
    Syslog::PrintErr("%" Px64 ": Isolate Data\n", file->Position());
  }
  if (!file->WriteFully(isolate_data_buffer, isolate_data_size)) {
    ErrorExit(kErrorExitCode, "Unable to write snapshot file '%s'\n", filename);
  }

  if (isolate_instructions_size != 0) {
    file->SetPosition(Utils::RoundUp(file->Position(), kAppSnapshotPageSize));
    if (LOG_SECTION_BOUNDARIES) {
      Syslog::PrintErr("%" Px64 ": Isolate Instructions\n", file->Position());
    }
    if (!file->WriteFully(isolate_instructions_buffer,
                          isolate_instructions_size)) {
      ErrorExit(kErrorExitCode, "Unable to write snapshot file '%s'\n",
                filename);
    }
  }

  file->Flush();
  file->Release();
}

void Snapshot::GenerateKernel(const char* snapshot_filename,
                              const char* script_name,
                              const char* package_config) {
#if !defined(EXCLUDE_CFE_AND_KERNEL_PLATFORM) && !defined(TESTING)
  ASSERT(Dart_CurrentIsolate() == nullptr);

  uint8_t* kernel_buffer = nullptr;
  intptr_t kernel_buffer_size = 0;
  dfe.ReadScript(script_name, nullptr, &kernel_buffer, &kernel_buffer_size);
  if (kernel_buffer != nullptr) {
    WriteSnapshotFile(snapshot_filename, kernel_buffer, kernel_buffer_size);
    free(kernel_buffer);
  } else {
    Dart_KernelCompilationResult result =
        dfe.CompileScript(script_name, /*incremental*/ false, package_config,
                          /*snapshot=*/true, /*embedd_sources=*/true);
    if (result.status != Dart_KernelCompilationStatus_Ok) {
      Syslog::PrintErr("%s\n", result.error);
      Platform::Exit(kCompilationErrorExitCode);
    }
    WriteSnapshotFile(snapshot_filename, result.kernel, result.kernel_size);
    free(result.kernel);
  }
#else
  UNREACHABLE();
#endif  // !defined(EXCLUDE_CFE_AND_KERNEL_PLATFORM) && !defined(TESTING)
}

void Snapshot::GenerateAppJIT(const char* snapshot_filename) {
  uint8_t* snapshot_data_buffer = nullptr;
  intptr_t snapshot_data_size = 0;
  uint8_t* snapshot_text_buffer = nullptr;
  intptr_t snapshot_text_size = 0;
#if defined(TARGET_ARCH_IA32)
  // Snapshots with code are not supported on IA32.
  Dart_Handle result =
      Dart_CreateSnapshot(&snapshot_data_buffer, &snapshot_data_size);
#else
  Dart_Handle result = Dart_CreateAppJITSnapshotAsBlobs(
      &snapshot_data_buffer, &snapshot_data_size, &snapshot_text_buffer,
      &snapshot_text_size);
#endif
  if (Dart_IsError(result)) {
    ErrorExit(kErrorExitCode, "%s\n", Dart_GetError(result));
  }
  WriteAppSnapshot(snapshot_filename, snapshot_data_buffer, snapshot_data_size,
                   snapshot_text_buffer, snapshot_text_size);
}

static void StreamingWriteCallback(void* callback_data,
                                   const uint8_t* buffer,
                                   intptr_t size) {
  File* file = reinterpret_cast<File*>(callback_data);
  if (!file->WriteFully(buffer, size)) {
    ErrorExit(kErrorExitCode, "Unable to write snapshot file\n");
  }
}

void Snapshot::GenerateAppAOTAsAssembly(const char* snapshot_filename) {
  File* file = File::Open(nullptr, snapshot_filename, File::kWriteTruncate);
  if (file == nullptr) {
    ErrorExit(kErrorExitCode, "Unable to open file %s for writing snapshot\n",
              snapshot_filename);
  }
  RefCntReleaseScope<File> rs(file);
  Dart_Handle result = Dart_CreateAppAOTSnapshotAsAssembly(
      StreamingWriteCallback, file, /*stripped=*/false,
      /*debug_callback_data=*/nullptr);
  if (Dart_IsError(result)) {
    ErrorExit(kErrorExitCode, "%s\n", Dart_GetError(result));
  }
}

}  // namespace bin
}  // namespace dart
