blob: 2a7f45f6db59c0e9bade3c322149e8cef8aaa51d [file]
// 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.
#ifndef NINJA_IPC_HANDLE_H_
#define NINJA_IPC_HANDLE_H_
#include <stddef.h>
#include <string>
#include "string_piece.h"
#ifdef _WIN32
#include <basetsd.h>
#include <windows.h>
using ssize_t = SSIZE_T;
#else
#include <signal.h>
#endif
///////////////////////////////////////////////////////////////////////////
///
/// IpcHandle
///
/// Support for basic inter-process communication.
///
/// The IpcHandle models a scoped pipe handle (Win32) or unix socket file
/// descriptor (Unix) and provides method to read/write data through
/// them, as well as passing other handles/file descriptors.
///
/// For Win32, all handles are created with FILE_FLAG_OVERLAPPED and
/// support overlapped operations. For Posix, the non-blocking flag of
/// a descriptor can be checked and changed with IsNonBlocking() and
/// SetNonBlocking() methods.
///
/// Wrapper for a local Unix socket or Win32 named pipe handle
/// used for inter-process communication.
class IpcHandle {
public:
#ifdef _WIN32
using HandleType = HANDLE;
static const HandleType kInvalid;
#else // !_WIN32
using HandleType = int;
static constexpr int kInvalid = -1;
#endif // !_WIN32
IpcHandle() = default;
/// Constructor. This is intentionally _not_ explicit to make
/// it easier to return values with expressions like
/// `return { value };` which would not compile otherwise.
IpcHandle(HandleType handle) : handle_(handle) {}
/// Disallow copy operations.
IpcHandle(const IpcHandle&) = delete;
IpcHandle& operator=(const IpcHandle&) = delete;
/// Allow move operations.
IpcHandle(IpcHandle&& other) noexcept : handle_(other.handle_) {
other.handle_ = kInvalid;
}
IpcHandle& operator=(IpcHandle&& other) noexcept {
if (this != &other) {
this->~IpcHandle();
new (this) IpcHandle(std::move(other));
}
return *this;
}
~IpcHandle() { Close(); }
/// bool conversion allows easy checks for valid handles with:
/// if (!handle) { ... handle is invalid };
explicit operator bool() const noexcept { return handle_ != kInvalid; }
/// Try to read |buffer_size| bytes into |buffer|. On success
/// return the number of bytes actually read into the buffer,
/// which will be 0 if the connection was closed by the peer.
/// On failure, return -1 and sets |*error_message|.
ssize_t Read(void* buffer, size_t buffer_size,
std::string* error_message) const;
/// Read |buffer_size| bytes exactly into |buffer|. On success
/// return true. On failure, return false and set |*error_message|.
bool ReadFull(void* buffer, size_t buffer_size,
std::string* error_message) const;
/// Try to write |buffer_size| bytes form |buffer|. On success
/// return the number of bytes actually written to the handle,
/// which will be 0 if the connection was called by the peer.
/// On failure, return -1 and sets |*error_message|.
ssize_t Write(const void* buffer, size_t buffer_size,
std::string* error_message) const;
/// Write |buffer_size| bytes exactly from |buffer|. On success
/// return true. On failure, return false and set |*error_message|.
bool WriteFull(const void* buffer, size_t buffer_size,
std::string* error_message) const;
/// Send a |native| handle to the peer. On success return true.
/// On failure, return false and sets |*error_message|.
bool SendNativeHandle(HandleType native, std::string* error_message) const;
/// Receive a |native| handle from the peer. On success return true
/// and sets |*handle|. On failure, return false and sets |*error_message|.
bool ReceiveNativeHandle(IpcHandle* handle, std::string* error_message) const;
/// Return true if the handle is inheritable (on Win32), or is _not_ closed
/// on exec (on Posix).
bool IsInheritable() const;
/// Reset the inheritable / non-O_CLOEXEC flag for this handle.
void SetInheritable(bool enable);
#ifndef _WIN32
/// Return true if the file descriptor is in non-blocking mode.
bool IsNonBlocking() const;
/// Reset the O_NONBLOCK flag for the corresponding file descriptor.
void SetNonBlocking(bool enable);
/// Return the current status of an asynchronous AsyncClientTo() connection.
/// This only makes sense on Posix, and this function should only be called
/// after a writable event was detected on the native handle with select()
/// or one of its variants. It returns an errno value, which will be 0 if
/// the connection succeeded, EAGAIN or EINPROGRESS in case of a spurious
/// select() wakeup, or any other error value in case of failed connection,
/// or using an invalid handle.
static int GetNativeAsyncConnectStatus(int fd);
#endif // !_WIN32
/// Create anonymous bi-directional pipe. On success return true and
/// sets |*read| and |*write|. On failure, return false and sets
/// |*error_message|.
///
/// On Win32, the handles will be created with FILE_FLAG_OVERLAPPED.
/// On Posix, the descriptors are in blocking mode.
static bool CreatePipe(IpcHandle* read, IpcHandle* write,
std::string* error_message);
#ifdef _WIN32
/// Return a unique Win32 named pipe path. |prefix| is an optional
/// prefix that will appear in the final name, and should not include
/// backslashes. The returned string can be passed to APIs like
/// CreateFileW() or CreateNamedPipeW(). Useful for unit-tests.
static std::wstring CreateUniqueNamedPipePath(const wchar_t* prefix);
/// Convenience wrapper around CreateNamedPipeW() to return a new
/// named pipe instance, to exchange bytes, wait mode enabled. Note
/// that this is also created with FILE_FLAG_OVERLAPPED which means
/// that ConnectNamedPipe() *must* be called with an OVERLAPPED pointer
/// argument (see Win32 documentation), hence the "Async" in the name.
/// Useful for unit-tests.
/// |pipe_path| is the named pipe path.
/// |first_instance| must be true when creating the first instance, or
/// false to create other ones.
/// |max_instances| is the maximum instance count, and must be >= 1.
/// On failure, return INVALID_HANDLE_VALUE (and GetLastError() should
/// be called to get the error code).
static HANDLE CreateAsyncNamedPipeInstance(const std::wstring& pipe_path,
bool first_instance,
size_t max_instances);
/// Convenience wrapper around CreateFileW() to connect to a named pipe
/// from the client side. The handle is created in read/write mode, with
/// FILE_FLAG_OVERLAPPED and with a default security descriptor.
/// |pipe_path| is the named pipe path.
/// On failure, return INVALID_HANDLE_VALUE (use GetLastError() to get
/// error code). On success return new handle.
///
/// NOTE: This is NOT a wrapper around ConnectNamedPipe() which is
/// the Win32 API used on the server side to wait for a client connection.
static HANDLE ClientConnectToNamedPipe(const std::wstring& pipe_path);
#endif // !_WIN32
/// Close the handle, making it invalid.
void Close();
/// Clone a native handle.
static HandleType CloneNativeHandle(HandleType handle,
bool inherited = false);
/// Clone existing instance. Result is not inheritable on Win32, and has
/// O_CLOEXEC on Posix.
IpcHandle Clone() { return { CloneNativeHandle(handle_) }; };
/// Flush |file| then clone its handle. Result is not inheritable on Win32,
/// and has O_CLOEXEC on Posix.
static IpcHandle CloneFromStdio(FILE* file);
/// Clone the handle into an stdio FILE instance.
/// |file| can only be one of stdout, stderr, or stdin.
/// On success return true, on failure, set errno (even on Win32)
/// then return false.
bool CloneIntoStdio(FILE* file);
/// Return the native handle value for an stdio stream.
static HandleType NativeForStdio(FILE* file);
/// Return native handle value.
HandleType native_handle() const { return handle_; }
/// Release the native handle, transferring ownership to the caller.
HandleType ReleaseNativeHandle();
/// Return user visible string for this handle.
std::string display() const;
protected:
HandleType handle_ = kInvalid;
};
///////////////////////////////////////////////////////////////////////////
///
/// IpcService
///
class IpcService {
public:
/// Default constructor creates invalid instance.
IpcService();
/// Destructor cleans up necessary system resources.
~IpcService();
/// Move operations are allowed.
IpcService(IpcService&&) noexcept;
IpcService& operator=(IpcService&&) noexcept;
/// Copy operations are forbidden.
IpcService(const IpcService&) = delete;
IpcService& operator=(const IpcService&) = delete;
/// Return true if this is a valid (started) instance.
bool IsValid() const;
explicit operator bool() const { return IsValid(); }
/// Return true if |service_name| is already being served on
/// this machine, e.g. by another thread or process.
static bool HasServer(StringPiece service_name);
/// Start a new IpcService instance. This will fail if the service
/// identified by |service_name| is already served on this machine,
/// e.g. from another thread of process.
///
/// On failure, set |*error| and return an invalid instance.
/// On success, return a new valid instance.
static IpcService StartServer(StringPiece service_name, std::string* error);
/// Wait until a new client connects to the service, using an
/// IpcService::ConnectTo() call from another thread or process. |timeout_ms|
/// is a timeout in milliseconds, a negative value indicates waiting
/// indefinitely.
///
/// - On success, return a new valid peer IpcHandle (representing the
/// connected server-side endpoint of the pipe). This can be used to
/// read or write bytes from / to the client. Closing the handle
/// will gracefully close the pipe from the server side (so buffered
/// writes will not be lost.
///
/// NOTE: This implementation supports one connected client at a time,
/// hence once a peer handle has been returned, any other call to
/// AcceptPeer() will fail until the peer handle is closed.
///
/// - On timeout, set |*did_timeout| to true, set |*error| to the hard
/// coded "timed out" string, and return an invalid IpcHandle value.
///
/// - On error, set |*did_teimout| to false, set |*error| to a relevant
/// error message, and return an invalid IpcHandle value.
///
IpcHandle AcceptPeer(int64_t timeout_ms, bool* did_timeout,
std::string* error);
/// Stop current server instance. Called implicitly by destructor.
void Stop();
/// Try to connect to a service identified by |service_name|, which is must be
/// being served by another thread or process on the local machine. This will
/// fail immediately if not server was started for the service.
///
/// If the server has not called AcceptPeer() yet, or is busy serving another
/// client, this will wait until the timeout expiration, or the server is
/// stopped, whichever happens first.
///
/// NOTE: On Posix, this means that the Unix domain socket used by the service
/// has a backlog size of 0.
///
/// |timeout_ms| is a timeout in milliseconds, a negative value means blocking
/// indefinitely.
///
/// On success, return a new handle to read from / send to bytes with the
/// server (on Posix, socket will be in blocking mode).
///
/// On timeout, set |*did_timeout| to true and |*error| to the hard-coded
/// "timed out" string, then return an invalid IpcHandle value.
///
/// On other erroris, set |*did_timeout| to false and |*error| to the
/// corresponding error message, then return an invalid IpcHandle value.
///
static IpcHandle ConnectTo(StringPiece service_name, int64_t timeout_ms,
bool* did_timeout, std::string* error);
#ifndef _WIN32
/// POSIX only: Connect asychronously to local |service_name|. On success, set
/// |*did_connect| and return a valid handle. On failure, set |*error_message|
/// and return an invalid handle.
///
/// Note that the connection can happen immediately, in which case
/// |*did_connect| will be set to true.
///
/// The returned handle is always in non-blocking mode, and if |*did_connect|
/// is false, the caller should wait for a writable event on it (using
/// select() or one of its variants) then use GetNativeAsyncConnectStatus() on
/// the native_handle() value to determine whether the connection succeeded
/// or failed.
static IpcHandle AsyncConnectTo(StringPiece service_name, bool* did_connect,
std::string* error_message);
#endif // !_WIN32
private:
#ifdef _WIN32
IpcService(IpcHandle pipe_handle, std::wstring&& pipe_path)
: pipe_handle_(std::move(pipe_handle)), pipe_path_(std::move(pipe_path)) {
}
IpcHandle pipe_handle_;
std::wstring pipe_path_;
#else
IpcService(IpcHandle server_handle, std::string&& socket_path)
: server_handle_(std::move(server_handle)),
socket_path_(std::move(socket_path)) {}
IpcHandle server_handle_;
std::string socket_path_;
#endif // !_WIN32
};
///////////////////////////////////////////////////////////////////////////
///
/// SigPipeIgnore
///
#ifndef _WIN32
/// Helper class to temporarily disable SIGPIPE, which halts
/// the current process by default on Posix. These happen when IPC
/// pipes are broken by the client.
class SigPipeIgnore {
public:
SigPipeIgnore() {
struct sigaction new_handler = {};
new_handler.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &new_handler, &prev_handler_);
}
~SigPipeIgnore() { sigaction(SIGPIPE, &prev_handler_, nullptr); }
private:
struct sigaction prev_handler_;
};
#endif // !_WIN32
#endif // NINJA_IPC_HANDLE_H_