blob: a28f4a6ccec42533ab3125f3f3151bc1d7f1fac3 [file] [log] [blame]
// 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_);
}