| // 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 unless OS_WIN |
| #ifdef OS_WIN |
| |
| #include "ipc/ipc.h" |
| |
| #include <Windows.h> |
| #include <Sddl.h> |
| |
| #include <string> |
| |
| #include "base/const.h" |
| #include "base/cpu_stats.h" |
| #include "base/logging.h" |
| #include "base/scoped_handle.h" |
| #include "base/singleton.h" |
| #include "base/system_util.h" |
| #include "base/thread.h" |
| #include "base/util.h" |
| #include "base/win_sandbox.h" |
| #include "base/win_util.h" |
| #include "ipc/ipc_path_manager.h" |
| |
| namespace mozc { |
| namespace { |
| |
| const bool kReadTypeACK = true; |
| const bool kReadTypeData = false; |
| const bool kSendTypeData = false; |
| const int kMaxSuccessiveConnectionFailureCount = 5; |
| |
| size_t GetNumberOfProcessors() { |
| // thread-safety is not required. |
| static size_t num = CPUStats().GetNumberOfProcessors(); |
| return max(num, 1); |
| } |
| |
| // Least significant bit of OVERLAPPED::hEvent can be used for special |
| // purpose against GetQueuedCompletionStatus API. |
| // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364986.aspx |
| // This function provides a safe way to retrieve the actual event handle |
| // even in this situation. |
| HANDLE GetEventHandleFromOverlapped(const OVERLAPPED *overlapped) { |
| return reinterpret_cast<HANDLE>( |
| reinterpret_cast<DWORD_PTR>(overlapped->hEvent) & ~1); |
| } |
| |
| // Returns true if the given |overlapped| is initialized in successful. |
| bool InitOverlapped(OVERLAPPED *overlapped, HANDLE wait_handle) { |
| if (wait_handle == 0 || wait_handle == INVALID_HANDLE_VALUE) { |
| LOG(ERROR) << "wait_handle is invalid."; |
| return false; |
| } |
| ::ZeroMemory(overlapped, sizeof(OVERLAPPED)); |
| if (::ResetEvent(wait_handle) == FALSE) { |
| const DWORD last_error = ::GetLastError(); |
| LOG(ERROR) << "::ResetEvent failed. error: " << last_error; |
| return false; |
| } |
| overlapped->hEvent = wait_handle; |
| return true; |
| } |
| |
| class IPCClientMutexBase { |
| public: |
| explicit IPCClientMutexBase(const string &ipc_channel_name) { |
| // Make a kernel mutex object so that multiple ipc connections are |
| // serialized here. In Windows, there is no useful way to serialize |
| // the multiple connections to the single-thread named pipe server. |
| // WaitForNamedPipe doesn't work for this propose as it just lets |
| // clients know that the connection becomes "available" right now. |
| // It doesn't mean that connection is available for the current |
| // thread. The "available" notification is sent to all waiting ipc |
| // clients at the same time and only one client gets the connection. |
| // This causes redundant and wasteful CreateFile calles. |
| string mutex_name = kMutexPathPrefix; |
| mutex_name += SystemUtil::GetUserSidAsString(); |
| mutex_name += "."; |
| mutex_name += ipc_channel_name; |
| mutex_name += ".ipc"; |
| wstring wmutex_name; |
| Util::UTF8ToWide(mutex_name.c_str(), &wmutex_name); |
| |
| LPSECURITY_ATTRIBUTES security_attributes_ptr = nullptr; |
| SECURITY_ATTRIBUTES security_attributes; |
| if (!WinSandbox::MakeSecurityAttributes(WinSandbox::kSharableMutex, |
| &security_attributes)) { |
| LOG(ERROR) << "Cannot make SecurityAttributes"; |
| } else { |
| security_attributes_ptr = &security_attributes; |
| } |
| |
| // http://msdn.microsoft.com/en-us/library/ms682411(VS.85).aspx: |
| // Two or more processes can call CreateMutex to create the same named |
| // mutex. The first process actually creates the mutex, and subsequent |
| // processes with sufficient access rights simply open a handle to |
| // the existing mutex. This enables multiple processes to get handles |
| // of the same mutex, while relieving the user of the responsibility |
| // of ensuring that the creating process is started first. |
| // When using this technique, you should set the |
| // bInitialOwner flag to FALSE; otherwise, it can be difficult to be |
| // certain which process has initial ownership. |
| ipc_mutex_.reset(::CreateMutex(security_attributes_ptr, |
| FALSE, wmutex_name.c_str())); |
| if (security_attributes_ptr != nullptr) { |
| ::LocalFree(security_attributes_ptr->lpSecurityDescriptor); |
| } |
| |
| const DWORD create_mutex_error = ::GetLastError(); |
| if (ipc_mutex_.get() == nullptr) { |
| LOG(ERROR) << "CreateMutex failed: " << create_mutex_error; |
| return; |
| } |
| } |
| |
| virtual ~IPCClientMutexBase() {} |
| |
| HANDLE get() const { |
| return ipc_mutex_.get(); |
| } |
| |
| private: |
| ScopedHandle ipc_mutex_; |
| }; |
| |
| class ConverterClientMutex : public IPCClientMutexBase { |
| public: |
| ConverterClientMutex() |
| : IPCClientMutexBase("converter") {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ConverterClientMutex); |
| }; |
| |
| class RendererClientMutex : public IPCClientMutexBase { |
| public: |
| RendererClientMutex() |
| : IPCClientMutexBase("renderer") {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(RendererClientMutex); |
| }; |
| |
| class FallbackClientMutex : public IPCClientMutexBase { |
| public: |
| FallbackClientMutex() |
| : IPCClientMutexBase("fallback") {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(FallbackClientMutex); |
| }; |
| |
| // In Mozc client, we should support different IPC channels (client-converter |
| // and client-renderer) so we need to have different global mutexes to |
| // serialize each client. Currently |ipc_name| starts with "session" and |
| // "renderer" are expected. |
| HANDLE GetClientMutex(const string &ipc_name) { |
| if (Util::StartsWith(ipc_name, "session")) { |
| return Singleton<ConverterClientMutex>::get()->get(); |
| } |
| if (Util::StartsWith(ipc_name, "renderer")) { |
| return Singleton<RendererClientMutex>::get()->get(); |
| } |
| LOG(WARNING) << "unexpected IPC name: " << ipc_name; |
| return Singleton<FallbackClientMutex>::get()->get(); |
| } |
| |
| // RAII class for calling ReleaseMutex in destructor. |
| class ScopedReleaseMutex { |
| public: |
| explicit ScopedReleaseMutex(HANDLE handle) |
| : pipe_handle_(handle) {} |
| |
| virtual ~ScopedReleaseMutex() { |
| if (nullptr != pipe_handle_) { |
| ::ReleaseMutex(pipe_handle_); |
| } |
| pipe_handle_ = nullptr; |
| } |
| |
| HANDLE get() const { |
| return pipe_handle_; |
| } |
| |
| private: |
| HANDLE pipe_handle_; |
| |
| DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedReleaseMutex); |
| }; |
| |
| uint32 GetServerProcessIdImpl(HANDLE handle) { |
| ULONG pid = 0; |
| if (::GetNamedPipeServerProcessId(handle, &pid) == 0) { |
| const DWORD get_named_pipe_server_process_id_error = ::GetLastError(); |
| LOG(ERROR) << "GetNamedPipeServerProcessId failed: " |
| << get_named_pipe_server_process_id_error; |
| return static_cast<uint32>(-1); // always deny the connection |
| } |
| |
| VLOG(1) << "Got server ProcessID: " << pid; |
| |
| return static_cast<uint32>(pid); |
| } |
| |
| void SafeCancelIO(HANDLE device_handle, OVERLAPPED *overlapped) { |
| if (::CancelIo(device_handle) == FALSE) { |
| const DWORD cancel_error = ::GetLastError(); |
| LOG(ERROR) << "Failed to CancelIo: " << cancel_error; |
| } |
| |
| // Wait for the completion of the on-going request forever. This is not |
| // _safe_ and should be fixed anyway. |
| // TODO(yukawa): Avoid INFINITE if possible. |
| ::WaitForSingleObject(GetEventHandleFromOverlapped(overlapped), INFINITE); |
| } |
| |
| bool WaitForQuitOrIOImpl( |
| HANDLE device_handle, HANDLE quit_event, DWORD timeout, |
| OVERLAPPED *overlapped, IPCErrorType *last_ipc_error) { |
| const HANDLE events[] = { |
| quit_event, GetEventHandleFromOverlapped(overlapped) |
| }; |
| const DWORD wait_result = ::WaitForMultipleObjects( |
| ARRAYSIZE(events), events, FALSE, timeout); |
| const DWORD wait_error = ::GetLastError(); |
| // Clear the I/O operation if still exists. |
| if (!HasOverlappedIoCompleted(overlapped)) { |
| // This is not safe because this operation may be blocked forever. |
| // TODO(yukawa): Implement safer cancelation mechanism. |
| SafeCancelIO(device_handle, overlapped); |
| } |
| if (wait_result == WAIT_TIMEOUT) { |
| LOG(WARNING) << "Timeout: " << timeout; |
| *last_ipc_error = IPC_TIMEOUT_ERROR; |
| return false; |
| } |
| if (wait_result == WAIT_OBJECT_0) { |
| // Should be quit immediately |
| *last_ipc_error = IPC_QUIT_EVENT_SIGNALED; |
| return false; |
| } |
| if (wait_result != (WAIT_OBJECT_0 + 1)) { |
| LOG(WARNING) << "Unknown result: " << wait_result |
| << ", Error: " << wait_error; |
| *last_ipc_error = IPC_UNKNOWN_ERROR; |
| return false; |
| } |
| return true; |
| } |
| |
| bool WaitForIOImpl(HANDLE device_handle, DWORD timeout, |
| OVERLAPPED *overlapped, IPCErrorType *last_ipc_error) { |
| const DWORD wait_result = ::WaitForSingleObject( |
| GetEventHandleFromOverlapped(overlapped), timeout); |
| // Clear the I/O operation if still exists. |
| if (!HasOverlappedIoCompleted(overlapped)) { |
| // This is not safe because this operation may be blocked forever. |
| // TODO(yukawa): Implement safer cancelation mechanism. |
| SafeCancelIO(device_handle, overlapped); |
| } |
| if (wait_result == WAIT_TIMEOUT) { |
| LOG(WARNING) << "Timeout: " << timeout; |
| *last_ipc_error = IPC_TIMEOUT_ERROR; |
| return false; |
| } |
| if (wait_result != WAIT_OBJECT_0) { |
| LOG(WARNING) << "Unknown result: " << wait_result; |
| *last_ipc_error = IPC_UNKNOWN_ERROR; |
| return false; |
| } |
| return true; |
| } |
| |
| bool WaitForQuitOrIO( |
| HANDLE device_handle, HANDLE quit_event, DWORD timeout, |
| OVERLAPPED *overlapped, IPCErrorType *last_ipc_error) { |
| if (quit_event != nullptr) { |
| return WaitForQuitOrIOImpl(device_handle, quit_event, timeout, |
| overlapped, last_ipc_error); |
| } |
| return WaitForIOImpl(device_handle, timeout, overlapped, last_ipc_error); |
| } |
| |
| // To work around a bug of GetOverlappedResult in Vista |
| // http://msdn.microsoft.com/en-us/library/dd371711.aspx |
| bool SafeWaitOverlappedResult( |
| HANDLE device_handle, HANDLE quit_event, DWORD timeout, |
| OVERLAPPED *overlapped, DWORD *num_bytes_updated, |
| IPCErrorType *last_ipc_error, bool wait_ack) { |
| DCHECK(overlapped); |
| DCHECK(num_bytes_updated); |
| DCHECK(last_ipc_error); |
| if (!WaitForQuitOrIO(device_handle, quit_event, timeout, |
| overlapped, last_ipc_error)) { |
| return false; |
| } |
| |
| *num_bytes_updated = 0; |
| const BOOL get_overlapped_result = ::GetOverlappedResult( |
| device_handle, overlapped, num_bytes_updated, FALSE); |
| if (get_overlapped_result == FALSE) { |
| const DWORD get_overlapped_error = ::GetLastError(); |
| if (get_overlapped_error == ERROR_BROKEN_PIPE) { |
| if (wait_ack) { |
| // This is an expected behavior. |
| return true; |
| } |
| LOG(ERROR) << "GetOverlappedResult() failed: ERROR_BROKEN_PIPE"; |
| } else { |
| LOG(ERROR) << "GetOverlappedResult() failed: " << get_overlapped_error; |
| } |
| *last_ipc_error = IPC_UNKNOWN_ERROR; |
| return false; |
| } |
| return true; |
| } |
| |
| bool SendIPCMessage(HANDLE device_handle, HANDLE write_wait_handle, |
| const 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; |
| } |
| |
| DWORD num_bytes_written = 0; |
| OVERLAPPED overlapped; |
| if (!InitOverlapped(&overlapped, write_wait_handle)) { |
| *last_ipc_error = IPC_WRITE_ERROR; |
| return false; |
| } |
| |
| const bool write_file_result = (::WriteFile( |
| device_handle, buf, static_cast<DWORD>(buf_length), |
| &num_bytes_written, &overlapped) != FALSE); |
| const DWORD write_file_error = ::GetLastError(); |
| if (write_file_result) { |
| // ::WriteFile is done as sync operation. |
| } else { |
| if (write_file_error != ERROR_IO_PENDING) { |
| LOG(ERROR) << "WriteFile() failed: " << write_file_error; |
| *last_ipc_error = IPC_WRITE_ERROR; |
| return false; |
| } |
| if (!SafeWaitOverlappedResult( |
| device_handle, nullptr, timeout, &overlapped, |
| &num_bytes_written, last_ipc_error, kSendTypeData)) { |
| return false; |
| } |
| } |
| |
| // As we use message-type namedpipe, all the data should be written in one |
| // shot. Otherwise, single message will be split into multiple packets. |
| if (num_bytes_written != buf_length) { |
| LOG(ERROR) << "Data truncated. buf_length: " << buf_length |
| << ", num_bytes_written: " << num_bytes_written; |
| *last_ipc_error = IPC_UNKNOWN_ERROR; |
| return false; |
| } |
| return true; |
| } |
| |
| bool RecvIPCMessage(HANDLE device_handle, HANDLE read_wait_handle, char *buf, |
| size_t *buf_length, int timeout, bool read_type_ack, |
| IPCErrorType *last_ipc_error) { |
| if (*buf_length == 0) { |
| LOG(WARNING) << "buf length is 0"; |
| *last_ipc_error = IPC_UNKNOWN_ERROR; |
| return false; |
| } |
| |
| OVERLAPPED overlapped; |
| if (!InitOverlapped(&overlapped, read_wait_handle)) { |
| *last_ipc_error = IPC_READ_ERROR; |
| return false; |
| } |
| |
| DWORD num_bytes_read = 0; |
| const bool read_file_result = (::ReadFile( |
| device_handle, buf, static_cast<DWORD>(*buf_length), &num_bytes_read, |
| &overlapped) != FALSE); |
| const DWORD read_file_error = ::GetLastError(); |
| if (read_file_result) { |
| // ::ReadFile is done as sync operation. |
| } else { |
| if (read_type_ack && (read_file_error == ERROR_BROKEN_PIPE)) { |
| // The client has already disconnected this pipe. This is an expected |
| // behavior and do not treat as an error. |
| return true; |
| } |
| if (read_file_error != ERROR_IO_PENDING) { |
| LOG(ERROR) << "ReadFile() failed: " << read_file_error; |
| *last_ipc_error = IPC_READ_ERROR; |
| return false; |
| } |
| // Actually this is an async operation. Let's wait for its completion. |
| if (!SafeWaitOverlappedResult( |
| device_handle, nullptr, timeout, &overlapped, |
| &num_bytes_read, last_ipc_error, read_type_ack)) { |
| return false; |
| } |
| } |
| |
| if (!read_type_ack && (num_bytes_read == 0)) { |
| LOG(WARNING) << "Received 0 result."; |
| } |
| |
| *buf_length = num_bytes_read; |
| |
| return true; |
| } |
| |
| HANDLE CreateManualResetEvent() { |
| return ::CreateEvent(nullptr, TRUE, FALSE, nullptr); |
| } |
| |
| // We do not care about the signaled state of the device handle itself. |
| // This slightly improves the performance. |
| // See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365538.aspx |
| void MaybeDisableFileCompletionNotification(HANDLE device_handle) { |
| // This is not a mandatory task. Just ignore the actual error (if any). |
| ::SetFileCompletionNotificationModes(device_handle, |
| FILE_SKIP_SET_EVENT_ON_HANDLE); |
| } |
| |
| } // namespace |
| |
| IPCServer::IPCServer(const string &name, |
| int32 num_connections, |
| int32 timeout) |
| : connected_(false), |
| pipe_event_(CreateManualResetEvent()), |
| quit_event_(CreateManualResetEvent()), |
| timeout_(timeout) { |
| IPCPathManager *manager = IPCPathManager::GetIPCPathManager(name); |
| string server_address; |
| |
| 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()); |
| |
| SECURITY_ATTRIBUTES security_attributes; |
| if (!WinSandbox::MakeSecurityAttributes(WinSandbox::kSharablePipe, |
| &security_attributes)) { |
| LOG(ERROR) << "Cannot make SecurityAttributes"; |
| return; |
| } |
| |
| // Create a named pipe. |
| wstring wserver_address; |
| Util::UTF8ToWide(server_address.c_str(), &wserver_address); |
| HANDLE handle = ::CreateNamedPipe(wserver_address.c_str(), |
| PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | |
| FILE_FLAG_FIRST_PIPE_INSTANCE, |
| PIPE_TYPE_MESSAGE | |
| PIPE_READMODE_MESSAGE | |
| PIPE_WAIT, |
| (num_connections <= 0 |
| ? PIPE_UNLIMITED_INSTANCES |
| : num_connections), |
| sizeof(request_), |
| sizeof(response_), |
| 0, |
| &security_attributes); |
| const DWORD create_named_pipe_error = ::GetLastError(); |
| ::LocalFree(security_attributes.lpSecurityDescriptor); |
| |
| if (INVALID_HANDLE_VALUE == handle) { |
| LOG(FATAL) << "CreateNamedPipe failed" << create_named_pipe_error; |
| return; |
| } |
| |
| pipe_handle_.reset(handle); |
| |
| MaybeDisableFileCompletionNotification(pipe_handle_.get()); |
| |
| if (!manager->SavePathName()) { |
| LOG(ERROR) << "Cannot save IPC path name"; |
| return; |
| } |
| |
| connected_ = true; |
| } |
| |
| IPCServer::~IPCServer() { |
| Terminate(); |
| } |
| |
| bool IPCServer::Connected() const { |
| return connected_; |
| } |
| |
| void IPCServer::Terminate() { |
| if (server_thread_.get() == nullptr) { |
| return; |
| } |
| |
| if (!server_thread_->IsRunning()) { |
| return; |
| } |
| |
| if (!::SetEvent(quit_event_.get())) { |
| LOG(ERROR) << "SetEvent failed"; |
| } |
| |
| // Close the named pipe. |
| // This is a workaround for killing child thread |
| if (server_thread_.get() != nullptr) { |
| server_thread_->Join(); |
| server_thread_->Terminate(); |
| } |
| |
| connected_ = false; |
| } |
| |
| void IPCServer::Loop() { |
| IPCErrorType last_ipc_error = IPC_NO_ERROR; |
| |
| int successive_connection_failure_count = 0; |
| while (connected_) { |
| OVERLAPPED overlapped; |
| if (!InitOverlapped(&overlapped, pipe_event_.get())) { |
| connected_ = false; |
| return; |
| } |
| |
| const BOOL result = ::ConnectNamedPipe(pipe_handle_.get(), &overlapped); |
| const DWORD connect_named_pipe_error = ::GetLastError(); |
| if (result == FALSE) { |
| if (connect_named_pipe_error == ERROR_PIPE_CONNECTED) { |
| // Already connected. Nothing to do. |
| } else if (connect_named_pipe_error == ERROR_NO_DATA) { |
| // client already closes the connection |
| ::DisconnectNamedPipe(pipe_handle_.get()); |
| continue; |
| } else if (connect_named_pipe_error == ERROR_IO_PENDING) { |
| // Actually this is async operation. |
| DWORD ignored = 0; |
| IPCErrorType ipc_error = IPC_NO_ERROR; |
| if (!SafeWaitOverlappedResult(pipe_handle_.get(), quit_event_.get(), |
| INFINITE, &overlapped, &ignored, |
| &ipc_error, kReadTypeData)) { |
| if (ipc_error == IPC_QUIT_EVENT_SIGNALED) { |
| VLOG(1) << "Recived Conrol event from other thread"; |
| connected_ = false; |
| return; |
| } |
| ++successive_connection_failure_count; |
| if (successive_connection_failure_count >= |
| kMaxSuccessiveConnectionFailureCount) { |
| LOG(ERROR) << "Give up to connect named pipe."; |
| connected_ = false; |
| return; |
| } |
| ::DisconnectNamedPipe(pipe_handle_.get()); |
| continue; |
| } |
| } else { |
| LOG(FATAL) << "Unexpected error: " << connect_named_pipe_error; |
| } |
| } |
| |
| successive_connection_failure_count = 0; |
| // Retrieve an incoming message. |
| size_t request_size = sizeof(request_); |
| if (RecvIPCMessage(pipe_handle_.get(), pipe_event_.get(), |
| &request_[0], &request_size, timeout_, |
| kReadTypeData, &last_ipc_error)) { |
| size_t response_size = sizeof(response_); |
| if (!Process(&request_[0], request_size, |
| &response_[0], &response_size)) { |
| connected_ = false; |
| } |
| |
| // When Process() returns 0 result, force to call DisconnectNamedPipe() |
| // instead of checking ACK message |
| if (response_size == 0) { |
| LOG(WARNING) << "Process() return 0 result"; |
| ::DisconnectNamedPipe(pipe_handle_.get()); |
| continue; |
| } |
| |
| // Send a response |
| SendIPCMessage(pipe_handle_.get(), pipe_event_.get(), |
| &response_[0], response_size, timeout_, &last_ipc_error); |
| } |
| |
| // Special treatment for Windows per discussion with thatanaka: |
| // It's hard to know that client has processed the server's response. |
| // We will be able to call ::FlushFileHandles() here, but |
| // FlushFileHandles() is blocked if client doesn't call ReadFile(). That |
| // means that a malicious user can easily block the server not by calling |
| // ReadFile. In order to know the transaction completes successfully, |
| // client needs to send an ACK message to the server. |
| |
| // Wait ACK-like signal from client for 0.1 second. If we detect the pipe |
| // disconnect event, so far so good. If we receive more data, we assume it |
| // is an ACK signal (the IPC client of Mozc 1.5.x or earlier does this). |
| char ack_request[1] = {0}; |
| size_t ack_request_size = 1; |
| static const int kAckTimeout = 100; |
| if (!RecvIPCMessage(pipe_handle_.get(), pipe_event_.get(), |
| ack_request, &ack_request_size, kAckTimeout, |
| kReadTypeACK, &last_ipc_error)) { |
| // This case happens when the client did not recive the server's response |
| // within timeout. Anyway we will close the connection so that the server |
| // will not be blocked. |
| LOG(WARNING) << "Client didn't respond within " |
| << kAckTimeout << " msec."; |
| } |
| ::DisconnectNamedPipe(pipe_handle_.get()); |
| } |
| |
| connected_ = false; |
| } |
| |
| // old interface |
| IPCClient::IPCClient(const string &name) |
| : pipe_event_(CreateManualResetEvent()), |
| connected_(false), |
| ipc_path_manager_(nullptr), |
| last_ipc_error_(IPC_NO_ERROR) { |
| Init(name, ""); |
| } |
| |
| IPCClient::IPCClient(const string &name, const string &server_path) |
| : pipe_event_(CreateManualResetEvent()), |
| connected_(false), |
| ipc_path_manager_(nullptr), |
| 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; |
| |
| // We should change the mutex based on which IPC server we will talk with. |
| ScopedReleaseMutex ipc_mutex(GetClientMutex(name)); |
| |
| if (ipc_mutex.get() == nullptr) { |
| LOG(ERROR) << "IPC mutex is not available"; |
| } else { |
| const int kMutexTimeout = 10 * 1000; // wait at most 10sec. |
| switch (::WaitForSingleObject(ipc_mutex.get(), kMutexTimeout)) { |
| case WAIT_TIMEOUT: |
| // TODO(taku): with suspend/resume, WaitForSingleObject may |
| // return WAIT_TIMEOUT. We have to consider the case |
| // in the future. |
| LOG(ERROR) << "IPC client was not available even after " |
| << kMutexTimeout << " msec."; |
| break; |
| case WAIT_ABANDONED: |
| DLOG(INFO) << "mutex object was removed"; |
| break; |
| case WAIT_OBJECT_0: |
| break; |
| default: |
| break; |
| } |
| } |
| |
| IPCPathManager *manager = IPCPathManager::GetIPCPathManager(name); |
| if (manager == nullptr) { |
| LOG(ERROR) << "IPCPathManager::GetIPCPathManager failed"; |
| return; |
| } |
| |
| ipc_path_manager_ = manager; |
| |
| // TODO(taku): enable them on Mac/Linux |
| #ifdef DEBUG |
| const size_t kMaxTrial = 256; |
| #else |
| const size_t kMaxTrial = 2; |
| #endif |
| |
| for (size_t trial = 0; trial < kMaxTrial; ++trial) { |
| string server_address; |
| if (!manager->LoadPathName() || !manager->GetPathName(&server_address)) { |
| continue; |
| } |
| wstring wserver_address; |
| Util::UTF8ToWide(server_address.c_str(), &wserver_address); |
| |
| if (GetNumberOfProcessors() == 1) { |
| // When the code is running in single processor environment, sometimes |
| // IPC server has not finished the clean-up tasks for the previous IPC |
| // session here. So we intentionally call WaitNamedPipe API so that IPC |
| // server has a chance to complete clean-up tasks if necessary. |
| // NOTE: We cannot set 0 for the wait time because 0 has a special meaning |
| // as |NMPWAIT_USE_DEFAULT_WAIT|. |
| const DWORD kMinWaitTimeForWaitNamedPipe = 1; |
| ::WaitNamedPipe(wserver_address.c_str(), kMinWaitTimeForWaitNamedPipe); |
| } |
| |
| ScopedHandle new_handle(::CreateFile(wserver_address.c_str(), |
| GENERIC_READ | GENERIC_WRITE, |
| 0, nullptr, OPEN_EXISTING, |
| FILE_FLAG_OVERLAPPED | |
| SECURITY_SQOS_PRESENT | |
| SECURITY_IDENTIFICATION | |
| SECURITY_EFFECTIVE_ONLY, |
| nullptr)); |
| const DWORD create_file_error = ::GetLastError(); |
| // ScopedHandle returns nullptr even when it received INVALID_HANDLE_VALUE. |
| if (new_handle.get() != nullptr) { |
| pipe_handle_.reset(new_handle.take()); |
| MaybeDisableFileCompletionNotification(pipe_handle_.get()); |
| if (!manager->IsValidServer(GetServerProcessIdImpl(pipe_handle_.get()), |
| server_path)) { |
| LOG(ERROR) << "Connecting to invalid server"; |
| last_ipc_error_ = IPC_INVALID_SERVER; |
| return; |
| } |
| |
| last_ipc_error_ = IPC_NO_ERROR; |
| connected_ = true; |
| return; |
| } |
| |
| if (ERROR_PIPE_BUSY != create_file_error) { |
| LOG(ERROR) << "Server is not running: " << create_file_error; |
| manager->Clear(); |
| continue; |
| } |
| |
| // wait for 10 second until server is ready |
| // TODO(taku): control the timeout via flag. |
| #ifdef DEBUG |
| const int kNamedPipeTimeout = 100000; // 100 sec |
| #else |
| const int kNamedPipeTimeout = 10000; // 10 sec |
| #endif |
| DLOG(ERROR) << "Server is busy. waiting for " |
| << kNamedPipeTimeout << " msec"; |
| if (!::WaitNamedPipe(wserver_address.c_str(), |
| kNamedPipeTimeout)) { |
| const DWORD wait_named_pipe_error = ::GetLastError(); |
| LOG(ERROR) << "WaitNamedPipe failed: " << wait_named_pipe_error; |
| if ((trial + 1) == kMaxTrial) { |
| last_ipc_error_ = IPC_TIMEOUT_ERROR; |
| return; |
| } |
| continue; // go 2nd trial |
| } |
| } |
| } |
| |
| IPCClient::~IPCClient() {} |
| |
| bool IPCClient::Connected() const { |
| return connected_; |
| } |
| |
| bool IPCClient::Call(const char *request, size_t request_size, |
| char *response, size_t *response_size, |
| int32 timeout) { |
| last_ipc_error_ = IPC_NO_ERROR; |
| if (!connected_) { |
| LOG(ERROR) << "IPCClient is not connected"; |
| last_ipc_error_ = IPC_NO_CONNECTION; |
| return false; |
| } |
| |
| if (!SendIPCMessage(pipe_handle_.get(), pipe_event_.get(), request, |
| request_size, timeout, &last_ipc_error_)) { |
| LOG(ERROR) << "SendIPCMessage() failed"; |
| return false; |
| } |
| |
| if (!RecvIPCMessage(pipe_handle_.get(), pipe_event_.get(), response, |
| response_size, timeout, kReadTypeData, |
| &last_ipc_error_)) { |
| LOG(ERROR) << "RecvIPCMessage() failed"; |
| return false; |
| } |
| |
| // Instead of sending ACK message to Server, we simply disconnect the named |
| // pile to notify that client can read the message successfully. |
| connected_ = false; |
| pipe_handle_.reset(INVALID_HANDLE_VALUE); |
| |
| return true; |
| } |
| |
| } // namespace mozc |
| |
| #endif // OS_WIN |