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