// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "mojo/edk/embedder/platform_shared_buffer.h"

#include <stddef.h>

#include <utility>

#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/shared_memory.h"
#include "base/process/process_handle.h"
#include "base/sys_info.h"
#include "mojo/edk/embedder/platform_handle_utils.h"

#if defined(OS_NACL)
// For getpagesize() on NaCl.
#include <unistd.h>
#endif

namespace mojo {
namespace edk {

namespace {

// Takes ownership of |memory_handle|.
ScopedPlatformHandle SharedMemoryToPlatformHandle(
    base::SharedMemoryHandle memory_handle) {
#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS))
  return ScopedPlatformHandle(PlatformHandle(memory_handle.fd));
#elif defined(OS_WIN)
  return ScopedPlatformHandle(PlatformHandle(memory_handle.GetHandle()));
#else
  return ScopedPlatformHandle(PlatformHandle(memory_handle.GetMemoryObject()));
#endif
}

}  // namespace

// static
PlatformSharedBuffer* PlatformSharedBuffer::Create(size_t num_bytes) {
  DCHECK_GT(num_bytes, 0u);

  PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, false);
  if (!rv->Init()) {
    // We can't just delete it directly, due to the "in destructor" (debug)
    // check.
    scoped_refptr<PlatformSharedBuffer> deleter(rv);
    return nullptr;
  }

  return rv;
}

// static
PlatformSharedBuffer* PlatformSharedBuffer::CreateFromPlatformHandle(
    size_t num_bytes,
    bool read_only,
    ScopedPlatformHandle platform_handle) {
  DCHECK_GT(num_bytes, 0u);

  PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, read_only);
  if (!rv->InitFromPlatformHandle(std::move(platform_handle))) {
    // We can't just delete it directly, due to the "in destructor" (debug)
    // check.
    scoped_refptr<PlatformSharedBuffer> deleter(rv);
    return nullptr;
  }

  return rv;
}

// static
PlatformSharedBuffer* PlatformSharedBuffer::CreateFromPlatformHandlePair(
    size_t num_bytes,
    ScopedPlatformHandle rw_platform_handle,
    ScopedPlatformHandle ro_platform_handle) {
  DCHECK_GT(num_bytes, 0u);
  DCHECK(rw_platform_handle.is_valid());
  DCHECK(ro_platform_handle.is_valid());

  PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, false);
  if (!rv->InitFromPlatformHandlePair(std::move(rw_platform_handle),
                                      std::move(ro_platform_handle))) {
    // We can't just delete it directly, due to the "in destructor" (debug)
    // check.
    scoped_refptr<PlatformSharedBuffer> deleter(rv);
    return nullptr;
  }

  return rv;
}

// static
PlatformSharedBuffer* PlatformSharedBuffer::CreateFromSharedMemoryHandle(
    size_t num_bytes,
    bool read_only,
    base::SharedMemoryHandle handle) {
  DCHECK_GT(num_bytes, 0u);

  PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, read_only);
  rv->InitFromSharedMemoryHandle(handle);

  return rv;
}

size_t PlatformSharedBuffer::GetNumBytes() const {
  return num_bytes_;
}

bool PlatformSharedBuffer::IsReadOnly() const {
  return read_only_;
}

std::unique_ptr<PlatformSharedBufferMapping> PlatformSharedBuffer::Map(
    size_t offset,
    size_t length) {
  if (!IsValidMap(offset, length))
    return nullptr;

  return MapNoCheck(offset, length);
}

bool PlatformSharedBuffer::IsValidMap(size_t offset, size_t length) {
  if (offset > num_bytes_ || length == 0)
    return false;

  // Note: This is an overflow-safe check of |offset + length > num_bytes_|
  // (that |num_bytes >= offset| is verified above).
  if (length > num_bytes_ - offset)
    return false;

  return true;
}

std::unique_ptr<PlatformSharedBufferMapping> PlatformSharedBuffer::MapNoCheck(
    size_t offset,
    size_t length) {
  DCHECK(IsValidMap(offset, length));
  DCHECK(shared_memory_);
  base::SharedMemoryHandle handle;
  {
    base::AutoLock locker(lock_);
    handle = base::SharedMemory::DuplicateHandle(shared_memory_->handle());
  }
  if (handle == base::SharedMemory::NULLHandle())
    return nullptr;

  std::unique_ptr<PlatformSharedBufferMapping> mapping(
      new PlatformSharedBufferMapping(handle, read_only_, offset, length));
  if (mapping->Map())
    return base::WrapUnique(mapping.release());

  return nullptr;
}

ScopedPlatformHandle PlatformSharedBuffer::DuplicatePlatformHandle() {
  DCHECK(shared_memory_);
  base::SharedMemoryHandle handle;
  {
    base::AutoLock locker(lock_);
    handle = base::SharedMemory::DuplicateHandle(shared_memory_->handle());
  }
  if (handle == base::SharedMemory::NULLHandle())
    return ScopedPlatformHandle();

  return SharedMemoryToPlatformHandle(handle);
}

ScopedPlatformHandle PlatformSharedBuffer::PassPlatformHandle() {
  DCHECK(HasOneRef());

  // The only way to pass a handle from base::SharedMemory is to duplicate it
  // and close the original.
  ScopedPlatformHandle handle = DuplicatePlatformHandle();

  base::AutoLock locker(lock_);
  shared_memory_->Close();
  return handle;
}

base::SharedMemoryHandle PlatformSharedBuffer::DuplicateSharedMemoryHandle() {
  DCHECK(shared_memory_);

  base::AutoLock locker(lock_);
  return base::SharedMemory::DuplicateHandle(shared_memory_->handle());
}

PlatformSharedBuffer* PlatformSharedBuffer::CreateReadOnlyDuplicate() {
  DCHECK(shared_memory_);

  if (ro_shared_memory_) {
    base::AutoLock locker(lock_);
    base::SharedMemoryHandle handle;
    handle = base::SharedMemory::DuplicateHandle(ro_shared_memory_->handle());
    if (handle == base::SharedMemory::NULLHandle())
      return nullptr;
    return CreateFromSharedMemoryHandle(num_bytes_, true, handle);
  }

  base::SharedMemoryHandle handle;
  bool success;
  {
    base::AutoLock locker(lock_);
    success = shared_memory_->ShareReadOnlyToProcess(
        base::GetCurrentProcessHandle(), &handle);
  }
  if (!success || handle == base::SharedMemory::NULLHandle())
      return nullptr;

  return CreateFromSharedMemoryHandle(num_bytes_, true, handle);
}

PlatformSharedBuffer::PlatformSharedBuffer(size_t num_bytes, bool read_only)
    : num_bytes_(num_bytes), read_only_(read_only) {}

PlatformSharedBuffer::~PlatformSharedBuffer() {}

bool PlatformSharedBuffer::Init() {
  DCHECK(!shared_memory_);
  DCHECK(!read_only_);

  base::SharedMemoryCreateOptions options;
  options.size = num_bytes_;
  // By default, we can share as read-only.
  options.share_read_only = true;

  shared_memory_.reset(new base::SharedMemory);
  return shared_memory_->Create(options);
}

bool PlatformSharedBuffer::InitFromPlatformHandle(
    ScopedPlatformHandle platform_handle) {
  DCHECK(!shared_memory_);

#if defined(OS_WIN)
  base::SharedMemoryHandle handle(platform_handle.release().handle,
                                  base::GetCurrentProcId());
#elif defined(OS_MACOSX) && !defined(OS_IOS)
  base::SharedMemoryHandle handle;
  handle = base::SharedMemoryHandle(platform_handle.release().port, num_bytes_,
                                    base::GetCurrentProcId());
#else
  base::SharedMemoryHandle handle(platform_handle.release().handle, false);
#endif

  shared_memory_.reset(new base::SharedMemory(handle, read_only_));
  return true;
}

bool PlatformSharedBuffer::InitFromPlatformHandlePair(
    ScopedPlatformHandle rw_platform_handle,
    ScopedPlatformHandle ro_platform_handle) {
#if defined(OS_MACOSX)
  NOTREACHED();
  return false;
#else  // defined(OS_MACOSX)

#if defined(OS_WIN)
  base::SharedMemoryHandle handle(rw_platform_handle.release().handle,
                                  base::GetCurrentProcId());
  base::SharedMemoryHandle ro_handle(ro_platform_handle.release().handle,
                                     base::GetCurrentProcId());
#else  // defined(OS_WIN)
  base::SharedMemoryHandle handle(rw_platform_handle.release().handle, false);
  base::SharedMemoryHandle ro_handle(ro_platform_handle.release().handle,
                                     false);
#endif  // defined(OS_WIN)

  DCHECK(!shared_memory_);
  shared_memory_.reset(new base::SharedMemory(handle, false));
  ro_shared_memory_.reset(new base::SharedMemory(ro_handle, true));
  return true;

#endif  // defined(OS_MACOSX)
}

void PlatformSharedBuffer::InitFromSharedMemoryHandle(
    base::SharedMemoryHandle handle) {
  DCHECK(!shared_memory_);

  shared_memory_.reset(new base::SharedMemory(handle, read_only_));
}

PlatformSharedBufferMapping::~PlatformSharedBufferMapping() {
  Unmap();
}

void* PlatformSharedBufferMapping::GetBase() const {
  return base_;
}

size_t PlatformSharedBufferMapping::GetLength() const {
  return length_;
}

bool PlatformSharedBufferMapping::Map() {
  // Mojo shared buffers can be mapped at any offset. However,
  // base::SharedMemory must be mapped at a page boundary. So calculate what the
  // nearest whole page offset is, and build a mapping that's offset from that.
#if defined(OS_NACL)
  // base::SysInfo isn't available under NaCl.
  size_t page_size = getpagesize();
#else
  size_t page_size = base::SysInfo::VMAllocationGranularity();
#endif
  size_t offset_rounding = offset_ % page_size;
  size_t real_offset = offset_ - offset_rounding;
  size_t real_length = length_ + offset_rounding;

  if (!shared_memory_.MapAt(static_cast<off_t>(real_offset), real_length))
    return false;

  base_ = static_cast<char*>(shared_memory_.memory()) + offset_rounding;
  return true;
}

void PlatformSharedBufferMapping::Unmap() {
  shared_memory_.Unmap();
}

}  // namespace edk
}  // namespace mojo