| // Copyright 2010-2014, 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 |