| // Copyright 2023 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Must appear before <stdio.h> which includes <fileapi.h> which will complain |
| // of "No Target Architecture" for some unknown reason (!?) |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <windows.h> |
| |
| // Must appear after <windows.h> |
| #include <io.h> |
| #include <lmcons.h> // required for UNLEN |
| |
| #include "ipc_handle.h" |
| #include "util.h" // For StringFormat() and GetLastErrorString() |
| |
| namespace { |
| |
| std::string Win32ErrorMessage(const char* prefix, LONG error) { |
| return StringFormat("%s: %s", prefix, GetLastErrorString(error).c_str()); |
| } |
| |
| std::string Win32ErrorMessage(const char* prefix) { |
| return Win32ErrorMessage(prefix, GetLastError()); |
| } |
| |
| std::wstring CurrentUserName() { |
| wchar_t user[UNLEN + 1]; |
| DWORD count = UNLEN + 1; |
| if (!GetUserNameW(user, &count) || count < 2) { |
| return std::wstring(L"unknown_user"); |
| } |
| // count includes the terminating zero. |
| return std::wstring(user, count - 1); |
| } |
| |
| std::wstring GetNamedPipePath(StringPiece service_name) { |
| std::wstring result = L"\\\\.\\pipe\\basic_ipc-"; |
| result += CurrentUserName(); |
| result += L'-'; |
| result += ConvertToUnicodeString(service_name.str_, service_name.len_); |
| return result; |
| } |
| |
| HANDLE CreateNamedPipeHandle(const std::wstring& pipe_path, bool async, |
| std::string* error_message) { |
| HANDLE handle = CreateNamedPipeW( |
| pipe_path.data(), |
| PIPE_ACCESS_DUPLEX | (async ? FILE_FLAG_OVERLAPPED : 0), // bidirectional |
| PIPE_TYPE_BYTE | // write bytes, not messages |
| PIPE_READMODE_BYTE | // read bytes, not messages |
| PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, // local only |
| 1, // max. instances |
| 4096, // out buffer size |
| 4096, // in buffer size |
| 0, // default time out |
| nullptr); // default security attribute |
| if (handle == INVALID_HANDLE_VALUE) |
| *error_message = Win32ErrorMessage("Could not create named pipe"); |
| |
| return handle; |
| } |
| |
| HANDLE ConnectToNamedPipe(const std::wstring& pipe_path, bool async, |
| std::string* error_message) { |
| HANDLE handle = CreateFileW(pipe_path.data(), GENERIC_READ | GENERIC_WRITE, |
| 0, // no sharing |
| nullptr, // default security attributes |
| CREATE_NEW, // fail if it already exists |
| async ? FILE_FLAG_OVERLAPPED : 0, |
| nullptr); // no template file |
| if (handle == INVALID_HANDLE_VALUE) |
| *error_message = Win32ErrorMessage("Coult not connect pipe"); |
| |
| return handle; |
| } |
| |
| std::wstring GetUniqueNamedPipePath() { |
| // Do not use CreatePipe because these are documented as only |
| // unidirectional and synchronous only. Instead create a |
| // named pipe with a unique name. This matches the current |
| // implementation in Windows, though it may change in future |
| // releases of the platform, see: |
| // https://stackoverflow.com/questions/60645/overlapped-i-o-on-anonymous-pipe/51448441#51448441 |
| static LONG serial_number = 1; |
| wchar_t pipe_path[64]; |
| swprintf_s(pipe_path, 64, L"\\\\.\\pipe\\IpcHandle.%08x.%08x", |
| GetCurrentProcessId(), InterlockedIncrement(&serial_number)); |
| return std::wstring(pipe_path); |
| } |
| |
| } // namespace |
| |
| // INVALID_HANDLE_VALUE expands to an expression that includes |
| // a reinterpret_cast<>, which is why it cannot be used to |
| // define a constexpr static member. |
| |
| // static |
| const HANDLE IpcHandle::kInvalid = INVALID_HANDLE_VALUE; |
| |
| // static |
| void IpcHandle::Close() { |
| if (handle_ != kInvalid) { |
| CloseHandle(handle_); |
| handle_ = kInvalid; |
| } |
| } |
| |
| HANDLE IpcHandle::ReleaseNativeHandle() { |
| HANDLE result = handle_; |
| handle_ = INVALID_HANDLE_VALUE; |
| return result; |
| } |
| |
| bool IpcHandle::IsInheritable() const { |
| DWORD flags; |
| if (!GetHandleInformation(handle_, &flags)) |
| return false; |
| return (flags & HANDLE_FLAG_INHERIT) != 0; |
| } |
| |
| void IpcHandle::SetInheritable(bool enabled) { |
| if (!SetHandleInformation(handle_, HANDLE_FLAG_INHERIT, |
| enabled ? HANDLE_FLAG_INHERIT : 0)) { |
| Win32Fatal("SetHandleInformation"); |
| } |
| } |
| |
| // static |
| IpcHandle::HandleType IpcHandle::CloneNativeHandle(HandleType handle, |
| bool inherited) { |
| HANDLE process = GetCurrentProcess(); |
| HANDLE peer = INVALID_HANDLE_VALUE; |
| if (!DuplicateHandle(process, handle, process, &peer, 0, inherited, |
| DUPLICATE_SAME_ACCESS)) { |
| return INVALID_HANDLE_VALUE; |
| } |
| return peer; |
| } |
| |
| // static |
| IpcHandle::HandleType IpcHandle::NativeForStdio(FILE* file) { |
| int fd = _fileno(file); |
| intptr_t int_handle = _get_osfhandle(fd); |
| if (int_handle < 0) |
| return INVALID_HANDLE_VALUE; |
| return reinterpret_cast<HANDLE>(int_handle); |
| } |
| |
| // static |
| IpcHandle IpcHandle::CloneFromStdio(FILE* file) { |
| return { CloneNativeHandle(NativeForStdio(file)) }; |
| } |
| |
| bool IpcHandle::CloneIntoStdio(FILE* file) { |
| HANDLE new_handle = CloneNativeHandle(handle_); |
| // Ensure low-level Win32 operations will write to the new handle. |
| // by calling SetStdHandle. |
| DWORD std_index = 0; |
| int std_fd; |
| int open_flags; |
| if (file == stdout) { |
| std_index = STD_OUTPUT_HANDLE; |
| std_fd = 1; |
| open_flags = _O_WRONLY | _fmode; |
| } else if (file == stderr) { |
| std_index = STD_ERROR_HANDLE; |
| std_fd = 2; |
| open_flags = _O_WRONLY | _fmode; |
| } else if (file == stdin) { |
| std_index = STD_INPUT_HANDLE; |
| std_fd = 0; |
| open_flags = _O_RDONLY | _fmode; |
| } else { |
| errno = EINVAL; |
| return false; |
| } |
| SetStdHandle(std_index, new_handle); |
| int new_fd = |
| ::_open_osfhandle(reinterpret_cast<intptr_t>(new_handle), open_flags); |
| ::_dup2(new_fd, std_fd); |
| ::close(new_fd); |
| return true; |
| } |
| |
| ssize_t IpcHandle::Read(void* buff, size_t buffer_size, |
| std::string* error_message) const { |
| auto* buffer = static_cast<char*>(buff); |
| DWORD count = 0; |
| if (!ReadFile(handle_, buffer, buffer_size, &count, nullptr)) { |
| DWORD error = GetLastError(); |
| if (error == ERROR_BROKEN_PIPE) // Pipe was closed. |
| return 0; |
| *error_message = Win32ErrorMessage("Could not read from pipe"); |
| return -1; |
| } |
| return static_cast<ssize_t>(count); |
| } |
| |
| ssize_t IpcHandle::Write(const void* buff, size_t buffer_size, |
| std::string* error_message) const { |
| const auto* buffer = static_cast<const char*>(buff); |
| DWORD count = 0; |
| if (!WriteFile(handle_, buffer, buffer_size, &count, nullptr)) { |
| LONG error = GetLastError(); |
| if (error == ERROR_BROKEN_PIPE) // Pipe was closed. |
| return 0; |
| *error_message = Win32ErrorMessage("Could not write to pipe", error); |
| return -1; |
| } |
| return static_cast<ssize_t>(count); |
| } |
| |
| struct HandleMessage { |
| HANDLE process_id; |
| HANDLE handle; |
| }; |
| |
| bool IpcHandle::SendNativeHandle(HandleType native, |
| std::string* error_message) const { |
| // Send a message that contains the current process ID, and the handle |
| // through the named pipe. The ReceiveNativeHandle() method will use them |
| // to call DuplicateHandle(). Note that this does not work for console |
| // input/output handles (the handles can be duplicated, but trying to use them |
| // from a different process returns an error), so in addition to using this |
| // method, the sender should use a specialized class to redirect stdout/stderr |
| // using new named pipe handles. See the Win32StdOutputBridge class. |
| HandleMessage msg; |
| msg.process_id = |
| OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId()); |
| msg.handle = native; |
| |
| ssize_t count = Write(&msg, sizeof(msg), error_message); |
| if (count < 0) |
| return false; |
| if (count != static_cast<ssize_t>(sizeof(msg))) { |
| *error_message = "Error when sending handle"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool IpcHandle::ReceiveNativeHandle(IpcHandle* handle, |
| std::string* error_message) const { |
| HandleMessage msg; |
| ssize_t count = Read(&msg, sizeof(msg), error_message); |
| if (count < 0) |
| return false; |
| if (count != static_cast<ssize_t>(sizeof(msg))) { |
| *error_message = "Error when receiving handle"; |
| return false; |
| } |
| |
| // Create a duplicate of the source handle in the current process. |
| HANDLE native = INVALID_HANDLE_VALUE; |
| if (!DuplicateHandle(msg.process_id, // source process |
| msg.handle, // source handle |
| GetCurrentProcess(), // target process |
| &native, // target handle pointer |
| 0, // ignored with DUPLICATE_SAME_ACCESS |
| FALSE, // not inheritable |
| DUPLICATE_SAME_ACCESS)) { |
| *error_message = Win32ErrorMessage("Could not duplicate handle"); |
| return false; |
| } |
| *handle = IpcHandle(native); |
| return true; |
| } |
| |
| IpcServiceHandle::~IpcServiceHandle() { |
| Close(); |
| } |
| |
| void IpcServiceHandle::Close() { |
| this->IpcHandle::Close(); |
| } |
| |
| // static |
| IpcServiceHandle IpcServiceHandle::BindTo(StringPiece service_name, |
| std::string* error_message) { |
| return IpcServiceHandle(CreateNamedPipeHandle(GetNamedPipePath(service_name), |
| true, error_message)); |
| } |
| |
| IpcHandle IpcServiceHandle::AcceptClient(std::string* error_message) const { |
| // Duplicate the handle to return it into a new IpcHandle. This |
| // will allow the caller to communicate with the client, and the next |
| // ConnectNamedPipe() call will wait for another client instead. |
| HANDLE peer = IpcHandle::CloneNativeHandle(handle_); |
| if (peer == INVALID_HANDLE_VALUE) { |
| DWORD error = GetLastError(); |
| *error_message = |
| Win32ErrorMessage("Could not duplicate client pipe handle", error); |
| return {}; |
| } |
| if (!ConnectNamedPipe(peer, NULL)) { |
| DWORD error = GetLastError(); |
| // ERROR_PIPE_CONNECTED is not an actual error and means that |
| // a client is already connected, which happens during unit-testing. |
| if (error != ERROR_PIPE_CONNECTED) { |
| ::CloseHandle(peer); |
| *error_message = |
| Win32ErrorMessage("Could not accept named pipe client", error); |
| return {}; |
| } |
| } |
| return IpcHandle(peer); |
| } |
| |
| IpcHandle IpcServiceHandle::AcceptClient(int64_t timeout_ms, bool* did_timeout, |
| std::string* error_message) const { |
| *did_timeout = false; |
| // Duplicate the handle to return it into a new IpcHandle. This |
| // will allow the caller to communicate with the client, and the next |
| // ConnectNamedPipe() call will wait for another client instead. |
| HANDLE peer = IpcHandle::CloneNativeHandle(handle_); |
| if (peer == INVALID_HANDLE_VALUE) { |
| DWORD error = GetLastError(); |
| *error_message = |
| Win32ErrorMessage("Could not duplicate client pipe handle", error); |
| return {}; |
| } |
| |
| HANDLE result = INVALID_HANDLE_VALUE; |
| OVERLAPPED overlapped = {}; |
| overlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); |
| |
| do { |
| if (ConnectNamedPipe(peer, &overlapped)) { |
| // Should not happen with overlapped i/o, but just in case. |
| result = peer; |
| peer = INVALID_HANDLE_VALUE; |
| break; |
| } |
| |
| DWORD error = GetLastError(); |
| if (error == ERROR_PIPE_CONNECTED) { |
| // Immediate connection, success! |
| result = peer; |
| peer = INVALID_HANDLE_VALUE; |
| break; |
| } |
| |
| if (error != ERROR_IO_PENDING) { |
| // Something unexpected happened here! |
| *error_message = |
| Win32ErrorMessage("Could not accept named pipe client", error); |
| break; |
| } |
| |
| // Wait for connection. |
| DWORD timeout = |
| (timeout_ms < 0) ? INFINITE : static_cast<DWORD>(timeout_ms); |
| DWORD ret = WaitForSingleObject(overlapped.hEvent, timeout); |
| |
| if (ret == WAIT_TIMEOUT) { |
| *did_timeout = true; |
| *error_message = "timed out"; |
| break; |
| } |
| |
| if (ret != WAIT_OBJECT_0) { |
| Win32Fatal("WaitForSingleObject"); |
| } |
| |
| // Success! |
| result = peer; |
| peer = INVALID_HANDLE_VALUE; |
| |
| } while (0); |
| |
| ::CloseHandle(overlapped.hEvent); |
| if (peer != INVALID_HANDLE_VALUE) |
| ::CloseHandle(peer); |
| |
| return { result }; |
| } |
| |
| // static |
| bool IpcServiceHandle::IsBound(StringPiece service_name) { |
| std::wstring pipe_path = GetNamedPipePath(service_name); |
| if (WaitNamedPipeW(pipe_path.data(), 1)) |
| return true; |
| |
| DWORD error = GetLastError(); |
| if (error == ERROR_SEM_TIMEOUT) |
| return true; |
| |
| return false; |
| } |
| |
| // static |
| IpcHandle IpcServiceHandle::ConnectTo(StringPiece service_name, |
| std::string* error_message) { |
| #if 0 |
| // For some reason, the line below generates code that crashes |
| // at runtime when compiled with the Mingw64 toolchain, so use |
| // the equivalent below. |
| return { ConnectToNamedPipe(GetNamedPipePath(service_name), true, error_message) }; |
| #else |
| std::wstring pipe_path = GetNamedPipePath(service_name); |
| HANDLE handle = ConnectToNamedPipe(pipe_path, true, error_message); |
| return { handle }; |
| #endif |
| } |
| |
| // static |
| IpcHandle IpcServiceHandle::ConnectTo(StringPiece service_name, |
| int64_t timeout_ms, bool* did_timeout, |
| std::string* error_message) { |
| // On Windows, connecting to a named pipe either succeeds or fails immediately |
| *did_timeout = (timeout_ms > 0); |
| return ConnectTo(service_name, error_message); |
| } |
| |
| // static |
| IpcHandle IpcServiceHandle::AsyncConnectTo(StringPiece service_name, |
| bool* did_connect, |
| std::string* error_message) { |
| IpcHandle result = |
| ConnectToNamedPipe(GetNamedPipePath(service_name), true, error_message); |
| if (result) { |
| // On Win32, connecting to a named pipe always fails or succeeds |
| // immediately. |
| *did_connect = true; |
| } |
| return result; |
| } |
| |
| static bool CreatePipeInternal(IpcHandle* read, IpcHandle* write, bool async, |
| std::string* error_message) { |
| std::wstring pipe_path = GetUniqueNamedPipePath(); |
| *read = IpcHandle( |
| CreateNamedPipeHandle(std::wstring(pipe_path), async, error_message)); |
| if (!*read) |
| return false; |
| |
| *write = IpcHandle(ConnectToNamedPipe(pipe_path, async, error_message)); |
| if (!*write) { |
| read->Close(); |
| return false; |
| } |
| return true; |
| } |
| |
| // static |
| bool IpcHandle::CreatePipe(IpcHandle* read, IpcHandle* write, |
| std::string* error_message) { |
| return CreatePipeInternal(read, write, false, error_message); |
| } |
| |
| // static |
| bool IpcHandle::CreateAsyncPipe(IpcHandle* read, IpcHandle* write, |
| std::string* error_message) { |
| return CreatePipeInternal(read, write, true, error_message); |
| } |
| |
| bool IpcHandle::ReadFull(void* buffer, size_t buffer_size, |
| std::string* error_message) const { |
| ssize_t count = Read(buffer, buffer_size, error_message); |
| if (count < 0) |
| return false; |
| if (count != static_cast<ssize_t>(buffer_size)) { |
| *error_message = StringFormat("Received %u bytes, expected %u", |
| static_cast<unsigned>(count), |
| static_cast<unsigned>(buffer_size)); |
| return false; |
| } |
| return true; |
| } |
| |
| bool IpcHandle::WriteFull(const void* buffer, size_t buffer_size, |
| std::string* error_message) const { |
| ssize_t count = Write(buffer, buffer_size, error_message); |
| if (count < 0) |
| return false; |
| if (count != static_cast<ssize_t>(buffer_size)) { |
| *error_message = |
| StringFormat("Sent %u bytes, expected %u", static_cast<unsigned>(count), |
| static_cast<unsigned>(buffer_size)); |
| } |
| return true; |
| } |
| |
| std::string IpcHandle::display() const { |
| return StringFormat("handle=%p", handle_); |
| } |