blob: 1d0c096083c00c5f9b76a9d01d7774c2d689fa15 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// The IPC implementation using core Mach APIs.
#ifdef OS_MACOSX
#include "ipc/ipc.h"
#include <map>
#include <vector>
#include <launch.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "base/logging.h"
#include "base/mac_util.h"
#include "base/singleton.h"
#include "base/thread.h"
#include "ipc/ipc_path_manager.h"
namespace mozc {
namespace {
// Returns the name of the service corresponding to the name.
// Since the behavior of launch_msg() is changed from Yosemite (10.10),
// this function no longer relies on the information from launch_msg().
// When we add new services, we should update this function too.
bool GetMachPortName(const string &name, string *port_name) {
// Defined in data/mac/com.google.inputmethod.Japanese.Converter.plist
if (name == "session") {
port_name->assign(MacUtil::GetLabelForSuffix("") + "Converter.session");
return true;
}
// Defined in data/mac/com.google.inputmethod.Japanese.Renderer.plist
if (name == "renderer") {
port_name->assign(MacUtil::GetLabelForSuffix("") + "Renderer.renderer");
return true;
}
LOG(ERROR) << "Port not found: " << name;
return false;
}
// The default port manager for clients: using bootstrap_look_up.
// Please take care of calling this manager because bootstrap_look_up
// automatically starts the server processes. We want to delay
// starting server as far as possible.
class DefaultClientMachPortManager : public MachPortManagerInterface {
public:
virtual bool GetMachPort(const string &name, mach_port_t *port) {
string port_name;
if (!GetMachPortName(name, &port_name)) {
LOG(ERROR) << "Failed to get the port name";
return false;
}
kern_return_t kr = bootstrap_look_up(
bootstrap_port, const_cast<char *>(port_name.c_str()), port);
return kr == BOOTSTRAP_SUCCESS;
}
virtual bool IsServerRunning(const string &name) const {
string server_label = MacUtil::GetLabelForSuffix("");
if (name == "session") {
server_label += "Converter";
} else if (name == "renderer") {
server_label += "Renderer";
} else {
LOG(ERROR) << "Unknown server name: " << name;
server_label.assign(MacUtil::GetLabelForSuffix(name));
}
launch_data_t request = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
launch_data_dict_insert(request,
launch_data_new_string(server_label.c_str()),
LAUNCH_KEY_GETJOB);
launch_data_t job = launch_msg(request);
launch_data_free(request);
if (job == NULL) {
LOG(ERROR) << "Server job not found";
return false;
}
if (launch_data_get_type(job) != LAUNCH_DATA_DICTIONARY) {
LOG(ERROR) << "Something goes wrong with getting server information: "
<< launch_data_get_type(job);
launch_data_free(job);
return false;
}
launch_data_t pid_data = launch_data_dict_lookup(job, LAUNCH_JOBKEY_PID);
if (pid_data == NULL ||
launch_data_get_type(pid_data) != LAUNCH_DATA_INTEGER) {
// PID information is unavailable, which means server is not running.
VLOG(2) << "Returned job is formatted wrongly: cannot find PID data.";
launch_data_free(job);
return false;
}
VLOG(2) << "Server is running with PID "
<< launch_data_get_integer(pid_data);
launch_data_free(job);
return true;
}
};
// The default port manager for servers: using bootstrap_check_in. It
// won't succeed if the port name is not registered by launchd or the
// process is not invoked by launchd.
class DefaultServerMachPortManager : public MachPortManagerInterface {
public:
~DefaultServerMachPortManager() {
for (map<string, mach_port_t>::const_iterator it = mach_ports_.begin();
it != mach_ports_.end(); ++it) {
mach_port_destroy(mach_task_self(), it->second);
}
mach_ports_.clear();
}
virtual bool GetMachPort(const string &name, mach_port_t *port) {
string port_name;
if (!GetMachPortName(name, &port_name)) {
LOG(ERROR) << "Failed to get the port name";
return false;
}
DLOG(INFO) << "\"" << port_name << "\"";
map<string, mach_port_t>::const_iterator it = mach_ports_.find(port_name);
if (it != mach_ports_.end()) {
*port = it->second;
return true;
}
kern_return_t kr = bootstrap_check_in(
bootstrap_port, const_cast<char *>(port_name.c_str()), port);
mach_ports_[port_name] = *port;
return kr == BOOTSTRAP_SUCCESS;
}
// In the server side, it always return "true" because the caller
// itself is the server.
virtual bool IsServerRunning(const string &name) const {
return true;
}
private:
map<string, mach_port_t> mach_ports_;
};
struct mach_ipc_send_message {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_ool_descriptor_t data;
mach_msg_type_number_t count;
};
struct mach_ipc_receive_message {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_ool_descriptor_t data;
mach_msg_type_number_t count;
mach_msg_trailer_t trailer;
};
} // anonymous namespace
// Client implementation
IPCClient::IPCClient(const string &name)
: name_(name), mach_port_manager_(NULL), ipc_path_manager_(NULL) {
Init(name, "");
}
IPCClient::IPCClient(const string &name, const string &server_path)
: name_(name), mach_port_manager_(NULL), ipc_path_manager_(NULL) {
Init(name, server_path);
}
IPCClient::~IPCClient() {
// Do nothing
}
void IPCClient::Init(const string &name, const string & /*server_path*/) {
ipc_path_manager_ = IPCPathManager::GetIPCPathManager(name);
if (!ipc_path_manager_->LoadPathName()) {
LOG(ERROR) << "Cannot load IPC path name";
}
}
bool IPCClient::Call(const char *request_,
size_t input_length,
char *response_,
size_t *response_size,
int32 timeout) {
last_ipc_error_ = IPC_NO_ERROR;
MachPortManagerInterface *manager = mach_port_manager_;
if (manager == NULL) {
manager = Singleton<DefaultClientMachPortManager>::get();
}
// Obtain the server port
mach_port_t server_port;
if (manager == NULL || !manager->GetMachPort(name_, &server_port)) {
last_ipc_error_ = IPC_NO_CONNECTION;
LOG(ERROR) << "Cannot connect to the server";
return false;
}
// Creating sending port
kern_return_t kr;
mach_port_t client_port = MACH_PORT_NULL;
kr = mach_port_allocate(
mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
if (kr != KERN_SUCCESS) {
last_ipc_error_ = IPC_WRITE_ERROR;
LOG(ERROR) << "Cannot allocate the client port: " << kr;
return false;
}
// Prepare the sending message with OOL. Out-of-Line is a message
// sending which doesn't copy the message data but share the memory
// area between client and server.
mach_ipc_send_message send_message;
mach_msg_header_t *send_header = &(send_message.header);
send_header->msgh_bits =
MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND) |
MACH_MSGH_BITS_COMPLEX; // To enable OOL message
send_header->msgh_size = sizeof(send_message);
send_header->msgh_remote_port = server_port;
send_header->msgh_local_port = client_port;
send_header->msgh_reserved = 0;
send_header->msgh_id = IPC_PROTOCOL_VERSION;
send_message.body.msgh_descriptor_count = 1;
send_message.data.address = const_cast<char *>(request_);
send_message.data.size = input_length;
send_message.data.deallocate = false; // Doesn't deallocate data immediately
send_message.data.copy = MACH_MSG_VIRTUAL_COPY; // Copy on write
send_message.data.type = MACH_MSG_OOL_DESCRIPTOR;
send_message.count = send_message.data.size;
// Actually send the message
kr = mach_msg(send_header,
MACH_SEND_MSG | MACH_SEND_TIMEOUT, // send with timeout
send_header->msgh_size, // send size
0, // receive size is 0 because sending
MACH_PORT_NULL, // receive port
timeout, // timeout in msec
MACH_PORT_NULL); // notoicication port in case of error
if (kr == MACH_SEND_TIMED_OUT) {
LOG(ERROR) << "sending message timeout";
last_ipc_error_ = IPC_TIMEOUT_ERROR;
mach_port_destroy(mach_task_self(), client_port);
return false;
} else if (kr != MACH_MSG_SUCCESS) {
LOG(ERROR) << "unknown error on sending request: " << kr;
last_ipc_error_ = IPC_WRITE_ERROR;
mach_port_destroy(mach_task_self(), client_port);
return false;
}
// Receive server response
mach_ipc_receive_message receive_message;
mach_msg_header_t *receive_header;
// try to receive multiple messages because more than one processes
// send responses.
const int trial = 2;
for (int i = 0; i < trial; ++i) {
receive_header = &(receive_message.header);
receive_header->msgh_remote_port = server_port;
receive_header->msgh_local_port = client_port;
receive_header->msgh_size = sizeof(receive_message);
kr = mach_msg(receive_header,
MACH_RCV_MSG | MACH_RCV_TIMEOUT, // receive with timeout
0, // send size is 0 because receiving
receive_header->msgh_size, // receive size
client_port, // receive port
timeout, // timeout in msec
MACH_PORT_NULL); // notification port in case of error
if (kr == MACH_RCV_TIMED_OUT) {
LOG(ERROR) << "receiving message timeout";
last_ipc_error_ = IPC_TIMEOUT_ERROR;
break;
} else if (kr != MACH_MSG_SUCCESS) {
LOG(ERROR) << "unknown error on receiving response: " << kr;
last_ipc_error_ = IPC_READ_ERROR;
// This can be a wrong message. Try to receive again.
continue;
}
if (receive_header->msgh_id == IPC_PROTOCOL_VERSION) {
last_ipc_error_ = IPC_NO_ERROR;
if (*response_size > receive_message.data.size) {
*response_size = receive_message.data.size;
}
::memcpy(response_, receive_message.data.address, *response_size);
mach_port_destroy(mach_task_self(), client_port);
vm_deallocate(mach_task_self(),
(vm_address_t)receive_message.data.address,
receive_message.data.size);
return true;
}
}
LOG(ERROR) << "Receiving message failed";
if (last_ipc_error_ == IPC_NO_ERROR) {
last_ipc_error_ = IPC_READ_ERROR;
}
mach_port_destroy(mach_task_self(), client_port);
return false;
}
bool IPCClient::Connected() const {
if (!ipc_path_manager_->LoadPathName()) {
// No server files found: not running server or not initialized yet.
return false;
}
MachPortManagerInterface *manager = mach_port_manager_;
if (manager == NULL) {
manager = Singleton<DefaultClientMachPortManager>::get();
}
return manager->IsServerRunning(name_);
}
// Server implementation
IPCServer::IPCServer(const string &name,
int32 num_connections,
int32 timeout)
: name_(name), mach_port_manager_(NULL), timeout_(timeout) {
// This is a fake IPC path manager: it just stores the server
// version and IPC name but we don't use the stored IPC name itself.
// It's just for compatibility.
IPCPathManager *manager = IPCPathManager::GetIPCPathManager(name);
// The IPC server uses old path name at this time.
// TODO(mukai): Switch to new path name when the new IPC client is
// widely distributed.
LOG(INFO) << "Server created";
if (!manager->CreateNewPathName()) {
LOG(ERROR) << "Cannot make IPC path name";
return;
}
if (!manager->SavePathName()) {
LOG(ERROR) << "Cannot save IPC path name";
}
}
IPCServer::~IPCServer() {
// Do nothing
}
bool IPCServer::Connected() const {
MachPortManagerInterface *manager = mach_port_manager_;
if (manager == NULL) {
manager = Singleton<DefaultServerMachPortManager>::get();
}
return manager->IsServerRunning(name_);
}
void IPCServer::Loop() {
MachPortManagerInterface *manager = mach_port_manager_;
if (manager == NULL) {
manager = Singleton<DefaultServerMachPortManager>::get();
}
// Obtain the server port
mach_port_t server_port;
if (manager == NULL || !manager->GetMachPort(name_, &server_port)) {
LOG(ERROR) << "Failed to reserve the port.";
return;
}
mach_ipc_send_message send_message;
mach_ipc_receive_message receive_message;
mach_msg_header_t *send_header, *receive_header;
kern_return_t kr;
bool finished = false;
char response[IPC_RESPONSESIZE];
while (!finished) {
// Receive request
receive_header = &(receive_message.header);
receive_header->msgh_local_port = server_port;
receive_header->msgh_size = sizeof(receive_message);
kr = mach_msg(receive_header,
MACH_RCV_MSG, // no timeout when receiving clients
0, // send size is 0 because receiving
receive_header->msgh_size, // receive size
server_port, // receive port
MACH_MSG_TIMEOUT_NONE, // no timeout
MACH_PORT_NULL); // notification port in case of error
if (kr != MACH_MSG_SUCCESS) {
LOG(ERROR) << "Something around mach ports goes wrong: " << kr;
break;
}
if (receive_header->msgh_id != IPC_PROTOCOL_VERSION) {
LOG(ERROR) << "Invalid message";
continue;
}
size_t response_size = IPC_RESPONSESIZE;
if (!Process(static_cast<char *>(receive_message.data.address),
receive_message.data.size,
response, &response_size)) {
LOG(INFO) << "Process() returns false. Quit the wait loop.";
finished = true;
}
vm_deallocate(mach_task_self(),
(vm_address_t)receive_message.data.address,
receive_message.data.size);
// Send response
send_header = &(send_message.header);
send_header->msgh_bits = MACH_MSGH_BITS_LOCAL(receive_header->msgh_bits) |
MACH_MSGH_BITS_COMPLEX; // To enable OOL message
send_header->msgh_size = sizeof(send_message);
send_header->msgh_local_port = MACH_PORT_NULL;
send_header->msgh_remote_port = receive_header->msgh_remote_port;
send_header->msgh_id = receive_header->msgh_id;
send_message.body.msgh_descriptor_count = 1;
send_message.data.address = response;
send_message.data.size = response_size;
// Doesn't deallocate data immediately
send_message.data.deallocate = false;
send_message.data.copy = MACH_MSG_VIRTUAL_COPY; // Copy on write
send_message.data.type = MACH_MSG_OOL_DESCRIPTOR;
send_message.count = send_message.data.size;
kr = mach_msg(send_header,
MACH_SEND_MSG | MACH_SEND_TIMEOUT, // send with timeout
send_header->msgh_size, // send size
0, // receive size is 0 because sending
MACH_PORT_NULL, // receive port
timeout_, // timeout
MACH_PORT_NULL); // notification port in case of error
if (kr != MACH_MSG_SUCCESS) {
LOG(ERROR) << "Something around mach ports goes wrong: " << kr;
continue;
}
}
}
void IPCServer::Terminate() {
server_thread_->Terminate();
}
} // namespace mozc
#endif // OS_MACOSX