//===-- DataBufferMemoryMap.cpp ---------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// C Includes
#include <fcntl.h>
#include <sys/stat.h>
#ifdef _WIN32
#include "lldb/Host/windows/windows.h"
#else
#include <sys/mman.h>

#define MAP_EXTRA_HOST_READ_FLAGS 0

#if defined(__APPLE__)
//----------------------------------------------------------------------
// Newer versions of MacOSX have a flag that will allow us to read from
// binaries whose code signature is invalid without crashing by using
// the MAP_RESILIENT_CODESIGN flag. Also if a file from removable media
// is mapped we can avoid crashing and return zeroes to any pages we try
// to read if the media becomes unavailable by using the
// MAP_RESILIENT_MEDIA flag.
//----------------------------------------------------------------------
#if defined(MAP_RESILIENT_CODESIGN)
#undef MAP_EXTRA_HOST_READ_FLAGS
#if defined(MAP_RESILIENT_MEDIA)
#define MAP_EXTRA_HOST_READ_FLAGS MAP_RESILIENT_CODESIGN | MAP_RESILIENT_MEDIA
#else
#define MAP_EXTRA_HOST_READ_FLAGS MAP_RESILIENT_CODESIGN
#endif
#endif // #if defined(MAP_RESILIENT_CODESIGN)
#endif // #if defined (__APPLE__)

#endif // #else #ifdef _WIN32
// C++ Includes
#include <cerrno>
#include <climits>

// Other libraries and framework includes
#include "llvm/Support/MathExtras.h"

// Project includes
#include "lldb/Core/DataBufferMemoryMap.h"
#include "lldb/Core/Error.h"
#include "lldb/Core/Log.h"
#include "lldb/Host/File.h"
#include "lldb/Host/FileSpec.h"
#include "lldb/Host/HostInfo.h"

using namespace lldb;
using namespace lldb_private;

//----------------------------------------------------------------------
// Default Constructor
//----------------------------------------------------------------------
DataBufferMemoryMap::DataBufferMemoryMap()
    : m_mmap_addr(nullptr), m_mmap_size(0), m_data(nullptr), m_size(0) {}

//----------------------------------------------------------------------
// Virtual destructor since this class inherits from a pure virtual
// base class.
//----------------------------------------------------------------------
DataBufferMemoryMap::~DataBufferMemoryMap() { Clear(); }

//----------------------------------------------------------------------
// Return a pointer to the bytes owned by this object, or nullptr if
// the object contains no bytes.
//----------------------------------------------------------------------
uint8_t *DataBufferMemoryMap::GetBytes() { return m_data; }

//----------------------------------------------------------------------
// Return a const pointer to the bytes owned by this object, or nullptr
// if the object contains no bytes.
//----------------------------------------------------------------------
const uint8_t *DataBufferMemoryMap::GetBytes() const { return m_data; }

//----------------------------------------------------------------------
// Return the number of bytes this object currently contains.
//----------------------------------------------------------------------
uint64_t DataBufferMemoryMap::GetByteSize() const { return m_size; }

//----------------------------------------------------------------------
// Reverts this object to an empty state by unmapping any memory
// that is currently owned.
//----------------------------------------------------------------------
void DataBufferMemoryMap::Clear() {
  if (m_mmap_addr != nullptr) {
    Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_MMAP));
    if (log)
      log->Printf("DataBufferMemoryMap::Clear() m_mmap_addr = %p, m_mmap_size "
                  "= %" PRIu64 "",
                  (void *)m_mmap_addr, (uint64_t)m_mmap_size);
#ifdef _WIN32
    UnmapViewOfFile(m_mmap_addr);
#else
    ::munmap((void *)m_mmap_addr, m_mmap_size);
#endif
    m_mmap_addr = nullptr;
    m_mmap_size = 0;
    m_data = nullptr;
    m_size = 0;
  }
}

//----------------------------------------------------------------------
// Memory map "length" bytes from "file" starting "offset"
// bytes into the file. If "length" is set to SIZE_MAX, then
// map as many bytes as possible.
//
// Returns the number of bytes mapped starting from the requested
// offset.
//----------------------------------------------------------------------
size_t DataBufferMemoryMap::MemoryMapFromFileSpec(const FileSpec *filespec,
                                                  lldb::offset_t offset,
                                                  size_t length,
                                                  bool writeable) {
  if (filespec != nullptr) {
    Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_MMAP));
    if (log) {
      log->Printf("DataBufferMemoryMap::MemoryMapFromFileSpec(file=\"%s\", "
                  "offset=0x%" PRIx64 ", length=0x%" PRIx64 ", writeable=%i",
                  filespec->GetPath().c_str(), offset, (uint64_t)length,
                  writeable);
    }
    char path[PATH_MAX];
    if (filespec->GetPath(path, sizeof(path))) {
      uint32_t options = File::eOpenOptionRead;
      if (writeable)
        options |= File::eOpenOptionWrite;

      File file;
      Error error(file.Open(path, options));
      if (error.Success()) {
        const bool fd_is_file = true;
        return MemoryMapFromFileDescriptor(file.GetDescriptor(), offset, length,
                                           writeable, fd_is_file);
      }
    }
  }
  // We should only get here if there was an error
  Clear();
  return 0;
}

#ifdef _WIN32
static size_t win32memmapalignment = 0;
void LoadWin32MemMapAlignment() {
  SYSTEM_INFO data;
  GetSystemInfo(&data);
  win32memmapalignment = data.dwAllocationGranularity;
}
#endif

//----------------------------------------------------------------------
// The file descriptor FD is assumed to already be opened as read only
// and the STAT structure is assumed to a valid pointer and already
// containing valid data from a call to stat().
//
// Memory map FILE_LENGTH bytes in FILE starting FILE_OFFSET bytes into
// the file. If FILE_LENGTH is set to SIZE_MAX, then map as many bytes
// as possible.
//
// RETURNS
//  Number of bytes mapped starting from the requested offset.
//----------------------------------------------------------------------
size_t DataBufferMemoryMap::MemoryMapFromFileDescriptor(int fd,
                                                        lldb::offset_t offset,
                                                        size_t length,
                                                        bool writeable,
                                                        bool fd_is_file) {
  Clear();
  if (fd >= 0) {
    Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_MMAP |
                                                    LIBLLDB_LOG_VERBOSE));
    if (log) {
      log->Printf("DataBufferMemoryMap::MemoryMapFromFileDescriptor(fd=%i, "
                  "offset=0x%" PRIx64 ", length=0x%" PRIx64
                  ", writeable=%i, fd_is_file=%i)",
                  fd, offset, (uint64_t)length, writeable, fd_is_file);
    }
#ifdef _WIN32
    HANDLE handle = (HANDLE)_get_osfhandle(fd);
    DWORD file_size_low, file_size_high;
    file_size_low = GetFileSize(handle, &file_size_high);
    const lldb::offset_t file_size =
        llvm::Make_64(file_size_high, file_size_low);
    const lldb::offset_t max_bytes_available = file_size - offset;
    const size_t max_bytes_mappable =
        (size_t)std::min<lldb::offset_t>(SIZE_MAX, max_bytes_available);
    if (length == SIZE_MAX || length > max_bytes_mappable) {
      // Cap the length if too much data was requested
      length = max_bytes_mappable;
    }

    if (length > 0) {
      HANDLE fileMapping = CreateFileMapping(
          handle, nullptr, writeable ? PAGE_READWRITE : PAGE_READONLY,
          file_size_high, file_size_low, nullptr);
      if (fileMapping != nullptr) {
        if (win32memmapalignment == 0)
          LoadWin32MemMapAlignment();
        lldb::offset_t realoffset = offset;
        lldb::offset_t delta = 0;
        if (realoffset % win32memmapalignment != 0) {
          realoffset = realoffset / win32memmapalignment * win32memmapalignment;
          delta = offset - realoffset;
        }

        LPVOID data = MapViewOfFile(fileMapping,
                                    writeable ? FILE_MAP_WRITE : FILE_MAP_READ,
                                    0, realoffset, length + delta);
        m_mmap_addr = (uint8_t *)data;
        if (!data) {
          Error error;
          error.SetErrorToErrno();
        } else {
          m_data = m_mmap_addr + delta;
          m_size = length;
        }
        CloseHandle(fileMapping);
      }
    }
#else
    struct stat stat;
    if (::fstat(fd, &stat) == 0) {
      if (S_ISREG(stat.st_mode) &&
          (stat.st_size > static_cast<off_t>(offset))) {
        const size_t max_bytes_available = stat.st_size - offset;
        if (length == SIZE_MAX) {
          length = max_bytes_available;
        } else if (length > max_bytes_available) {
          // Cap the length if too much data was requested
          length = max_bytes_available;
        }

        if (length > 0) {
          int prot = PROT_READ;
          int flags = MAP_PRIVATE;
          if (writeable)
            prot |= PROT_WRITE;
          else
            flags |= MAP_EXTRA_HOST_READ_FLAGS;

          if (fd_is_file)
            flags |= MAP_FILE;

          m_mmap_addr =
              (uint8_t *)::mmap(nullptr, length, prot, flags, fd, offset);
          Error error;

          if (m_mmap_addr == (void *)-1) {
            error.SetErrorToErrno();
            if (error.GetError() == EINVAL) {
              // We may still have a shot at memory mapping if we align things
              // correctly
              size_t page_offset = offset % HostInfo::GetPageSize();
              if (page_offset != 0) {
                m_mmap_addr =
                    (uint8_t *)::mmap(nullptr, length + page_offset, prot,
                                      flags, fd, offset - page_offset);
                if (m_mmap_addr == (void *)-1) {
                  // Failed to map file
                  m_mmap_addr = nullptr;
                } else if (m_mmap_addr != nullptr) {
                  // We recovered and were able to memory map
                  // after we aligned things to page boundaries

                  // Save the actual mmap'ed size
                  m_mmap_size = length + page_offset;
                  // Our data is at an offset into the mapped data
                  m_data = m_mmap_addr + page_offset;
                  // Our pretend size is the size that was requested
                  m_size = length;
                }
              }
            }
            if (error.GetError() == ENOMEM) {
              error.SetErrorStringWithFormat("could not allocate %" PRId64
                                             " bytes of memory to mmap in file",
                                             (uint64_t)length);
            }
          } else {
            // We were able to map the requested data in one chunk
            // where our mmap and actual data are the same.
            m_mmap_size = length;
            m_data = m_mmap_addr;
            m_size = length;
          }

          if (log) {
            log->Printf(
                "DataBufferMemoryMap::MemoryMapFromFileSpec() m_mmap_addr = "
                "%p, m_mmap_size = %" PRIu64 ", error = %s",
                (void *)m_mmap_addr, (uint64_t)m_mmap_size, error.AsCString());
          }
        }
      }
    }
#endif
  }
  return GetByteSize();
}
