diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /ipc/chromium/src/chrome/common | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/chromium/src/chrome/common')
27 files changed, 5073 insertions, 0 deletions
diff --git a/ipc/chromium/src/chrome/common/child_process.cc b/ipc/chromium/src/chrome/common/child_process.cc new file mode 100644 index 0000000000..637564c30c --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_process.cc @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 "chrome/common/child_process.h" + +#include "base/basictypes.h" +#include "base/string_util.h" +#include "chrome/common/child_thread.h" + +ChildProcess* ChildProcess::child_process_; + +ChildProcess::ChildProcess(ChildThread* child_thread) + : child_thread_(child_thread) { + DCHECK(!child_process_); + child_process_ = this; + if (child_thread_.get()) // null in unittests. + child_thread_->Run(); +} + +ChildProcess::~ChildProcess() { + DCHECK(child_process_ == this); + + if (child_thread_.get()) child_thread_->Stop(); + + child_process_ = NULL; +} diff --git a/ipc/chromium/src/chrome/common/child_process.h b/ipc/chromium/src/chrome/common/child_process.h new file mode 100644 index 0000000000..7ce4cffa81 --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_process.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 CHROME_COMMON_CHILD_PROCESS_H__ +#define CHROME_COMMON_CHILD_PROCESS_H__ + +#include <string> +#include <vector> +#include "base/basictypes.h" +#include "base/message_loop.h" +#include "base/waitable_event.h" +#include "mozilla/UniquePtr.h" + +class ChildThread; + +// Base class for child processes of the browser process (i.e. renderer and +// plugin host). This is a singleton object for each child process. +class ChildProcess { + public: + // Child processes should have an object that derives from this class. The + // constructor will return once ChildThread has started. + explicit ChildProcess(ChildThread* child_thread); + virtual ~ChildProcess(); + + // Getter for this process' main thread. + ChildThread* child_thread() { return child_thread_.get(); } + + // Getter for the one ChildProcess object for this process. + static ChildProcess* current() { return child_process_; } + + private: + // NOTE: make sure that child_thread_ is listed before shutdown_event_, since + // it depends on it (indirectly through IPC::SyncChannel). + mozilla::UniquePtr<ChildThread> child_thread_; + + // The singleton instance for this process. + static ChildProcess* child_process_; + + DISALLOW_EVIL_CONSTRUCTORS(ChildProcess); +}; + +#endif // CHROME_COMMON_CHILD_PROCESS_H__ diff --git a/ipc/chromium/src/chrome/common/child_process_host.cc b/ipc/chromium/src/chrome/common/child_process_host.cc new file mode 100644 index 0000000000..dcbf14196a --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_process_host.cc @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2009 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 "chrome/common/child_process_host.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/waitable_event.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/Transport.h" +typedef mozilla::ipc::BrowserProcessSubThread ChromeThread; +#include "chrome/common/process_watcher.h" + +using mozilla::ipc::FileDescriptor; + +ChildProcessHost::ChildProcessHost() + : ALLOW_THIS_IN_INITIALIZER_LIST(listener_(this)), + opening_channel_(false) {} + +ChildProcessHost::~ChildProcessHost() {} + +bool ChildProcessHost::CreateChannel() { + channel_id_ = IPC::Channel::GenerateVerifiedChannelID(); + channel_.reset( + new IPC::Channel(channel_id_, IPC::Channel::MODE_SERVER, &listener_)); + if (!channel_->Connect()) return false; + + opening_channel_ = true; + + return true; +} + +bool ChildProcessHost::CreateChannel(FileDescriptor& aFileDescriptor) { + if (channel_.get()) { + channel_->Close(); + } + channel_ = + mozilla::ipc::OpenDescriptor(aFileDescriptor, IPC::Channel::MODE_SERVER); + if (!channel_->Connect()) { + return false; + } + + opening_channel_ = true; + + return true; +} + +ChildProcessHost::ListenerHook::ListenerHook(ChildProcessHost* host) + : host_(host) {} + +void ChildProcessHost::ListenerHook::OnMessageReceived(IPC::Message&& msg) { + host_->OnMessageReceived(std::move(msg)); +} + +void ChildProcessHost::ListenerHook::OnChannelConnected(int32_t peer_pid) { + host_->opening_channel_ = false; + host_->OnChannelConnected(peer_pid); +} + +void ChildProcessHost::ListenerHook::OnChannelError() { + host_->opening_channel_ = false; + host_->OnChannelError(); +} + +void ChildProcessHost::ListenerHook::GetQueuedMessages( + std::queue<IPC::Message>& queue) { + host_->GetQueuedMessages(queue); +} diff --git a/ipc/chromium/src/chrome/common/child_process_host.h b/ipc/chromium/src/chrome/common/child_process_host.h new file mode 100644 index 0000000000..d96bc9cd0b --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_process_host.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2009 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 CHROME_COMMON_CHILD_PROCESS_HOST_H_ +#define CHROME_COMMON_CHILD_PROCESS_HOST_H_ + +#include "build/build_config.h" + +#include <list> + +#include "base/basictypes.h" +#include "chrome/common/ipc_channel.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace ipc { +class FileDescriptor; +} +} // namespace mozilla + +// Plugins/workers and other child processes that live on the IO thread should +// derive from this class. +class ChildProcessHost : public IPC::Channel::Listener { + public: + virtual ~ChildProcessHost(); + + using ChannelId = IPC::Channel::ChannelId; + + protected: + explicit ChildProcessHost(); + + // Derived classes return true if it's ok to shut down the child process. + virtual bool CanShutdown() = 0; + + // Creates the IPC channel. Returns true iff it succeeded. + bool CreateChannel(); + + bool CreateChannel(mozilla::ipc::FileDescriptor& aFileDescriptor); + + // IPC::Channel::Listener implementation: + virtual void OnMessageReceived(IPC::Message&& msg) override {} + virtual void OnChannelConnected(int32_t peer_pid) override {} + virtual void OnChannelError() override {} + + bool opening_channel() { return opening_channel_; } + const ChannelId& channel_id() { return channel_id_; } + + IPC::Channel* channelp() const { return channel_.get(); } + mozilla::UniquePtr<IPC::Channel> TakeChannel() { return std::move(channel_); } + + private: + // By using an internal class as the IPC::Channel::Listener, we can intercept + // OnMessageReceived/OnChannelConnected and do our own processing before + // calling the subclass' implementation. + class ListenerHook : public IPC::Channel::Listener { + public: + explicit ListenerHook(ChildProcessHost* host); + virtual void OnMessageReceived(IPC::Message&& msg) override; + virtual void OnChannelConnected(int32_t peer_pid) override; + virtual void OnChannelError() override; + virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override; + + private: + ChildProcessHost* host_; + }; + + ListenerHook listener_; + + // True while we're waiting the channel to be opened. + bool opening_channel_; + + // The IPC::Channel. + mozilla::UniquePtr<IPC::Channel> channel_; + + // IPC Channel's id. + ChannelId channel_id_; +}; + +#endif // CHROME_COMMON_CHILD_PROCESS_HOST_H_ diff --git a/ipc/chromium/src/chrome/common/child_thread.cc b/ipc/chromium/src/chrome/common/child_thread.cc new file mode 100644 index 0000000000..e8426b20b6 --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_thread.cc @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2009 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 "chrome/common/child_thread.h" + +#include "chrome/common/child_process.h" + +ChildThread::ChildThread(Thread::Options options) + : Thread("IPC I/O Child"), + owner_loop_(MessageLoop::current()), + options_(options) { + DCHECK(owner_loop_); + channel_name_ = IPC::Channel::ChannelIDForCurrentProcess(); +} + +ChildThread::~ChildThread() {} + +bool ChildThread::Run() { + bool r = StartWithOptions(options_); + return r; +} + +void ChildThread::OnChannelError() { + RefPtr<mozilla::Runnable> task = new MessageLoop::QuitTask(); + owner_loop_->PostTask(task.forget()); +} + +void ChildThread::OnMessageReceived(IPC::Message&& msg) {} + +ChildThread* ChildThread::current() { + return ChildProcess::current()->child_thread(); +} + +void ChildThread::Init() { + channel_ = mozilla::MakeUnique<IPC::Channel>(channel_name_, + IPC::Channel::MODE_CLIENT, this); +} + +void ChildThread::CleanUp() { + // Need to destruct the SyncChannel to the browser before we go away because + // it caches a pointer to this thread. + channel_ = nullptr; +} diff --git a/ipc/chromium/src/chrome/common/child_thread.h b/ipc/chromium/src/chrome/common/child_thread.h new file mode 100644 index 0000000000..0261a1229f --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_thread.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2009 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 CHROME_COMMON_CHILD_THREAD_H_ +#define CHROME_COMMON_CHILD_THREAD_H_ + +#include "base/thread.h" +#include "chrome/common/ipc_channel.h" +#include "mozilla/UniquePtr.h" + +class ResourceDispatcher; + +// Child processes's background thread should derive from this class. +class ChildThread : public IPC::Channel::Listener, public base::Thread { + public: + // Creates the thread. + explicit ChildThread(Thread::Options options); + virtual ~ChildThread(); + + protected: + friend class ChildProcess; + + // Starts the thread. + bool Run(); + + protected: + // Returns the one child thread. + static ChildThread* current(); + + mozilla::UniquePtr<IPC::Channel> TakeChannel() { return std::move(channel_); } + + // Thread implementation. + virtual void Init() override; + virtual void CleanUp() override; + + private: + // IPC::Channel::Listener implementation: + virtual void OnMessageReceived(IPC::Message&& msg) override; + virtual void OnChannelError() override; + + // The message loop used to run tasks on the thread that started this thread. + MessageLoop* owner_loop_; + + IPC::Channel::ChannelId channel_name_; + mozilla::UniquePtr<IPC::Channel> channel_; + + Thread::Options options_; + + DISALLOW_EVIL_CONSTRUCTORS(ChildThread); +}; + +#endif // CHROME_COMMON_CHILD_THREAD_H_ diff --git a/ipc/chromium/src/chrome/common/chrome_switches.cc b/ipc/chromium/src/chrome/common/chrome_switches.cc new file mode 100644 index 0000000000..185934c87d --- /dev/null +++ b/ipc/chromium/src/chrome/common/chrome_switches.cc @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2009 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 "chrome/common/chrome_switches.h" + +namespace switches { + +// Can't find the switch you are looking for? try looking in +// base/base_switches.cc instead. + +// The value of this switch tells the child process which +// IPC channel the browser expects to use to communicate with it. +const wchar_t kProcessChannelID[] = L"channel"; + +} // namespace switches diff --git a/ipc/chromium/src/chrome/common/chrome_switches.h b/ipc/chromium/src/chrome/common/chrome_switches.h new file mode 100644 index 0000000000..d0a60508c0 --- /dev/null +++ b/ipc/chromium/src/chrome/common/chrome_switches.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2009 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. + +// Defines all the command-line switches used by Chrome. + +#ifndef CHROME_COMMON_CHROME_SWITCHES_H__ +#define CHROME_COMMON_CHROME_SWITCHES_H__ + +#if defined(COMPILER_MSVC) +# include <string.h> +#endif + +namespace switches { + +extern const wchar_t kProcessChannelID[]; + +} // namespace switches + +#endif // CHROME_COMMON_CHROME_SWITCHES_H__ diff --git a/ipc/chromium/src/chrome/common/file_descriptor_set_posix.cc b/ipc/chromium/src/chrome/common/file_descriptor_set_posix.cc new file mode 100644 index 0000000000..6c13d2953b --- /dev/null +++ b/ipc/chromium/src/chrome/common/file_descriptor_set_posix.cc @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 "chrome/common/file_descriptor_set_posix.h" + +#include "base/eintr_wrapper.h" +#include "base/logging.h" + +#include <unistd.h> + +FileDescriptorSet::FileDescriptorSet() : consumed_descriptor_highwater_(0) {} + +FileDescriptorSet::~FileDescriptorSet() { + if (consumed_descriptor_highwater_ == descriptors_.size()) return; + + CHROMIUM_LOG(WARNING) + << "FileDescriptorSet destroyed with unconsumed descriptors"; + + // We close all the descriptors where the close flag is set. If this + // message should have been transmitted, then closing those with close + // flags set mirrors the expected behaviour. + // + // If this message was received with more descriptors than expected + // (which could a DOS against the browser by a rogue renderer) then all + // the descriptors have their close flag set and we free all the extra + // kernel resources. + for (unsigned i = consumed_descriptor_highwater_; i < descriptors_.size(); + ++i) { + if (descriptors_[i].auto_close) IGNORE_EINTR(close(descriptors_[i].fd)); + } +} + +void FileDescriptorSet::CopyFrom(const FileDescriptorSet& other) { + for (std::vector<base::FileDescriptor>::const_iterator i = + other.descriptors_.begin(); + i != other.descriptors_.end(); ++i) { + int fd = IGNORE_EINTR(dup(i->fd)); + AddAndAutoClose(fd); + } +} + +bool FileDescriptorSet::Add(int fd) { + if (descriptors_.size() == MAX_DESCRIPTORS_PER_MESSAGE) return false; + + struct base::FileDescriptor sd; + sd.fd = fd; + sd.auto_close = false; + descriptors_.push_back(sd); + return true; +} + +bool FileDescriptorSet::AddAndAutoClose(int fd) { + if (descriptors_.size() == MAX_DESCRIPTORS_PER_MESSAGE) return false; + + struct base::FileDescriptor sd; + sd.fd = fd; + sd.auto_close = true; + descriptors_.push_back(sd); + DCHECK(descriptors_.size() <= MAX_DESCRIPTORS_PER_MESSAGE); + return true; +} + +int FileDescriptorSet::GetDescriptorAt(unsigned index) const { + if (index >= descriptors_.size()) return -1; + + // We should always walk the descriptors in order, so it's reasonable to + // enforce this. Consider the case where a compromised renderer sends us + // the following message: + // + // ExampleMsg: + // num_fds:2 msg:FD(index = 1) control:SCM_RIGHTS {n, m} + // + // Here the renderer sent us a message which should have a descriptor, but + // actually sent two in an attempt to fill our fd table and kill us. By + // setting the index of the descriptor in the message to 1 (it should be + // 0), we would record a highwater of 1 and then consider all the + // descriptors to have been used. + // + // So we can either track of the use of each descriptor in a bitset, or we + // can enforce that we walk the indexes strictly in order. + // + // There's one more wrinkle: When logging messages, we may reparse them. So + // we have an exception: When the consumed_descriptor_highwater_ is at the + // end of the array and index 0 is requested, we reset the highwater value. + if (index == 0 && consumed_descriptor_highwater_ == descriptors_.size()) + consumed_descriptor_highwater_ = 0; + + if (index != consumed_descriptor_highwater_) return -1; + + consumed_descriptor_highwater_ = index + 1; + return descriptors_[index].fd; +} + +void FileDescriptorSet::GetDescriptors(int* buffer) const { + for (std::vector<base::FileDescriptor>::const_iterator i = + descriptors_.begin(); + i != descriptors_.end(); ++i) { + *(buffer++) = i->fd; + } +} + +void FileDescriptorSet::CommitAll() { + for (std::vector<base::FileDescriptor>::iterator i = descriptors_.begin(); + i != descriptors_.end(); ++i) { + if (i->auto_close) IGNORE_EINTR(close(i->fd)); + } + descriptors_.clear(); + consumed_descriptor_highwater_ = 0; +} + +void FileDescriptorSet::SetDescriptors(const int* buffer, unsigned count) { + DCHECK_LE(count, MAX_DESCRIPTORS_PER_MESSAGE); + DCHECK_EQ(descriptors_.size(), 0u); + DCHECK_EQ(consumed_descriptor_highwater_, 0u); + + descriptors_.reserve(count); + for (unsigned i = 0; i < count; ++i) { + struct base::FileDescriptor sd; + sd.fd = buffer[i]; + sd.auto_close = true; + descriptors_.push_back(sd); + } +} diff --git a/ipc/chromium/src/chrome/common/file_descriptor_set_posix.h b/ipc/chromium/src/chrome/common/file_descriptor_set_posix.h new file mode 100644 index 0000000000..4192f5b56b --- /dev/null +++ b/ipc/chromium/src/chrome/common/file_descriptor_set_posix.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2009 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 CHROME_COMMON_FILE_DESCRIPTOR_SET_POSIX_H_ +#define CHROME_COMMON_FILE_DESCRIPTOR_SET_POSIX_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/file_descriptor_posix.h" +#include "nsISupportsImpl.h" + +// ----------------------------------------------------------------------------- +// A FileDescriptorSet is an ordered set of POSIX file descriptors. These are +// associated with IPC messages so that descriptors can be transmitted over a +// UNIX domain socket. +// ----------------------------------------------------------------------------- +class FileDescriptorSet { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileDescriptorSet) + FileDescriptorSet(); + + // Mac and Linux both limit the number of file descriptors per message to + // slightly more than 250. + enum { MAX_DESCRIPTORS_PER_MESSAGE = 200 }; + + void CopyFrom(const FileDescriptorSet& other); + + // --------------------------------------------------------------------------- + // Interfaces for building during message serialisation... + + // Add a descriptor to the end of the set. Returns false iff the set is full. + bool Add(int fd); + // Add a descriptor to the end of the set and automatically close it after + // transmission. Returns false iff the set is full. + bool AddAndAutoClose(int fd); + + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + // Interfaces for accessing during message deserialisation... + + // Return the number of descriptors + unsigned size() const { return descriptors_.size(); } + // Return true if no unconsumed descriptors remain + bool empty() const { return descriptors_.empty(); } + // Fetch the nth descriptor from the beginning of the set. Code using this + // /must/ access the descriptors in order, except that it may wrap from the + // end to index 0 again. + // + // This interface is designed for the deserialising code as it doesn't + // support close flags. + // returns: file descriptor, or -1 on error + int GetDescriptorAt(unsigned n) const; + + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + // Interfaces for transmission... + + // Fill an array with file descriptors without 'consuming' them. CommitAll + // must be called after these descriptors have been transmitted. + // buffer: (output) a buffer of, at least, size() integers. + void GetDescriptors(int* buffer) const; + // This must be called after transmitting the descriptors returned by + // GetDescriptors. It marks all the descriptors as consumed and closes those + // which are auto-close. + void CommitAll(); + + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + // Interfaces for receiving... + + // Set the contents of the set from the given buffer. This set must be empty + // before calling. The auto-close flag is set on all the descriptors so that + // unconsumed descriptors are closed on destruction. + void SetDescriptors(const int* buffer, unsigned count); + + // --------------------------------------------------------------------------- + + private: + ~FileDescriptorSet(); + + // A vector of descriptors and close flags. If this message is sent, then + // these descriptors are sent as control data. After sending, any descriptors + // with a true flag are closed. If this message has been received, then these + // are the descriptors which were received and all close flags are true. + std::vector<base::FileDescriptor> descriptors_; + + // This contains the index of the next descriptor which should be consumed. + // It's used in a couple of ways. Firstly, at destruction we can check that + // all the descriptors have been read (with GetNthDescriptor). Secondly, we + // can check that they are read in order. + mutable unsigned consumed_descriptor_highwater_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorSet); +}; + +#endif // CHROME_COMMON_FILE_DESCRIPTOR_SET_POSIX_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_channel.h b/ipc/chromium/src/chrome/common/ipc_channel.h new file mode 100644 index 0000000000..92280bb0eb --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel.h @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 CHROME_COMMON_IPC_CHANNEL_H_ +#define CHROME_COMMON_IPC_CHANNEL_H_ + +#include <cstdint> +#include <queue> +#include "base/basictypes.h" +#include "build/build_config.h" +#include "mozilla/UniquePtr.h" + +#ifdef OS_WIN +# include <string> +#endif + +namespace IPC { + +class Message; + +//------------------------------------------------------------------------------ + +class Channel { + // Security tests need access to the pipe handle. + friend class ChannelTest; + + public: + // Windows channels use named objects and connect to them by name, + // but on Unix we use unnamed socketpairs and pass capabilities + // directly using SCM_RIGHTS messages. This type abstracts away + // that difference. +#ifdef OS_WIN + typedef std::wstring ChannelId; +#else + struct ChannelId {}; +#endif + + // Implemented by consumers of a Channel to receive messages. + class Listener { + public: + virtual ~Listener() {} + + // Called when a message is received. + virtual void OnMessageReceived(Message&& message) = 0; + + // Called when the channel is connected and we have received the internal + // Hello message from the peer. + virtual void OnChannelConnected(int32_t peer_pid) {} + + // Called when an error is detected that causes the channel to close. + // This method is not called when a channel is closed normally. + virtual void OnChannelError() {} + + // If the listener has queued messages, swap them for |queue| like so + // swap(impl->my_queued_messages, queue); + virtual void GetQueuedMessages(std::queue<Message>& queue) {} + }; + + enum Mode { MODE_SERVER, MODE_CLIENT }; + + enum { + // The maximum message size in bytes. Attempting to receive a + // message of this size or bigger results in a channel error. + kMaximumMessageSize = 256 * 1024 * 1024, + + // Ammount of data to read at once from the pipe. + kReadBufferSize = 4 * 1024, + + // Maximum size of a message that we allow to be copied (rather than moved). + kMaxCopySize = 32 * 1024, + }; + + // Initialize a Channel. + // + // |channel_id| identifies the communication Channel. + // |mode| specifies whether this Channel is to operate in server mode or + // client mode. In server mode, the Channel is responsible for setting up the + // IPC object, whereas in client mode, the Channel merely connects to the + // already established IPC object. + // |listener| receives a callback on the current thread for each newly + // received message. + // + Channel(const ChannelId& channel_id, Mode mode, Listener* listener); + + // XXX it would nice not to have yet more platform-specific code in + // here but it's just not worth the trouble. +#if defined(OS_POSIX) + // Connect to a pre-created channel |fd| as |mode|. + Channel(int fd, Mode mode, Listener* listener); +#elif defined(OS_WIN) + // Connect to a pre-created channel as |mode|. Clients connect to + // the pre-existing server pipe, and servers take over |server_pipe|. + Channel(const ChannelId& channel_id, void* server_pipe, Mode mode, + Listener* listener); +#endif + + ~Channel(); + + // Connect the pipe. On the server side, this will initiate + // waiting for connections. On the client, it attempts to + // connect to a pre-existing pipe. Note, calling Connect() + // will not block the calling thread and may complete + // asynchronously. + bool Connect(); + + // Close this Channel explicitly. May be called multiple times. + void Close(); + + // Modify the Channel's listener. + Listener* set_listener(Listener* listener); + + // Send a message over the Channel to the listener on the other end. + // + // |message| must be allocated using operator new. This object will be + // deleted once the contents of the Message have been sent. + // + // If you Send() a message on a Close()'d channel, we delete the message + // immediately. + bool Send(mozilla::UniquePtr<Message> message); + + // Unsound_IsClosed() and Unsound_NumQueuedMessages() are safe to call from + // any thread, but the value returned may be out of date, because we don't + // use any synchronization when reading or writing it. + bool Unsound_IsClosed() const; + uint32_t Unsound_NumQueuedMessages() const; + +#if defined(OS_POSIX) + // On POSIX an IPC::Channel wraps a socketpair(), this method returns the + // FD # for the client end of the socket and the equivalent FD# to use for + // mapping it into the Child process. + // This method may only be called on the server side of a channel. + // + // If the kTestingChannelID flag is specified on the command line then + // a named FIFO is used as the channel transport mechanism rather than a + // socketpair() in which case this method returns -1 for both parameters. + void GetClientFileDescriptorMapping(int* src_fd, int* dest_fd) const; + + // Return the file descriptor for communication with the peer. + int GetFileDescriptor() const; + + // Reset the file descriptor for communication with the peer. + void ResetFileDescriptor(int fd); + + // Close the client side of the socketpair. + void CloseClientFileDescriptor(); + +#elif defined(OS_WIN) + // Return the server pipe handle. + void* GetServerPipeHandle() const; +#endif // defined(OS_POSIX) + + // On Windows: Generates a channel ID that, if passed to the client + // as a shared secret, will validate the client's authenticity. + // Other platforms don't use channel IDs, so this returns the dummy + // ChannelId value. + static ChannelId GenerateVerifiedChannelID(); + + // On Windows: Retrieves the initial channel ID passed to the + // current process by its parent. Other platforms don't do this; + // the dummy ChannelId value is returned instead. + static ChannelId ChannelIDForCurrentProcess(); + +#if defined(MOZ_WIDGET_ANDROID) + // Used to set the first IPC file descriptor in the child process on Android. + // See ipc_channel_posix.cc for further details on how this is used. + static void SetClientChannelFd(int fd); +#endif // defined(MOZ_WIDGET_ANDROID) + + private: + // PIMPL to which all channel calls are delegated. + class ChannelImpl; + ChannelImpl* channel_impl_; + + enum { +#if defined(OS_MACOSX) + // If the channel receives a message that contains file descriptors, then + // it will reply back with this message, indicating that the message has + // been received. The sending channel can then close any descriptors that + // had been marked as auto_close. This works around a sendmsg() bug on BSD + // where the kernel can eagerly close file descriptors that are in message + // queues but not yet delivered. + RECEIVED_FDS_MESSAGE_TYPE = kuint16max - 1, +#endif + + // The Hello message is internal to the Channel class. It is sent + // by the peer when the channel is connected. The message contains + // just the process id (pid). The message has a special routing_id + // (MSG_ROUTING_NONE) and type (HELLO_MESSAGE_TYPE). + HELLO_MESSAGE_TYPE = kuint16max // Maximum value of message type + // (uint16_t), to avoid conflicting with + // normal message types, which are + // enumeration constants starting from 0. + }; +}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_channel_posix.cc b/ipc/chromium/src/chrome/common/ipc_channel_posix.cc new file mode 100644 index 0000000000..d79c898356 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_posix.cc @@ -0,0 +1,959 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 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 "chrome/common/ipc_channel_posix.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#if defined(OS_MACOSX) || defined(OS_NETBSD) +# include <sched.h> +#endif +#include <stddef.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <sys/uio.h> + +#include <string> +#include <map> + +#include "base/command_line.h" +#include "base/eintr_wrapper.h" +#include "base/logging.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/file_descriptor_set_posix.h" +#include "chrome/common/ipc_channel_utils.h" +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" + +#ifdef FUZZING +# include "mozilla/ipc/Faulty.h" +#endif + +// Use OS specific iovec array limit where it's possible. +#if defined(IOV_MAX) +static const size_t kMaxIOVecSize = IOV_MAX; +#elif defined(ANDROID) +static const size_t kMaxIOVecSize = 256; +#else +static const size_t kMaxIOVecSize = 16; +#endif + +#ifdef MOZ_TASK_TRACER +# include "GeckoTaskTracerImpl.h" +using namespace mozilla::tasktracer; +#endif + +using namespace mozilla::ipc; + +namespace IPC { + +// IPC channels on Windows use named pipes (CreateNamedPipe()) with +// channel ids as the pipe names. Channels on POSIX use anonymous +// Unix domain sockets created via socketpair() as pipes. These don't +// quite line up. +// +// When creating a child subprocess, the parent side of the fork +// arranges it such that the initial control channel ends up on the +// magic file descriptor gClientChannelFd in the child. Future +// connections (file descriptors) can then be passed via that +// connection via sendmsg(). +// +// On Android, child processes are created as a service instead of +// forking the parent process. The Android Binder service is used to +// transport the IPC channel file descriptor to the child process. +// So rather than re-mapping the file descriptor to a known value, +// the received channel file descriptor is set by calling +// SetClientChannelFd before gecko has been initialized and started +// in the child process. + +//------------------------------------------------------------------------------ +namespace { + +// This is the file descriptor number that a client process expects to find its +// IPC socket. +static int gClientChannelFd = +#if defined(MOZ_WIDGET_ANDROID) + // On android the fd is set at the time of child creation. + -1 +#else + 3 +#endif // defined(MOZ_WIDGET_ANDROID) + ; + +//------------------------------------------------------------------------------ +const size_t kMaxPipeNameLength = sizeof(((sockaddr_un*)0)->sun_path); + +bool SetCloseOnExec(int fd) { + int flags = fcntl(fd, F_GETFD); + if (flags == -1) return false; + + flags |= FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) return false; + + return true; +} + +bool ErrorIsBrokenPipe(int err) { return err == EPIPE || err == ECONNRESET; } + +// Some Android ARM64 devices appear to have a bug where sendmsg +// sometimes returns 0xFFFFFFFF, which we're assuming is a -1 that was +// incorrectly truncated to 32-bit and then zero-extended. +// See bug 1660826 for details. +// +// This is a workaround to detect that value and replace it with -1 +// (and check that there really was an error), because the largest +// amount we'll ever write is Channel::kMaximumMessageSize (256MiB). +// +// The workaround is also enabled on x86_64 Android on debug builds, +// although the bug isn't known to manifest there, so that there will +// be some CI coverage of this code. + +static inline ssize_t corrected_sendmsg(int socket, + const struct msghdr* message, + int flags) { +#if defined(ANDROID) && \ + (defined(__aarch64__) || (defined(DEBUG) && defined(__x86_64__))) + static constexpr auto kBadValue = static_cast<ssize_t>(0xFFFFFFFF); + static_assert(kBadValue > 0); + +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + errno = 0; +# endif + ssize_t bytes_written = sendmsg(socket, message, flags); + if (bytes_written == kBadValue) { + MOZ_DIAGNOSTIC_ASSERT(errno != 0); + bytes_written = -1; + } + MOZ_DIAGNOSTIC_ASSERT(bytes_written < kBadValue); + return bytes_written; +#else + return sendmsg(socket, message, flags); +#endif +} + +} // namespace +//------------------------------------------------------------------------------ + +#if defined(MOZ_WIDGET_ANDROID) +void Channel::SetClientChannelFd(int fd) { gClientChannelFd = fd; } +#endif // defined(MOZ_WIDGET_ANDROID) + +Channel::ChannelImpl::ChannelImpl(const ChannelId& channel_id, Mode mode, + Listener* listener) + : factory_(this) { + Init(mode, listener); + + if (!CreatePipe(mode)) { + CHROMIUM_LOG(WARNING) << "Unable to create pipe in " + << (mode == MODE_SERVER ? "server" : "client") + << " mode error(" << strerror(errno) << ")."; + closed_ = true; + return; + } + + EnqueueHelloMessage(); +} + +Channel::ChannelImpl::ChannelImpl(int fd, Mode mode, Listener* listener) + : factory_(this) { + Init(mode, listener); + pipe_ = fd; + waiting_connect_ = (MODE_SERVER == mode); + + EnqueueHelloMessage(); +} + +void Channel::ChannelImpl::Init(Mode mode, Listener* listener) { + // Verify that we fit in a "quantum-spaced" jemalloc bucket. + static_assert(sizeof(*this) <= 512, "Exceeded expected size class"); + + DCHECK(kControlBufferHeaderSize >= CMSG_SPACE(0)); + + mode_ = mode; + is_blocked_on_write_ = false; + partial_write_iter_.reset(); + input_buf_offset_ = 0; + input_buf_ = mozilla::MakeUnique<char[]>(Channel::kReadBufferSize); + input_cmsg_buf_ = mozilla::MakeUnique<char[]>(kControlBufferSize); + server_listen_pipe_ = -1; + pipe_ = -1; + client_pipe_ = -1; + listener_ = listener; + waiting_connect_ = true; + processing_incoming_ = false; + closed_ = false; +#if defined(OS_MACOSX) + last_pending_fd_id_ = 0; +#endif + output_queue_length_ = 0; +} + +bool Channel::ChannelImpl::CreatePipe(Mode mode) { + DCHECK(server_listen_pipe_ == -1 && pipe_ == -1); + + if (mode == MODE_SERVER) { + // socketpair() + int pipe_fds[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds) != 0) { + mozilla::ipc::AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreatePipeSocketPairErrno, errno); + return false; + } + // Set both ends to be non-blocking. + if (fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK) == -1 || + fcntl(pipe_fds[1], F_SETFL, O_NONBLOCK) == -1) { + mozilla::ipc::AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreatePipeFcntlErrno, errno); + IGNORE_EINTR(close(pipe_fds[0])); + IGNORE_EINTR(close(pipe_fds[1])); + return false; + } + + if (!SetCloseOnExec(pipe_fds[0]) || !SetCloseOnExec(pipe_fds[1])) { + mozilla::ipc::AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreatePipeCloExecErrno, errno); + IGNORE_EINTR(close(pipe_fds[0])); + IGNORE_EINTR(close(pipe_fds[1])); + return false; + } + + pipe_ = pipe_fds[0]; + client_pipe_ = pipe_fds[1]; + } else { + static mozilla::Atomic<bool> consumed(false); + CHECK(!consumed.exchange(true)) + << "child process main channel can be created only once"; + pipe_ = gClientChannelFd; + waiting_connect_ = false; + } + + return true; +} + +/** + * Reset the file descriptor for communication with the peer. + */ +void Channel::ChannelImpl::ResetFileDescriptor(int fd) { + NS_ASSERTION(fd > 0 && fd == pipe_, "Invalid file descriptor"); + + EnqueueHelloMessage(); +} + +bool Channel::ChannelImpl::EnqueueHelloMessage() { + mozilla::UniquePtr<Message> msg( + new Message(MSG_ROUTING_NONE, HELLO_MESSAGE_TYPE)); + if (!msg->WriteInt(base::GetCurrentProcId())) { + Close(); + return false; + } + + OutputQueuePush(std::move(msg)); + return true; +} + +bool Channel::ChannelImpl::Connect() { + if (pipe_ == -1) { + return false; + } + + MessageLoopForIO::current()->WatchFileDescriptor( + pipe_, true, MessageLoopForIO::WATCH_READ, &read_watcher_, this); + waiting_connect_ = false; + + if (!waiting_connect_) return ProcessOutgoingMessages(); + return true; +} + +bool Channel::ChannelImpl::ProcessIncomingMessages() { + struct msghdr msg = {0}; + struct iovec iov; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = input_cmsg_buf_.get(); + + for (;;) { + msg.msg_controllen = kControlBufferSize; + + if (pipe_ == -1) return false; + + // In some cases the beginning of a message will be stored in input_buf_. We + // don't want to overwrite that, so we store the new data after it. + iov.iov_base = input_buf_.get() + input_buf_offset_; + iov.iov_len = Channel::kReadBufferSize - input_buf_offset_; + + // Read from pipe. + // recvmsg() returns 0 if the connection has closed or EAGAIN if no data + // is waiting on the pipe. + ssize_t bytes_read = HANDLE_EINTR(recvmsg(pipe_, &msg, MSG_DONTWAIT)); + + if (bytes_read < 0) { + if (errno == EAGAIN) { + return true; + } else { + if (!ErrorIsBrokenPipe(errno)) { + CHROMIUM_LOG(ERROR) + << "pipe error (fd " << pipe_ << "): " << strerror(errno); + } + return false; + } + } else if (bytes_read == 0) { + // The pipe has closed... + Close(); + return false; + } + DCHECK(bytes_read); + + // a pointer to an array of |num_wire_fds| file descriptors from the read + const int* wire_fds = NULL; + unsigned num_wire_fds = 0; + + // walk the list of control messages and, if we find an array of file + // descriptors, save a pointer to the array + + // This next if statement is to work around an OSX issue where + // CMSG_FIRSTHDR will return non-NULL in the case that controllen == 0. + // Here's a test case: + // + // int main() { + // struct msghdr msg; + // msg.msg_control = &msg; + // msg.msg_controllen = 0; + // if (CMSG_FIRSTHDR(&msg)) + // printf("Bug found!\n"); + // } + if (msg.msg_controllen > 0) { + // On OSX, CMSG_FIRSTHDR doesn't handle the case where controllen is 0 + // and will return a pointer into nowhere. + for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK(payload_len % sizeof(int) == 0); + wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + num_wire_fds = payload_len / 4; + + if (msg.msg_flags & MSG_CTRUNC) { + CHROMIUM_LOG(ERROR) + << "SCM_RIGHTS message was truncated" + << " cmsg_len:" << cmsg->cmsg_len << " fd:" << pipe_; + for (unsigned i = 0; i < num_wire_fds; ++i) + IGNORE_EINTR(close(wire_fds[i])); + return false; + } + break; + } + } + } + + // Process messages from input buffer. + const char* p = input_buf_.get(); + const char* end = input_buf_.get() + input_buf_offset_ + bytes_read; + + // A pointer to an array of |num_fds| file descriptors which includes any + // fds that have spilled over from a previous read. + const int* fds; + unsigned num_fds; + unsigned fds_i = 0; // the index of the first unused descriptor + + if (input_overflow_fds_.empty()) { + fds = wire_fds; + num_fds = num_wire_fds; + } else { + // This code may look like a no-op in the case where + // num_wire_fds == 0, but in fact: + // + // 1. wire_fds will be nullptr, so passing it to memcpy is + // undefined behavior according to the C standard, even though + // the memcpy length is 0. + // + // 2. prev_size will be an out-of-bounds index for + // input_overflow_fds_; this is undefined behavior according to + // the C++ standard, even though the element only has its + // pointer taken and isn't accessed (and the corresponding + // operation on a C array would be defined). + // + // UBSan makes #1 a fatal error, and assertions in libstdc++ do + // the same for #2 if enabled. + if (num_wire_fds > 0) { + const size_t prev_size = input_overflow_fds_.size(); + input_overflow_fds_.resize(prev_size + num_wire_fds); + memcpy(&input_overflow_fds_[prev_size], wire_fds, + num_wire_fds * sizeof(int)); + } + fds = &input_overflow_fds_[0]; + num_fds = input_overflow_fds_.size(); + } + + // The data for the message we're currently reading consists of any data + // stored in incoming_message_ followed by data in input_buf_ (followed by + // other messages). + + while (p < end) { + // Try to figure out how big the message is. Size is 0 if we haven't read + // enough of the header to know the size. + uint32_t message_length = 0; + if (incoming_message_.isSome()) { + message_length = incoming_message_.ref().size(); + } else { + message_length = Message::MessageSize(p, end); + } + + if (!message_length) { + // We haven't seen the full message header. + MOZ_ASSERT(incoming_message_.isNothing()); + + // Move everything we have to the start of the buffer. We'll finish + // reading this message when we get more data. For now we leave it in + // input_buf_. + memmove(input_buf_.get(), p, end - p); + input_buf_offset_ = end - p; + + break; + } + + input_buf_offset_ = 0; + + bool partial; + if (incoming_message_.isSome()) { + // We already have some data for this message stored in + // incoming_message_. We want to append the new data there. + Message& m = incoming_message_.ref(); + + // How much data from this message remains to be added to + // incoming_message_? + MOZ_DIAGNOSTIC_ASSERT(message_length > m.CurrentSize()); + uint32_t remaining = message_length - m.CurrentSize(); + + // How much data from this message is stored in input_buf_? + uint32_t in_buf = std::min(remaining, uint32_t(end - p)); + + m.InputBytes(p, in_buf); + p += in_buf; + + // Are we done reading this message? + partial = in_buf != remaining; + } else { + // How much data from this message is stored in input_buf_? + uint32_t in_buf = std::min(message_length, uint32_t(end - p)); + + incoming_message_.emplace(p, in_buf); + p += in_buf; + + // Are we done reading this message? + partial = in_buf != message_length; + } + + if (partial) { + break; + } + + Message& m = incoming_message_.ref(); + + if (m.header()->num_fds) { + // the message has file descriptors + const char* error = NULL; + if (m.header()->num_fds > num_fds - fds_i) { + // the message has been completely received, but we didn't get + // enough file descriptors. + error = "Message needs unreceived descriptors"; + } + + if (m.header()->num_fds > + FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE) { + // There are too many descriptors in this message + error = "Message requires an excessive number of descriptors"; + } + + if (error) { + CHROMIUM_LOG(WARNING) + << error << " channel:" << this << " message-type:" << m.type() + << " header()->num_fds:" << m.header()->num_fds + << " num_fds:" << num_fds << " fds_i:" << fds_i; + // close the existing file descriptors so that we don't leak them + for (unsigned i = fds_i; i < num_fds; ++i) + IGNORE_EINTR(close(fds[i])); + input_overflow_fds_.clear(); + // abort the connection + return false; + } + +#if defined(OS_MACOSX) + // Send a message to the other side, indicating that we are now + // responsible for closing the descriptor. + auto fdAck = mozilla::MakeUnique<Message>(MSG_ROUTING_NONE, + RECEIVED_FDS_MESSAGE_TYPE); + DCHECK(m.fd_cookie() != 0); + fdAck->set_fd_cookie(m.fd_cookie()); + OutputQueuePush(std::move(fdAck)); +#endif + + m.file_descriptor_set()->SetDescriptors(&fds[fds_i], + m.header()->num_fds); + fds_i += m.header()->num_fds; + } + + // Note: We set other_pid_ below when we receive a Hello message (which + // has no routing ID), but we only emit a profiler marker for messages + // with a routing ID, so there's no conflict here. + AddIPCProfilerMarker(m, other_pid_, MessageDirection::eReceiving, + MessagePhase::TransferEnd); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "received message on channel @" << this << " with type " + << m.type(); +#endif + + if (m.routing_id() == MSG_ROUTING_NONE && + m.type() == HELLO_MESSAGE_TYPE) { + // The Hello message contains only the process id. + other_pid_ = MessageIterator(m).NextInt(); + listener_->OnChannelConnected(other_pid_); +#if defined(OS_MACOSX) + } else if (m.routing_id() == MSG_ROUTING_NONE && + m.type() == RECEIVED_FDS_MESSAGE_TYPE) { + DCHECK(m.fd_cookie() != 0); + CloseDescriptors(m.fd_cookie()); +#endif + } else { + mozilla::LogIPCMessage::Run run(&m); + listener_->OnMessageReceived(std::move(m)); + } + + incoming_message_.reset(); + } + + input_overflow_fds_ = std::vector<int>(&fds[fds_i], &fds[num_fds]); + + // When the input data buffer is empty, the overflow fds should be too. If + // this is not the case, we probably have a rogue renderer which is trying + // to fill our descriptor table. + if (incoming_message_.isNothing() && input_buf_offset_ == 0 && + !input_overflow_fds_.empty()) { + // We close these descriptors in Close() + return false; + } + } + + return true; +} + +bool Channel::ChannelImpl::ProcessOutgoingMessages() { + DCHECK(!waiting_connect_); // Why are we trying to send messages if there's + // no connection? + is_blocked_on_write_ = false; + + if (output_queue_.IsEmpty()) return true; + + if (pipe_ == -1) return false; + + // Write out all the messages we can till the write blocks or there are no + // more outgoing messages. + while (!output_queue_.IsEmpty()) { +#ifdef FUZZING + mozilla::ipc::Faulty::instance().MaybeCollectAndClosePipe(pipe_); +#endif + Message* msg = output_queue_.FirstElement().get(); + + struct msghdr msgh = {0}; + + static const int tmp = + CMSG_SPACE(sizeof(int[FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE])); + char buf[tmp]; + + if (partial_write_iter_.isNothing()) { + Pickle::BufferList::IterImpl iter(msg->Buffers()); + MOZ_DIAGNOSTIC_ASSERT(!iter.Done(), "empty message"); + partial_write_iter_.emplace(iter); + } + + if (partial_write_iter_.ref().Done()) { + MOZ_DIAGNOSTIC_ASSERT(false, "partial_write_iter_ should not be null"); + // report a send error to our caller, which will close the channel. + return false; + } + + if (partial_write_iter_.value().Data() == msg->Buffers().Start()) { + AddIPCProfilerMarker(*msg, other_pid_, MessageDirection::eSending, + MessagePhase::TransferStart); + + if (!msg->file_descriptor_set()->empty()) { + // This is the first chunk of a message which has descriptors to send + struct cmsghdr* cmsg; + const unsigned num_fds = msg->file_descriptor_set()->size(); + + if (num_fds > FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE) { + MOZ_DIAGNOSTIC_ASSERT(false, "Too many file descriptors!"); + CHROMIUM_LOG(FATAL) << "Too many file descriptors!"; + // This should not be reached. + return false; + } + + msgh.msg_control = buf; + msgh.msg_controllen = CMSG_SPACE(sizeof(int) * num_fds); + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds); + msg->file_descriptor_set()->GetDescriptors( + reinterpret_cast<int*>(CMSG_DATA(cmsg))); + msgh.msg_controllen = cmsg->cmsg_len; + + msg->header()->num_fds = num_fds; +#if defined(OS_MACOSX) + msg->set_fd_cookie(++last_pending_fd_id_); +#endif + } + } + + struct iovec iov[kMaxIOVecSize]; + size_t iov_count = 0; + size_t amt_to_write = 0; + + // How much of this message have we written so far? + Pickle::BufferList::IterImpl iter = partial_write_iter_.value(); + + // Store the unwritten part of the first segment to write into the iovec. + iov[0].iov_base = const_cast<char*>(iter.Data()); + iov[0].iov_len = iter.RemainingInSegment(); + amt_to_write += iov[0].iov_len; + iter.Advance(msg->Buffers(), iov[0].iov_len); + iov_count++; + + // Store remaining segments to write into iovec. + while (!iter.Done()) { + char* data = iter.Data(); + size_t size = iter.RemainingInSegment(); + + // Don't add more than kMaxIOVecSize to the iovec so that we avoid + // OS-dependent limits. + if (iov_count < kMaxIOVecSize) { + iov[iov_count].iov_base = data; + iov[iov_count].iov_len = size; + iov_count++; + } + amt_to_write += size; + iter.Advance(msg->Buffers(), size); + } + + msgh.msg_iov = iov; + msgh.msg_iovlen = iov_count; + + ssize_t bytes_written = + HANDLE_EINTR(corrected_sendmsg(pipe_, &msgh, MSG_DONTWAIT)); + +#if !defined(OS_MACOSX) + // On OSX CommitAll gets called later, once we get the + // RECEIVED_FDS_MESSAGE_TYPE message. + if (bytes_written > 0) msg->file_descriptor_set()->CommitAll(); +#endif + + if (bytes_written < 0) { + switch (errno) { + case EAGAIN: + // Not an error; the sendmsg would have blocked, so return to the + // event loop and try again later. + break; +#if defined(OS_MACOSX) || defined(OS_NETBSD) + // (Note: this comment is copied from https://crrev.com/86c3d9ef4fdf6; + // see also bug 1142693 comment #73.) + // + // On OS X if sendmsg() is trying to send fds between processes and + // there isn't enough room in the output buffer to send the fd + // structure over atomically then EMSGSIZE is returned. The same + // applies to NetBSD as well. + // + // EMSGSIZE presents a problem since the system APIs can only call us + // when there's room in the socket buffer and not when there is + // "enough" room. + // + // The current behavior is to return to the event loop when EMSGSIZE + // is received and hopefull service another FD. This is however still + // technically a busy wait since the event loop will call us right + // back until the receiver has read enough data to allow passing the + // FD over atomically. + case EMSGSIZE: + // Because this is likely to result in a busy-wait, we'll try to make + // it easier for the receiver to make progress. + sched_yield(); + break; +#endif + default: + if (!ErrorIsBrokenPipe(errno)) { + CHROMIUM_LOG(ERROR) << "pipe error: " << strerror(errno); + } + return false; + } + } + + if (static_cast<size_t>(bytes_written) != amt_to_write) { + // If write() fails with EAGAIN then bytes_written will be -1. + if (bytes_written > 0) { + MOZ_DIAGNOSTIC_ASSERT(static_cast<size_t>(bytes_written) < + amt_to_write); + partial_write_iter_.ref().AdvanceAcrossSegments(msg->Buffers(), + bytes_written); + // We should not hit the end of the buffer. + MOZ_DIAGNOSTIC_ASSERT(!partial_write_iter_.ref().Done()); + } + + // Tell libevent to call us back once things are unblocked. + is_blocked_on_write_ = true; + MessageLoopForIO::current()->WatchFileDescriptor( + pipe_, + false, // One shot + MessageLoopForIO::WATCH_WRITE, &write_watcher_, this); + return true; + } else { + partial_write_iter_.reset(); + +#if defined(OS_MACOSX) + if (!msg->file_descriptor_set()->empty()) + pending_fds_.push_back( + PendingDescriptors(msg->fd_cookie(), msg->file_descriptor_set())); +#endif + + // Message sent OK! + + AddIPCProfilerMarker(*msg, other_pid_, MessageDirection::eSending, + MessagePhase::TransferEnd); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sent message @" << msg << " on channel @" << this + << " with type " << msg->type(); +#endif + OutputQueuePop(); + // msg has been destroyed, so clear the dangling reference. + msg = nullptr; + } + } + return true; +} + +bool Channel::ChannelImpl::Send(mozilla::UniquePtr<Message> message) { +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sending message @" << message.get() << " on channel @" << this + << " with type " << message->type() << " (" + << output_queue_.Count() << " in queue)"; +#endif + +#ifdef FUZZING + message = mozilla::ipc::Faulty::instance().MutateIPCMessage( + "Channel::ChannelImpl::Send", std::move(message)); +#endif + + // If the channel has been closed, ProcessOutgoingMessages() is never going + // to pop anything off output_queue; output_queue will only get emptied when + // the channel is destructed. We might as well delete message now, instead + // of waiting for the channel to be destructed. + if (closed_) { + if (mozilla::ipc::LoggingEnabled()) { + fprintf(stderr, + "Can't send message %s, because this channel is closed.\n", + message->name()); + } + return false; + } + + OutputQueuePush(std::move(message)); + if (!waiting_connect_) { + if (!is_blocked_on_write_) { + if (!ProcessOutgoingMessages()) return false; + } + } + + return true; +} + +void Channel::ChannelImpl::GetClientFileDescriptorMapping(int* src_fd, + int* dest_fd) const { + DCHECK(mode_ == MODE_SERVER); + *src_fd = client_pipe_; + *dest_fd = gClientChannelFd; +} + +void Channel::ChannelImpl::CloseClientFileDescriptor() { + if (client_pipe_ != -1) { + IGNORE_EINTR(close(client_pipe_)); + client_pipe_ = -1; + } +} + +// Called by libevent when we can read from th pipe without blocking. +void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { + if (!waiting_connect_ && fd == pipe_) { + if (!ProcessIncomingMessages()) { + Close(); + listener_->OnChannelError(); + // The OnChannelError() call may delete this, so we need to exit now. + return; + } + } +} + +#if defined(OS_MACOSX) +void Channel::ChannelImpl::CloseDescriptors(uint32_t pending_fd_id) { + DCHECK(pending_fd_id != 0); + for (std::list<PendingDescriptors>::iterator i = pending_fds_.begin(); + i != pending_fds_.end(); i++) { + if ((*i).id == pending_fd_id) { + (*i).fds->CommitAll(); + pending_fds_.erase(i); + return; + } + } + DCHECK(false) << "pending_fd_id not in our list!"; +} +#endif + +void Channel::ChannelImpl::OutputQueuePush(mozilla::UniquePtr<Message> msg) { + mozilla::LogIPCMessage::LogDispatchWithPid(msg.get(), other_pid_); + + MOZ_DIAGNOSTIC_ASSERT(!closed_); + msg->AssertAsLargeAsHeader(); + output_queue_.Push(std::move(msg)); + output_queue_length_++; +} + +void Channel::ChannelImpl::OutputQueuePop() { + // Clear any reference to the front of output_queue_ before we destroy it. + partial_write_iter_.reset(); + + mozilla::UniquePtr<Message> message = output_queue_.Pop(); + output_queue_length_--; +} + +// Called by libevent when we can write to the pipe without blocking. +void Channel::ChannelImpl::OnFileCanWriteWithoutBlocking(int fd) { + if (!ProcessOutgoingMessages()) { + Close(); + listener_->OnChannelError(); + } +} + +void Channel::ChannelImpl::Close() { + // Close can be called multiple times, so we need to make sure we're + // idempotent. + + // Unregister libevent for the listening socket and close it. + server_listen_connection_watcher_.StopWatchingFileDescriptor(); + + if (server_listen_pipe_ != -1) { + IGNORE_EINTR(close(server_listen_pipe_)); + server_listen_pipe_ = -1; + } + + // Unregister libevent for the FIFO and close it. + read_watcher_.StopWatchingFileDescriptor(); + write_watcher_.StopWatchingFileDescriptor(); + if (pipe_ != -1) { + IGNORE_EINTR(close(pipe_)); + pipe_ = -1; + } + if (client_pipe_ != -1) { + IGNORE_EINTR(close(client_pipe_)); + client_pipe_ = -1; + } + + while (!output_queue_.IsEmpty()) { + OutputQueuePop(); + } + + // Close any outstanding, received file descriptors + for (std::vector<int>::iterator i = input_overflow_fds_.begin(); + i != input_overflow_fds_.end(); ++i) { + IGNORE_EINTR(close(*i)); + } + input_overflow_fds_.clear(); + +#if defined(OS_MACOSX) + for (std::list<PendingDescriptors>::iterator i = pending_fds_.begin(); + i != pending_fds_.end(); i++) { + (*i).fds->CommitAll(); + } + pending_fds_.clear(); +#endif + + closed_ = true; +} + +bool Channel::ChannelImpl::Unsound_IsClosed() const { return closed_; } + +uint32_t Channel::ChannelImpl::Unsound_NumQueuedMessages() const { + return output_queue_length_; +} + +//------------------------------------------------------------------------------ +// Channel's methods simply call through to ChannelImpl. +Channel::Channel(const ChannelId& channel_id, Mode mode, Listener* listener) + : channel_impl_(new ChannelImpl(channel_id, mode, listener)) { + MOZ_COUNT_CTOR(IPC::Channel); +} + +Channel::Channel(int fd, Mode mode, Listener* listener) + : channel_impl_(new ChannelImpl(fd, mode, listener)) { + MOZ_COUNT_CTOR(IPC::Channel); +} + +Channel::~Channel() { + MOZ_COUNT_DTOR(IPC::Channel); + delete channel_impl_; +} + +bool Channel::Connect() { return channel_impl_->Connect(); } + +void Channel::Close() { channel_impl_->Close(); } + +Channel::Listener* Channel::set_listener(Listener* listener) { + return channel_impl_->set_listener(listener); +} + +bool Channel::Send(mozilla::UniquePtr<Message> message) { + return channel_impl_->Send(std::move(message)); +} + +void Channel::GetClientFileDescriptorMapping(int* src_fd, int* dest_fd) const { + return channel_impl_->GetClientFileDescriptorMapping(src_fd, dest_fd); +} + +void Channel::ResetFileDescriptor(int fd) { + channel_impl_->ResetFileDescriptor(fd); +} + +int Channel::GetFileDescriptor() const { + return channel_impl_->GetFileDescriptor(); +} + +void Channel::CloseClientFileDescriptor() { + channel_impl_->CloseClientFileDescriptor(); +} + +bool Channel::Unsound_IsClosed() const { + return channel_impl_->Unsound_IsClosed(); +} + +uint32_t Channel::Unsound_NumQueuedMessages() const { + return channel_impl_->Unsound_NumQueuedMessages(); +} + +// static +Channel::ChannelId Channel::GenerateVerifiedChannelID() { return {}; } + +// static +Channel::ChannelId Channel::ChannelIDForCurrentProcess() { return {}; } + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_channel_posix.h b/ipc/chromium/src/chrome/common/ipc_channel_posix.h new file mode 100644 index 0000000000..c48121f776 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_posix.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 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 CHROME_COMMON_IPC_CHANNEL_POSIX_H_ +#define CHROME_COMMON_IPC_CHANNEL_POSIX_H_ + +#include "chrome/common/ipc_channel.h" + +#include <sys/socket.h> // for CMSG macros + +#include <string> +#include <vector> +#include <list> + +#include "base/message_loop.h" +#include "base/task.h" +#include "chrome/common/file_descriptor_set_posix.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Queue.h" +#include "mozilla/UniquePtr.h" + +namespace IPC { + +// An implementation of ChannelImpl for POSIX systems that works via +// socketpairs. See the .cc file for an overview of the implementation. +class Channel::ChannelImpl : public MessageLoopForIO::Watcher { + public: + using ChannelId = Channel::ChannelId; + + // Mirror methods of Channel, see ipc_channel.h for description. + ChannelImpl(const ChannelId& channel_id, Mode mode, Listener* listener); + ChannelImpl(int fd, Mode mode, Listener* listener); + ~ChannelImpl() { Close(); } + bool Connect(); + void Close(); + Listener* set_listener(Listener* listener) { + Listener* old = listener_; + listener_ = listener; + return old; + } + bool Send(mozilla::UniquePtr<Message> message); + void GetClientFileDescriptorMapping(int* src_fd, int* dest_fd) const; + + void ResetFileDescriptor(int fd); + + int GetFileDescriptor() const { return pipe_; } + void CloseClientFileDescriptor(); + + // See the comment in ipc_channel.h for info on Unsound_IsClosed() and + // Unsound_NumQueuedMessages(). + bool Unsound_IsClosed() const; + uint32_t Unsound_NumQueuedMessages() const; + + private: + void Init(Mode mode, Listener* listener); + bool CreatePipe(Mode mode); + bool EnqueueHelloMessage(); + + bool ProcessIncomingMessages(); + bool ProcessOutgoingMessages(); + + // MessageLoopForIO::Watcher implementation. + virtual void OnFileCanReadWithoutBlocking(int fd) override; + virtual void OnFileCanWriteWithoutBlocking(int fd) override; + +#if defined(OS_MACOSX) + void CloseDescriptors(uint32_t pending_fd_id); +#endif + + void OutputQueuePush(mozilla::UniquePtr<Message> msg); + void OutputQueuePop(); + + Mode mode_; + + // After accepting one client connection on our server socket we want to + // stop listening. + MessageLoopForIO::FileDescriptorWatcher server_listen_connection_watcher_; + MessageLoopForIO::FileDescriptorWatcher read_watcher_; + MessageLoopForIO::FileDescriptorWatcher write_watcher_; + + // Indicates whether we're currently blocked waiting for a write to complete. + bool is_blocked_on_write_; + + // If sending a message blocks then we use this iterator to keep track of + // where in the message we are. It gets reset when the message is finished + // sending. + mozilla::Maybe<Pickle::BufferList::IterImpl> partial_write_iter_; + + int server_listen_pipe_; + int pipe_; + int client_pipe_; // The client end of our socketpair(). + + Listener* listener_; + + // Messages to be sent are queued here. + mozilla::Queue<mozilla::UniquePtr<Message>, 64> output_queue_; + + // We read from the pipe into these buffers. + size_t input_buf_offset_; + mozilla::UniquePtr<char[]> input_buf_; + mozilla::UniquePtr<char[]> input_cmsg_buf_; + + // The control message buffer will hold all of the file descriptors that will + // be read in during a single recvmsg call. Message::WriteFileDescriptor + // always writes one word of data for every file descriptor added to the + // message, and the number of file descriptors per message will not exceed + // MAX_DESCRIPTORS_PER_MESSAGE. + // + // This buffer also holds a control message header of size CMSG_SPACE(0) + // bytes. However, CMSG_SPACE is not a constant on Macs, so we can't use it + // here. Consequently, we pick a number here that is at least CMSG_SPACE(0) on + // all platforms. We assert at runtime, in Channel::ChannelImpl::Init, that + // it's big enough. + static constexpr size_t kControlBufferHeaderSize = 32; + static constexpr size_t kControlBufferSize = + FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE * sizeof(int) + + kControlBufferHeaderSize; + + // Large incoming messages that span multiple pipe buffers get built-up in the + // buffers of this message. + mozilla::Maybe<Message> incoming_message_; + std::vector<int> input_overflow_fds_; + + // In server-mode, we have to wait for the client to connect before we + // can begin reading. We make use of the input_state_ when performing + // the connect operation in overlapped mode. + bool waiting_connect_; + + // This flag is set when processing incoming messages. It is used to + // avoid recursing through ProcessIncomingMessages, which could cause + // problems. TODO(darin): make this unnecessary + bool processing_incoming_; + + // This flag is set after we've closed the channel. + bool closed_; + + // We keep track of the PID of the other side of this channel so that we can + // record this when generating logs of IPC messages. + int32_t other_pid_ = -1; + +#if defined(OS_MACOSX) + struct PendingDescriptors { + uint32_t id; + RefPtr<FileDescriptorSet> fds; + + PendingDescriptors() : id(0) {} + PendingDescriptors(uint32_t id, FileDescriptorSet* fds) + : id(id), fds(fds) {} + }; + + std::list<PendingDescriptors> pending_fds_; + + // A generation ID for RECEIVED_FD messages. + uint32_t last_pending_fd_id_; +#endif + + // This variable is updated so it matches output_queue_.Count(), except we can + // read output_queue_length_ from any thread (if we're OK getting an + // occasional out-of-date or bogus value). We use output_queue_length_ to + // implement Unsound_NumQueuedMessages. + size_t output_queue_length_; + + ScopedRunnableMethodFactory<ChannelImpl> factory_; + + DISALLOW_COPY_AND_ASSIGN(ChannelImpl); +}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_POSIX_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_channel_utils.cc b/ipc/chromium/src/chrome/common/ipc_channel_utils.cc new file mode 100644 index 0000000000..150e54693e --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_utils.cc @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "chrome/common/ipc_channel_utils.h" + +#include "GeckoProfiler.h" +#include "chrome/common/ipc_message.h" + +namespace IPC { + +void AddIPCProfilerMarker(const Message& aMessage, int32_t aOtherPid, + mozilla::ipc::MessageDirection aDirection, + mozilla::ipc::MessagePhase aPhase) { +#ifdef MOZ_GECKO_PROFILER + if (aMessage.routing_id() != MSG_ROUTING_NONE && + profiler_feature_active(ProfilerFeature::IPCMessages)) { + if (aOtherPid == -1) { + DLOG(WARNING) << "Unable to record IPC profile marker, other PID not set"; + return; + } + + if (profiler_is_locked_on_current_thread()) { + // One of the profiler mutexes is locked on this thread, don't record + // markers, because we don't want to expose profiler IPCs due to the + // profiler itself, and also to avoid possible re-entrancy issues. + return; + } + + // The current timestamp must be given to the `IPCMarker` payload. + const mozilla::TimeStamp now = mozilla::TimeStamp::NowUnfuzzed(); + PROFILER_MARKER("IPC", IPC, mozilla::MarkerTiming::InstantAt(now), + IPCMarker, now, now, aOtherPid, aMessage.seqno(), + aMessage.type(), mozilla::ipc::UnknownSide, aDirection, + aPhase, aMessage.is_sync()); + } +#endif +} + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_channel_utils.h b/ipc/chromium/src/chrome/common/ipc_channel_utils.h new file mode 100644 index 0000000000..68787e0c24 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_utils.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CHROME_COMMON_IPC_CHANNEL_UTILS_H_ +#define CHROME_COMMON_IPC_CHANNEL_UTILS_H_ + +#include "chrome/common/ipc_message.h" +#include "MessageChannel.h" + +namespace IPC { + +void AddIPCProfilerMarker(const Message& aMessage, int32_t aOtherPid, + mozilla::ipc::MessageDirection aDirection, + mozilla::ipc::MessagePhase aPhase); + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_UTILS_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_channel_win.cc b/ipc/chromium/src/chrome/common/ipc_channel_win.cc new file mode 100644 index 0000000000..8dd0475735 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_win.cc @@ -0,0 +1,664 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 "chrome/common/ipc_channel_win.h" + +#include <windows.h> +#include <sstream> + +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/process_util.h" +#include "base/rand_util.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/ipc_channel_utils.h" +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/LateWriteChecks.h" +#include "nsThreadUtils.h" + +#ifdef FUZZING +# include "mozilla/ipc/Faulty.h" +#endif + +using namespace mozilla::ipc; + +// ChannelImpl is used on the IPC thread, but constructed on a different thread, +// so it has to hold the nsAutoOwningThread as a pointer, and we need a slightly +// different macro. +#ifdef DEBUG +# define ASSERT_OWNINGTHREAD(_class) \ + if (nsAutoOwningThread* owningThread = _mOwningThread.get()) { \ + owningThread->AssertOwnership(#_class " not thread-safe"); \ + } +#else +# define ASSERT_OWNINGTHREAD(_class) ((void)0) +#endif + +namespace IPC { +//------------------------------------------------------------------------------ + +Channel::ChannelImpl::State::State(ChannelImpl* channel) : is_pending(false) { + memset(&context.overlapped, 0, sizeof(context.overlapped)); + context.handler = channel; +} + +Channel::ChannelImpl::State::~State() { + COMPILE_ASSERT(!offsetof(Channel::ChannelImpl::State, context), + starts_with_io_context); +} + +//------------------------------------------------------------------------------ + +Channel::ChannelImpl::ChannelImpl(const ChannelId& channel_id, Mode mode, + Listener* listener) + : ALLOW_THIS_IN_INITIALIZER_LIST(input_state_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(output_state_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)), + shared_secret_(0), + waiting_for_shared_secret_(false) { + Init(mode, listener); + + if (!CreatePipe(channel_id, mode)) { + // The pipe may have been closed already. + CHROMIUM_LOG(WARNING) << "Unable to create pipe named \"" << channel_id + << "\" in " << (mode == 0 ? "server" : "client") + << " mode."; + } +} + +Channel::ChannelImpl::ChannelImpl(const ChannelId& channel_id, + HANDLE server_pipe, Mode mode, + Listener* listener) + : ALLOW_THIS_IN_INITIALIZER_LIST(input_state_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(output_state_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)), + shared_secret_(0), + waiting_for_shared_secret_(false) { + Init(mode, listener); + + if (mode == MODE_SERVER) { + // We don't need the pipe name because we've been passed a handle, but we do + // need to get the shared secret from the channel_id. + PipeName(channel_id, &shared_secret_); + waiting_for_shared_secret_ = !!shared_secret_; + + // Use the existing handle that was dup'd to us + pipe_ = server_pipe; + EnqueueHelloMessage(); + } else { + // Take the normal init path to connect to the server pipe + CreatePipe(channel_id, mode); + } +} + +void Channel::ChannelImpl::Init(Mode mode, Listener* listener) { + // Verify that we fit in a "quantum-spaced" jemalloc bucket. + static_assert(sizeof(*this) <= 512, "Exceeded expected size class"); + + pipe_ = INVALID_HANDLE_VALUE; + listener_ = listener; + waiting_connect_ = (mode == MODE_SERVER); + processing_incoming_ = false; + closed_ = false; + output_queue_length_ = 0; + input_buf_offset_ = 0; + input_buf_ = mozilla::MakeUnique<char[]>(Channel::kReadBufferSize); +} + +void Channel::ChannelImpl::OutputQueuePush(mozilla::UniquePtr<Message> msg) { + mozilla::LogIPCMessage::LogDispatchWithPid(msg.get(), other_pid_); + + output_queue_.Push(std::move(msg)); + output_queue_length_++; +} + +void Channel::ChannelImpl::OutputQueuePop() { + mozilla::UniquePtr<Message> message = output_queue_.Pop(); + output_queue_length_--; +} + +HANDLE Channel::ChannelImpl::GetServerPipeHandle() const { return pipe_; } + +void Channel::ChannelImpl::Close() { + ASSERT_OWNINGTHREAD(ChannelImpl); + + bool waited = false; + if (input_state_.is_pending || output_state_.is_pending) { + CancelIo(pipe_); + waited = true; + } + + // Closing the handle at this point prevents us from issuing more requests + // form OnIOCompleted(). + if (pipe_ != INVALID_HANDLE_VALUE) { + CloseHandle(pipe_); + pipe_ = INVALID_HANDLE_VALUE; + } + + while (input_state_.is_pending || output_state_.is_pending) { + MessageLoopForIO::current()->WaitForIOCompletion(INFINITE, this); + } + + while (!output_queue_.IsEmpty()) { + OutputQueuePop(); + } + +#ifdef DEBUG + _mOwningThread = nullptr; +#endif + closed_ = true; +} + +bool Channel::ChannelImpl::Send(mozilla::UniquePtr<Message> message) { + ASSERT_OWNINGTHREAD(ChannelImpl); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sending message @" << message.get() << " on channel @" << this + << " with type " << message->type() << " (" + << output_queue_.Count() << " in queue)"; +#endif + +#ifdef FUZZING + message = mozilla::ipc::Faulty::instance().MutateIPCMessage( + "Channel::ChannelImpl::Send", std::move(message)); +#endif + + if (closed_) { + if (mozilla::ipc::LoggingEnabled()) { + fprintf(stderr, + "Can't send message %s, because this channel is closed.\n", + message->name()); + } + return false; + } + + OutputQueuePush(std::move(message)); + // ensure waiting to write + if (!waiting_connect_) { + if (!output_state_.is_pending) { + if (!ProcessOutgoingMessages(NULL, 0)) return false; + } + } + + return true; +} + +const Channel::ChannelId Channel::ChannelImpl::PipeName( + const ChannelId& channel_id, int32_t* secret) const { + MOZ_ASSERT(secret); + + std::wostringstream ss; + ss << L"\\\\.\\pipe\\chrome."; + + // Prevent the shared secret from ending up in the pipe name. + size_t index = channel_id.find_first_of(L'\\'); + if (index != std::string::npos) { + StringToInt(channel_id.substr(index + 1), secret); + ss << channel_id.substr(0, index - 1); + } else { + // This case is here to support predictable named pipes in tests. + *secret = 0; + ss << channel_id; + } + return ss.str(); +} + +bool Channel::ChannelImpl::CreatePipe(const ChannelId& channel_id, Mode mode) { + DCHECK(pipe_ == INVALID_HANDLE_VALUE); + const ChannelId pipe_name = PipeName(channel_id, &shared_secret_); + if (mode == MODE_SERVER) { + waiting_for_shared_secret_ = !!shared_secret_; + pipe_ = CreateNamedPipeW(pipe_name.c_str(), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | + FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, // number of pipe instances + // output buffer size (XXX tune) + Channel::kReadBufferSize, + // input buffer size (XXX tune) + Channel::kReadBufferSize, + 5000, // timeout in milliseconds (XXX tune) + NULL); + } else { + pipe_ = CreateFileW( + pipe_name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION | FILE_FLAG_OVERLAPPED, + NULL); + } + if (pipe_ == INVALID_HANDLE_VALUE) { + // If this process is being closed, the pipe may be gone already. + CHROMIUM_LOG(WARNING) << "failed to create pipe: " << GetLastError(); + closed_ = true; + return false; + } + + // Create the Hello message to be sent when Connect is called + return EnqueueHelloMessage(); +} + +bool Channel::ChannelImpl::EnqueueHelloMessage() { + auto m = mozilla::MakeUnique<Message>(MSG_ROUTING_NONE, HELLO_MESSAGE_TYPE); + + // If we're waiting for our shared secret from the other end's hello message + // then don't give the game away by sending it in ours. + int32_t secret = waiting_for_shared_secret_ ? 0 : shared_secret_; + + // Also, don't send if the value is zero (for IPC backwards compatability). + if (!m->WriteInt(GetCurrentProcessId()) || + (secret && !m->WriteUInt32(secret))) { + CloseHandle(pipe_); + pipe_ = INVALID_HANDLE_VALUE; + return false; + } + + OutputQueuePush(std::move(m)); + return true; +} + +bool Channel::ChannelImpl::Connect() { +#ifdef DEBUG + if (!_mOwningThread) { + _mOwningThread = mozilla::MakeUnique<nsAutoOwningThread>(); + } +#endif + + if (pipe_ == INVALID_HANDLE_VALUE) return false; + + MessageLoopForIO::current()->RegisterIOHandler(pipe_, this); + + // Check to see if there is a client connected to our pipe... + if (waiting_connect_) { + if (!ProcessConnection()) { + return false; + } + } + + if (!input_state_.is_pending) { + // Complete setup asynchronously. By not setting input_state_.is_pending + // to true, we indicate to OnIOCompleted that this is the special + // initialization signal. + MessageLoopForIO::current()->PostTask(factory_.NewRunnableMethod( + &Channel::ChannelImpl::OnIOCompleted, &input_state_.context, 0, 0)); + } + + if (!waiting_connect_) ProcessOutgoingMessages(NULL, 0); + return true; +} + +bool Channel::ChannelImpl::ProcessConnection() { + ASSERT_OWNINGTHREAD(ChannelImpl); + if (input_state_.is_pending) input_state_.is_pending = false; + + // Do we have a client connected to our pipe? + if (INVALID_HANDLE_VALUE == pipe_) return false; + + BOOL ok = ConnectNamedPipe(pipe_, &input_state_.context.overlapped); + + DWORD err = GetLastError(); + if (ok) { + // Uhm, the API documentation says that this function should never + // return success when used in overlapped mode. + NOTREACHED(); + return false; + } + + switch (err) { + case ERROR_IO_PENDING: + input_state_.is_pending = true; + break; + case ERROR_PIPE_CONNECTED: + waiting_connect_ = false; + break; + case ERROR_NO_DATA: + // The pipe is being closed. + return false; + default: + NOTREACHED(); + return false; + } + + return true; +} + +bool Channel::ChannelImpl::ProcessIncomingMessages( + MessageLoopForIO::IOContext* context, DWORD bytes_read) { + ASSERT_OWNINGTHREAD(ChannelImpl); + if (input_state_.is_pending) { + input_state_.is_pending = false; + DCHECK(context); + + if (!context || !bytes_read) return false; + } else { + // This happens at channel initialization. + DCHECK(!bytes_read && context == &input_state_.context); + } + + for (;;) { + if (bytes_read == 0) { + if (INVALID_HANDLE_VALUE == pipe_) return false; + + // Read from pipe... + BOOL ok = ReadFile(pipe_, input_buf_.get() + input_buf_offset_, + Channel::kReadBufferSize - input_buf_offset_, + &bytes_read, &input_state_.context.overlapped); + if (!ok) { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) { + input_state_.is_pending = true; + return true; + } + if (err != ERROR_BROKEN_PIPE) { + CHROMIUM_LOG(ERROR) << "pipe error: " << err; + } + return false; + } + input_state_.is_pending = true; + return true; + } + DCHECK(bytes_read); + + // Process messages from input buffer. + + const char* p = input_buf_.get(); + const char* end = input_buf_.get() + input_buf_offset_ + bytes_read; + + while (p < end) { + // Try to figure out how big the message is. Size is 0 if we haven't read + // enough of the header to know the size. + uint32_t message_length = 0; + if (incoming_message_.isSome()) { + message_length = incoming_message_.ref().size(); + } else { + message_length = Message::MessageSize(p, end); + } + + if (!message_length) { + // We haven't seen the full message header. + MOZ_ASSERT(incoming_message_.isNothing()); + + // Move everything we have to the start of the buffer. We'll finish + // reading this message when we get more data. For now we leave it in + // input_buf_. + memmove(input_buf_.get(), p, end - p); + input_buf_offset_ = end - p; + + break; + } + + input_buf_offset_ = 0; + + bool partial; + if (incoming_message_.isSome()) { + // We already have some data for this message stored in + // incoming_message_. We want to append the new data there. + Message& m = incoming_message_.ref(); + + // How much data from this message remains to be added to + // incoming_message_? + MOZ_ASSERT(message_length > m.CurrentSize()); + uint32_t remaining = message_length - m.CurrentSize(); + + // How much data from this message is stored in input_buf_? + uint32_t in_buf = std::min(remaining, uint32_t(end - p)); + + m.InputBytes(p, in_buf); + p += in_buf; + + // Are we done reading this message? + partial = in_buf != remaining; + } else { + // How much data from this message is stored in input_buf_? + uint32_t in_buf = std::min(message_length, uint32_t(end - p)); + + incoming_message_.emplace(p, in_buf); + p += in_buf; + + // Are we done reading this message? + partial = in_buf != message_length; + } + + if (partial) { + break; + } + + Message& m = incoming_message_.ref(); + + // Note: We set other_pid_ below when we receive a Hello message (which + // has no routing ID), but we only emit a profiler marker for messages + // with a routing ID, so there's no conflict here. + AddIPCProfilerMarker(m, other_pid_, MessageDirection::eReceiving, + MessagePhase::TransferStart); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "received message on channel @" << this << " with type " + << m.type(); +#endif + if (m.routing_id() == MSG_ROUTING_NONE && + m.type() == HELLO_MESSAGE_TYPE) { + // The Hello message contains the process id and must include the + // shared secret, if we are waiting for it. + MessageIterator it = MessageIterator(m); + other_pid_ = it.NextInt(); + if (waiting_for_shared_secret_ && (it.NextInt() != shared_secret_)) { + NOTREACHED(); + // Something went wrong. Abort connection. + Close(); + listener_->OnChannelError(); + return false; + } + waiting_for_shared_secret_ = false; + listener_->OnChannelConnected(other_pid_); + } else { + mozilla::LogIPCMessage::Run run(&m); + listener_->OnMessageReceived(std::move(m)); + } + + incoming_message_.reset(); + } + + bytes_read = 0; // Get more data. + } + + return true; +} + +bool Channel::ChannelImpl::ProcessOutgoingMessages( + MessageLoopForIO::IOContext* context, DWORD bytes_written) { + DCHECK(!waiting_connect_); // Why are we trying to send messages if there's + // no connection? + ASSERT_OWNINGTHREAD(ChannelImpl); + + if (output_state_.is_pending) { + DCHECK(context); + output_state_.is_pending = false; + if (!context || bytes_written == 0) { + DWORD err = GetLastError(); + if (err != ERROR_BROKEN_PIPE) { + CHROMIUM_LOG(ERROR) << "pipe error: " << err; + } + return false; + } + // Message was sent. + DCHECK(!output_queue_.IsEmpty()); + Message* m = output_queue_.FirstElement().get(); + + MOZ_RELEASE_ASSERT(partial_write_iter_.isSome()); + Pickle::BufferList::IterImpl& iter = partial_write_iter_.ref(); + iter.Advance(m->Buffers(), bytes_written); + if (iter.Done()) { + AddIPCProfilerMarker(*m, other_pid_, MessageDirection::eSending, + MessagePhase::TransferEnd); + + partial_write_iter_.reset(); + OutputQueuePop(); + // m has been destroyed, so clear the dangling reference. + m = nullptr; + } + } + + if (output_queue_.IsEmpty()) return true; + + if (INVALID_HANDLE_VALUE == pipe_) return false; + + // Write to pipe... + Message* m = output_queue_.FirstElement().get(); + + if (partial_write_iter_.isNothing()) { + Pickle::BufferList::IterImpl iter(m->Buffers()); + partial_write_iter_.emplace(iter); + } + + Pickle::BufferList::IterImpl& iter = partial_write_iter_.ref(); + + AddIPCProfilerMarker(*m, other_pid_, MessageDirection::eSending, + MessagePhase::TransferStart); + + // Don't count this write for the purposes of late write checking. If this + // message results in a legitimate file write, that will show up when it + // happens. + mozilla::PushSuspendLateWriteChecks(); + BOOL ok = WriteFile(pipe_, iter.Data(), iter.RemainingInSegment(), + &bytes_written, &output_state_.context.overlapped); + mozilla::PopSuspendLateWriteChecks(); + + if (!ok) { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) { + output_state_.is_pending = true; + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sent pending message @" << m << " on channel @" << this + << " with type " << m->type(); +#endif + + return true; + } + if (err != ERROR_BROKEN_PIPE) { + CHROMIUM_LOG(ERROR) << "pipe error: " << err; + } + return false; + } + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sent message @" << m << " on channel @" << this + << " with type " << m->type(); +#endif + + output_state_.is_pending = true; + return true; +} + +void Channel::ChannelImpl::OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error) { + bool ok; + ASSERT_OWNINGTHREAD(ChannelImpl); + if (context == &input_state_.context) { + if (waiting_connect_) { + if (!ProcessConnection()) return; + // We may have some messages queued up to send... + if (!output_queue_.IsEmpty() && !output_state_.is_pending) + ProcessOutgoingMessages(NULL, 0); + if (input_state_.is_pending) return; + // else, fall-through and look for incoming messages... + } + // we don't support recursion through OnMessageReceived yet! + DCHECK(!processing_incoming_); + processing_incoming_ = true; + ok = ProcessIncomingMessages(context, bytes_transfered); + processing_incoming_ = false; + } else { + DCHECK(context == &output_state_.context); + ok = ProcessOutgoingMessages(context, bytes_transfered); + } + if (!ok && INVALID_HANDLE_VALUE != pipe_) { + // We don't want to re-enter Close(). + Close(); + listener_->OnChannelError(); + } +} + +bool Channel::ChannelImpl::Unsound_IsClosed() const { return closed_; } + +uint32_t Channel::ChannelImpl::Unsound_NumQueuedMessages() const { + return output_queue_length_; +} + +//------------------------------------------------------------------------------ +// Channel's methods simply call through to ChannelImpl. +Channel::Channel(const ChannelId& channel_id, Mode mode, Listener* listener) + : channel_impl_(new ChannelImpl(channel_id, mode, listener)) { + MOZ_COUNT_CTOR(IPC::Channel); +} + +Channel::Channel(const ChannelId& channel_id, void* server_pipe, Mode mode, + Listener* listener) + : channel_impl_(new ChannelImpl(channel_id, server_pipe, mode, listener)) { + MOZ_COUNT_CTOR(IPC::Channel); +} + +Channel::~Channel() { + MOZ_COUNT_DTOR(IPC::Channel); + delete channel_impl_; +} + +bool Channel::Connect() { return channel_impl_->Connect(); } + +void Channel::Close() { channel_impl_->Close(); } + +void* Channel::GetServerPipeHandle() const { + return channel_impl_->GetServerPipeHandle(); +} + +Channel::Listener* Channel::set_listener(Listener* listener) { + return channel_impl_->set_listener(listener); +} + +bool Channel::Send(mozilla::UniquePtr<Message> message) { + return channel_impl_->Send(std::move(message)); +} + +bool Channel::Unsound_IsClosed() const { + return channel_impl_->Unsound_IsClosed(); +} + +uint32_t Channel::Unsound_NumQueuedMessages() const { + return channel_impl_->Unsound_NumQueuedMessages(); +} + +namespace { + +// Global atomic used to guarantee channel IDs are unique. +mozilla::Atomic<int> g_last_id; + +} // namespace + +// static +Channel::ChannelId Channel::GenerateVerifiedChannelID() { + // Windows pipes can be enumerated by low-privileged processes. So, we + // append a strong random value after the \ character. This value is not + // included in the pipe name, but sent as part of the client hello, to + // prevent hijacking the pipe name to spoof the client. + int secret; + do { // Guarantee we get a non-zero value. + secret = base::RandInt(0, std::numeric_limits<int>::max()); + } while (secret == 0); + return StringPrintf(L"%d.%u.%d\\%d", base::GetCurrentProcId(), g_last_id++, + base::RandInt(0, std::numeric_limits<int32_t>::max()), + secret); +} + +// static +Channel::ChannelId Channel::ChannelIDForCurrentProcess() { + return CommandLine::ForCurrentProcess()->GetSwitchValue( + switches::kProcessChannelID); +} + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_channel_win.h b/ipc/chromium/src/chrome/common/ipc_channel_win.h new file mode 100644 index 0000000000..ac21c16b8c --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_win.h @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 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 CHROME_COMMON_IPC_CHANNEL_WIN_H_ +#define CHROME_COMMON_IPC_CHANNEL_WIN_H_ + +#include "chrome/common/ipc_channel.h" +#include "chrome/common/ipc_message.h" + +#include <string> + +#include "base/message_loop.h" +#include "base/task.h" +#include "nsISupportsImpl.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Queue.h" +#include "mozilla/UniquePtr.h" + +namespace IPC { + +class Channel::ChannelImpl : public MessageLoopForIO::IOHandler { + public: + using ChannelId = Channel::ChannelId; + + // Mirror methods of Channel, see ipc_channel.h for description. + ChannelImpl(const ChannelId& channel_id, Mode mode, Listener* listener); + ChannelImpl(const ChannelId& channel_id, HANDLE server_pipe, Mode mode, + Listener* listener); + ~ChannelImpl() { + if (pipe_ != INVALID_HANDLE_VALUE) { + Close(); + } + } + bool Connect(); + void Close(); + HANDLE GetServerPipeHandle() const; + Listener* set_listener(Listener* listener) { + Listener* old = listener_; + listener_ = listener; + return old; + } + bool Send(mozilla::UniquePtr<Message> message); + + // See the comment in ipc_channel.h for info on Unsound_IsClosed() and + // Unsound_NumQueuedMessages(). + bool Unsound_IsClosed() const; + uint32_t Unsound_NumQueuedMessages() const; + + private: + void Init(Mode mode, Listener* listener); + + void OutputQueuePush(mozilla::UniquePtr<Message> msg); + void OutputQueuePop(); + + const ChannelId PipeName(const ChannelId& channel_id, int32_t* secret) const; + bool CreatePipe(const ChannelId& channel_id, Mode mode); + bool EnqueueHelloMessage(); + + bool ProcessConnection(); + bool ProcessIncomingMessages(MessageLoopForIO::IOContext* context, + DWORD bytes_read); + bool ProcessOutgoingMessages(MessageLoopForIO::IOContext* context, + DWORD bytes_written); + + // MessageLoop::IOHandler implementation. + virtual void OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error); + + private: + struct State { + explicit State(ChannelImpl* channel); + ~State(); + MessageLoopForIO::IOContext context; + bool is_pending; + }; + + State input_state_; + State output_state_; + + HANDLE pipe_; + + Listener* listener_; + + // Messages to be sent are queued here. + mozilla::Queue<mozilla::UniquePtr<Message>, 64> output_queue_; + + // If sending a message blocks then we use this iterator to keep track of + // where in the message we are. It gets reset when the message is finished + // sending. + mozilla::Maybe<Pickle::BufferList::IterImpl> partial_write_iter_; + + // We read from the pipe into this buffer + mozilla::UniquePtr<char[]> input_buf_; + size_t input_buf_offset_; + + // Large incoming messages that span multiple pipe buffers get built-up in the + // buffers of this message. + mozilla::Maybe<Message> incoming_message_; + + // In server-mode, we have to wait for the client to connect before we + // can begin reading. We make use of the input_state_ when performing + // the connect operation in overlapped mode. + bool waiting_connect_; + + // This flag is set when processing incoming messages. It is used to + // avoid recursing through ProcessIncomingMessages, which could cause + // problems. TODO(darin): make this unnecessary + bool processing_incoming_; + + // This flag is set after Close() is run on the channel. + bool closed_; + + // We keep track of the PID of the other side of this channel so that we can + // record this when generating logs of IPC messages. + int32_t other_pid_ = -1; + + // This variable is updated so it matches output_queue_.Count(), except we can + // read output_queue_length_ from any thread (if we're OK getting an + // occasional out-of-date or bogus value). We use output_queue_length_ to + // implement Unsound_NumQueuedMessages. + size_t output_queue_length_; + + ScopedRunnableMethodFactory<ChannelImpl> factory_; + + // This is a unique per-channel value used to authenticate the client end of + // a connection. If the value is non-zero, the client passes it in the hello + // and the host validates. (We don't send the zero value to preserve IPC + // compatibility with existing clients that don't validate the channel.) + int32_t shared_secret_; + + // In server-mode, we wait for the channel at the other side of the pipe to + // send us back our shared secret, if we are using one. + bool waiting_for_shared_secret_; + +#ifdef DEBUG + mozilla::UniquePtr<nsAutoOwningThread> _mOwningThread; +#endif + + DISALLOW_COPY_AND_ASSIGN(ChannelImpl); +}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_WIN_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_message.cc b/ipc/chromium/src/chrome/common/ipc_message.cc new file mode 100644 index 0000000000..dc83f9acdf --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_message.cc @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 "chrome/common/ipc_message.h" + +#include "base/logging.h" +#include "build/build_config.h" + +#if defined(OS_POSIX) +# include "chrome/common/file_descriptor_set_posix.h" +#endif +#ifdef MOZ_TASK_TRACER +# include "GeckoTaskTracerImpl.h" +#endif + +#include <utility> + +#include "nsISupportsImpl.h" + +#ifdef MOZ_TASK_TRACER +using namespace mozilla::tasktracer; + +# define MSG_HEADER_SZ \ + (IsStartLogging() && GetOrCreateTraceInfo() == nullptr \ + ? sizeof(Header) \ + : sizeof(HeaderTaskTracer)) +#else +# define MSG_HEADER_SZ sizeof(Header) +#endif + +namespace IPC { + +//------------------------------------------------------------------------------ + +Message::~Message() { MOZ_COUNT_DTOR(IPC::Message); } + +Message::Message() : Pickle(MSG_HEADER_SZ) { + MOZ_COUNT_CTOR(IPC::Message); + header()->routing = header()->type = 0; +#if defined(OS_POSIX) + header()->num_fds = 0; +#endif +#ifdef MOZ_TASK_TRACER + if (UseTaskTracerHeader()) { + header()->flags.SetTaskTracer(); + HeaderTaskTracer* _header = static_cast<HeaderTaskTracer*>(header()); + GetCurTraceInfo(&_header->source_event_id, &_header->parent_task_id, + &_header->source_event_type); + } +#endif +} + +Message::Message(int32_t routing_id, msgid_t type, uint32_t segment_capacity, + HeaderFlags flags, bool recordWriteLatency) + : Pickle(MSG_HEADER_SZ, segment_capacity) { + MOZ_COUNT_CTOR(IPC::Message); + header()->routing = routing_id; + header()->type = type; + header()->flags = flags; +#if defined(OS_POSIX) + header()->num_fds = 0; +#endif + header()->interrupt_remote_stack_depth_guess = static_cast<uint32_t>(-1); + header()->interrupt_local_stack_depth = static_cast<uint32_t>(-1); + header()->seqno = 0; +#if defined(OS_MACOSX) + header()->cookie = 0; +#endif +#ifdef MOZ_TASK_TRACER + if (UseTaskTracerHeader()) { + header()->flags.SetTaskTracer(); + HeaderTaskTracer* _header = static_cast<HeaderTaskTracer*>(header()); + GetCurTraceInfo(&_header->source_event_id, &_header->parent_task_id, + &_header->source_event_type); + } +#endif + if (recordWriteLatency) { + create_time_ = mozilla::TimeStamp::Now(); + } +} + +#ifndef MOZ_TASK_TRACER +# define MSG_HEADER_SZ_DATA sizeof(Header) +#else +# define MSG_HEADER_SZ_DATA \ + (reinterpret_cast<const Header*>(data)->flags.IsTaskTracer() \ + ? sizeof(HeaderTaskTracer) \ + : sizeof(Header)) +#endif + +Message::Message(const char* data, int data_len) + : Pickle(MSG_HEADER_SZ_DATA, data, data_len) { + MOZ_COUNT_CTOR(IPC::Message); +} + +Message::Message(Message&& other) : Pickle(std::move(other)) { + MOZ_COUNT_CTOR(IPC::Message); +#if defined(OS_POSIX) + file_descriptor_set_ = std::move(other.file_descriptor_set_); +#endif +} + +/*static*/ Message* Message::IPDLMessage(int32_t routing_id, msgid_t type, + HeaderFlags flags) { + return new Message(routing_id, type, 0, flags, true); +} + +/*static*/ Message* Message::ForSyncDispatchError(NestedLevel level) { + auto* m = new Message(0, 0, 0, HeaderFlags(level)); + auto& flags = m->header()->flags; + flags.SetSync(); + flags.SetReply(); + flags.SetReplyError(); + return m; +} + +/*static*/ Message* Message::ForInterruptDispatchError() { + auto* m = new Message(); + auto& flags = m->header()->flags; + flags.SetInterrupt(); + flags.SetReply(); + flags.SetReplyError(); + return m; +} + +Message& Message::operator=(Message&& other) { + *static_cast<Pickle*>(this) = std::move(other); +#if defined(OS_POSIX) + file_descriptor_set_.swap(other.file_descriptor_set_); +#endif + return *this; +} + +void Message::CopyFrom(const Message& other) { + Pickle::CopyFrom(other); +#if defined(OS_POSIX) + MOZ_ASSERT(!file_descriptor_set_); + if (other.file_descriptor_set_) { + file_descriptor_set_ = new FileDescriptorSet; + file_descriptor_set_->CopyFrom(*other.file_descriptor_set_); + } +#endif +} + +#if defined(OS_POSIX) +bool Message::WriteFileDescriptor(const base::FileDescriptor& descriptor) { + // We write the index of the descriptor so that we don't have to + // keep the current descriptor as extra decoding state when deserialising. + // Also, we rely on each file descriptor being accompanied by sizeof(int) + // bytes of data in the message. See the comment for input_cmsg_buf_. + WriteInt(file_descriptor_set()->size()); + if (descriptor.auto_close) { + return file_descriptor_set()->AddAndAutoClose(descriptor.fd); + } else { + return file_descriptor_set()->Add(descriptor.fd); + } +} + +bool Message::ReadFileDescriptor(PickleIterator* iter, + base::FileDescriptor* descriptor) const { + int descriptor_index; + if (!ReadInt(iter, &descriptor_index)) return false; + + FileDescriptorSet* file_descriptor_set = file_descriptor_set_.get(); + if (!file_descriptor_set) return false; + + descriptor->fd = file_descriptor_set->GetDescriptorAt(descriptor_index); + descriptor->auto_close = false; + + return descriptor->fd >= 0; +} + +void Message::EnsureFileDescriptorSet() { + if (file_descriptor_set_.get() == NULL) + file_descriptor_set_ = new FileDescriptorSet; +} + +uint32_t Message::num_fds() const { + return file_descriptor_set() ? file_descriptor_set()->size() : 0; +} + +#endif + +void Message::AssertAsLargeAsHeader() const { + MOZ_DIAGNOSTIC_ASSERT(size() >= MSG_HEADER_SZ); + MOZ_DIAGNOSTIC_ASSERT(CurrentSize() >= MSG_HEADER_SZ); + // Our buffers should agree with what our header specifies. + MOZ_DIAGNOSTIC_ASSERT(size() == CurrentSize()); +} + +#ifdef MOZ_TASK_TRACER +void* MessageTask() { return reinterpret_cast<void*>(&MessageTask); } + +void Message::TaskTracerDispatch() { + if (header()->flags.IsTaskTracer()) { + HeaderTaskTracer* _header = static_cast<HeaderTaskTracer*>(header()); + _header->task_id = GenNewUniqueTaskId(); + uintptr_t* vtab = reinterpret_cast<uintptr_t*>(&MessageTask); + LogVirtualTablePtr(_header->task_id, _header->source_event_id, vtab); + LogDispatch(_header->task_id, _header->parent_task_id, + _header->source_event_id, _header->source_event_type); + } +} + +Message::AutoTaskTracerRun::AutoTaskTracerRun(Message& aMsg) + : mMsg(aMsg), mTaskId(0), mSourceEventId(0) { + if (mMsg.header()->flags.IsTaskTracer()) { + const HeaderTaskTracer* _header = + static_cast<HeaderTaskTracer*>(mMsg.header()); + LogBegin(_header->task_id, _header->source_event_id); + SetCurTraceInfo(_header->source_event_id, _header->task_id, + _header->source_event_type); + mTaskId = _header->task_id; + mSourceEventId = _header->source_event_id; + } else { + SetCurTraceInfo(0, 0, SourceEventType::Unknown); + } +} + +Message::AutoTaskTracerRun::~AutoTaskTracerRun() { + if (mTaskId) { + AddLabel("IPC Message %s", mMsg.name()); + LogEnd(mTaskId, mSourceEventId); + } +} +#endif + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_message.h b/ipc/chromium/src/chrome/common/ipc_message.h new file mode 100644 index 0000000000..934a9b3459 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_message.h @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 CHROME_COMMON_IPC_MESSAGE_H__ +#define CHROME_COMMON_IPC_MESSAGE_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/pickle.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" + +#ifdef MOZ_TASK_TRACER +# include "GeckoTaskTracer.h" +#endif + +#ifdef FUZZING +# include "mozilla/ipc/Faulty.h" +#endif + +namespace base { +struct FileDescriptor; +} + +namespace mozilla { +namespace ipc { +class MiniTransceiver; +} +} // namespace mozilla + +class FileDescriptorSet; + +namespace IPC { + +//------------------------------------------------------------------------------ + +// Generated by IPDL compiler +const char* StringFromIPCMessageType(uint32_t aMessageType); + +class Channel; +class Message; +#ifdef FUZZING +class Faulty; +#endif +struct LogData; + +class Message : public Pickle { + public: + typedef uint32_t msgid_t; + + enum NestedLevel { + NOT_NESTED = 1, + NESTED_INSIDE_SYNC = 2, + NESTED_INSIDE_CPOW = 3 + }; + + enum PriorityValue { + NORMAL_PRIORITY = 0, + INPUT_PRIORITY = 1, + HIGH_PRIORITY = 2, + MEDIUMHIGH_PRIORITY = 3, + }; + + enum MessageCompression { + COMPRESSION_NONE, + COMPRESSION_ENABLED, + COMPRESSION_ALL + }; + + enum Sync { + SYNC = 0, + ASYNC = 1, + }; + + enum Interrupt { + NOT_INTERRUPT = 0, + INTERRUPT = 1, + }; + + enum Constructor { + NOT_CONSTRUCTOR = 0, + CONSTRUCTOR = 1, + }; + + enum Reply { + NOT_REPLY = 0, + REPLY = 1, + }; + + class HeaderFlags { + friend class Message; + + enum { + NESTED_MASK = 0x0003, + PRIO_MASK = 0x000C, + SYNC_BIT = 0x0010, + REPLY_BIT = 0x0020, + REPLY_ERROR_BIT = 0x0040, + INTERRUPT_BIT = 0x0080, + COMPRESS_BIT = 0x0100, + COMPRESSALL_BIT = 0x0200, + COMPRESS_MASK = 0x0300, + CONSTRUCTOR_BIT = 0x0400, +#ifdef MOZ_TASK_TRACER + TASKTRACER_BIT = 0x0800, +#endif + }; + + public: + constexpr HeaderFlags() : mFlags(NOT_NESTED) {} + + explicit constexpr HeaderFlags(NestedLevel level) : mFlags(level) {} + + constexpr HeaderFlags(NestedLevel level, PriorityValue priority, + MessageCompression compression, + Constructor constructor, Sync sync, + Interrupt interrupt, Reply reply) + : mFlags(level | (priority << 2) | + (compression == COMPRESSION_ENABLED ? COMPRESS_BIT + : compression == COMPRESSION_ALL ? COMPRESSALL_BIT + : 0) | + (constructor == CONSTRUCTOR ? CONSTRUCTOR_BIT : 0) | + (sync == SYNC ? SYNC_BIT : 0) | + (interrupt == INTERRUPT ? INTERRUPT_BIT : 0) | + (reply == REPLY ? REPLY_BIT : 0)) {} + + NestedLevel Level() const { + return static_cast<NestedLevel>(mFlags & NESTED_MASK); + } + + PriorityValue Priority() const { + return static_cast<PriorityValue>((mFlags & PRIO_MASK) >> 2); + } + + MessageCompression Compression() const { + return ((mFlags & COMPRESS_BIT) ? COMPRESSION_ENABLED + : (mFlags & COMPRESSALL_BIT) ? COMPRESSION_ALL + : COMPRESSION_NONE); + } + + bool IsConstructor() const { return (mFlags & CONSTRUCTOR_BIT) != 0; } + bool IsSync() const { return (mFlags & SYNC_BIT) != 0; } + bool IsInterrupt() const { return (mFlags & INTERRUPT_BIT) != 0; } + bool IsReply() const { return (mFlags & REPLY_BIT) != 0; } + + bool IsReplyError() const { return (mFlags & REPLY_ERROR_BIT) != 0; } + +#ifdef MOZ_TASK_TRACER + bool IsTaskTracer() const { return (mFlags & TASKTRACER_BIT) != 0; } +#endif + + private: + void SetSync() { mFlags |= SYNC_BIT; } + void SetInterrupt() { mFlags |= INTERRUPT_BIT; } + void SetReply() { mFlags |= REPLY_BIT; } + void SetReplyError() { mFlags |= REPLY_ERROR_BIT; } + +#ifdef MOZ_TASK_TRACER + void SetTaskTracer() { mFlags |= TASKTRACER_BIT; } +#endif + + uint32_t mFlags; + }; + + virtual ~Message(); + + Message(); + + // Initialize a message with a user-defined type, priority value, and + // destination WebView ID. + // + // NOTE: `recordWriteLatency` is only passed by IPDL generated message code, + // and is used to trigger the IPC_WRITE_LATENCY_MS telemetry. + Message(int32_t routing_id, msgid_t type, + uint32_t segmentCapacity = 0, // 0 for the default capacity. + HeaderFlags flags = HeaderFlags(), bool recordWriteLatency = false); + + Message(const char* data, int data_len); + + Message(const Message& other) = delete; + Message(Message&& other); + Message& operator=(const Message& other) = delete; + Message& operator=(Message&& other); + + void CopyFrom(const Message& other); + + // Helper method for the common case (default segmentCapacity, recording + // the write latency of messages) of IPDL message creation. This helps + // move the malloc and some of the parameter setting out of autogenerated + // code. + static Message* IPDLMessage(int32_t routing_id, msgid_t type, + HeaderFlags flags); + + // One-off constructors for special error-handling messages. + static Message* ForSyncDispatchError(NestedLevel level); + static Message* ForInterruptDispatchError(); + + NestedLevel nested_level() const { return header()->flags.Level(); } + + PriorityValue priority() const { return header()->flags.Priority(); } + + bool is_constructor() const { return header()->flags.IsConstructor(); } + + // True if this is a synchronous message. + bool is_sync() const { return header()->flags.IsSync(); } + + // True if this is a synchronous message. + bool is_interrupt() const { return header()->flags.IsInterrupt(); } + + MessageCompression compress_type() const { + return header()->flags.Compression(); + } + + bool is_reply() const { return header()->flags.IsReply(); } + + bool is_reply_error() const { return header()->flags.IsReplyError(); } + + bool is_valid() const { return !!header(); } + + msgid_t type() const { return header()->type; } + + int32_t routing_id() const { return header()->routing; } + + void set_routing_id(int32_t new_id) { header()->routing = new_id; } + + int32_t transaction_id() const { return header()->txid; } + + void set_transaction_id(int32_t txid) { header()->txid = txid; } + + uint32_t interrupt_remote_stack_depth_guess() const { + return header()->interrupt_remote_stack_depth_guess; + } + + void set_interrupt_remote_stack_depth_guess(uint32_t depth) { + DCHECK(is_interrupt()); + header()->interrupt_remote_stack_depth_guess = depth; + } + + uint32_t interrupt_local_stack_depth() const { + return header()->interrupt_local_stack_depth; + } + + void set_interrupt_local_stack_depth(uint32_t depth) { + DCHECK(is_interrupt()); + header()->interrupt_local_stack_depth = depth; + } + + int32_t seqno() const { return header()->seqno; } + + void set_seqno(int32_t aSeqno) { header()->seqno = aSeqno; } + + const char* name() const { return StringFromIPCMessageType(type()); } + + const mozilla::TimeStamp& create_time() const { return create_time_; } + +#if defined(OS_POSIX) + uint32_t num_fds() const; +#endif + + template <class T> + static bool Dispatch(const Message* msg, T* obj, void (T::*func)()) { + (obj->*func)(); + return true; + } + + template <class T> + static bool Dispatch(const Message* msg, T* obj, void (T::*func)() const) { + (obj->*func)(); + return true; + } + + template <class T> + static bool Dispatch(const Message* msg, T* obj, + void (T::*func)(const Message&)) { + (obj->*func)(*msg); + return true; + } + + template <class T> + static bool Dispatch(const Message* msg, T* obj, + void (T::*func)(const Message&) const) { + (obj->*func)(*msg); + return true; + } + + // We should not be sending messages that are smaller than our header size. + void AssertAsLargeAsHeader() const; + + // Used for async messages with no parameters. + static void Log(const Message* msg, std::wstring* l) {} + + static int HeaderSizeFromData(const char* range_start, + const char* range_end) { +#ifdef MOZ_TASK_TRACER + return ((static_cast<unsigned int>(range_end - range_start) >= + sizeof(Header)) && + (reinterpret_cast<const Header*>(range_start) + ->flags.IsTaskTracer())) + ? sizeof(HeaderTaskTracer) + : sizeof(Header); +#else + return sizeof(Header); +#endif + } + + // Figure out how big the message starting at range_start is. Returns 0 if + // there's no enough data to determine (i.e., if [range_start, range_end) does + // not contain enough of the message header to know the size). + static uint32_t MessageSize(const char* range_start, const char* range_end) { + return Pickle::MessageSize(HeaderSizeFromData(range_start, range_end), + range_start, range_end); + } + +#if defined(OS_POSIX) + // On POSIX, a message supports reading / writing FileDescriptor objects. + // This is used to pass a file descriptor to the peer of an IPC channel. + + // Add a descriptor to the end of the set. Returns false iff the set is full. + bool WriteFileDescriptor(const base::FileDescriptor& descriptor); + // Get a file descriptor from the message. Returns false on error. + // iter: a Pickle iterator to the current location in the message. + bool ReadFileDescriptor(PickleIterator* iter, + base::FileDescriptor* descriptor) const; + +# if defined(OS_MACOSX) + void set_fd_cookie(uint32_t cookie) { header()->cookie = cookie; } + uint32_t fd_cookie() const { return header()->cookie; } +# endif +#endif + + friend class Channel; + friend class MessageReplyDeserializer; + friend class SyncMessage; +#ifdef FUZZING + friend class mozilla::ipc::Faulty; +#endif + friend class mozilla::ipc::MiniTransceiver; + +#ifdef MOZ_TASK_TRACER + void TaskTracerDispatch(); + class AutoTaskTracerRun : public mozilla::tasktracer::AutoSaveCurTraceInfo { + Message& mMsg; + uint64_t mTaskId; + uint64_t mSourceEventId; + + public: + explicit AutoTaskTracerRun(Message& aMsg); + ~AutoTaskTracerRun(); + }; +#endif + +#if !defined(OS_MACOSX) + protected: +#endif + + struct Header : Pickle::Header { + int32_t routing; // ID of the view that this message is destined for + msgid_t type; // specifies the user-defined message type + HeaderFlags flags; // specifies control flags for the message +#if defined(OS_POSIX) + uint32_t num_fds; // the number of descriptors included with this message +# if defined(OS_MACOSX) + uint32_t cookie; // cookie to ACK that the descriptors have been read. +# endif +#endif + union { + // For Interrupt messages, a guess at what the *other* side's stack depth + // is. + uint32_t interrupt_remote_stack_depth_guess; + + // For RPC and Urgent messages, a transaction ID for message ordering. + int32_t txid; + }; + // The actual local stack depth. + uint32_t interrupt_local_stack_depth; + // Sequence number + int32_t seqno; + }; + +#ifdef MOZ_TASK_TRACER + /** + * The type is used as headers of Messages only if TaskTracer is + * enabled, or type |Header| would be used instead. + */ + struct HeaderTaskTracer : public Header { + uint64_t task_id; + uint64_t source_event_id; + uint64_t parent_task_id; + mozilla::tasktracer::SourceEventType source_event_type; + }; +#endif + +#ifdef MOZ_TASK_TRACER + bool UseTaskTracerHeader() const { + return sizeof(HeaderTaskTracer) == (size() - payload_size()); + } + + Header* header() { + return UseTaskTracerHeader() ? headerT<HeaderTaskTracer>() + : headerT<Header>(); + } + const Header* header() const { + return UseTaskTracerHeader() ? headerT<HeaderTaskTracer>() + : headerT<Header>(); + } +#else + Header* header() { return headerT<Header>(); } + const Header* header() const { return headerT<Header>(); } +#endif + +#if defined(OS_POSIX) + // The set of file descriptors associated with this message. + RefPtr<FileDescriptorSet> file_descriptor_set_; + + // Ensure that a FileDescriptorSet is allocated + void EnsureFileDescriptorSet(); + + FileDescriptorSet* file_descriptor_set() { + EnsureFileDescriptorSet(); + return file_descriptor_set_.get(); + } + const FileDescriptorSet* file_descriptor_set() const { + return file_descriptor_set_.get(); + } +#endif + + mozilla::TimeStamp create_time_; +}; + +class MessageInfo { + public: + typedef uint32_t msgid_t; + + explicit MessageInfo(const Message& aMsg) + : mSeqno(aMsg.seqno()), mType(aMsg.type()) {} + + int32_t seqno() const { return mSeqno; } + msgid_t type() const { return mType; } + + private: + int32_t mSeqno; + msgid_t mType; +}; + +//------------------------------------------------------------------------------ + +} // namespace IPC + +enum SpecialRoutingIDs { + // indicates that we don't have a routing ID yet. + MSG_ROUTING_NONE = kint32min, + + // indicates a general message not sent to a particular tab. + MSG_ROUTING_CONTROL = kint32max +}; + +#define IPC_REPLY_ID 0xFFF0 // Special message id for replies +#define IPC_LOGGING_ID 0xFFF1 // Special message id for logging + +#endif // CHROME_COMMON_IPC_MESSAGE_H__ diff --git a/ipc/chromium/src/chrome/common/ipc_message_utils.h b/ipc/chromium/src/chrome/common/ipc_message_utils.h new file mode 100644 index 0000000000..d8bed78ac7 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_message_utils.h @@ -0,0 +1,495 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 CHROME_COMMON_IPC_MESSAGE_UTILS_H_ +#define CHROME_COMMON_IPC_MESSAGE_UTILS_H_ + +#include <cstdint> +#include <map> +#include <string> +#include <type_traits> +#include <utility> +#include "ErrorList.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/string_util.h" +#include "build/build_config.h" +#include "chrome/common/ipc_message.h" + +#if defined(OS_POSIX) +# include "chrome/common/file_descriptor_set_posix.h" +#endif +#if defined(OS_WIN) +# include "windows.h" +#endif + +template <typename T> +class RefPtr; +template <typename T> +class nsCOMPtr; + +namespace IPC { + +//----------------------------------------------------------------------------- +// An iterator class for reading the fields contained within a Message. + +class MessageIterator { + public: + explicit MessageIterator(const Message& m) : msg_(m), iter_(m) {} + int NextInt() const { + int val; + if (!msg_.ReadInt(&iter_, &val)) NOTREACHED(); + return val; + } + intptr_t NextIntPtr() const { + intptr_t val; + if (!msg_.ReadIntPtr(&iter_, &val)) NOTREACHED(); + return val; + } + const std::string NextString() const { + std::string val; + if (!msg_.ReadString(&iter_, &val)) NOTREACHED(); + return val; + } + const std::wstring NextWString() const { + std::wstring val; + if (!msg_.ReadWString(&iter_, &val)) NOTREACHED(); + return val; + } + + private: + const Message& msg_; + mutable PickleIterator iter_; +}; + +//----------------------------------------------------------------------------- +// ParamTraits specializations, etc. +// +// The full set of types ParamTraits is specialized upon contains *possibly* +// repeated types: unsigned long may be uint32_t or size_t, unsigned long long +// may be uint64_t or size_t, nsresult may be uint32_t, and so on. You can't +// have ParamTraits<unsigned int> *and* ParamTraits<uint32_t> if unsigned int +// is uint32_t -- that's multiple definitions, and you can only have one. +// +// You could use #ifs and macro conditions to avoid duplicates, but they'd be +// hairy: heavily dependent upon OS and compiler author choices, forced to +// address all conflicts by hand. Happily there's a better way. The basic +// idea looks like this, where T -> U represents T inheriting from U: +// +// class ParamTraits<P> +// | +// --> class ParamTraits1<P> +// | +// --> class ParamTraits2<P> +// | +// --> class ParamTraitsN<P> // or however many levels +// +// The default specialization of ParamTraits{M}<P> is an empty class that +// inherits from ParamTraits{M + 1}<P> (or nothing in the base case). +// +// Now partition the set of parameter types into sets without duplicates. +// Assign each set of types to a level M. Then specialize ParamTraitsM for +// each of those types. A reference to ParamTraits<P> will consist of some +// number of empty classes inheriting in sequence, ending in a non-empty +// ParamTraits{N}<P>. It's okay for the parameter types to be duplicative: +// either name of a type will resolve to the same ParamTraits{N}<P>. +// +// The nice thing is that because templates are instantiated lazily, if we +// indeed have uint32_t == unsigned int, say, with the former in level N and +// the latter in M > N, ParamTraitsM<unsigned int> won't be created (as long as +// nobody uses ParamTraitsM<unsigned int>, but why would you), and no duplicate +// code will be compiled or extra symbols generated. It's as efficient at +// runtime as manually figuring out and avoiding conflicts by #ifs. +// +// The scheme we follow below names the various classes according to the types +// in them, and the number of ParamTraits levels is larger, but otherwise it's +// exactly the above idea. +// + +template <class P> +struct ParamTraits; + +template <typename P> +static inline void WriteParam(Message* m, P&& p) { + ParamTraits<std::decay_t<P>>::Write(m, std::forward<P>(p)); +} + +template <typename P> +static inline bool WARN_UNUSED_RESULT ReadParam(const Message* m, + PickleIterator* iter, P* p) { + return ParamTraits<P>::Read(m, iter, p); +} + +template <typename P> +static inline void LogParam(const P& p, std::wstring* l) { + ParamTraits<P>::Log(p, l); +} + +// Fundamental types. + +template <class P> +struct ParamTraitsFundamental {}; + +template <> +struct ParamTraitsFundamental<bool> { + typedef bool param_type; + static void Write(Message* m, const param_type& p) { m->WriteBool(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadBool(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(p ? L"true" : L"false"); + } +}; + +template <> +struct ParamTraitsFundamental<int> { + typedef int param_type; + static void Write(Message* m, const param_type& p) { m->WriteInt(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadInt(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%d", p)); + } +}; + +template <> +struct ParamTraitsFundamental<long> { + typedef long param_type; + static void Write(Message* m, const param_type& p) { m->WriteLong(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadLong(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%l", p)); + } +}; + +template <> +struct ParamTraitsFundamental<unsigned long> { + typedef unsigned long param_type; + static void Write(Message* m, const param_type& p) { m->WriteULong(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadULong(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%ul", p)); + } +}; + +template <> +struct ParamTraitsFundamental<long long> { + typedef long long param_type; + static void Write(Message* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadBytesInto(iter, r, sizeof(*r)); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%ll", p)); + } +}; + +template <> +struct ParamTraitsFundamental<unsigned long long> { + typedef unsigned long long param_type; + static void Write(Message* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadBytesInto(iter, r, sizeof(*r)); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%ull", p)); + } +}; + +template <> +struct ParamTraitsFundamental<double> { + typedef double param_type; + static void Write(Message* m, const param_type& p) { m->WriteDouble(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadDouble(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"e", p)); + } +}; + +// Fixed-size <stdint.h> types. + +template <class P> +struct ParamTraitsFixed : ParamTraitsFundamental<P> {}; + +template <> +struct ParamTraitsFixed<int16_t> { + typedef int16_t param_type; + static void Write(Message* m, const param_type& p) { m->WriteInt16(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadInt16(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%hd", p)); + } +}; + +template <> +struct ParamTraitsFixed<uint16_t> { + typedef uint16_t param_type; + static void Write(Message* m, const param_type& p) { m->WriteUInt16(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadUInt16(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%hu", p)); + } +}; + +template <> +struct ParamTraitsFixed<uint32_t> { + typedef uint32_t param_type; + static void Write(Message* m, const param_type& p) { m->WriteUInt32(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadUInt32(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%u", p)); + } +}; + +template <> +struct ParamTraitsFixed<int64_t> { + typedef int64_t param_type; + static void Write(Message* m, const param_type& p) { m->WriteInt64(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadInt64(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%" PRId64L, p)); + } +}; + +template <> +struct ParamTraitsFixed<uint64_t> { + typedef uint64_t param_type; + static void Write(Message* m, const param_type& p) { + m->WriteInt64(static_cast<int64_t>(p)); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadInt64(iter, reinterpret_cast<int64_t*>(r)); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%" PRIu64L, p)); + } +}; + +// std::* types. + +template <class P> +struct ParamTraitsStd : ParamTraitsFixed<P> {}; + +template <> +struct ParamTraitsStd<std::string> { + typedef std::string param_type; + static void Write(Message* m, const param_type& p) { m->WriteString(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadString(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(UTF8ToWide(p)); + } +}; + +template <> +struct ParamTraitsStd<std::wstring> { + typedef std::wstring param_type; + static void Write(Message* m, const param_type& p) { m->WriteWString(p); } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadWString(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { l->append(p); } +}; + +template <class K, class V> +struct ParamTraitsStd<std::map<K, V>> { + typedef std::map<K, V> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, static_cast<int>(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + WriteParam(m, iter->first); + WriteParam(m, iter->second); + } + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + int size; + if (!ReadParam(m, iter, &size) || size < 0) return false; + for (int i = 0; i < size; ++i) { + K k; + if (!ReadParam(m, iter, &k)) return false; + V& value = (*r)[k]; + if (!ReadParam(m, iter, &value)) return false; + } + return true; + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"<std::map>"); + } +}; + +// Windows-specific types. + +template <class P> +struct ParamTraitsWindows : ParamTraitsStd<P> {}; + +#if defined(OS_WIN) +template <> +struct ParamTraitsWindows<HANDLE> { + static_assert(sizeof(HANDLE) == sizeof(intptr_t), "Wrong size for HANDLE?"); + + static void Write(Message* m, HANDLE p) { + m->WriteIntPtr(reinterpret_cast<intptr_t>(p)); + } + static bool Read(const Message* m, PickleIterator* iter, HANDLE* r) { + return m->ReadIntPtr(iter, reinterpret_cast<intptr_t*>(r)); + } + static void Log(const HANDLE& p, std::wstring* l) { + l->append(StringPrintf(L"0x%X", p)); + } +}; + +template <> +struct ParamTraitsWindows<HWND> { + static_assert(sizeof(HWND) == sizeof(intptr_t), "Wrong size for HWND?"); + + static void Write(Message* m, HWND p) { + m->WriteIntPtr(reinterpret_cast<intptr_t>(p)); + } + static bool Read(const Message* m, PickleIterator* iter, HWND* r) { + return m->ReadIntPtr(iter, reinterpret_cast<intptr_t*>(r)); + } + static void Log(const HWND& p, std::wstring* l) { + l->append(StringPrintf(L"0x%X", p)); + } +}; +#endif // defined(OS_WIN) + +// Various ipc/chromium types. + +template <class P> +struct ParamTraitsIPC : ParamTraitsWindows<P> {}; + +#if defined(OS_POSIX) +// FileDescriptors may be serialised over IPC channels on POSIX. On the +// receiving side, the FileDescriptor is a valid duplicate of the file +// descriptor which was transmitted: *it is not just a copy of the integer like +// HANDLEs on Windows*. The only exception is if the file descriptor is < 0. In +// this case, the receiving end will see a value of -1. *Zero is a valid file +// descriptor*. +// +// The received file descriptor will have the |auto_close| flag set to true. The +// code which handles the message is responsible for taking ownership of it. +// File descriptors are OS resources and must be closed when no longer needed. +// +// When sending a file descriptor, the file descriptor must be valid at the time +// of transmission. Since transmission is not synchronous, one should consider +// dup()ing any file descriptors to be transmitted and setting the |auto_close| +// flag, which causes the file descriptor to be closed after writing. +template <> +struct ParamTraitsIPC<base::FileDescriptor> { + typedef base::FileDescriptor param_type; + static void Write(Message* m, const param_type& p) { + const bool valid = p.fd >= 0; + WriteParam(m, valid); + + if (valid) { + if (!m->WriteFileDescriptor(p)) { + NOTREACHED() << "Too many file descriptors for one message!"; + } + } + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + bool valid; + if (!ReadParam(m, iter, &valid)) return false; + + if (!valid) { + r->fd = -1; + r->auto_close = false; + return true; + } + + return m->ReadFileDescriptor(iter, r); + } + static void Log(const param_type& p, std::wstring* l) { + if (p.auto_close) { + l->append(StringPrintf(L"FD(%d auto-close)", p.fd)); + } else { + l->append(StringPrintf(L"FD(%d)", p.fd)); + } + } +}; +#endif // defined(OS_POSIX) + +// Mozilla-specific types. + +template <class P> +struct ParamTraitsMozilla : ParamTraitsIPC<P> {}; + +template <> +struct ParamTraitsMozilla<nsresult> { + typedef nsresult param_type; + static void Write(Message* m, const param_type& p) { + m->WriteUInt32(static_cast<uint32_t>(p)); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadUInt32(iter, reinterpret_cast<uint32_t*>(r)); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(StringPrintf(L"%u", static_cast<uint32_t>(p))); + } +}; + +// See comments for the IPDLParamTraits specializations for RefPtr<T> and +// nsCOMPtr<T> for more details. +template <class T> +struct ParamTraitsMozilla<RefPtr<T>> { + static void Write(Message* m, const RefPtr<T>& p) { + ParamTraits<T*>::Write(m, p.get()); + } + + static bool Read(const Message* m, PickleIterator* iter, RefPtr<T>* r) { + return ParamTraits<T*>::Read(m, iter, r); + } +}; + +template <class T> +struct ParamTraitsMozilla<nsCOMPtr<T>> { + static void Write(Message* m, const nsCOMPtr<T>& p) { + ParamTraits<T*>::Write(m, p.get()); + } + + static bool Read(const Message* m, PickleIterator* iter, nsCOMPtr<T>* r) { + RefPtr<T> refptr; + if (!ParamTraits<T*>::Read(m, iter, &refptr)) { + return false; + } + *r = std::move(refptr); + return true; + } +}; + +// Finally, ParamTraits itself. + +template <class P> +struct ParamTraits : ParamTraitsMozilla<P> {}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_MESSAGE_UTILS_H_ diff --git a/ipc/chromium/src/chrome/common/mach_ipc_mac.h b/ipc/chromium/src/chrome/common/mach_ipc_mac.h new file mode 100644 index 0000000000..d9a8483600 --- /dev/null +++ b/ipc/chromium/src/chrome/common/mach_ipc_mac.h @@ -0,0 +1,307 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 BASE_MACH_IPC_MAC_H_ +#define BASE_MACH_IPC_MAC_H_ + +#include <mach/mach.h> +#include <mach/message.h> +#include <servers/bootstrap.h> +#include <sys/types.h> + +#include "base/basictypes.h" + +//============================================================================== +// DISCUSSION: +// +// The three main classes of interest are +// +// MachMessage: a wrapper for a mach message of the following form +// mach_msg_header_t +// mach_msg_body_t +// optional descriptors +// optional extra message data +// +// MachReceiveMessage and MachSendMessage subclass MachMessage +// and are used instead of MachMessage which is an abstract base class +// +// ReceivePort: +// Represents a mach port for which we have receive rights +// +// MachPortSender: +// Represents a mach port for which we have send rights +// +// Here's an example to receive a message on a server port: +// +// // This creates our named server port +// ReceivePort receivePort("com.Google.MyService"); +// +// MachReceiveMessage message; +// kern_return_t result = receivePort.WaitForMessage(&message, 0); +// +// if (result == KERN_SUCCESS && message.GetMessageID() == 57) { +// mach_port_t task = message.GetTranslatedPort(0); +// mach_port_t thread = message.GetTranslatedPort(1); +// +// char *messageString = message.GetData(); +// +// printf("message string = %s\n", messageString); +// } +// +// Here is an example of using these classes to send a message to this port: +// +// // send to already named port +// MachPortSender sender("com.Google.MyService"); +// MachSendMessage message(57); // our message ID is 57 +// +// // add some ports to be translated for us +// message.AddDescriptor(mach_task_self()); // our task +// message.AddDescriptor(mach_thread_self()); // this thread +// +// char messageString[] = "Hello server!\n"; +// message.SetData(messageString, strlen(messageString)+1); +// // timeout 1000ms +// kern_return_t result = sender.SendMessage(message, 1000); +// + +#ifndef PRINT_MACH_RESULT +# define PRINT_MACH_RESULT(result_, message_) \ + printf(message_ " %s (%d)\n", mach_error_string(result_), result_); +#endif + +//============================================================================== +// A wrapper class for mach_msg_port_descriptor_t (with same memory layout) +// with convenient constructors and accessors +class MachMsgPortDescriptor : public mach_msg_port_descriptor_t { + public: + // General-purpose constructor + MachMsgPortDescriptor(mach_port_t in_name, + mach_msg_type_name_t in_disposition) { + name = in_name; + pad1 = 0; + pad2 = 0; + disposition = in_disposition; + type = MACH_MSG_PORT_DESCRIPTOR; + } + + // For passing send rights to a port + explicit MachMsgPortDescriptor(mach_port_t in_name) { + name = in_name; + pad1 = 0; + pad2 = 0; + disposition = MACH_MSG_TYPE_PORT_SEND; + type = MACH_MSG_PORT_DESCRIPTOR; + } + + // Copy constructor + MachMsgPortDescriptor(const MachMsgPortDescriptor& desc) { + name = desc.name; + pad1 = desc.pad1; + pad2 = desc.pad2; + disposition = desc.disposition; + type = desc.type; + } + + mach_port_t GetMachPort() const { return name; } + + mach_msg_type_name_t GetDisposition() const { return disposition; } + + // For convenience + operator mach_port_t() const { return GetMachPort(); } +}; + +//============================================================================== +// MachMessage: a wrapper for a mach message +// (mach_msg_header_t, mach_msg_body_t, extra data) +// +// This considerably simplifies the construction of a message for sending +// and the getting at relevant data and descriptors for the receiver. +// +// This class can be initialized using external storage of an arbitrary size +// or it can manage storage internally. +// 1. If storage is allocated internally, the combined size of the descriptors +// plus data must be less than 1024. But as a benefit no memory allocation is +// necessary. +// 2. For external storage, a buffer of at least EmptyMessageSize() must be +// provided. +// +// A MachMessage object is used by ReceivePort::WaitForMessage +// and MachPortSender::SendMessage +// +class MachMessage { + public: + virtual ~MachMessage(); + + // The receiver of the message can retrieve the raw data this way + u_int8_t* GetData() { + return GetDataLength() > 0 ? GetDataPacket()->data : NULL; + } + + u_int32_t GetDataLength(); + + // The message ID may be used as a code identifying the type of message + void SetMessageID(int32_t message_id); + + int32_t GetMessageID(); + + // Adds a descriptor (typically a mach port) to be translated + // returns true if successful, otherwise not enough space + bool AddDescriptor(const MachMsgPortDescriptor& desc); + + int GetDescriptorCount() const { + return storage_->body.msgh_descriptor_count; + } + MachMsgPortDescriptor* GetDescriptor(int n); + + // Convenience method which gets the mach port described by the descriptor + mach_port_t GetTranslatedPort(int n); + + // A simple message is one with no descriptors + bool IsSimpleMessage() const { return GetDescriptorCount() == 0; } + + // Sets raw data for the message (returns false if not enough space) + bool SetData(const void* data, int32_t data_length); + + protected: + // Consider this an abstract base class - must create an actual instance + // of MachReceiveMessage or MachSendMessage + MachMessage(); + + // Constructor for use with preallocate storage. + // storage_length must be >= EmptyMessageSize() + MachMessage(void* storage, size_t storage_length); + + friend class ReceivePort; + friend class MachPortSender; + + // Represents raw data in our message + struct MessageDataPacket { + int32_t id; // little-endian + int32_t data_length; // little-endian + u_int8_t data[1]; // actual size limited by storage_length_bytes_ + }; + + MessageDataPacket* GetDataPacket(); + + void SetDescriptorCount(int n); + void SetDescriptor(int n, const MachMsgPortDescriptor& desc); + + // Returns total message size setting msgh_size in the header to this value + int CalculateSize(); + + // Returns total storage size that this object can grow to, this is inclusive + // of the mach header. + size_t MaxSize() const { return storage_length_bytes_; } + + protected: + mach_msg_header_t* Head() { return &(storage_->head); } + + private: + struct MachMessageData { + mach_msg_header_t head; + mach_msg_body_t body; + // descriptors and data may be embedded here. + u_int8_t padding[1024]; + }; + + // kEmptyMessageSize needs to have the definition of MachMessageData before + // it. + public: + // The size of an empty message with no data. + static const size_t kEmptyMessageSize = sizeof(mach_msg_header_t) + + sizeof(mach_msg_body_t) + + sizeof(MessageDataPacket); + + private: + MachMessageData* storage_; + size_t storage_length_bytes_; + bool own_storage_; // Is storage owned by this object? +}; + +//============================================================================== +// MachReceiveMessage and MachSendMessage are useful to separate the idea +// of a mach message being sent and being received, and adds increased type +// safety: +// ReceivePort::WaitForMessage() only accepts a MachReceiveMessage +// MachPortSender::SendMessage() only accepts a MachSendMessage + +//============================================================================== +class MachReceiveMessage : public MachMessage { + public: + MachReceiveMessage() : MachMessage() {} + MachReceiveMessage(void* storage, size_t storage_length) + : MachMessage(storage, storage_length) {} + + private: + DISALLOW_COPY_AND_ASSIGN(MachReceiveMessage); +}; + +//============================================================================== +class MachSendMessage : public MachMessage { + public: + explicit MachSendMessage(int32_t message_id); + MachSendMessage(void* storage, size_t storage_length, int32_t message_id); + + private: + void Initialize(int32_t message_id); + + DISALLOW_COPY_AND_ASSIGN(MachSendMessage); +}; + +//============================================================================== +// Represents a mach port for which we have receive rights +class ReceivePort { + public: + // Creates a new mach port for receiving messages and registers a name for it + explicit ReceivePort(const char* receive_port_name); + + // Given an already existing mach port, use it. We take ownership of the + // port and deallocate it in our destructor. + explicit ReceivePort(mach_port_t receive_port); + + // Create a new mach port for receiving messages + ReceivePort(); + + ~ReceivePort(); + + // Waits on the mach port until message received or timeout + kern_return_t WaitForMessage(MachReceiveMessage* out_message, + mach_msg_timeout_t timeout); + + kern_return_t SendMessageToSelf(MachSendMessage& msg, + mach_msg_timeout_t timeout); + + // The underlying mach port that we wrap + mach_port_t GetPort() const { return port_; } + + private: + mach_port_t port_; + kern_return_t init_result_; + + DISALLOW_COPY_AND_ASSIGN(ReceivePort); +}; + +//============================================================================== +// Represents a mach port for which we have send rights +class MachPortSender { + public: + // get a port with send rights corresponding to a named registered service + explicit MachPortSender(const char* receive_port_name); + + // Given an already existing mach port, use it. + explicit MachPortSender(mach_port_t send_port); + + kern_return_t SendMessage(MachSendMessage& message, + mach_msg_timeout_t timeout); + + private: + mach_port_t send_port_; + kern_return_t init_result_; + + DISALLOW_COPY_AND_ASSIGN(MachPortSender); +}; + +#endif // BASE_MACH_IPC_MAC_H_ diff --git a/ipc/chromium/src/chrome/common/mach_ipc_mac.mm b/ipc/chromium/src/chrome/common/mach_ipc_mac.mm new file mode 100644 index 0000000000..f0c5671782 --- /dev/null +++ b/ipc/chromium/src/chrome/common/mach_ipc_mac.mm @@ -0,0 +1,303 @@ +// Copyright (c) 2006-2008 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 "chrome/common/mach_ipc_mac.h" + +#import <Foundation/Foundation.h> + +#include <stdio.h> +#include "base/logging.h" + +//============================================================================== +MachSendMessage::MachSendMessage(int32_t message_id) : MachMessage() { Initialize(message_id); } + +MachSendMessage::MachSendMessage(void* storage, size_t storage_length, int32_t message_id) + : MachMessage(storage, storage_length) { + Initialize(message_id); +} + +void MachSendMessage::Initialize(int32_t message_id) { + Head()->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); + + // head.msgh_remote_port = ...; // filled out in MachPortSender::SendMessage() + Head()->msgh_local_port = MACH_PORT_NULL; + Head()->msgh_reserved = 0; + Head()->msgh_id = 0; + + SetDescriptorCount(0); // start out with no descriptors + + SetMessageID(message_id); + SetData(NULL, 0); // client may add data later +} + +//============================================================================== +MachMessage::MachMessage() + : storage_(new MachMessageData), // Allocate storage_ ourselves + storage_length_bytes_(sizeof(MachMessageData)), + own_storage_(true) { + memset(storage_, 0, storage_length_bytes_); +} + +//============================================================================== +MachMessage::MachMessage(void* storage, size_t storage_length) + : storage_(static_cast<MachMessageData*>(storage)), + storage_length_bytes_(storage_length), + own_storage_(false) { + DCHECK(storage); + DCHECK(storage_length >= kEmptyMessageSize); +} + +//============================================================================== +MachMessage::~MachMessage() { + if (own_storage_) { + delete storage_; + storage_ = NULL; + } +} + +u_int32_t MachMessage::GetDataLength() { return EndianU32_LtoN(GetDataPacket()->data_length); } + +// The message ID may be used as a code identifying the type of message +void MachMessage::SetMessageID(int32_t message_id) { + GetDataPacket()->id = EndianU32_NtoL(message_id); +} + +int32_t MachMessage::GetMessageID() { return EndianU32_LtoN(GetDataPacket()->id); } + +//============================================================================== +// returns true if successful +bool MachMessage::SetData(const void* data, int32_t data_length) { + // Enforce the fact that it's only safe to call this method once on a + // message. + DCHECK(GetDataPacket()->data_length == 0); + + // first check to make sure we have enough space + int size = CalculateSize(); + int new_size = size + data_length; + + if ((unsigned)new_size > storage_length_bytes_) { + return false; // not enough space + } + + GetDataPacket()->data_length = EndianU32_NtoL(data_length); + if (data) memcpy(GetDataPacket()->data, data, data_length); + + // Update the Mach header with the new aligned size of the message. + CalculateSize(); + + return true; +} + +//============================================================================== +// calculates and returns the total size of the message +// Currently, the entire message MUST fit inside of the MachMessage +// messsage size <= EmptyMessageSize() +int MachMessage::CalculateSize() { + int size = sizeof(mach_msg_header_t) + sizeof(mach_msg_body_t); + + // add space for MessageDataPacket + int32_t alignedDataLength = (GetDataLength() + 3) & ~0x3; + size += 2 * sizeof(int32_t) + alignedDataLength; + + // add space for descriptors + size += GetDescriptorCount() * sizeof(MachMsgPortDescriptor); + + Head()->msgh_size = size; + + return size; +} + +//============================================================================== +MachMessage::MessageDataPacket* MachMessage::GetDataPacket() { + int desc_size = sizeof(MachMsgPortDescriptor) * GetDescriptorCount(); + MessageDataPacket* packet = reinterpret_cast<MessageDataPacket*>(storage_->padding + desc_size); + + return packet; +} + +//============================================================================== +void MachMessage::SetDescriptor(int n, const MachMsgPortDescriptor& desc) { + MachMsgPortDescriptor* desc_array = reinterpret_cast<MachMsgPortDescriptor*>(storage_->padding); + desc_array[n] = desc; +} + +//============================================================================== +// returns true if successful otherwise there was not enough space +bool MachMessage::AddDescriptor(const MachMsgPortDescriptor& desc) { + // first check to make sure we have enough space + int size = CalculateSize(); + int new_size = size + sizeof(MachMsgPortDescriptor); + + if ((unsigned)new_size > storage_length_bytes_) { + return false; // not enough space + } + + // unfortunately, we need to move the data to allow space for the + // new descriptor + u_int8_t* p = reinterpret_cast<u_int8_t*>(GetDataPacket()); + bcopy(p, p + sizeof(MachMsgPortDescriptor), GetDataLength() + 2 * sizeof(int32_t)); + + SetDescriptor(GetDescriptorCount(), desc); + SetDescriptorCount(GetDescriptorCount() + 1); + + CalculateSize(); + + return true; +} + +//============================================================================== +void MachMessage::SetDescriptorCount(int n) { + storage_->body.msgh_descriptor_count = n; + + if (n > 0) { + Head()->msgh_bits |= MACH_MSGH_BITS_COMPLEX; + } else { + Head()->msgh_bits &= ~MACH_MSGH_BITS_COMPLEX; + } +} + +//============================================================================== +MachMsgPortDescriptor* MachMessage::GetDescriptor(int n) { + if (n < GetDescriptorCount()) { + MachMsgPortDescriptor* desc = reinterpret_cast<MachMsgPortDescriptor*>(storage_->padding); + return desc + n; + } + + return nil; +} + +//============================================================================== +mach_port_t MachMessage::GetTranslatedPort(int n) { + if (n < GetDescriptorCount()) { + return GetDescriptor(n)->GetMachPort(); + } + return MACH_PORT_NULL; +} + +#pragma mark - + +//============================================================================== +// create a new mach port for receiving messages and register a name for it +ReceivePort::ReceivePort(const char* receive_port_name) { + mach_port_t current_task = mach_task_self(); + + init_result_ = mach_port_allocate(current_task, MACH_PORT_RIGHT_RECEIVE, &port_); + + if (init_result_ != KERN_SUCCESS) return; + + init_result_ = mach_port_insert_right(current_task, port_, port_, MACH_MSG_TYPE_MAKE_SEND); + + if (init_result_ != KERN_SUCCESS) return; + + NSPort* ns_port = [NSMachPort portWithMachPort:port_]; + NSString* port_name = [NSString stringWithUTF8String:receive_port_name]; + [[NSMachBootstrapServer sharedInstance] registerPort:ns_port name:port_name]; +} + +//============================================================================== +// create a new mach port for receiving messages +ReceivePort::ReceivePort() { + mach_port_t current_task = mach_task_self(); + + init_result_ = mach_port_allocate(current_task, MACH_PORT_RIGHT_RECEIVE, &port_); + + if (init_result_ != KERN_SUCCESS) return; + + init_result_ = mach_port_insert_right(current_task, port_, port_, MACH_MSG_TYPE_MAKE_SEND); +} + +//============================================================================== +// Given an already existing mach port, use it. We take ownership of the +// port and deallocate it in our destructor. +ReceivePort::ReceivePort(mach_port_t receive_port) + : port_(receive_port), init_result_(KERN_SUCCESS) {} + +//============================================================================== +ReceivePort::~ReceivePort() { + if (init_result_ == KERN_SUCCESS) mach_port_deallocate(mach_task_self(), port_); +} + +//============================================================================== +kern_return_t ReceivePort::WaitForMessage(MachReceiveMessage* out_message, + mach_msg_timeout_t timeout) { + if (!out_message) { + return KERN_INVALID_ARGUMENT; + } + + // return any error condition encountered in constructor + if (init_result_ != KERN_SUCCESS) return init_result_; + + out_message->Head()->msgh_bits = 0; + out_message->Head()->msgh_local_port = port_; + out_message->Head()->msgh_remote_port = MACH_PORT_NULL; + out_message->Head()->msgh_reserved = 0; + out_message->Head()->msgh_id = 0; + + kern_return_t result = mach_msg( + out_message->Head(), MACH_RCV_MSG | (timeout == MACH_MSG_TIMEOUT_NONE ? 0 : MACH_RCV_TIMEOUT), + 0, out_message->MaxSize(), port_, + timeout, // timeout in ms + MACH_PORT_NULL); + + return result; +} + +//============================================================================== +// send a message to this port +kern_return_t ReceivePort::SendMessageToSelf(MachSendMessage& message, mach_msg_timeout_t timeout) { + if (message.Head()->msgh_size == 0) { + NOTREACHED(); + return KERN_INVALID_VALUE; // just for safety -- never should occur + }; + + if (init_result_ != KERN_SUCCESS) return init_result_; + + message.Head()->msgh_remote_port = port_; + message.Head()->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); + kern_return_t result = mach_msg( + message.Head(), MACH_SEND_MSG | (timeout == MACH_MSG_TIMEOUT_NONE ? 0 : MACH_SEND_TIMEOUT), + message.Head()->msgh_size, 0, MACH_PORT_NULL, + timeout, // timeout in ms + MACH_PORT_NULL); + + return result; +} + +#pragma mark - + +//============================================================================== +// get a port with send rights corresponding to a named registered service +MachPortSender::MachPortSender(const char* receive_port_name) { + mach_port_t bootstrap_port = 0; + init_result_ = task_get_bootstrap_port(mach_task_self(), &bootstrap_port); + + if (init_result_ != KERN_SUCCESS) return; + + init_result_ = + bootstrap_look_up(bootstrap_port, const_cast<char*>(receive_port_name), &send_port_); +} + +//============================================================================== +MachPortSender::MachPortSender(mach_port_t send_port) + : send_port_(send_port), init_result_(KERN_SUCCESS) {} + +//============================================================================== +kern_return_t MachPortSender::SendMessage(MachSendMessage& message, mach_msg_timeout_t timeout) { + if (message.Head()->msgh_size == 0) { + NOTREACHED(); + return KERN_INVALID_VALUE; // just for safety -- never should occur + }; + + if (init_result_ != KERN_SUCCESS) return init_result_; + + message.Head()->msgh_remote_port = send_port_; + + kern_return_t result = mach_msg( + message.Head(), MACH_SEND_MSG | (timeout == MACH_MSG_TIMEOUT_NONE ? 0 : MACH_SEND_TIMEOUT), + message.Head()->msgh_size, 0, MACH_PORT_NULL, + timeout, // timeout in ms + MACH_PORT_NULL); + + return result; +} diff --git a/ipc/chromium/src/chrome/common/mach_message_source_mac.cc b/ipc/chromium/src/chrome/common/mach_message_source_mac.cc new file mode 100644 index 0000000000..d3be7efdb5 --- /dev/null +++ b/ipc/chromium/src/chrome/common/mach_message_source_mac.cc @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 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 "chrome/common/mach_message_source_mac.h" + +#include "base/logging.h" + +MachMessageSource::MachMessageSource(mach_port_t port, + MachPortListener* msg_listener, + bool* success) { + DCHECK(msg_listener); + DCHECK(success); + DCHECK(port != MACH_PORT_NULL); + + CFMachPortContext port_context = {0}; + port_context.info = msg_listener; + + scoped_cftyperef<CFMachPortRef> cf_mach_port_ref(CFMachPortCreateWithPort( + kCFAllocatorDefault, port, MachMessageSource::OnReceiveMachMessage, + &port_context, NULL)); + + if (cf_mach_port_ref.get() == NULL) { + CHROMIUM_LOG(WARNING) << "CFMachPortCreate failed"; + *success = false; + return; + } + + // Create a RL source. + machport_runloop_ref_.reset(CFMachPortCreateRunLoopSource( + kCFAllocatorDefault, cf_mach_port_ref.get(), 0)); + + if (machport_runloop_ref_.get() == NULL) { + CHROMIUM_LOG(WARNING) << "CFMachPortCreateRunLoopSource failed"; + *success = false; + return; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), machport_runloop_ref_.get(), + kCFRunLoopCommonModes); + *success = true; +} + +MachMessageSource::~MachMessageSource() { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), machport_runloop_ref_.get(), + kCFRunLoopCommonModes); +} + +// static +void MachMessageSource::OnReceiveMachMessage(CFMachPortRef port, void* msg, + CFIndex size, void* closure) { + MachPortListener* msg_listener = static_cast<MachPortListener*>(closure); + size_t msg_size = (size < 0) ? 0 : static_cast<size_t>(size); + DCHECK(msg && msg_size > 0); // this should never happen! + + if (msg_listener && msg && msg_size > 0) { + msg_listener->OnMachMessageReceived(msg, msg_size); + } +} diff --git a/ipc/chromium/src/chrome/common/mach_message_source_mac.h b/ipc/chromium/src/chrome/common/mach_message_source_mac.h new file mode 100644 index 0000000000..92ce0a73d4 --- /dev/null +++ b/ipc/chromium/src/chrome/common/mach_message_source_mac.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 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 CHROME_COMMON_MACH_MESSAGE_SOURCE_MAC_H_ +#define CHROME_COMMON_MACH_MESSAGE_SOURCE_MAC_H_ + +#include <CoreServices/CoreServices.h> + +#include "base/scoped_cftyperef.h" + +// Handles registering and cleaning up after a CFRunloopSource for a Mach port. +// Messages received on the port are piped through to a delegate. +// +// Example: +// class MyListener : public MachMessageSource::MachPortListener { +// public: +// void OnMachMessageReceived(void* mach_msg, size_t size) { +// printf("received message on Mach port\n"); +// } +// }; +// +// mach_port_t a_port = ...; +// MyListener listener; +// bool success = false; +// MachMessageSource message_source(port, listener, &success); +// +// if (!success) { +// exit(1); // Couldn't register mach runloop source. +// } +// +// CFRunLoopRun(); // Process messages on runloop... +class MachMessageSource { + public: + // Classes that want to listen on a Mach port can implement + // OnMachMessageReceived, |mach_msg| is a pointer to the raw message data and + // |size| is the buffer size; + class MachPortListener { + public: + virtual void OnMachMessageReceived(void* mach_msg, size_t size) = 0; + }; + + // |listener| is a week reference passed to CF, it needs to remain in + // existence till this object is destroeyd. + MachMessageSource(mach_port_t port, MachPortListener* listener, + bool* success); + ~MachMessageSource(); + + private: + // Called by CF when a new message arrives on the Mach port. + static void OnReceiveMachMessage(CFMachPortRef port, void* msg, CFIndex size, + void* closure); + + scoped_cftyperef<CFRunLoopSourceRef> machport_runloop_ref_; + DISALLOW_COPY_AND_ASSIGN(MachMessageSource); +}; + +#endif // CHROME_COMMON_MACH_MESSAGE_SOURCE_MAC_H_ diff --git a/ipc/chromium/src/chrome/common/process_watcher.h b/ipc/chromium/src/chrome/common/process_watcher.h new file mode 100644 index 0000000000..f1ccae6310 --- /dev/null +++ b/ipc/chromium/src/chrome/common/process_watcher.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 CHROME_COMMON_PROCESS_WATCHER_H_ +#define CHROME_COMMON_PROCESS_WATCHER_H_ + +#include "base/basictypes.h" +#include "base/process_util.h" + +class ProcessWatcher { + public: + // This method ensures that the specified process eventually terminates, and + // then it closes the given process handle. + // + // It assumes that the process has already been signalled to exit, and it + // begins by waiting a small amount of time for it to exit. If the process + // does not appear to have exited, then this function starts to become + // aggressive about ensuring that the process terminates. + // + // This method does not block the calling thread. + // + // NOTE: The process handle must have been opened with the PROCESS_TERMINATE + // and SYNCHRONIZE permissions. + // + static void EnsureProcessTerminated(base::ProcessHandle process_handle, + bool force = true); + + private: + // Do not instantiate this class. + ProcessWatcher(); + + DISALLOW_COPY_AND_ASSIGN(ProcessWatcher); +}; + +#endif // CHROME_COMMON_PROCESS_WATCHER_H_ diff --git a/ipc/chromium/src/chrome/common/process_watcher_posix_sigchld.cc b/ipc/chromium/src/chrome/common/process_watcher_posix_sigchld.cc new file mode 100644 index 0000000000..c3c7b512b1 --- /dev/null +++ b/ipc/chromium/src/chrome/common/process_watcher_posix_sigchld.cc @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <errno.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "base/eintr_wrapper.h" +#include "base/message_loop.h" +#include "base/process_util.h" + +#include "chrome/common/process_watcher.h" + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +// XXX/cjones: fairly arbitrary, chosen to match process_watcher_win.cc +static const int kMaxWaitMs = 2000; + +namespace { + +bool IsProcessDead(pid_t process) { + bool exited = false; + // don't care if the process crashed, just if it exited + base::DidProcessCrash(&exited, process); + return exited; +} + +class ChildReaper : public base::MessagePumpLibevent::SignalEvent, + public base::MessagePumpLibevent::SignalWatcher { + public: + explicit ChildReaper(pid_t process) : process_(process) {} + + virtual ~ChildReaper() { + // subclasses should have cleaned up |process_| already + DCHECK(!process_); + + // StopCatching() is implicit + } + + virtual void OnSignal(int sig) override { + DCHECK(SIGCHLD == sig); + DCHECK(process_); + + // this may be the SIGCHLD for a process other than |process_| + if (IsProcessDead(process_)) { + process_ = 0; + StopCatching(); + } + } + + protected: + void WaitForChildExit() { + DCHECK(process_); + HANDLE_EINTR(waitpid(process_, NULL, 0)); + } + + pid_t process_; + + private: + ChildReaper(const ChildReaper&) = delete; + + const ChildReaper& operator=(const ChildReaper&) = delete; +}; + +// Fear the reaper +class ChildGrimReaper : public ChildReaper, public mozilla::Runnable { + public: + explicit ChildGrimReaper(pid_t process) + : ChildReaper(process), mozilla::Runnable("ChildGrimReaper") {} + + virtual ~ChildGrimReaper() { + if (process_) KillProcess(); + } + + NS_IMETHOD Run() override { + // we may have already been signaled by the time this runs + if (process_) KillProcess(); + + return NS_OK; + } + + private: + void KillProcess() { + DCHECK(process_); + + if (IsProcessDead(process_)) { + process_ = 0; + return; + } + + if (0 == kill(process_, SIGKILL)) { + // XXX this will block for whatever amount of time it takes the + // XXX OS to tear down the process's resources. might need to + // XXX rethink this if it proves expensive + WaitForChildExit(); + } else { + CHROMIUM_LOG(ERROR) << "Failed to deliver SIGKILL to " << process_ << "!" + << "(" << errno << ")."; + } + process_ = 0; + } + + ChildGrimReaper(const ChildGrimReaper&) = delete; + + const ChildGrimReaper& operator=(const ChildGrimReaper&) = delete; +}; + +class ChildLaxReaper : public ChildReaper, + public MessageLoop::DestructionObserver { + public: + explicit ChildLaxReaper(pid_t process) : ChildReaper(process) {} + + virtual ~ChildLaxReaper() { + // WillDestroyCurrentMessageLoop() should have reaped process_ already + DCHECK(!process_); + } + + virtual void OnSignal(int sig) override { + ChildReaper::OnSignal(sig); + + if (!process_) { + MessageLoop::current()->RemoveDestructionObserver(this); + delete this; + } + } + + virtual void WillDestroyCurrentMessageLoop() override { + DCHECK(process_); + + WaitForChildExit(); + process_ = 0; + + // XXX don't think this is necessary, since destruction can only + // be observed once, but can't hurt + MessageLoop::current()->RemoveDestructionObserver(this); + delete this; + } + + private: + ChildLaxReaper(const ChildLaxReaper&) = delete; + + const ChildLaxReaper& operator=(const ChildLaxReaper&) = delete; +}; + +} // namespace + +/** + * Do everything possible to ensure that |process| has been reaped + * before this process exits. + * + * |grim| decides how strict to be with the child's shutdown. + * + * | child exit timeout | upon parent shutdown: + * +--------------------+---------------------------------- + * force=true | 2 seconds | kill(child, SIGKILL) + * force=false | infinite | waitpid(child) + * + * If a child process doesn't shut down properly, and |grim=false| + * used, then the parent will wait on the child forever. So, + * |force=false| is expected to be used when an external entity can be + * responsible for terminating hung processes, e.g. automated test + * harnesses. + */ +void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process, + bool force) { + DCHECK(process != base::GetCurrentProcId()); + DCHECK(process > 0); + + if (IsProcessDead(process)) return; + + MessageLoopForIO* loop = MessageLoopForIO::current(); + if (force) { + RefPtr<ChildGrimReaper> reaper = new ChildGrimReaper(process); + + loop->CatchSignal(SIGCHLD, reaper, reaper); + // |loop| takes ownership of |reaper| + loop->PostDelayedTask(reaper.forget(), kMaxWaitMs); + } else { + ChildLaxReaper* reaper = new ChildLaxReaper(process); + + loop->CatchSignal(SIGCHLD, reaper, reaper); + // |reaper| destroys itself after destruction notification + loop->AddDestructionObserver(reaper); + } +} diff --git a/ipc/chromium/src/chrome/common/process_watcher_win.cc b/ipc/chromium/src/chrome/common/process_watcher_win.cc new file mode 100644 index 0000000000..29b1b39fbd --- /dev/null +++ b/ipc/chromium/src/chrome/common/process_watcher_win.cc @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 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 "chrome/common/process_watcher.h" + +#include "base/message_loop.h" +#include "base/object_watcher.h" + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +static const int kWaitInterval = 2000; + +namespace { + +class ChildReaper : public mozilla::Runnable, + public base::ObjectWatcher::Delegate, + public MessageLoop::DestructionObserver { + public: + explicit ChildReaper(base::ProcessHandle process, bool force) + : mozilla::Runnable("ChildReaper"), process_(process), force_(force) { + watcher_.StartWatching(process_, this); + } + + virtual ~ChildReaper() { + if (process_) { + KillProcess(); + DCHECK(!process_) << "Make sure to close the handle."; + } + } + + // MessageLoop::DestructionObserver ----------------------------------------- + + virtual void WillDestroyCurrentMessageLoop() { + MOZ_ASSERT(!force_); + if (process_) { + WaitForSingleObject(process_, INFINITE); + base::CloseProcessHandle(process_); + process_ = 0; + + MessageLoop::current()->RemoveDestructionObserver(this); + delete this; + } + } + + // Task --------------------------------------------------------------------- + + NS_IMETHOD Run() override { + MOZ_ASSERT(force_); + if (process_) { + KillProcess(); + } + return NS_OK; + } + + // MessageLoop::Watcher ----------------------------------------------------- + + virtual void OnObjectSignaled(HANDLE object) { + // When we're called from KillProcess, the ObjectWatcher may still be + // watching. the process handle, so make sure it has stopped. + watcher_.StopWatching(); + + base::CloseProcessHandle(process_); + process_ = 0; + + if (!force_) { + MessageLoop::current()->RemoveDestructionObserver(this); + delete this; + } + } + + private: + void KillProcess() { + MOZ_ASSERT(force_); + + // OK, time to get frisky. We don't actually care when the process + // terminates. We just care that it eventually terminates, and that's what + // TerminateProcess should do for us. Don't check for the result code since + // it fails quite often. This should be investigated eventually. + TerminateProcess(process_, base::PROCESS_END_PROCESS_WAS_HUNG); + + // Now, just cleanup as if the process exited normally. + OnObjectSignaled(process_); + } + + // The process that we are watching. + base::ProcessHandle process_; + + base::ObjectWatcher watcher_; + + bool force_; + + DISALLOW_EVIL_CONSTRUCTORS(ChildReaper); +}; + +} // namespace + +// static +void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process, + bool force) { + DCHECK(process != GetCurrentProcess()); + + // If already signaled, then we are done! + if (WaitForSingleObject(process, 0) == WAIT_OBJECT_0) { + base::CloseProcessHandle(process); + return; + } + + MessageLoopForIO* loop = MessageLoopForIO::current(); + if (force) { + RefPtr<mozilla::Runnable> task = new ChildReaper(process, force); + loop->PostDelayedTask(task.forget(), kWaitInterval); + } else { + loop->AddDestructionObserver(new ChildReaper(process, force)); + } +} |