blob: f58f7cd200e3b2a5afc8d5187c18a89f2d49ac71 [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.
#ifndef NINJA_IPC_HANDLE_H_
#define NINJA_IPC_HANDLE_H_
#include <stddef.h>
#include <string>
#include <string_view>
#include "string_piece.h"
#ifdef _WIN32
#include <basetsd.h>
#include <windows.h>
using ssize_t = SSIZE_T;
#else
#include <signal.h>
#endif
/// 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
using HandleType = int;
static constexpr int kInvalid = -1;
#endif
IpcHandle() = default;
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
/// Create anonymous bi-directional pipe. On success return true and
/// sets |*read| and |*write|. On failure, return false and sets
/// |*error_message|.
static bool CreatePipe(IpcHandle* read, IpcHandle* write,
std::string* error_message);
/// Same as CreatePipe() but returns handles that can be used for asychronous
/// operations (i.e. O_NONBLOCK on Posix, and FILE_FLAG_OVERLAPPED on Win32).
static bool CreateAsyncPipe(IpcHandle* read, IpcHandle* write,
std::string* error_message);
/// 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;
};
/// Models an IpcHandle used to bind to specific service.
/// Only one process can bind to a specific named service at
/// a time on a given machine.
class IpcServiceHandle : public IpcHandle {
public:
IpcServiceHandle() = default;
~IpcServiceHandle();
IpcServiceHandle(IpcServiceHandle&&) noexcept = default;
IpcServiceHandle& operator=(IpcServiceHandle&&) noexcept = default;
/// Create a server handle for |service_name|, which is an arbitrary name
/// used to identify a specific global service, which can have only one
/// serving instance per (machine, user) combination.
///
/// On success, return a valid IpcHandle, that can be used with
/// AcceptClient(). On failure, return an invalid handle, and sets
/// |*error_message|.
///
/// This will fail if another server is already running with the
/// same name on the current machine (for the same user). Closing
/// the service handle will release the corresponding socket or
/// named pipe immediately, and of course the destructor closes
/// automatically.
///
/// Note that the implementation should be resilient to program
/// crashes as well, i.e. on Linux and Win32, it uses kernel features
/// that ensure proper socket/pipe cleanup on process exit. On
/// other Unix systems, a Unix-domain socket and associated PID file
/// are used to detect stale socket files and remove them properly.
static IpcServiceHandle BindTo(StringPiece service_name,
std::string* error_message);
/// Return true if a given service is already bound. This is racy by
/// definition, but useful during unit-tests.
static bool IsBound(StringPiece service_name);
/// Accept one client connection. This is only valid for instances
/// returned from BindTo().
IpcHandle AcceptClient(std::string* error_message) const;
/// Accept one client connection, with a timeout. On success, return a valid
/// IpcHandle value. On failure, set |*did_timeout| to true if the timeout
/// occured, and set |*error_message| to an error message.
IpcHandle AcceptClient(int64_t timeout_ms, bool* did_timeout,
std::string* error_message) const;
/// Connect to the server implementing |service_name|.
/// On success, return valid IpcHandle. On failure, set |*error_message|
/// then return an invalid value.
static IpcHandle ConnectTo(StringPiece service_name,
std::string* error_message);
/// Connect to the server implementing |service_name|, with a timeout
/// in milliseconds. On success, return a valid IpcHandle. On error or
/// timeout, set |*error_message| and |*did_timeout| before returning
/// an invalid value.
static IpcHandle ConnectTo(StringPiece service_name, int64_t timeout_ms,
bool* did_timeout, std::string* error_message);
/// 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.
///
/// On Win32, the returned handle has FILE_FLAG_OVERLAPPED and connection
/// always succeeds.
///
/// On Posix, the returned handle is non-blocking, and 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.
static IpcHandle AsyncConnectTo(StringPiece service_name, bool* did_connect,
std::string* error_message);
/// Close the handle, make it invalid and remove service socket.
void Close();
private:
#ifdef _WIN32
IpcServiceHandle(IpcHandle::HandleType handle) : IpcHandle(handle) {}
#else
IpcServiceHandle(IpcHandle::HandleType handle, const std::string& path)
: IpcHandle(handle), socket_path_(path) {}
std::string socket_path_;
#endif
};
#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_