blob: d29da5d3413a7e06dbe2b8611a45a00c021bc9ab [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.
// skip all if non-Linux or Android.
#if defined(OS_LINUX) && !defined(OS_ANDROID)
#include "ipc/ipc.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <libgen.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include "base/file_util.h"
#include "base/logging.h"
#include "base/thread.h"
#include "ipc/ipc_path_manager.h"
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif // UNIX_PATH_MAX
namespace mozc {
namespace {
const int kInvalidSocket = -1;
void mkdir_p(const string &dirname) {
const string parent_dir = FileUtil::Dirname(dirname);
struct stat st;
if (!parent_dir.empty() &&
::stat(parent_dir.c_str(), &st) < 0) {
mkdir_p(parent_dir);
}
FileUtil::CreateDirectory(dirname);
}
bool IsReadTimeout(int socket, int timeout) {
if (timeout < 0) {
return false;
}
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(socket, &fds);
tv.tv_sec = timeout / 1000;
tv.tv_usec = 1000 * (timeout % 1000);
if (select(socket + 1, &fds, NULL, NULL, &tv) < 0) {
// Mac OS X and glibc implementations of strerror() return a pointer to a
// string literal whenever errno is in a valid range, and thus thread-safe.
// Probably we don't have to use the cumbersome strerror_r() function.
LOG(WARNING) << "select() failed: " << strerror(errno);
return true;
}
if (FD_ISSET(socket, &fds)) {
return false;
}
LOG(ERROR) << "FD_ISSET failed";
return true;
}
bool IsWriteTimeout(int socket, int timeout) {
if (timeout < 0) {
return false;
}
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(socket, &fds);
tv.tv_sec = timeout / 1000;
tv.tv_usec = 1000 * (timeout % 1000);
if (select(socket + 1, NULL, &fds, NULL, &tv) < 0) {
LOG(WARNING) << "select() failed: " << strerror(errno);
return true;
}
if (FD_ISSET(socket, &fds)) {
return false;
}
LOG(ERROR) << "FD_ISSET failed";
return true;
}
bool IsPeerValid(int socket, pid_t *pid) {
*pid = 0;
// On ARM Linux, we do nothing and just return true since the platform
// sometimes doesn't support the getsockopt(sock, SOL_SOCKET, SO_PEERCRED)
// system call.
// TODO(yusukes): Add implementation for ARM Linux.
#ifndef __arm__
struct ucred peer_cred;
int peer_cred_len = sizeof(peer_cred);
if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED,
reinterpret_cast<void *>(&peer_cred),
reinterpret_cast<socklen_t *>(&peer_cred_len)) < 0) {
LOG(ERROR) << "cannot get peer credential. Not a Unix socket?";
return false;
}
if (peer_cred.uid != ::geteuid()) {
LOG(WARNING) << "uid mismatch." << peer_cred.uid << "!=" << ::geteuid();
return false;
}
*pid = peer_cred.pid;
#endif // __arm__
return true;
}
bool SendMessage(int socket,
const char *buf,
size_t buf_length, int timeout,
IPCErrorType *last_ipc_error) {
size_t buf_length_left = buf_length;
while (buf_length_left > 0) {
if (IsWriteTimeout(socket, timeout)) {
LOG(WARNING) << "Write timeout " << timeout;
*last_ipc_error = IPC_TIMEOUT_ERROR;
return false;
}
const ssize_t l = ::send(socket, buf, buf_length_left, MSG_NOSIGNAL);
if (l < 0) {
// An error occurs.
LOG(ERROR) << "an error occurred during sending \""
<< string(buf, buf_length_left) << "\": " << strerror(errno);
*last_ipc_error = IPC_WRITE_ERROR;
return false;
}
buf += l;
buf_length_left -= l;
}
VLOG(1) << buf_length << " bytes sent";
return true;
}
bool RecvMessage(int socket,
char *buf,
size_t *buf_length,
int timeout,
IPCErrorType *last_ipc_error) {
if (*buf_length == 0) {
LOG(WARNING) << "buf_length is 0";
*last_ipc_error = IPC_UNKNOWN_ERROR;
return false;
}
ssize_t buf_left = *buf_length;
ssize_t read_length = 0;
*buf_length = 0;
do {
if (IsReadTimeout(socket, timeout)) {
LOG(WARNING) << "Read timeout " << timeout;
*last_ipc_error = IPC_TIMEOUT_ERROR;
return false;
}
read_length = ::recv(socket, buf, buf_left, 0);
if (read_length < 0) {
LOG(ERROR) << "an error occurred during recv(): " << strerror(errno);
*buf_length = 0;
*last_ipc_error = IPC_READ_ERROR;
return false;
}
*buf_length += read_length;
buf += read_length;
buf_left -= read_length;
} while (read_length != 0 && buf_left > 0);
VLOG(1) << *buf_length << " bytes received";
return true;
}
void SetCloseOnExecFlag(int fd) {
int flags = ::fcntl(fd, F_GETFD, 0);
if (flags < 0) {
LOG(WARNING) << "fcntl(F_GETFD) for fd " << fd << " failed: "
<< strerror(errno);
return;
}
flags |= FD_CLOEXEC;
if (::fcntl(fd, F_SETFD, flags) != 0) {
LOG(WARNING) << "fcntl(F_SETFD) for fd " << fd << " failed: "
<< strerror(errno);
}
}
// Returns true if address is in abstract namespace. See unix(7) on Linux for
// details.
bool IsAbstractSocket(const string& address) {
return (!address.empty()) && (address[0] == '\0');
}
} // namespace
// Client
IPCClient::IPCClient(const string &name)
: socket_(kInvalidSocket), connected_(false),
ipc_path_manager_(NULL),
last_ipc_error_(IPC_NO_ERROR) {
Init(name, "");
}
IPCClient::IPCClient(const string &name, const string &server_path)
: socket_(kInvalidSocket), connected_(false),
ipc_path_manager_(NULL),
last_ipc_error_(IPC_NO_ERROR) {
Init(name, server_path);
}
void IPCClient::Init(const string &name, const string &server_path) {
last_ipc_error_ = IPC_NO_CONNECTION;
// Try twice, because key may be changed.
IPCPathManager *manager = IPCPathManager::GetIPCPathManager(name);
if (manager == NULL) {
LOG(ERROR) << "IPCPathManager::GetIPCPathManager failed";
return;
}
ipc_path_manager_ = manager;
for (size_t trial = 0; trial < 2; ++trial) {
string server_address;
if (!manager->LoadPathName() || !manager->GetPathName(&server_address)) {
continue;
}
sockaddr_un address;
::memset(&address, 0, sizeof(address));
const size_t server_address_length =
(server_address.size() >= UNIX_PATH_MAX) ?
UNIX_PATH_MAX - 1 : server_address.size();
if (server_address.size() >= UNIX_PATH_MAX) {
LOG(WARNING) << "too long path: " << server_address;
}
socket_ = socket(PF_UNIX, SOCK_STREAM, 0);
if (socket_ < 0) {
LOG(WARNING) << "socket failed: " << strerror(errno);
continue;
}
SetCloseOnExecFlag(socket_);
address.sun_family = AF_UNIX;
::memcpy(address.sun_path, server_address.data(), server_address_length);
address.sun_path[server_address_length] = '\0';
const size_t sun_len = sizeof(address.sun_family) + server_address_length;
pid_t pid = 0;
if (::connect(socket_,
reinterpret_cast<const sockaddr*>(&address),
sun_len) != 0 ||
!IsPeerValid(socket_, &pid)) {
if ((errno == ENOTSOCK || errno == ECONNREFUSED) &&
!IsAbstractSocket(server_address)) {
// If abstract namepace is not enabled, recreate server_addresss path.
::unlink(server_address.c_str());
}
LOG(WARNING) << "connect failed: " << strerror(errno);
connected_ = false;
manager->Clear();
continue;
} else {
if (!manager->IsValidServer(static_cast<uint32>(pid),
server_path)) {
LOG(ERROR) << "Connecting to invalid server";
last_ipc_error_ = IPC_INVALID_SERVER;
break;
}
last_ipc_error_ = IPC_NO_ERROR;
connected_ = true;
break;
}
}
}
IPCClient::~IPCClient() {
if (socket_ != kInvalidSocket) {
if (::close(socket_) < 0) {
LOG(WARNING) << "close failed: " << strerror(errno);
}
socket_ = kInvalidSocket;
}
connected_ = false;
VLOG(1) << "connection closed (IPCClient destructed)";
}
// RPC call
bool IPCClient::Call(const char *request_,
size_t input_length,
char *response_,
size_t *response_size,
int32 timeout) {
last_ipc_error_ = IPC_NO_ERROR;
if (!SendMessage(socket_, request_, input_length, timeout,
&last_ipc_error_)) {
LOG(ERROR) << "SendMessage failed";
return false;
}
// Half-close the socket so that mozc_server could know the length of the
// request data. Without this, RecvMessage() in mozc_server would fail with
// timeout.
// TODO(yusukes): It would be also possible to modify Send and RecvMessage()
// so that they send/recv the payload size explicitly together with the actual
// data. Will revisit later.
::shutdown(socket_, SHUT_WR);
if (!RecvMessage(socket_, response_, response_size, timeout,
&last_ipc_error_)) {
LOG(ERROR) << "RecvMessage failed";
return false;
}
VLOG(1) << "Call succeeded";
return true;
}
bool IPCClient::Connected() const {
return connected_;
}
// Server
IPCServer::IPCServer(const string &name,
int32 num_connections,
int32 timeout)
: connected_(false), socket_(kInvalidSocket), timeout_(timeout) {
IPCPathManager *manager = IPCPathManager::GetIPCPathManager(name);
if (!manager->CreateNewPathName() && !manager->LoadPathName()) {
LOG(ERROR) << "Cannot prepare IPC path name";
return;
}
if (!manager->GetPathName(&server_address_)) {
LOG(ERROR) << "Cannot make IPC path name";
return;
}
DCHECK(!server_address_.empty());
const string dirname = FileUtil::Dirname(server_address_);
mkdir_p(dirname);
sockaddr_un addr;
::memset(&addr, 0, sizeof(addr));
socket_ = ::socket(PF_UNIX, SOCK_STREAM, 0);
if (socket_ < 0) {
LOG(WARNING) << "socket failed: " << strerror(errno);
return;
}
SetCloseOnExecFlag(socket_);
if (server_address_.size() >= UNIX_PATH_MAX) {
LOG(WARNING) << "server address is too long";
return;
}
addr.sun_family = AF_UNIX;
::memcpy(addr.sun_path,
server_address_.data(),
server_address_.size());
addr.sun_path[server_address_.size()] = '\0';
int on = 1;
::setsockopt(socket_,
SOL_SOCKET,
SO_REUSEADDR,
reinterpret_cast<char *>(&on),
sizeof(on));
const size_t sun_len = sizeof(addr.sun_family) + server_address_.size();
if (!IsAbstractSocket(server_address_)) {
// Linux does not use files for IPC.
::chmod(server_address_.c_str(), 0600);
}
if (::bind(socket_, reinterpret_cast<sockaddr *>(&addr), sun_len) != 0) {
// The UNIX domain socket file (server_address_) already exists?
LOG(FATAL) << "bind() failed: " << strerror(errno);
return;
}
if (::listen(socket_, num_connections) < 0) {
LOG(FATAL) << "listen() failed: " << strerror(errno);
return;
}
if (!manager->SavePathName()) {
LOG(ERROR) << "Cannot save IPC path name";
return;
}
connected_ = true;
VLOG(1) << "IPCServer ready";
}
IPCServer::~IPCServer() {
if (server_thread_.get() != NULL) {
server_thread_->Terminate();
}
::shutdown(socket_, SHUT_RDWR);
::close(socket_);
if (!IsAbstractSocket(server_address_)) {
// When abstract namespace is used, unlink() is not necessary.
::unlink(server_address_.c_str());
}
connected_ = false;
socket_ = kInvalidSocket;
VLOG(1) << "IPCServer destructed";
}
bool IPCServer::Connected() const {
return connected_;
}
void IPCServer::Loop() {
// The most portable and straightforward single-thread server
bool error = false;
IPCErrorType last_ipc_error = IPC_NO_ERROR;
pid_t pid = 0;
while (!error) {
const int new_sock = ::accept(socket_, NULL, NULL);
if (new_sock < 0) {
LOG(FATAL) << "accept() failed: " << strerror(errno);
return;
}
if (!IsPeerValid(new_sock, &pid)) {
continue;
}
size_t request_size = sizeof(request_);
size_t response_size = sizeof(response_);
if (RecvMessage(new_sock,
&request_[0],
&request_size, timeout_, &last_ipc_error)) {
if (!Process(&request_[0], request_size,
&response_[0], &response_size)) {
LOG(WARNING) << "Process() failed";
error = true;
}
if (response_size > 0) {
SendMessage(new_sock,
&response_[0],
response_size, timeout_, &last_ipc_error);
}
}
::close(new_sock);
}
::shutdown(socket_, SHUT_RDWR);
::close(socket_);
if (!IsAbstractSocket(server_address_)) {
// When abstract namespace is used, unlink() is not necessary.
::unlink(server_address_.c_str());
}
connected_ = false;
socket_ = kInvalidSocket;
}
void IPCServer::Terminate() {
server_thread_->Terminate();
}
}; // namespace mozc
#endif // OS_LINUX && !OS_ANDROID