summaryrefslogtreecommitdiffstats
path: root/mojo
diff options
context:
space:
mode:
authoramistry <amistry@chromium.org>2016-03-16 17:53:46 -0700
committerCommit bot <commit-bot@chromium.org>2016-03-17 00:54:57 +0000
commitba362cd27ec5b4410bd992daccc88a164988384a (patch)
tree8c4ef53df62869d9e4e79458c605da15af1d9344 /mojo
parent2668bea689fd4a5ce07011903bca095aaf67bb07 (diff)
downloadchromium_src-ba362cd27ec5b4410bd992daccc88a164988384a.zip
chromium_src-ba362cd27ec5b4410bd992daccc88a164988384a.tar.gz
chromium_src-ba362cd27ec5b4410bd992daccc88a164988384a.tar.bz2
[mojo-edk] Add support for transferring mach ports.
This change adds the ability to transfer mach ports over Mojo. Mach ports can either be wrapped using CreatePlatformHandleWrapper() or a Mojo shared buffer can be created using CreateSharedBufferWrapper(). For now, Mojo shared buffers created using MojoCreateSharedBuffer() (or MojoCreateDataPipe()) will still use posix shared memory. BUG=582468 Review URL: https://codereview.chromium.org/1712143002 Cr-Commit-Position: refs/heads/master@{#381615}
Diffstat (limited to 'mojo')
-rw-r--r--mojo/edk/embedder/embedder.cc7
-rw-r--r--mojo/edk/embedder/embedder.h11
-rw-r--r--mojo/edk/embedder/embedder_unittest.cc134
-rw-r--r--mojo/edk/embedder/platform_handle.cc8
-rw-r--r--mojo/edk/embedder/platform_handle.h7
-rw-r--r--mojo/edk/embedder/platform_shared_buffer.cc5
-rw-r--r--mojo/edk/system/BUILD.gn7
-rw-r--r--mojo/edk/system/channel.cc88
-rw-r--r--mojo/edk/system/channel.h24
-rw-r--r--mojo/edk/system/channel_posix.cc38
-rw-r--r--mojo/edk/system/core.cc6
-rw-r--r--mojo/edk/system/core.h7
-rw-r--r--mojo/edk/system/mach_port_relay.cc155
-rw-r--r--mojo/edk/system/mach_port_relay.h94
-rw-r--r--mojo/edk/system/node_channel.cc166
-rw-r--r--mojo/edk/system/node_channel.h37
-rw-r--r--mojo/edk/system/node_controller.cc91
-rw-r--r--mojo/edk/system/node_controller.h21
-rw-r--r--mojo/edk/test/mojo_test_base.cc43
-rw-r--r--mojo/edk/test/multiprocess_test_helper.cc6
-rw-r--r--mojo/edk/test/multiprocess_test_helper.h2
-rw-r--r--mojo/mojo_edk.gyp6
22 files changed, 913 insertions, 50 deletions
diff --git a/mojo/edk/embedder/embedder.cc b/mojo/edk/embedder/embedder.cc
index d471c3a..793b708 100644
--- a/mojo/edk/embedder/embedder.cc
+++ b/mojo/edk/embedder/embedder.cc
@@ -119,6 +119,13 @@ void ShutdownIPCSupport() {
base::Unretained(internal::g_process_delegate)));
}
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+void SetMachPortProvider(base::PortProvider* port_provider) {
+ DCHECK(port_provider);
+ internal::g_core->SetMachPortProvider(port_provider);
+}
+#endif
+
ScopedMessagePipeHandle CreateMessagePipe(
ScopedPlatformHandle platform_handle) {
CHECK(internal::g_process_delegate);
diff --git a/mojo/edk/embedder/embedder.h b/mojo/edk/embedder/embedder.h
index b96d847..3eadc36 100644
--- a/mojo/edk/embedder/embedder.h
+++ b/mojo/edk/embedder/embedder.h
@@ -20,6 +20,10 @@
#include "mojo/edk/system/system_impl_export.h"
#include "mojo/public/cpp/system/message_pipe.h"
+namespace base {
+class PortProvider;
+}
+
namespace mojo {
namespace edk {
@@ -138,6 +142,13 @@ MOJO_SYSTEM_IMPL_EXPORT void InitIPCSupport(
// |OnShutdownComplete()| method is invoked.
MOJO_SYSTEM_IMPL_EXPORT void ShutdownIPCSupport();
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// Set the |base::PortProvider| for this process. Can be called on any thread,
+// but must be set in the root process before any Mach ports can be transferred.
+MOJO_SYSTEM_IMPL_EXPORT void SetMachPortProvider(
+ base::PortProvider* port_provider);
+#endif
+
// Creates a message pipe over an arbitrary platform channel. The other end of
// the channel must also be passed to this function. Either endpoint can be in
// any process.
diff --git a/mojo/edk/embedder/embedder_unittest.cc b/mojo/edk/embedder/embedder_unittest.cc
index b5927b3..c781f42 100644
--- a/mojo/edk/embedder/embedder_unittest.cc
+++ b/mojo/edk/embedder/embedder_unittest.cc
@@ -13,6 +13,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
+#include "base/macros.h"
#include "base/memory/shared_memory.h"
#include "base/message_loop/message_loop.h"
#include "base/synchronization/waitable_event.h"
@@ -376,6 +377,139 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessSharedMemoryClient, EmbedderTest,
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(sb1));
}
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+TEST_F(EmbedderTest, MultiprocessMachSharedMemory) {
+ RUN_CHILD_ON_PIPE(MultiprocessSharedMemoryClient, server_mp)
+ // 1. Create a Mach base::SharedMemory object and create a mojo shared
+ // buffer from it.
+ base::SharedMemoryCreateOptions options;
+ options.size = 123;
+ options.type = base::SharedMemoryHandle::MACH;
+ base::SharedMemory shared_memory;
+ ASSERT_TRUE(shared_memory.Create(options));
+ base::SharedMemoryHandle shm_handle = base::SharedMemory::DuplicateHandle(
+ shared_memory.handle());
+ MojoHandle sb1;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ CreateSharedBufferWrapper(shm_handle, 123, false, &sb1));
+
+ // 2. Map |sb1| and write something into it.
+ char* buffer = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoMapBuffer(sb1, 0, 123, reinterpret_cast<void**>(&buffer), 0));
+ ASSERT_TRUE(buffer);
+ memcpy(buffer, kHelloWorld, sizeof(kHelloWorld));
+
+ // 3. Duplicate |sb1| into |sb2| and pass to |server_mp|.
+ MojoHandle sb2 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(sb1, 0, &sb2));
+ EXPECT_NE(MOJO_HANDLE_INVALID, sb2);
+ WriteMessageWithHandles(server_mp, "hello", &sb2, 1);
+
+ // 4. Read a message from |server_mp|.
+ EXPECT_EQ("bye", ReadMessage(server_mp));
+
+ // 5. Expect that the contents of the shared buffer have changed.
+ EXPECT_EQ(kByeWorld, std::string(buffer));
+
+ // 6. Map the original base::SharedMemory and expect it contains the
+ // expected value.
+ ASSERT_TRUE(shared_memory.Map(123));
+ EXPECT_EQ(kByeWorld,
+ std::string(static_cast<char*>(shared_memory.memory())));
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(sb1));
+ END_CHILD()
+}
+
+const base::SharedMemoryHandle::Type kTestHandleTypes[] = {
+ base::SharedMemoryHandle::MACH,
+ base::SharedMemoryHandle::POSIX,
+ base::SharedMemoryHandle::POSIX,
+ base::SharedMemoryHandle::MACH,
+};
+
+// Test that we can mix file descriptor and mach port handles.
+TEST_F(EmbedderTest, MultiprocessMixMachAndFds) {
+ const size_t kShmSize = 1234;
+ RUN_CHILD_ON_PIPE(MultiprocessMixMachAndFdsClient, server_mp)
+ // 1. Create the base::SharedMemory objects and mojo handles from them.
+ MojoHandle platform_handles[arraysize(kTestHandleTypes)];
+ for (size_t i = 0; i < arraysize(kTestHandleTypes); i++) {
+ const auto type = kTestHandleTypes[i];
+ base::SharedMemoryCreateOptions options;
+ options.size = kShmSize;
+ options.type = type;
+ base::SharedMemory shared_memory;
+ ASSERT_TRUE(shared_memory.Create(options));
+ base::SharedMemoryHandle shm_handle = base::SharedMemory::DuplicateHandle(
+ shared_memory.handle());
+ ScopedPlatformHandle scoped_handle;
+ if (type == base::SharedMemoryHandle::POSIX)
+ scoped_handle.reset(PlatformHandle(shm_handle.GetFileDescriptor().fd));
+ else
+ scoped_handle.reset(PlatformHandle(shm_handle.GetMemoryObject()));
+ ASSERT_EQ(MOJO_RESULT_OK, CreatePlatformHandleWrapper(
+ std::move(scoped_handle), platform_handles + i));
+
+ // Map the shared memory object and write the type into it. 'P' for POSIX,
+ // and 'M' for Mach.
+ ASSERT_TRUE(shared_memory.Map(kShmSize));
+ static_cast<char*>(shared_memory.memory())[0] =
+ type == base::SharedMemoryHandle::POSIX ? 'P' : 'M';
+ }
+
+ // 2. Send all the handles to the child.
+ WriteMessageWithHandles(server_mp, "hello", platform_handles,
+ arraysize(kTestHandleTypes));
+
+ // 3. Read a message from |server_mp|.
+ EXPECT_EQ("bye", ReadMessage(server_mp));
+ END_CHILD()
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessMixMachAndFdsClient, EmbedderTest,
+ client_mp) {
+ const int kNumHandles = 4;
+ const size_t kShmSize = 1234;
+ MojoHandle platform_handles[kNumHandles];
+
+ // 1. Read from |client_mp|, which should have a message containing
+ // |kNumHandles| handles.
+ EXPECT_EQ("hello",
+ ReadMessageWithHandles(client_mp, platform_handles, kNumHandles));
+
+ // 2. Extract each handle, map it, and verify the type.
+ for (int i = 0; i < kNumHandles; i++) {
+ ScopedPlatformHandle scoped_handle;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ PassWrappedPlatformHandle(platform_handles[i], &scoped_handle));
+ base::SharedMemoryHandle shm_handle;
+ char type = 0;
+ if (scoped_handle.get().type == PlatformHandle::Type::POSIX) {
+ shm_handle = base::SharedMemoryHandle(scoped_handle.release().handle,
+ false);
+ type = 'P';
+ } else {
+ shm_handle = base::SharedMemoryHandle(scoped_handle.release().port,
+ kShmSize, base::GetCurrentProcId());
+ type = 'M';
+ }
+
+ // Verify the type order.
+ EXPECT_EQ(kTestHandleTypes[i], shm_handle.GetType());
+
+ base::SharedMemory shared_memory(shm_handle, false);
+ ASSERT_TRUE(shared_memory.Map(kShmSize));
+ EXPECT_EQ(type, static_cast<char*>(shared_memory.memory())[0]);
+ }
+
+ // 3. Say bye!
+ WriteMessage(client_mp, "bye");
+}
+
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
// TODO(vtl): Test immediate write & close.
// TODO(vtl): Test broken-connection cases.
diff --git a/mojo/edk/embedder/platform_handle.cc b/mojo/edk/embedder/platform_handle.cc
index 62dc850..5709b1e 100644
--- a/mojo/edk/embedder/platform_handle.cc
+++ b/mojo/edk/embedder/platform_handle.cc
@@ -29,10 +29,10 @@ void PlatformHandle::CloseIfNecessary() {
handle = -1;
}
#if defined(OS_MACOSX) && !defined(OS_IOS)
- else {
- kern_return_t rv = mach_port_deallocate(mach_task_self(), port);
- DPCHECK(rv == KERN_SUCCESS);
- port = MACH_PORT_NULL;
+ else if (type == Type::MACH) {
+ kern_return_t rv = mach_port_deallocate(mach_task_self(), port);
+ DPCHECK(rv == KERN_SUCCESS);
+ port = MACH_PORT_NULL;
}
#endif // defined(OS_MACOSX) && !defined(OS_IOS)
#elif defined(OS_WIN)
diff --git a/mojo/edk/embedder/platform_handle.h b/mojo/edk/embedder/platform_handle.h
index 1c0c752..3c945c6 100644
--- a/mojo/edk/embedder/platform_handle.h
+++ b/mojo/edk/embedder/platform_handle.h
@@ -30,7 +30,7 @@ struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle {
bool is_valid() const {
#if defined(OS_MACOSX) && !defined(OS_IOS)
- if (type == Type::MACH)
+ if (type == Type::MACH || type == Type::MACH_NAME)
return port != MACH_PORT_NULL;
#endif
return handle != -1;
@@ -40,6 +40,11 @@ struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle {
POSIX,
#if defined(OS_MACOSX) && !defined(OS_IOS)
MACH,
+ // MACH_NAME isn't a real Mach port. But rather the "name" of one that can
+ // be resolved to a real port later. This distinction is needed so that the
+ // "port" doesn't try to be closed if CloseIfNecessary() is called. Having
+ // this also allows us to do checks in other places.
+ MACH_NAME,
#endif
};
Type type = Type::POSIX;
diff --git a/mojo/edk/embedder/platform_shared_buffer.cc b/mojo/edk/embedder/platform_shared_buffer.cc
index 9516634..f3f9b70 100644
--- a/mojo/edk/embedder/platform_shared_buffer.cc
+++ b/mojo/edk/embedder/platform_shared_buffer.cc
@@ -210,11 +210,6 @@ void PlatformSharedBuffer::InitFromSharedMemoryHandle(
base::SharedMemoryHandle handle) {
DCHECK(!shared_memory_);
-#if defined(OS_MACOSX) && !defined(OS_IOS)
- // TODO(crbug.com/582468): Support Mach shared memory.
- CHECK(handle.GetType() == base::SharedMemoryHandle::POSIX);
-#endif
-
// TODO(crbug.com/556587): Support read-only handles.
shared_memory_.reset(new base::SharedMemory(handle, false));
}
diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn
index 1bd597f..5d83bb6 100644
--- a/mojo/edk/system/BUILD.gn
+++ b/mojo/edk/system/BUILD.gn
@@ -105,6 +105,13 @@ component("system") {
# which is uninteresting.
}
+ if (is_mac && !is_ios) {
+ sources += [
+ "mach_port_relay.cc",
+ "mach_port_relay.h",
+ ]
+ }
+
allow_circular_includes_from = [ "//mojo/edk/embedder" ]
}
diff --git a/mojo/edk/system/channel.cc b/mojo/edk/system/channel.cc
index e74b900..67c7398 100644
--- a/mojo/edk/system/channel.cc
+++ b/mojo/edk/system/channel.cc
@@ -8,11 +8,16 @@
#include <algorithm>
#include <limits>
+#include <utility>
#include "base/macros.h"
#include "base/memory/aligned_memory.h"
#include "mojo/edk/embedder/platform_handle.h"
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/mach_logging.h"
+#endif
+
namespace mojo {
namespace edk {
@@ -43,6 +48,12 @@ Channel::Message::Message(size_t payload_size,
#if defined(OS_WIN)
// On Windows we serialize platform handles into the extra header space.
extra_header_size = max_handles_ * sizeof(PlatformHandle);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ // On OSX, some of the platform handles may be mach ports, which are
+ // serialised into the message buffer. Since there could be a mix of fds and
+ // mach ports, we store the mach ports as an <index, port> pair (of uint32_t),
+ // so that the original ordering of handles can be re-created.
+ extra_header_size = max_handles * sizeof(MachPortsEntry);
#endif
// Pad extra header data to be aliged to |kChannelMessageAlignment| bytes.
if (extra_header_size % kChannelMessageAlignment) {
@@ -78,14 +89,19 @@ Channel::Message::Message(size_t payload_size,
static_cast<uint16_t>(sizeof(Header) + extra_header_size);
#endif
-#if defined(OS_WIN)
if (max_handles_ > 0) {
+#if defined(OS_WIN)
handles_ = reinterpret_cast<PlatformHandle*>(mutable_extra_header());
// Initialize all handles to invalid values.
for (size_t i = 0; i < max_handles_; ++i)
handles()[i] = PlatformHandle();
- }
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ mach_ports_ = reinterpret_cast<MachPortsEntry*>(mutable_extra_header());
+ // Initialize all handles to invalid values.
+ for (size_t i = 0; i < max_handles_; ++i)
+ mach_ports_[i] = {0, static_cast<uint32_t>(MACH_PORT_NULL)};
#endif
+ }
}
Channel::Message::~Message() {
@@ -100,9 +116,9 @@ Channel::Message::~Message() {
// static
Channel::MessagePtr Channel::Message::Deserialize(const void* data,
size_t data_num_bytes) {
-#if !defined(OS_WIN)
+#if !defined(OS_WIN) && !(defined(OS_MACOSX) && !defined(OS_IOS))
// We only serialize messages into other messages when performing message
- // relay on Windows.
+ // relay on Windows and OSX.
NOTREACHED();
return nullptr;
#else
@@ -123,16 +139,19 @@ Channel::MessagePtr Channel::Message::Deserialize(const void* data,
}
uint32_t extra_header_size = header->num_header_bytes - sizeof(Header);
+#if defined(OS_WIN)
uint32_t max_handles = extra_header_size / sizeof(PlatformHandle);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ uint32_t max_handles = extra_header_size / sizeof(MachPortsEntry);
+#endif
if (header->num_handles > max_handles) {
- DLOG(ERROR) << "Decoding invalid message:" << header->num_handles << " > "
- << max_handles;
+ DLOG(ERROR) << "Decoding invalid message:" << header->num_handles
+ << " > " << max_handles;
return nullptr;
}
- MessagePtr message(
- new Message(data_num_bytes - header->num_header_bytes, max_handles));
-
+ MessagePtr message(new Message(data_num_bytes - header->num_header_bytes,
+ max_handles));
DCHECK_EQ(message->data_num_bytes(), data_num_bytes);
DCHECK_EQ(message->extra_header_size(), extra_header_size);
DCHECK_EQ(message->header_->num_header_bytes, header->num_header_bytes);
@@ -184,8 +203,10 @@ bool Channel::Message::has_mach_ports() const {
return false;
for (const auto& handle : (*handle_vector_)) {
- if (handle.type == PlatformHandle::Type::MACH)
+ if (handle.type == PlatformHandle::Type::MACH ||
+ handle.type == PlatformHandle::Type::MACH_NAME) {
return true;
+ }
}
return false;
}
@@ -217,6 +238,21 @@ void Channel::Message::SetHandles(ScopedPlatformHandleVectorPtr new_handles) {
std::swap(handle_vector_, new_handles);
#endif // defined(OS_WIN)
#endif // defined(OS_CHROMEOS) || defined(OS_ANDROID)
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ size_t mach_port_index = 0;
+ for (size_t i = 0; i < max_handles_; ++i)
+ mach_ports_[i] = {0, static_cast<uint32_t>(MACH_PORT_NULL)};
+ for (size_t i = 0; i < handle_vector_->size(); i++) {
+ if ((*handle_vector_)[i].type == PlatformHandle::Type::MACH ||
+ (*handle_vector_)[i].type == PlatformHandle::Type::MACH_NAME) {
+ mach_port_t port = (*handle_vector_)[i].port;
+ mach_ports_[mach_port_index].index = i;
+ mach_ports_[mach_port_index].mach_port = port;
+ mach_port_index++;
+ }
+ }
+#endif
}
ScopedPlatformHandleVectorPtr Channel::Message::TakeHandles() {
@@ -227,7 +263,39 @@ ScopedPlatformHandleVectorPtr Channel::Message::TakeHandles() {
new PlatformHandleVector(header_->num_handles));
for (size_t i = 0; i < header_->num_handles; ++i)
std::swap(moved_handles->at(i), handles()[i]);
+ header_->num_handles = 0;
return moved_handles;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ for (size_t i = 0; i < max_handles_; ++i)
+ mach_ports_[i] = {0, static_cast<uint32_t>(MACH_PORT_NULL)};
+ header_->num_handles = 0;
+ return std::move(handle_vector_);
+#else
+ header_->num_handles = 0;
+ return std::move(handle_vector_);
+#endif
+}
+
+ScopedPlatformHandleVectorPtr Channel::Message::TakeHandlesForTransport() {
+#if defined(OS_WIN)
+ // Not necessary on Windows.
+ NOTREACHED();
+ return nullptr;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ if (handle_vector_) {
+ for (auto it = handle_vector_->begin(); it != handle_vector_->end(); ) {
+ if (it->type == PlatformHandle::Type::MACH ||
+ it->type == PlatformHandle::Type::MACH_NAME) {
+ // For Mach port names, we can can just leak them. They're not real
+ // ports anyways. For real ports, they're leaked because this is a child
+ // process and the remote process will take ownership.
+ it = handle_vector_->erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ return std::move(handle_vector_);
#else
return std::move(handle_vector_);
#endif
diff --git a/mojo/edk/system/channel.h b/mojo/edk/system/channel.h
index 54b6412..9932c9c 100644
--- a/mojo/edk/system/channel.h
+++ b/mojo/edk/system/channel.h
@@ -66,6 +66,17 @@ class Channel : public base::RefCountedThreadSafe<Channel> {
char padding[6];
#endif // defined(OS_CHROMEOS) || defined(OS_ANDROID)
};
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ struct MachPortsEntry {
+ uint16_t index;
+ uint32_t mach_port;
+ static_assert(sizeof(mach_port_t) <= sizeof(uint32_t),
+ "mach_port_t must be no larger than uint32_t");
+ };
+ static_assert(sizeof(MachPortsEntry) == 6,
+ "sizeof(MachPortsEntry) must be 6 bytes");
+#endif
#pragma pack(pop)
// Allocates and owns a buffer for message data with enough capacity for
@@ -111,6 +122,11 @@ class Channel : public base::RefCountedThreadSafe<Channel> {
// handles().
void SetHandles(ScopedPlatformHandleVectorPtr new_handles);
ScopedPlatformHandleVectorPtr TakeHandles();
+ // Version of TakeHandles that returns a vector of platform handles suitable
+ // for transfer over an underlying OS mechanism. i.e. file descriptors over
+ // a unix domain socket. Any handle that cannot be transferred this way,
+ // such as Mach ports, will be removed.
+ ScopedPlatformHandleVectorPtr TakeHandlesForTransport();
#if defined(OS_WIN)
// Prepares the handles in this message for use in a different process.
@@ -131,13 +147,17 @@ class Channel : public base::RefCountedThreadSafe<Channel> {
Header* header_;
#if defined(OS_WIN)
- // On Windows, handles are serialized in the data buffer along with the
- // rest of the payload.
+ // On Windows, handles are serialised into the extra header section.
PlatformHandle* handles_ = nullptr;
#else
ScopedPlatformHandleVectorPtr handle_vector_;
#endif
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // On OSX, handles are serialised into the extra header section.
+ MachPortsEntry* mach_ports_ = nullptr;
+#endif
+
DISALLOW_COPY_AND_ASSIGN(Message);
};
diff --git a/mojo/edk/system/channel_posix.cc b/mojo/edk/system/channel_posix.cc
index 2576eac..bc759e0 100644
--- a/mojo/edk/system/channel_posix.cc
+++ b/mojo/edk/system/channel_posix.cc
@@ -36,7 +36,7 @@ class MessageView {
MessageView(Channel::MessagePtr message, size_t offset)
: message_(std::move(message)),
offset_(offset),
- handles_(message_->TakeHandles()) {
+ handles_(message_->TakeHandlesForTransport()) {
DCHECK_GT(message_->data_num_bytes(), offset_);
}
@@ -136,6 +136,41 @@ class ChannelPosix : public Channel,
size_t num_handles,
const void* extra_header,
size_t extra_header_size) override {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // On OSX, we can have mach ports which are located in the extra header
+ // section.
+ using MachPortsEntry = Channel::Message::MachPortsEntry;
+ CHECK(extra_header_size >= num_handles * sizeof(MachPortsEntry));
+ size_t num_mach_ports = 0;
+ const MachPortsEntry* mach_ports =
+ reinterpret_cast<const MachPortsEntry*>(extra_header);
+ for (size_t i = 0; i < num_handles; i++) {
+ if (mach_ports[i].mach_port != MACH_PORT_NULL)
+ num_mach_ports++;
+ }
+ CHECK(num_mach_ports <= num_handles);
+ if (incoming_platform_handles_.size() + num_mach_ports < num_handles)
+ return nullptr;
+
+ ScopedPlatformHandleVectorPtr handles(
+ new PlatformHandleVector(num_handles));
+ for (size_t i = 0, mach_port_index = 0; i < num_handles; ++i) {
+ if (mach_port_index < num_mach_ports &&
+ mach_ports[mach_port_index].index == i) {
+ (*handles)[i] = PlatformHandle(
+ static_cast<mach_port_t>(mach_ports[mach_port_index].mach_port));
+ CHECK((*handles)[i].type == PlatformHandle::Type::MACH);
+ // These are actually just Mach port names until they're resolved from
+ // the remote process.
+ (*handles)[i].type = PlatformHandle::Type::MACH_NAME;
+ mach_port_index++;
+ } else {
+ CHECK(!incoming_platform_handles_.empty());
+ (*handles)[i] = incoming_platform_handles_.front();
+ incoming_platform_handles_.pop_front();
+ }
+ }
+#else
if (incoming_platform_handles_.size() < num_handles)
return nullptr;
@@ -145,6 +180,7 @@ class ChannelPosix : public Channel,
(*handles)[i] = incoming_platform_handles_.front();
incoming_platform_handles_.pop_front();
}
+#endif
return handles;
}
diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc
index d436307..49cb242 100644
--- a/mojo/edk/system/core.cc
+++ b/mojo/edk/system/core.cc
@@ -99,6 +99,12 @@ void Core::InitChild(ScopedPlatformHandle platform_handle) {
GetNodeController()->ConnectToParent(std::move(platform_handle));
}
+void Core::SetMachPortProvider(base::PortProvider* port_provider) {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ GetNodeController()->CreateMachPortRelay(port_provider);
+#endif
+}
+
MojoHandle Core::AddDispatcher(scoped_refptr<Dispatcher> dispatcher) {
base::AutoLock lock(handles_lock_);
return handles_.AddDispatcher(dispatcher);
diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h
index 11aff94..18da0ba 100644
--- a/mojo/edk/system/core.h
+++ b/mojo/edk/system/core.h
@@ -27,6 +27,10 @@
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/system/message_pipe.h"
+namespace base {
+class PortProvider;
+}
+
namespace mojo {
namespace edk {
@@ -67,6 +71,9 @@ class MOJO_SYSTEM_IMPL_EXPORT Core {
// associated with |token|.
ScopedMessagePipeHandle CreateChildMessagePipe(const std::string& token);
+ // Sets the mach port provider for this process.
+ void SetMachPortProvider(base::PortProvider* port_provider);
+
MojoHandle AddDispatcher(scoped_refptr<Dispatcher> dispatcher);
// Adds new dispatchers for non-message-pipe handles received in a message.
diff --git a/mojo/edk/system/mach_port_relay.cc b/mojo/edk/system/mach_port_relay.cc
new file mode 100644
index 0000000..2dd40e5
--- /dev/null
+++ b/mojo/edk/system/mach_port_relay.cc
@@ -0,0 +1,155 @@
+// Copyright 2016 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/system/mach_port_relay.h"
+
+#include <mach/mach.h>
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/mac/mach_port_util.h"
+#include "base/mac/scoped_mach_port.h"
+#include "base/process/process.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+
+namespace mojo {
+namespace edk {
+
+// static
+bool MachPortRelay::ReceivePorts(PlatformHandleVector* handles) {
+ DCHECK(handles);
+
+ for (size_t i = 0; i < handles->size(); i++) {
+ PlatformHandle* handle = handles->data() + i;
+ DCHECK(handle->type != PlatformHandle::Type::MACH);
+ if (handle->type != PlatformHandle::Type::MACH_NAME)
+ continue;
+
+ base::mac::ScopedMachReceiveRight message_port(handle->port);
+ base::mac::ScopedMachSendRight received_port(
+ base::ReceiveMachPort(message_port.get()));
+ if (received_port.get() == MACH_PORT_NULL) {
+ handle->port = MACH_PORT_NULL;
+ LOG(ERROR) << "Error receiving mach port";
+ return false;
+ }
+
+ handle->port = received_port.release();
+ handle->type = PlatformHandle::Type::MACH;
+ }
+
+ return true;
+}
+
+MachPortRelay::MachPortRelay(base::PortProvider* port_provider)
+ : port_provider_(port_provider) {
+ DCHECK(port_provider);
+ port_provider_->AddObserver(this);
+}
+
+MachPortRelay::~MachPortRelay() {
+ port_provider_->RemoveObserver(this);
+}
+
+bool MachPortRelay::SendPortsToProcess(Channel::Message* message,
+ base::ProcessHandle process) {
+ DCHECK(message);
+ mach_port_t task_port = port_provider_->TaskForPid(process);
+ if (task_port == MACH_PORT_NULL)
+ return false;
+
+ size_t num_sent = 0;
+ bool error = false;
+ ScopedPlatformHandleVectorPtr handles = message->TakeHandles();
+ // Message should have handles, otherwise there's no point in calling this
+ // function.
+ DCHECK(handles);
+ for (size_t i = 0; i < handles->size(); i++) {
+ PlatformHandle* handle = &(*handles)[i];
+ DCHECK(handle->type != PlatformHandle::Type::MACH_NAME);
+ if (handle->type != PlatformHandle::Type::MACH)
+ continue;
+
+ mach_port_name_t intermediate_port;
+ DCHECK(handle->port != MACH_PORT_NULL);
+ intermediate_port = base::CreateIntermediateMachPort(
+ task_port, base::mac::ScopedMachSendRight(handle->port), nullptr);
+ if (intermediate_port == MACH_PORT_NULL) {
+ handle->port = MACH_PORT_NULL;
+ error = true;
+ break;
+ }
+ handle->port = intermediate_port;
+ handle->type = PlatformHandle::Type::MACH_NAME;
+ num_sent++;
+ }
+ DCHECK(error || num_sent);
+ message->SetHandles(std::move(handles));
+
+ return !error;
+}
+
+bool MachPortRelay::ExtractPortRights(Channel::Message* message,
+ base::ProcessHandle process) {
+ DCHECK(message);
+
+ mach_port_t task_port = port_provider_->TaskForPid(process);
+ if (task_port == MACH_PORT_NULL)
+ return false;
+
+ size_t num_received = 0;
+ bool error = false;
+ ScopedPlatformHandleVectorPtr handles = message->TakeHandles();
+ // Message should have handles, otherwise there's no point in calling this
+ // function.
+ DCHECK(handles);
+ for (size_t i = 0; i < handles->size(); i++) {
+ PlatformHandle* handle = handles->data() + i;
+ DCHECK(handle->type != PlatformHandle::Type::MACH);
+ if (handle->type != PlatformHandle::Type::MACH_NAME)
+ continue;
+
+ mach_port_t extracted_right = MACH_PORT_NULL;
+ mach_msg_type_name_t extracted_right_type;
+ kern_return_t kr =
+ mach_port_extract_right(task_port, handle->port,
+ MACH_MSG_TYPE_MOVE_SEND,
+ &extracted_right, &extracted_right_type);
+ if (kr != KERN_SUCCESS) {
+ error = true;
+ break;
+ }
+
+ DCHECK_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND),
+ extracted_right_type);
+ handle->port = extracted_right;
+ handle->type = PlatformHandle::Type::MACH;
+ num_received++;
+ }
+ DCHECK(error || num_received);
+ message->SetHandles(std::move(handles));
+
+ return !error;
+}
+
+void MachPortRelay::AddObserver(Observer* observer) {
+ base::AutoLock locker(observers_lock_);
+ bool inserted = observers_.insert(observer).second;
+ DCHECK(inserted);
+}
+
+void MachPortRelay::RemoveObserver(Observer* observer) {
+ base::AutoLock locker(observers_lock_);
+ observers_.erase(observer);
+}
+
+void MachPortRelay::OnReceivedTaskPort(base::ProcessHandle process) {
+ base::AutoLock locker(observers_lock_);
+ for (const auto observer : observers_)
+ observer->OnProcessReady(process);
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/mach_port_relay.h b/mojo/edk/system/mach_port_relay.h
new file mode 100644
index 0000000..87bc56c
--- /dev/null
+++ b/mojo/edk/system/mach_port_relay.h
@@ -0,0 +1,94 @@
+// Copyright 2016 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.
+
+#ifndef MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_
+#define MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/process/port_provider_mac.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/channel.h"
+
+namespace mojo {
+namespace edk {
+
+// The MachPortRelay is used by a privileged process, usually the root process,
+// to manipulate Mach ports in a child process. Ports can be added to and
+// extracted from a child process that has registered itself with the
+// |base::PortProvider| used by this class.
+class MachPortRelay : public base::PortProvider::Observer {
+ public:
+ class Observer {
+ public:
+ // Called by the MachPortRelay to notify observers that a new process is
+ // ready for Mach ports to be sent/received. There are no guarantees about
+ // the thread this is called on, including the presence of a MessageLoop.
+ // Implementations must not call AddObserver() or RemoveObserver() during
+ // this function, as doing so will deadlock.
+ virtual void OnProcessReady(base::ProcessHandle process) = 0;
+ };
+
+ // Used by a child process to receive Mach ports from a sender (privileged)
+ // process. Each Mach port in |handles| is interpreted as an intermediate Mach
+ // port. It replaces each Mach port with the final Mach port received from the
+ // intermediate port. This method takes ownership of the intermediate Mach
+ // port and gives ownership of the final Mach port to the caller. Any handles
+ // that are not Mach ports will remain unchanged, and the number and ordering
+ // of handles is preserved.
+ // Returns |false| on failure and there is no guarantee about whether a Mach
+ // port is intermediate or final.
+ //
+ // See SendPortsToProcess() for the definition of intermediate and final Mach
+ // ports.
+ static bool ReceivePorts(PlatformHandleVector* handles);
+
+ explicit MachPortRelay(base::PortProvider* port_provider);
+ ~MachPortRelay() override;
+
+ // Sends the Mach ports attached to |message| to |process|.
+ // For each Mach port attached to |message|, a new Mach port, the intermediate
+ // port, is created in |process|. The message's Mach port is then sent over
+ // this intermediate port and the message is modified to refer to the name of
+ // the intermediate port. The Mach port received over the intermediate port in
+ // the child is referred to as the final Mach port.
+ // Returns |false| on failure and |message| may contain a mix of actual Mach
+ // ports and names.
+ bool SendPortsToProcess(Channel::Message* message,
+ base::ProcessHandle process);
+
+ // Extracts the Mach ports attached to |message| from |process|.
+ // Any Mach ports attached to |message| are names and not actual Mach ports
+ // that are valid in this process. For each of those Mach port names, a send
+ // right is extracted from |process| and the port name is replaced with the
+ // send right.
+ // Returns |false| on failure and |message| may contain a mix of actual Mach
+ // ports and names.
+ bool ExtractPortRights(Channel::Message* message,
+ base::ProcessHandle process);
+
+ // Observer interface.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ base::PortProvider* port_provider() const { return port_provider_; }
+
+ private:
+ // base::PortProvider::Observer implementation.
+ void OnReceivedTaskPort(base::ProcessHandle process) override;
+
+ base::PortProvider* const port_provider_;
+
+ base::Lock observers_lock_;
+ std::set<Observer*> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(MachPortRelay);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_
diff --git a/mojo/edk/system/node_channel.cc b/mojo/edk/system/node_channel.cc
index 0e2ef52..7e76fe4 100644
--- a/mojo/edk/system/node_channel.cc
+++ b/mojo/edk/system/node_channel.cc
@@ -8,9 +8,15 @@
#include <limits>
#include <sstream>
+#include "base/bind.h"
+#include "base/location.h"
#include "base/logging.h"
#include "mojo/edk/system/channel.h"
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "mojo/edk/system/mach_port_relay.h"
+#endif
+
namespace mojo {
namespace edk {
@@ -32,7 +38,7 @@ enum class MessageType : uint32_t {
REQUEST_PORT_MERGE,
REQUEST_INTRODUCTION,
INTRODUCE,
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
RELAY_PORTS_MESSAGE,
#endif
};
@@ -98,7 +104,7 @@ struct IntroductionData {
ports::NodeName name;
};
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
// This struct is followed by the full payload of a message to be relayed.
struct RelayPortsMessageData {
ports::NodeName destination;
@@ -117,7 +123,7 @@ Channel::MessagePtr CreateMessage(MessageType type,
header->padding = 0;
*out_data = reinterpret_cast<DataType*>(&header[1]);
return message;
-};
+}
template <typename DataType>
void GetMessagePayload(const void* bytes, DataType** out_data) {
@@ -152,12 +158,24 @@ void NodeChannel::GetPortsMessageData(Channel::Message* message,
}
void NodeChannel::Start() {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ MachPortRelay* relay = delegate_->GetMachPortRelay();
+ if (relay)
+ relay->AddObserver(this);
+#endif
+
base::AutoLock lock(channel_lock_);
DCHECK(channel_);
channel_->Start();
}
void NodeChannel::ShutDown() {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ MachPortRelay* relay = delegate_->GetMachPortRelay();
+ if (relay)
+ relay->RemoveObserver(this);
+#endif
+
base::AutoLock lock(channel_lock_);
if (channel_) {
channel_->ShutDown();
@@ -303,9 +321,10 @@ void NodeChannel::Introduce(const ports::NodeName& name,
WriteChannelMessage(std::move(message));
}
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
void NodeChannel::RelayPortsMessage(const ports::NodeName& destination,
Channel::MessagePtr message) {
+#if defined(OS_WIN)
DCHECK(message->has_handles());
// Note that this is only used on Windows, and on Windows all platform
@@ -325,9 +344,27 @@ void NodeChannel::RelayPortsMessage(const ports::NodeName& destination,
ScopedPlatformHandleVectorPtr handles = message->TakeHandles();
handles->clear();
+#else
+ DCHECK(message->has_mach_ports());
+
+ // On OSX, the handles are extracted from the relayed message and attached to
+ // the wrapper. The broker then takes the handles attached to the wrapper and
+ // moves them back to the relayed message. This is necessary because the
+ // message may contain fds which need to be attached to the outer message so
+ // that they can be transferred to the broker.
+ ScopedPlatformHandleVectorPtr handles = message->TakeHandles();
+ size_t num_bytes = sizeof(RelayPortsMessageData) + message->data_num_bytes();
+ RelayPortsMessageData* data;
+ Channel::MessagePtr relay_message = CreateMessage(
+ MessageType::RELAY_PORTS_MESSAGE, num_bytes, handles->size(), &data);
+ data->destination = destination;
+ memcpy(data + 1, message->data(), message->data_num_bytes());
+ relay_message->SetHandles(std::move(handles));
+#endif // defined(OS_WIN)
+
WriteChannelMessage(std::move(relay_message));
}
-#endif
+#endif // defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
NodeChannel::NodeChannel(Delegate* delegate,
ScopedPlatformHandle platform_handle,
@@ -361,7 +398,19 @@ void NodeChannel::OnChannelMessage(const void* payload,
}
}
}
-#endif
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ // If we're not the root, receive any mach ports from the message. If we're
+ // the root, the only message containing mach ports should be a
+ // RELAY_PORTS_MESSAGE.
+ {
+ MachPortRelay* relay = delegate_->GetMachPortRelay();
+ if (handles && !relay) {
+ if (!MachPortRelay::ReceivePorts(handles.get())) {
+ LOG(ERROR) << "Error receiving mach ports.";
+ }
+ }
+ }
+#endif // defined(OS_WIN)
const Header* header = static_cast<const Header*>(payload);
switch (header->type) {
@@ -484,7 +533,7 @@ void NodeChannel::OnChannelMessage(const void* payload,
break;
}
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
case MessageType::RELAY_PORTS_MESSAGE: {
base::ProcessHandle from_process;
{
@@ -500,6 +549,24 @@ void NodeChannel::OnChannelMessage(const void* payload,
DLOG(ERROR) << "Dropping invalid relay message.";
break;
}
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ message->SetHandles(std::move(handles));
+ MachPortRelay* relay = delegate_->GetMachPortRelay();
+ if (!relay) {
+ LOG(ERROR) << "Receiving mach ports without a port relay from "
+ << remote_node_name_ << ". Dropping message.";
+ break;
+ }
+ {
+ base::AutoLock lock(pending_mach_messages_lock_);
+ if (relay->port_provider()->TaskForPid(from_process) ==
+ MACH_PORT_NULL) {
+ pending_relay_messages_.push(
+ std::make_pair(data->destination, std::move(message)));
+ break;
+ }
+ }
+#endif
delegate_->OnRelayPortsMessage(remote_node_name_, from_process,
data->destination, std::move(message));
break;
@@ -526,6 +593,58 @@ void NodeChannel::OnChannelError() {
delegate_->OnChannelError(node_name);
}
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+void NodeChannel::OnProcessReady(base::ProcessHandle process) {
+ io_task_runner_->PostTask(FROM_HERE, base::Bind(
+ &NodeChannel::ProcessPendingMessagesWithMachPorts, this));
+}
+
+void NodeChannel::ProcessPendingMessagesWithMachPorts() {
+ MachPortRelay* relay = delegate_->GetMachPortRelay();
+ DCHECK(relay);
+
+ base::ProcessHandle remote_process_handle;
+ {
+ base::AutoLock lock(remote_process_handle_lock_);
+ remote_process_handle = remote_process_handle_;
+ }
+ PendingMessageQueue pending_writes;
+ PendingRelayMessageQueue pending_relays;
+ {
+ base::AutoLock lock(pending_mach_messages_lock_);
+ pending_writes.swap(pending_write_messages_);
+ pending_relays.swap(pending_relay_messages_);
+ }
+ DCHECK(pending_writes.empty() && pending_relays.empty());
+
+ while (!pending_writes.empty()) {
+ Channel::MessagePtr message = std::move(pending_writes.front());
+ pending_writes.pop();
+ if (!relay->SendPortsToProcess(message.get(), remote_process_handle)) {
+ LOG(ERROR) << "Error on sending mach ports. Remote process is likely "
+ << "gone. Dropping message.";
+ return;
+ }
+
+ base::AutoLock lock(channel_lock_);
+ if (!channel_) {
+ DLOG(ERROR) << "Dropping message on closed channel.";
+ break;
+ } else {
+ channel_->Write(std::move(message));
+ }
+ }
+
+ while (!pending_relays.empty()) {
+ ports::NodeName destination = pending_relays.front().first;
+ Channel::MessagePtr message = std::move(pending_relays.front().second);
+ pending_relays.pop();
+ delegate_->OnRelayPortsMessage(remote_node_name_, remote_process_handle,
+ destination, std::move(message));
+ }
+}
+#endif
+
void NodeChannel::WriteChannelMessage(Channel::MessagePtr message) {
#if defined(OS_WIN)
// Map handles to the destination process. Note: only messages from a
@@ -550,6 +669,39 @@ void NodeChannel::WriteChannelMessage(Channel::MessagePtr message) {
}
}
}
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ // On OSX, we need to transfer mach ports to the destination process before
+ // transferring the message itself.
+ if (message->has_mach_ports()) {
+ MachPortRelay* relay = delegate_->GetMachPortRelay();
+ if (relay) {
+ base::ProcessHandle remote_process_handle;
+ {
+ base::AutoLock lock(remote_process_handle_lock_);
+ // Expect that the receiving node is a child.
+ DCHECK(remote_process_handle_ != base::kNullProcessHandle);
+ remote_process_handle = remote_process_handle_;
+ }
+ {
+ base::AutoLock lock(pending_mach_messages_lock_);
+ if (relay->port_provider()->TaskForPid(remote_process_handle) ==
+ MACH_PORT_NULL) {
+ // It is also possible for TaskForPid() to return MACH_PORT_NULL when
+ // the process has started, then died. In that case, the queued
+ // message will never be processed. But that's fine since we're about
+ // to die anyway.
+ pending_write_messages_.push(std::move(message));
+ return;
+ }
+ }
+
+ if (!relay->SendPortsToProcess(message.get(), remote_process_handle)) {
+ LOG(ERROR) << "Error on sending mach ports. Remote process is likely "
+ << "gone. Dropping message.";
+ return;
+ }
+ }
+ }
#endif
base::AutoLock lock(channel_lock_);
diff --git a/mojo/edk/system/node_channel.h b/mojo/edk/system/node_channel.h
index 7dc1050..c8a97ca 100644
--- a/mojo/edk/system/node_channel.h
+++ b/mojo/edk/system/node_channel.h
@@ -5,7 +5,9 @@
#ifndef MOJO_EDK_SYSTEM_NODE_CHANNEL_H_
#define MOJO_EDK_SYSTEM_NODE_CHANNEL_H_
+#include <queue>
#include <unordered_map>
+#include <utility>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
@@ -18,12 +20,20 @@
#include "mojo/edk/system/channel.h"
#include "mojo/edk/system/ports/name.h"
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "mojo/edk/system/mach_port_relay.h"
+#endif
+
namespace mojo {
namespace edk {
// Wraps a Channel to send and receive Node control messages.
class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
- public Channel::Delegate {
+ public Channel::Delegate
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ , public MachPortRelay::Observer
+#endif
+ {
public:
class Delegate {
public:
@@ -52,7 +62,7 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
virtual void OnIntroduce(const ports::NodeName& from_node,
const ports::NodeName& name,
ScopedPlatformHandle channel_handle) = 0;
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
virtual void OnRelayPortsMessage(const ports::NodeName& from_node,
base::ProcessHandle from_process,
const ports::NodeName& destination,
@@ -60,6 +70,10 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
#endif
virtual void OnChannelError(const ports::NodeName& node) = 0;
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ virtual MachPortRelay* GetMachPortRelay() = 0;
+#endif
};
static scoped_refptr<NodeChannel> Create(
@@ -106,7 +120,7 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
void Introduce(const ports::NodeName& name,
ScopedPlatformHandle channel_handle);
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
// Relay the message to the specified node via this channel. This is used to
// pass windows handles between two processes that do not have permission to
// duplicate handles into the other's address space. The relay process is
@@ -118,6 +132,10 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
private:
friend class base::RefCountedThreadSafe<NodeChannel>;
+ using PendingMessageQueue = std::queue<Channel::MessagePtr>;
+ using PendingRelayMessageQueue =
+ std::queue<std::pair<ports::NodeName, Channel::MessagePtr>>;
+
NodeChannel(Delegate* delegate,
ScopedPlatformHandle platform_handle,
scoped_refptr<base::TaskRunner> io_task_runner);
@@ -129,6 +147,13 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
ScopedPlatformHandleVectorPtr handles) override;
void OnChannelError() override;
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // MachPortRelay::Observer:
+ void OnProcessReady(base::ProcessHandle process) override;
+
+ void ProcessPendingMessagesWithMachPorts();
+#endif
+
void WriteChannelMessage(Channel::MessagePtr message);
Delegate* const delegate_;
@@ -143,6 +168,12 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
base::Lock remote_process_handle_lock_;
base::ProcessHandle remote_process_handle_ = base::kNullProcessHandle;
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ base::Lock pending_mach_messages_lock_;
+ PendingMessageQueue pending_write_messages_;
+ PendingRelayMessageQueue pending_relay_messages_;
+#endif
+
DISALLOW_COPY_AND_ASSIGN(NodeChannel);
};
diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc
index 1472f03..3733c6c 100644
--- a/mojo/edk/system/node_controller.cc
+++ b/mojo/edk/system/node_controller.cc
@@ -22,6 +22,10 @@
#include "mojo/edk/system/core.h"
#include "mojo/edk/system/ports_message.h"
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "mojo/edk/system/mach_port_relay.h"
+#endif
+
namespace mojo {
namespace edk {
@@ -107,6 +111,15 @@ NodeController::NodeController(Core* core)
DVLOG(1) << "Initializing node " << name_;
}
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+void NodeController::CreateMachPortRelay(
+ base::PortProvider* port_provider) {
+ base::AutoLock lock(mach_port_relay_lock_);
+ DCHECK(!mach_port_relay_);
+ mach_port_relay_.reset(new MachPortRelay(port_provider));
+}
+#endif
+
void NodeController::SetIOTaskRunner(
scoped_refptr<base::TaskRunner> task_runner) {
io_task_runner_ = task_runner;
@@ -278,14 +291,16 @@ void NodeController::ConnectToParentOnIOThread(
ScopedPlatformHandle platform_handle) {
DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
- base::AutoLock lock(parent_lock_);
- DCHECK(parent_name_ == ports::kInvalidNodeName);
+ {
+ base::AutoLock lock(parent_lock_);
+ DCHECK(parent_name_ == ports::kInvalidNodeName);
- // At this point we don't know the parent's name, so we can't yet insert it
- // into our |peers_| map. That will happen as soon as we receive an
- // AcceptChild message from them.
- bootstrap_parent_channel_ =
- NodeChannel::Create(this, std::move(platform_handle), io_task_runner_);
+ // At this point we don't know the parent's name, so we can't yet insert it
+ // into our |peers_| map. That will happen as soon as we receive an
+ // AcceptChild message from them.
+ bootstrap_parent_channel_ =
+ NodeChannel::Create(this, std::move(platform_handle), io_task_runner_);
+ }
bootstrap_parent_channel_->Start();
}
@@ -406,7 +421,28 @@ void NodeController::SendPeerMessage(const ports::NodeName& name,
return;
}
}
-#endif
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ if (channel_message->has_mach_ports()) {
+ // Messages containing Mach ports are always routed through the broker, even
+ // if the broker process is the intended recipient.
+ bool use_broker = false;
+ {
+ base::AutoLock lock(parent_lock_);
+ use_broker = (bootstrap_parent_channel_ ||
+ parent_name_ != ports::kInvalidNodeName);
+ }
+ if (use_broker) {
+ scoped_refptr<NodeChannel> broker = GetBrokerChannel();
+ if (broker) {
+ broker->RelayPortsMessage(name, std::move(channel_message));
+ } else {
+ base::AutoLock lock(broker_lock_);
+ pending_relay_messages_[name].emplace(std::move(channel_message));
+ }
+ return;
+ }
+ }
+#endif // defined(OS_WIN)
if (peer) {
peer->PortsMessage(std::move(channel_message));
@@ -659,7 +695,7 @@ void NodeController::OnBrokerClientAdded(const ports::NodeName& from_node,
}
// This should have come from our own broker.
- if(GetBrokerChannel() != GetPeerChannel(from_node)) {
+ if (GetBrokerChannel() != GetPeerChannel(from_node)) {
DLOG(ERROR) << "BrokerClientAdded from non-broker node " << from_node;
return;
}
@@ -727,7 +763,7 @@ void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node,
pending_broker_clients.pop();
}
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
// Have the broker relay any messages we have waiting.
for (auto& entry : pending_relay_messages) {
const ports::NodeName& destination = entry.first;
@@ -836,11 +872,13 @@ void NodeController::OnIntroduce(const ports::NodeName& from_node,
AddPeer(name, channel, true /* start_channel */);
}
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node,
base::ProcessHandle from_process,
const ports::NodeName& destination,
Channel::MessagePtr message) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+
if (GetBrokerChannel()) {
// Only the broker should be asked to relay a message.
LOG(ERROR) << "Non-broker refusing to relay message.";
@@ -851,6 +889,7 @@ void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node,
// The parent should always know which process this came from.
DCHECK(from_process != base::kNullProcessHandle);
+#if defined(OS_WIN)
// Rewrite the handles to this (the parent) process. If the message is
// destined for another child process, the handles will be rewritten to that
// process before going out (see NodeChannel::WriteChannelMessage).
@@ -862,6 +901,22 @@ void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node,
message->num_handles())) {
DLOG(ERROR) << "Failed to relay one or more handles.";
}
+#else
+ MachPortRelay* relay = GetMachPortRelay();
+ if (!relay) {
+ LOG(ERROR) << "Receiving Mach ports without a port relay from "
+ << from_node << ". Dropping message.";
+ return;
+ }
+ if (!relay->ExtractPortRights(message.get(), from_process)) {
+ // NodeChannel should ensure that MachPortRelay is ready for the remote
+ // process. At this point, if the port extraction failed, either something
+ // went wrong in the mach stuff, or the remote process died.
+ LOG(ERROR) << "Error on receiving Mach ports " << from_node
+ << ". Dropping message.";
+ return;
+ }
+#endif // defined(OS_WIN)
if (destination == name_) {
// Great, we can deliver this message locally.
@@ -888,6 +943,20 @@ void NodeController::OnChannelError(const ports::NodeName& from_node) {
}
}
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+MachPortRelay* NodeController::GetMachPortRelay() {
+ {
+ base::AutoLock lock(parent_lock_);
+ // Return null if we're not the root.
+ if (bootstrap_parent_channel_ || parent_name_ != ports::kInvalidNodeName)
+ return nullptr;
+ }
+
+ base::AutoLock lock(mach_port_relay_lock_);
+ return mach_port_relay_.get();
+}
+#endif
+
void NodeController::DestroyOnIOThreadShutdown() {
destroy_on_io_thread_shutdown_ = true;
}
diff --git a/mojo/edk/system/node_controller.h b/mojo/edk/system/node_controller.h
index 75d2892..34c2644 100644
--- a/mojo/edk/system/node_controller.h
+++ b/mojo/edk/system/node_controller.h
@@ -25,11 +25,16 @@
#include "mojo/edk/system/ports/node.h"
#include "mojo/edk/system/ports/node_delegate.h"
+namespace base {
+class PortProvider;
+}
+
namespace mojo {
namespace edk {
class Broker;
class Core;
+class MachPortRelay;
class PortsMessage;
// The owner of ports::Node which facilitates core EDK implementation. All
@@ -56,6 +61,11 @@ class NodeController : public ports::NodeDelegate,
return io_task_runner_;
}
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Create the relay used to transfer mach ports between processes.
+ void CreateMachPortRelay(base::PortProvider* port_provider);
+#endif
+
// Called exactly once, shortly after construction, and before any other
// methods are called on this object.
void SetIOTaskRunner(scoped_refptr<base::TaskRunner> io_task_runner);
@@ -162,13 +172,16 @@ class NodeController : public ports::NodeDelegate,
void OnIntroduce(const ports::NodeName& from_node,
const ports::NodeName& name,
ScopedPlatformHandle channel_handle) override;
-#if defined(OS_WIN)
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
void OnRelayPortsMessage(const ports::NodeName& from_node,
base::ProcessHandle from_process,
const ports::NodeName& destination,
Channel::MessagePtr message) override;
#endif
void OnChannelError(const ports::NodeName& from_node) override;
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ MachPortRelay* GetMachPortRelay() override;
+#endif
// Marks this NodeController for destruction when the IO thread shuts down.
// This is used in case Core is torn down before the IO thread. Must only be
@@ -258,6 +271,12 @@ class NodeController : public ports::NodeDelegate,
scoped_ptr<Broker> broker_;
#endif
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ base::Lock mach_port_relay_lock_;
+ // Relay for transferring mach ports to/from children.
+ scoped_ptr<MachPortRelay> mach_port_relay_;
+#endif
+
DISALLOW_COPY_AND_ASSIGN(NodeController);
};
diff --git a/mojo/edk/test/mojo_test_base.cc b/mojo/edk/test/mojo_test_base.cc
index 7ec067a..4b044f6 100644
--- a/mojo/edk/test/mojo_test_base.cc
+++ b/mojo/edk/test/mojo_test_base.cc
@@ -13,11 +13,29 @@
#include "mojo/public/c/system/functions.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/mach_port_broker.h"
+#endif
+
namespace mojo {
namespace edk {
namespace test {
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+namespace {
+base::MachPortBroker* g_mach_broker = nullptr;
+}
+#endif
+
MojoTestBase::MojoTestBase() {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ if (!g_mach_broker) {
+ g_mach_broker = new base::MachPortBroker("mojo_test");
+ CHECK(g_mach_broker->Init());
+ SetMachPortProvider(g_mach_broker);
+ }
+#endif
}
MojoTestBase::~MojoTestBase() {}
@@ -31,12 +49,22 @@ MojoTestBase::ClientController& MojoTestBase::StartClient(
MojoTestBase::ClientController::ClientController(const std::string& client_name,
MojoTestBase* test)
- : test_(test)
+ : test_(test) {
#if !defined(OS_IOS)
- ,
- pipe_(helper_.StartChild(client_name))
+#if defined(OS_MACOSX)
+ // This lock needs to be held while launching the child because the Mach port
+ // broker only allows task ports to be received from known child processes.
+ // However, it can only know the child process's pid after the child has
+ // launched. To prevent a race where the child process sends its task port
+ // before the pid has been registered, the lock needs to be held over both
+ // launch and child pid registration.
+ base::AutoLock lock(g_mach_broker->GetLock());
+#endif
+ pipe_ = helper_.StartChild(client_name);
+#if defined(OS_MACOSX)
+ g_mach_broker->AddPlaceholderForPid(helper_.test_child().Handle());
+#endif
#endif
-{
}
MojoTestBase::ClientController::~ClientController() {
@@ -47,7 +75,12 @@ MojoTestBase::ClientController::~ClientController() {
int MojoTestBase::ClientController::WaitForShutdown() {
was_shutdown_ = true;
#if !defined(OS_IOS)
- return helper_.WaitForChildShutdown();
+ int retval = helper_.WaitForChildShutdown();
+#if defined(OS_MACOSX)
+ base::AutoLock lock(g_mach_broker->GetLock());
+ g_mach_broker->InvalidatePid(helper_.test_child().Handle());
+#endif
+ return retval;
#else
NOTREACHED();
return 1;
diff --git a/mojo/edk/test/multiprocess_test_helper.cc b/mojo/edk/test/multiprocess_test_helper.cc
index efc4e6b..7098934 100644
--- a/mojo/edk/test/multiprocess_test_helper.cc
+++ b/mojo/edk/test/multiprocess_test_helper.cc
@@ -27,6 +27,8 @@
#if defined(OS_WIN)
#include "base/win/windows_version.h"
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/mach_port_broker.h"
#endif
namespace mojo {
@@ -148,6 +150,10 @@ void MultiprocessTestHelper::ChildSetup() {
->GetSwitchValueASCII(kMojoPrimordialPipeToken);
CHECK(!primordial_pipe_token.empty());
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ CHECK(base::MachPortBroker::ChildSendTaskPortToParent("mojo_test"));
+#endif
+
SetParentPipeHandle(
PlatformChannelPair::PassClientHandleFromParentProcess(
*base::CommandLine::ForCurrentProcess()));
diff --git a/mojo/edk/test/multiprocess_test_helper.h b/mojo/edk/test/multiprocess_test_helper.h
index 7ee6024..b3bed6b 100644
--- a/mojo/edk/test/multiprocess_test_helper.h
+++ b/mojo/edk/test/multiprocess_test_helper.h
@@ -55,6 +55,8 @@ class MultiprocessTestHelper {
// |EXPECT_TRUE(WaitForChildTestShutdown());|.
bool WaitForChildTestShutdown();
+ const base::Process& test_child() const { return test_child_; }
+
// Used by macros in mojo/edk/test/mojo_test_base.h to support multiprocess
// test client initialization.
static void ChildSetup();
diff --git a/mojo/mojo_edk.gyp b/mojo/mojo_edk.gyp
index f95cde4..db57fc5 100644
--- a/mojo/mojo_edk.gyp
+++ b/mojo/mojo_edk.gyp
@@ -146,6 +146,12 @@
# uninteresting.
'msvs_disabled_warnings': [ 4324 ],
}],
+ ['OS=="mac" and OS!="ios"', {
+ 'sources': [
+ 'edk/system/mach_port_relay.cc',
+ 'edk/system/mach_port_relay.h',
+ ],
+ }],
],
},
{