blob: dae892275fb7e20ace78bb86d3f54fbb739ddc5f [file] [log] [blame]
// 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.
// This file implements the factory functions declared in
// //mojo/edk/platform/simple_platform_shared_buffer.h.
#include "mojo/edk/platform/simple_platform_shared_buffer.h"
#include <stdint.h>
#include <stdio.h> // For |fileno()|.
#include <sys/mman.h> // For |mmap()|/|munmap()|.
#include <sys/stat.h>
#include <sys/types.h> // For |off_t|.
#include <unistd.h>
#include <limits>
#include <utility>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "mojo/edk/util/scoped_file.h"
#include "mojo/public/cpp/system/macros.h"
#if defined(OS_ANDROID)
#include "third_party/ashmem/ashmem.h"
#endif // defined(OS_ANDROID)
using mojo::util::RefPtr;
// We assume that |size_t| and |off_t| (type for |ftruncate()|) fits in a
// |uint64_t|.
static_assert(sizeof(size_t) <= sizeof(uint64_t), "size_t too big");
static_assert(sizeof(off_t) <= sizeof(uint64_t), "off_t too big");
namespace mojo {
namespace platform {
namespace {
// SimplePlatformSharedBufferMapping -------------------------------------------
// An implementation of |PlatformSharedBufferMapping|, produced by
// |SimplePlatformSharedBuffer| (declared further below).
class SimplePlatformSharedBufferMapping final
: public PlatformSharedBufferMapping {
public:
~SimplePlatformSharedBufferMapping() override { Unmap(); }
void* GetBase() const override { return base_; }
size_t GetLength() const override { return length_; }
private:
friend class SimplePlatformSharedBuffer;
SimplePlatformSharedBufferMapping(void* base,
size_t length,
void* real_base,
size_t real_length)
: base_(base),
length_(length),
real_base_(real_base),
real_length_(real_length) {}
void Unmap() {
int result = munmap(real_base_, real_length_);
PLOG_IF(ERROR, result != 0) << "munmap";
}
void* const base_;
const size_t length_;
void* const real_base_;
const size_t real_length_;
MOJO_DISALLOW_COPY_AND_ASSIGN(SimplePlatformSharedBufferMapping);
};
// SimplePlatformSharedBuffer --------------------------------------------------
// A simple implementation of |PlatformSharedBuffer|.
class SimplePlatformSharedBuffer final : public PlatformSharedBuffer {
public:
explicit SimplePlatformSharedBuffer(size_t num_bytes)
: num_bytes_(num_bytes) {}
// This is called by |CreateSimplePlatformSharedBuffer()| before this object
// is given to anyone.
bool Init();
// This is like |Init()|, but for
// |CreateSimplePlatformSharedBufferFromPlatformHandle()|. (Note: It should
// verify that |platform_handle| is an appropriate handle for the claimed
// |num_bytes_|.)
bool InitFromPlatformHandle(ScopedPlatformHandle platform_handle);
// |PlatformSharedBuffer| implementation:
size_t GetNumBytes() const override;
std::unique_ptr<PlatformSharedBufferMapping> Map(size_t offset,
size_t length) override;
bool IsValidMap(size_t offset, size_t length) override;
std::unique_ptr<PlatformSharedBufferMapping> MapNoCheck(
size_t offset,
size_t length) override;
ScopedPlatformHandle DuplicatePlatformHandle() override;
ScopedPlatformHandle PassPlatformHandle() override;
private:
~SimplePlatformSharedBuffer() override {}
const size_t num_bytes_;
// This is set in |Init()|/|InitFromPlatformHandle()| and never modified
// (except by |PassPlatformHandle()|; see the comments above its declaration),
// hence does not need to be protected by a lock.
ScopedPlatformHandle handle_;
MOJO_DISALLOW_COPY_AND_ASSIGN(SimplePlatformSharedBuffer);
};
bool SimplePlatformSharedBuffer::Init() {
DCHECK(!handle_.is_valid());
if (static_cast<uint64_t>(num_bytes_) >
static_cast<uint64_t>(std::numeric_limits<off_t>::max())) {
return false;
}
ScopedPlatformHandle handle;
// Use ashmem on Android.
#if defined(OS_ANDROID)
handle.reset(PlatformHandle(ashmem_create_region(nullptr, num_bytes_)));
if (!handle.is_valid()) {
DPLOG(ERROR) << "ashmem_create_region()";
return false;
}
if (ashmem_set_prot_region(handle.get().fd, PROT_READ | PROT_WRITE) < 0) {
DPLOG(ERROR) << "ashmem_set_prot_region()";
return false;
}
#else
base::ThreadRestrictions::ScopedAllowIO allow_io;
// TODO(vtl): This is stupid. The implementation of
// |CreateAndOpenTemporaryFileInDir()| starts with an FD, |fdopen()|s to get a
// |FILE*|, and then we have to |dup(fileno(fp))| to get back to an FD that we
// can own. (base/memory/shared_memory_posix.cc does this too, with more
// |fstat()|s thrown in for good measure.)
base::FilePath shared_buffer_dir;
if (!base::GetShmemTempDir(false, &shared_buffer_dir)) {
LOG(ERROR) << "Failed to get temporary directory for shared memory";
return false;
}
base::FilePath shared_buffer_file;
util::ScopedFILE fp(base::CreateAndOpenTemporaryFileInDir(
shared_buffer_dir, &shared_buffer_file));
if (!fp) {
LOG(ERROR) << "Failed to create/open temporary file for shared memory";
return false;
}
// Note: |unlink()| is not interruptible.
if (unlink(shared_buffer_file.value().c_str()) != 0) {
PLOG(WARNING) << "unlink";
// This isn't "fatal" (e.g., someone else may have unlinked the file first),
// so we may as well continue.
}
// Note: |dup()| is not interruptible (but |dup2()|/|dup3()| are).
handle.reset(PlatformHandle(dup(fileno(fp.get()))));
if (!handle.is_valid()) {
PLOG(ERROR) << "dup";
return false;
}
if (HANDLE_EINTR(
ftruncate(handle.get().fd, static_cast<off_t>(num_bytes_))) != 0) {
PLOG(ERROR) << "ftruncate";
return false;
}
#endif // defined(OS_ANDROID)
handle_ = std::move(handle);
return true;
}
bool SimplePlatformSharedBuffer::InitFromPlatformHandle(
ScopedPlatformHandle platform_handle) {
DCHECK(!handle_.is_valid());
if (static_cast<uint64_t>(num_bytes_) >
static_cast<uint64_t>(std::numeric_limits<off_t>::max())) {
return false;
}
// Use ashmem on Android.
#if defined(OS_ANDROID)
int size = ashmem_get_size_region(platform_handle.get().fd);
if (size < 0) {
DPLOG(ERROR) << "ashmem_get_size_region()";
return false;
}
if (static_cast<size_t>(size) != num_bytes_) {
LOG(ERROR) << "Shared memory region has the wrong size";
return false;
}
#else
struct stat sb = {};
// Note: |fstat()| isn't interruptible.
if (fstat(platform_handle.get().fd, &sb) != 0) {
PLOG(ERROR) << "fstat";
return false;
}
if (!S_ISREG(sb.st_mode)) {
LOG(ERROR) << "Platform handle not to a regular file";
return false;
}
if (sb.st_size != static_cast<off_t>(num_bytes_)) {
LOG(ERROR) << "Shared memory file has the wrong size";
return false;
}
// TODO(vtl): More checks?
#endif // defined(OS_ANDROID)
handle_ = platform_handle.Pass();
return true;
}
size_t SimplePlatformSharedBuffer::GetNumBytes() const {
return num_bytes_;
}
std::unique_ptr<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::Map(
size_t offset,
size_t length) {
if (!IsValidMap(offset, length))
return nullptr;
return MapNoCheck(offset, length);
}
bool SimplePlatformSharedBuffer::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>
SimplePlatformSharedBuffer::MapNoCheck(size_t offset, size_t length) {
DCHECK(IsValidMap(offset, length));
long page_size = sysconf(_SC_PAGESIZE);
// This is a Debug-only check, since (according to POSIX), the only possible
// error is EINVAL (if the argument is unrecognized).
DPCHECK(page_size != -1);
size_t offset_rounding = offset % static_cast<size_t>(page_size);
size_t real_offset = offset - offset_rounding;
size_t real_length = length + offset_rounding;
// This should hold (since we checked |num_bytes| versus the maximum value of
// |off_t| on creation, but it never hurts to be paranoid.
DCHECK_LE(static_cast<uint64_t>(real_offset),
static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
void* real_base =
mmap(nullptr, real_length, PROT_READ | PROT_WRITE, MAP_SHARED,
handle_.get().fd, static_cast<off_t>(real_offset));
// |mmap()| should return |MAP_FAILED| (a.k.a. -1) on error. But it shouldn't
// return null either.
if (real_base == MAP_FAILED || !real_base) {
PLOG(ERROR) << "mmap";
return nullptr;
}
void* base = static_cast<char*>(real_base) + offset_rounding;
// Note: We can't use |MakeUnique| here, since it's not a friend of
// |SimplePlatformSharedBufferMapping| (only we are).
return std::unique_ptr<SimplePlatformSharedBufferMapping>(
new SimplePlatformSharedBufferMapping(base, length, real_base,
real_length));
}
ScopedPlatformHandle SimplePlatformSharedBuffer::DuplicatePlatformHandle() {
return handle_.Duplicate();
}
ScopedPlatformHandle SimplePlatformSharedBuffer::PassPlatformHandle() {
DCHECK(HasOneRef());
return std::move(handle_);
}
} // namespace
// Public factory functions ----------------------------------------------------
util::RefPtr<PlatformSharedBuffer> CreateSimplePlatformSharedBuffer(
size_t num_bytes) {
DCHECK_GT(num_bytes, 0u);
RefPtr<SimplePlatformSharedBuffer> rv(
AdoptRef(new SimplePlatformSharedBuffer(num_bytes)));
return rv->Init() ? rv : nullptr;
}
RefPtr<PlatformSharedBuffer> CreateSimplePlatformSharedBufferFromPlatformHandle(
size_t num_bytes,
ScopedPlatformHandle platform_handle) {
DCHECK_GT(num_bytes, 0u);
RefPtr<SimplePlatformSharedBuffer> rv(
AdoptRef(new SimplePlatformSharedBuffer(num_bytes)));
return rv->InitFromPlatformHandle(std::move(platform_handle)) ? rv : nullptr;
}
} // namespace platform
} // namespace mojo