blob: 10a9bff7baa54d2b05a973861798c7e6b376bb92 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/edk/system/raw_channel.h"
#include <windows.h>
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/process/process.h"
#include "base/synchronization/lock.h"
#include "base/win/windows_version.h"
#include "mojo/edk/embedder/platform_handle.h"
#include "mojo/public/cpp/system/macros.h"
namespace mojo {
namespace system {
namespace {
class VistaOrHigherFunctions {
public:
VistaOrHigherFunctions();
bool is_vista_or_higher() const { return is_vista_or_higher_; }
BOOL SetFileCompletionNotificationModes(HANDLE handle, UCHAR flags) {
return set_file_completion_notification_modes_(handle, flags);
}
BOOL CancelIoEx(HANDLE handle, LPOVERLAPPED overlapped) {
return cancel_io_ex_(handle, overlapped);
}
private:
using SetFileCompletionNotificationModesFunc = BOOL(WINAPI*)(HANDLE, UCHAR);
using CancelIoExFunc = BOOL(WINAPI*)(HANDLE, LPOVERLAPPED);
bool is_vista_or_higher_;
SetFileCompletionNotificationModesFunc
set_file_completion_notification_modes_;
CancelIoExFunc cancel_io_ex_;
};
VistaOrHigherFunctions::VistaOrHigherFunctions()
: is_vista_or_higher_(base::win::GetVersion() >= base::win::VERSION_VISTA),
set_file_completion_notification_modes_(nullptr),
cancel_io_ex_(nullptr) {
if (!is_vista_or_higher_)
return;
HMODULE module = GetModuleHandleW(L"kernel32.dll");
set_file_completion_notification_modes_ =
reinterpret_cast<SetFileCompletionNotificationModesFunc>(
GetProcAddress(module, "SetFileCompletionNotificationModes"));
DCHECK(set_file_completion_notification_modes_);
cancel_io_ex_ =
reinterpret_cast<CancelIoExFunc>(GetProcAddress(module, "CancelIoEx"));
DCHECK(cancel_io_ex_);
}
base::LazyInstance<VistaOrHigherFunctions> g_vista_or_higher_functions =
LAZY_INSTANCE_INITIALIZER;
class RawChannelWin final : public RawChannel {
public:
RawChannelWin(embedder::ScopedPlatformHandle handle);
~RawChannelWin() override;
// |RawChannel| public methods:
size_t GetSerializedPlatformHandleSize() const override;
private:
// RawChannelIOHandler receives OS notifications for I/O completion. It must
// be created on the I/O thread.
//
// It manages its own destruction. Destruction happens on the I/O thread when
// all the following conditions are satisfied:
// - |DetachFromOwnerNoLock()| has been called;
// - there is no pending read;
// - there is no pending write.
class RawChannelIOHandler : public base::MessageLoopForIO::IOHandler {
public:
RawChannelIOHandler(RawChannelWin* owner,
embedder::ScopedPlatformHandle handle);
HANDLE handle() const { return handle_.get().handle; }
// The following methods are only called by the owner on the I/O thread.
bool pending_read() const;
base::MessageLoopForIO::IOContext* read_context();
// Instructs the object to wait for an |OnIOCompleted()| notification.
void OnPendingReadStarted();
// The following methods are only called by the owner under
// |owner_->write_lock()|.
bool pending_write_no_lock() const;
base::MessageLoopForIO::IOContext* write_context_no_lock();
// Instructs the object to wait for an |OnIOCompleted()| notification.
void OnPendingWriteStartedNoLock(size_t platform_handles_written);
// |base::MessageLoopForIO::IOHandler| implementation:
// Must be called on the I/O thread. It could be called before or after
// detached from the owner.
void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
DWORD bytes_transferred,
DWORD error) override;
// Must be called on the I/O thread under |owner_->write_lock()|.
// After this call, the owner must not make any further calls on this
// object, and therefore the object is used on the I/O thread exclusively
// (if it stays alive).
void DetachFromOwnerNoLock(scoped_ptr<ReadBuffer> read_buffer,
scoped_ptr<WriteBuffer> write_buffer);
private:
~RawChannelIOHandler() override;
// Returns true if |owner_| has been reset and there is not pending read or
// write.
// Must be called on the I/O thread.
bool ShouldSelfDestruct() const;
// Must be called on the I/O thread. It may be called before or after
// detaching from the owner.
void OnReadCompleted(DWORD bytes_read, DWORD error);
// Must be called on the I/O thread. It may be called before or after
// detaching from the owner.
void OnWriteCompleted(DWORD bytes_written, DWORD error);
embedder::ScopedPlatformHandle handle_;
// |owner_| is reset on the I/O thread under |owner_->write_lock()|.
// Therefore, it may be used on any thread under lock; or on the I/O thread
// without locking.
RawChannelWin* owner_;
// The following members must be used on the I/O thread.
scoped_ptr<ReadBuffer> preserved_read_buffer_after_detach_;
scoped_ptr<WriteBuffer> preserved_write_buffer_after_detach_;
bool suppress_self_destruct_;
bool pending_read_;
base::MessageLoopForIO::IOContext read_context_;
// The following members must be used under |owner_->write_lock()| while the
// object is still attached to the owner, and only on the I/O thread
// afterwards.
bool pending_write_;
size_t platform_handles_written_;
base::MessageLoopForIO::IOContext write_context_;
MOJO_DISALLOW_COPY_AND_ASSIGN(RawChannelIOHandler);
};
// |RawChannel| private methods:
IOResult Read(size_t* bytes_read) override;
IOResult ScheduleRead() override;
embedder::ScopedPlatformHandleVectorPtr GetReadPlatformHandles(
size_t num_platform_handles,
const void* platform_handle_table) override;
IOResult WriteNoLock(size_t* platform_handles_written,
size_t* bytes_written) override;
IOResult ScheduleWriteNoLock() override;
void OnInit() override;
void OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer,
scoped_ptr<WriteBuffer> write_buffer) override;
// Passed to |io_handler_| during initialization.
embedder::ScopedPlatformHandle handle_;
RawChannelIOHandler* io_handler_;
const bool skip_completion_port_on_success_;
MOJO_DISALLOW_COPY_AND_ASSIGN(RawChannelWin);
};
RawChannelWin::RawChannelIOHandler::RawChannelIOHandler(
RawChannelWin* owner,
embedder::ScopedPlatformHandle handle)
: handle_(handle.Pass()),
owner_(owner),
suppress_self_destruct_(false),
pending_read_(false),
pending_write_(false),
platform_handles_written_(0) {
memset(&read_context_.overlapped, 0, sizeof(read_context_.overlapped));
read_context_.handler = this;
memset(&write_context_.overlapped, 0, sizeof(write_context_.overlapped));
write_context_.handler = this;
owner_->message_loop_for_io()->RegisterIOHandler(handle_.get().handle, this);
}
RawChannelWin::RawChannelIOHandler::~RawChannelIOHandler() {
DCHECK(ShouldSelfDestruct());
}
bool RawChannelWin::RawChannelIOHandler::pending_read() const {
DCHECK(owner_);
DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
return pending_read_;
}
base::MessageLoopForIO::IOContext*
RawChannelWin::RawChannelIOHandler::read_context() {
DCHECK(owner_);
DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
return &read_context_;
}
void RawChannelWin::RawChannelIOHandler::OnPendingReadStarted() {
DCHECK(owner_);
DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
DCHECK(!pending_read_);
pending_read_ = true;
}
bool RawChannelWin::RawChannelIOHandler::pending_write_no_lock() const {
DCHECK(owner_);
owner_->write_lock().AssertAcquired();
return pending_write_;
}
base::MessageLoopForIO::IOContext*
RawChannelWin::RawChannelIOHandler::write_context_no_lock() {
DCHECK(owner_);
owner_->write_lock().AssertAcquired();
return &write_context_;
}
void RawChannelWin::RawChannelIOHandler::OnPendingWriteStartedNoLock(
size_t platform_handles_written) {
DCHECK(owner_);
owner_->write_lock().AssertAcquired();
DCHECK(!pending_write_);
pending_write_ = true;
platform_handles_written_ = platform_handles_written;
}
void RawChannelWin::RawChannelIOHandler::OnIOCompleted(
base::MessageLoopForIO::IOContext* context,
DWORD bytes_transferred,
DWORD error) {
DCHECK(!owner_ ||
base::MessageLoop::current() == owner_->message_loop_for_io());
// Suppress self-destruction inside |OnReadCompleted()|, etc. (in case they
// result in a call to |Shutdown()|).
bool old_suppress_self_destruct = suppress_self_destruct_;
suppress_self_destruct_ = true;
if (context == &read_context_)
OnReadCompleted(bytes_transferred, error);
else if (context == &write_context_)
OnWriteCompleted(bytes_transferred, error);
else
NOTREACHED();
// Maybe allow self-destruction again.
suppress_self_destruct_ = old_suppress_self_destruct;
if (ShouldSelfDestruct())
delete this;
}
void RawChannelWin::RawChannelIOHandler::DetachFromOwnerNoLock(
scoped_ptr<ReadBuffer> read_buffer,
scoped_ptr<WriteBuffer> write_buffer) {
DCHECK(owner_);
DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
owner_->write_lock().AssertAcquired();
// If read/write is pending, we have to retain the corresponding buffer.
if (pending_read_)
preserved_read_buffer_after_detach_ = read_buffer.Pass();
if (pending_write_)
preserved_write_buffer_after_detach_ = write_buffer.Pass();
owner_ = nullptr;
if (ShouldSelfDestruct())
delete this;
}
bool RawChannelWin::RawChannelIOHandler::ShouldSelfDestruct() const {
if (owner_ || suppress_self_destruct_)
return false;
// Note: Detached, hence no lock needed for |pending_write_|.
return !pending_read_ && !pending_write_;
}
void RawChannelWin::RawChannelIOHandler::OnReadCompleted(DWORD bytes_read,
DWORD error) {
DCHECK(!owner_ ||
base::MessageLoop::current() == owner_->message_loop_for_io());
DCHECK(suppress_self_destruct_);
CHECK(pending_read_);
pending_read_ = false;
if (!owner_)
return;
// Note: |OnReadCompleted()| may detach us from |owner_|.
if (error == ERROR_SUCCESS) {
DCHECK_GT(bytes_read, 0u);
owner_->OnReadCompleted(IO_SUCCEEDED, bytes_read);
} else if (error == ERROR_BROKEN_PIPE) {
DCHECK_EQ(bytes_read, 0u);
owner_->OnReadCompleted(IO_FAILED_SHUTDOWN, 0);
} else {
DCHECK_EQ(bytes_read, 0u);
LOG(WARNING) << "ReadFile: " << logging::SystemErrorCodeToString(error);
owner_->OnReadCompleted(IO_FAILED_UNKNOWN, 0);
}
}
void RawChannelWin::RawChannelIOHandler::OnWriteCompleted(DWORD bytes_written,
DWORD error) {
DCHECK(!owner_ ||
base::MessageLoop::current() == owner_->message_loop_for_io());
DCHECK(suppress_self_destruct_);
if (!owner_) {
// No lock needed.
CHECK(pending_write_);
pending_write_ = false;
return;
}
{
base::AutoLock locker(owner_->write_lock());
CHECK(pending_write_);
pending_write_ = false;
}
// Note: |OnWriteCompleted()| may detach us from |owner_|.
if (error == ERROR_SUCCESS) {
// Reset |platform_handles_written_| before calling |OnWriteCompleted()|
// since that function may call back to this class and set it again.
size_t local_platform_handles_written_ = platform_handles_written_;
platform_handles_written_ = 0;
owner_->OnWriteCompleted(IO_SUCCEEDED, local_platform_handles_written_,
bytes_written);
} else if (error == ERROR_BROKEN_PIPE) {
owner_->OnWriteCompleted(IO_FAILED_SHUTDOWN, 0, 0);
} else {
LOG(WARNING) << "WriteFile: " << logging::SystemErrorCodeToString(error);
owner_->OnWriteCompleted(IO_FAILED_UNKNOWN, 0, 0);
}
}
RawChannelWin::RawChannelWin(embedder::ScopedPlatformHandle handle)
: handle_(handle.Pass()),
io_handler_(nullptr),
skip_completion_port_on_success_(
g_vista_or_higher_functions.Get().is_vista_or_higher()) {
DCHECK(handle_.is_valid());
}
RawChannelWin::~RawChannelWin() {
DCHECK(!io_handler_);
}
size_t RawChannelWin::GetSerializedPlatformHandleSize() const {
return sizeof(DWORD) + sizeof(HANDLE);
}
RawChannel::IOResult RawChannelWin::Read(size_t* bytes_read) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
DCHECK(io_handler_);
DCHECK(!io_handler_->pending_read());
char* buffer = nullptr;
size_t bytes_to_read = 0;
read_buffer()->GetBuffer(&buffer, &bytes_to_read);
BOOL result =
ReadFile(io_handler_->handle(), buffer, static_cast<DWORD>(bytes_to_read),
nullptr, &io_handler_->read_context()->overlapped);
if (!result) {
DWORD error = GetLastError();
if (error == ERROR_BROKEN_PIPE)
return IO_FAILED_SHUTDOWN;
if (error != ERROR_IO_PENDING) {
LOG(WARNING) << "ReadFile: " << logging::SystemErrorCodeToString(error);
return IO_FAILED_UNKNOWN;
}
}
if (result && skip_completion_port_on_success_) {
DWORD bytes_read_dword = 0;
BOOL get_size_result = GetOverlappedResult(
io_handler_->handle(), &io_handler_->read_context()->overlapped,
&bytes_read_dword, FALSE);
DPCHECK(get_size_result);
*bytes_read = bytes_read_dword;
return IO_SUCCEEDED;
}
// If the read is pending or the read has succeeded but we don't skip
// completion port on success, instruct |io_handler_| to wait for the
// completion packet.
//
// TODO(yzshen): It seems there isn't document saying that all error cases
// (other than ERROR_IO_PENDING) are guaranteed to *not* queue a completion
// packet. If we do get one for errors, |RawChannelIOHandler::OnIOCompleted()|
// will crash so we will learn about it.
io_handler_->OnPendingReadStarted();
return IO_PENDING;
}
RawChannel::IOResult RawChannelWin::ScheduleRead() {
DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
DCHECK(io_handler_);
DCHECK(!io_handler_->pending_read());
size_t bytes_read = 0;
IOResult io_result = Read(&bytes_read);
if (io_result == IO_SUCCEEDED) {
DCHECK(skip_completion_port_on_success_);
// We have finished reading successfully. Queue a notification manually.
io_handler_->OnPendingReadStarted();
// |io_handler_| won't go away before the task is run, so it is safe to use
// |base::Unretained()|.
message_loop_for_io()->PostTask(
FROM_HERE, base::Bind(&RawChannelIOHandler::OnIOCompleted,
base::Unretained(io_handler_),
base::Unretained(io_handler_->read_context()),
static_cast<DWORD>(bytes_read), ERROR_SUCCESS));
return IO_PENDING;
}
return io_result;
}
embedder::ScopedPlatformHandleVectorPtr RawChannelWin::GetReadPlatformHandles(
size_t num_platform_handles,
const void* platform_handle_table) {
// TODO(jam): this code will have to be updated once it's used in a sandbox
// and the receiving process doesn't have duplicate permission for the
// receiver. Once there's a broker and we have a connection to it (possibly
// through ConnectionManager), then we can make a sync IPC to it here to get a
// token for this handle, and it will duplicate the handle to is process. Then
// we pass the token to the receiver, which will then make a sync call to the
// broker to get a duplicated handle. This will also allow us to avoid leaks
// of the handle if the receiver dies, since the broker can notice that.
DCHECK_GT(num_platform_handles, 0u);
embedder::ScopedPlatformHandleVectorPtr rv(
new embedder::PlatformHandleVector());
const char* serialization_data =
static_cast<const char*>(platform_handle_table);
for (size_t i = 0; i < num_platform_handles; i++) {
DWORD pid = *reinterpret_cast<const DWORD*>(serialization_data);
serialization_data += sizeof(DWORD);
HANDLE source_handle = *reinterpret_cast<const HANDLE*>(serialization_data);
serialization_data += sizeof(HANDLE);
base::Process sender =
base::Process::OpenWithAccess(pid, PROCESS_DUP_HANDLE);
DCHECK(sender.IsValid());
HANDLE target_handle = NULL;
BOOL dup_result =
DuplicateHandle(sender.Handle(), source_handle,
base::GetCurrentProcessHandle(), &target_handle, 0,
FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
DCHECK(dup_result);
rv->push_back(embedder::PlatformHandle(target_handle));
}
return rv.Pass();
}
RawChannel::IOResult RawChannelWin::WriteNoLock(
size_t* platform_handles_written,
size_t* bytes_written) {
write_lock().AssertAcquired();
DCHECK(io_handler_);
DCHECK(!io_handler_->pending_write_no_lock());
size_t num_platform_handles = 0;
if (write_buffer_no_lock()->HavePlatformHandlesToSend()) {
// Since we're not sure which process might ultimately deserialize this
// message, we can't duplicate the handle now. Instead, write the process ID
// and handle now and let the receiver duplicate it.
embedder::PlatformHandle* platform_handles;
void* serialization_data_temp;
write_buffer_no_lock()->GetPlatformHandlesToSend(
&num_platform_handles, &platform_handles, &serialization_data_temp);
char* serialization_data = static_cast<char*>(serialization_data_temp);
DCHECK_GT(num_platform_handles, 0u);
DCHECK(platform_handles);
DWORD current_process_id = base::GetCurrentProcId();
for (size_t i = 0; i < num_platform_handles; i++) {
*reinterpret_cast<DWORD*>(serialization_data) = current_process_id;
serialization_data += sizeof(DWORD);
*reinterpret_cast<HANDLE*>(serialization_data) =
platform_handles[i].handle;
serialization_data += sizeof(HANDLE);
platform_handles[i] = embedder::PlatformHandle();
}
}
std::vector<WriteBuffer::Buffer> buffers;
write_buffer_no_lock()->GetBuffers(&buffers);
DCHECK(!buffers.empty());
// TODO(yzshen): Handle multi-segment writes more efficiently.
DWORD bytes_written_dword = 0;
BOOL result =
WriteFile(io_handler_->handle(), buffers[0].addr,
static_cast<DWORD>(buffers[0].size), &bytes_written_dword,
&io_handler_->write_context_no_lock()->overlapped);
if (!result) {
DWORD error = GetLastError();
if (error == ERROR_BROKEN_PIPE)
return IO_FAILED_SHUTDOWN;
if (error != ERROR_IO_PENDING) {
LOG(WARNING) << "WriteFile: " << logging::SystemErrorCodeToString(error);
return IO_FAILED_UNKNOWN;
}
}
if (result && skip_completion_port_on_success_) {
*platform_handles_written = num_platform_handles;
*bytes_written = bytes_written_dword;
return IO_SUCCEEDED;
}
// If the write is pending or the write has succeeded but we don't skip
// completion port on success, instruct |io_handler_| to wait for the
// completion packet.
//
// TODO(yzshen): it seems there isn't document saying that all error cases
// (other than ERROR_IO_PENDING) are guaranteed to *not* queue a completion
// packet. If we do get one for errors, |RawChannelIOHandler::OnIOCompleted()|
// will crash so we will learn about it.
io_handler_->OnPendingWriteStartedNoLock(num_platform_handles);
return IO_PENDING;
}
RawChannel::IOResult RawChannelWin::ScheduleWriteNoLock() {
write_lock().AssertAcquired();
DCHECK(io_handler_);
DCHECK(!io_handler_->pending_write_no_lock());
size_t platform_handles_written = 0;
size_t bytes_written = 0;
IOResult io_result = WriteNoLock(&platform_handles_written, &bytes_written);
if (io_result == IO_SUCCEEDED) {
DCHECK(skip_completion_port_on_success_);
// We have finished writing successfully. Queue a notification manually.
io_handler_->OnPendingWriteStartedNoLock(platform_handles_written);
// |io_handler_| won't go away before that task is run, so it is safe to use
// |base::Unretained()|.
message_loop_for_io()->PostTask(
FROM_HERE,
base::Bind(&RawChannelIOHandler::OnIOCompleted,
base::Unretained(io_handler_),
base::Unretained(io_handler_->write_context_no_lock()),
static_cast<DWORD>(bytes_written), ERROR_SUCCESS));
return IO_PENDING;
}
return io_result;
}
void RawChannelWin::OnInit() {
DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
DCHECK(handle_.is_valid());
if (skip_completion_port_on_success_) {
// I don't know how this can fail (unless |handle_| is bad, in which case
// it's a bug in our code).
CHECK(g_vista_or_higher_functions.Get().SetFileCompletionNotificationModes(
handle_.get().handle, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS));
}
DCHECK(!io_handler_);
io_handler_ = new RawChannelIOHandler(this, handle_.Pass());
}
void RawChannelWin::OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer,
scoped_ptr<WriteBuffer> write_buffer) {
DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
DCHECK(io_handler_);
write_lock().AssertAcquired();
if (io_handler_->pending_read() || io_handler_->pending_write_no_lock()) {
// |io_handler_| will be alive until pending read/write (if any) completes.
// Call |CancelIoEx()| or |CancelIo()| so that resources can be freed up as
// soon as possible.
// Note: |CancelIo()| only cancels read/write requests started from this
// thread.
if (g_vista_or_higher_functions.Get().is_vista_or_higher()) {
g_vista_or_higher_functions.Get().CancelIoEx(io_handler_->handle(),
nullptr);
} else {
CancelIo(io_handler_->handle());
}
}
io_handler_->DetachFromOwnerNoLock(read_buffer.Pass(), write_buffer.Pass());
io_handler_ = nullptr;
}
} // namespace
// -----------------------------------------------------------------------------
// Static factory method declared in raw_channel.h.
// static
scoped_ptr<RawChannel> RawChannel::Create(
embedder::ScopedPlatformHandle handle) {
return make_scoped_ptr(new RawChannelWin(handle.Pass()));
}
} // namespace system
} // namespace mojo