| // 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_ |