/* * Copyright (C) 2017 The Android Open Source Project * * 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 SRC_IPC_UNIX_SOCKET_H_ #define SRC_IPC_UNIX_SOCKET_H_ #include <stdint.h> #include <sys/types.h> #include <memory> #include <string> #include "perfetto/base/logging.h" #include "perfetto/base/scoped_file.h" #include "perfetto/base/weak_ptr.h" #include "perfetto/ipc/basic_types.h" namespace perfetto { namespace base { class TaskRunner; } // namespace base. namespace ipc { // A non-blocking UNIX domain socket in SOCK_STREAM mode. Allows also to // transfer file descriptors. None of the methods in this class are blocking. // The main design goal is API simplicity and strong guarantees on the // EventListener callbacks, in order to avoid ending in some undefined state. // In case of any error it will aggressively just shut down the socket and // notify the failure with OnConnect(false) or OnDisconnect() depending on the // state of the socket (see below). // EventListener callbacks stop happening as soon as the instance is destroyed. // // Lifecycle of a client socket: // // Connect() // | // +------------------+------------------+ // | (success) | (failure or Shutdown()) // V V // OnConnect(true) OnConnect(false) // | // V // OnDataAvailable() // | // V // OnDisconnect() (failure or shutdown) // // // Lifecycle of a server socket: // // Listen() --> returns false in case of errors. // | // V // OnNewIncomingConnection(new_socket) // // (|new_socket| inherits the same EventListener) // | // V // OnDataAvailable() // | (failure or Shutdown()) // V // OnDisconnect() class UnixSocket { public: class EventListener { public: virtual ~EventListener(); // After Listen(). virtual void OnNewIncomingConnection( UnixSocket* self, std::unique_ptr<UnixSocket> new_connection); // After Connect(), whether successful or not. virtual void OnConnect(UnixSocket* self, bool connected); // After a successful Connect() or OnNewIncomingConnection(). Either the // other endpoint did disconnect or some other error happened. virtual void OnDisconnect(UnixSocket* self); // Whenever there is data available to Receive(). Note that spurious FD // watch events are possible, so it is possible that Receive() soon after // OnDataAvailable() returns 0 (just ignore those). virtual void OnDataAvailable(UnixSocket* self); }; enum class State { kDisconnected = 0, // Failed connection, peer disconnection or Shutdown(). kConnecting, // Soon after Connect(), before it either succeeds or fails. kConnected, // After a successful Connect(). kListening // After Listen(), until Shutdown(). }; enum class BlockingMode { kNonBlocking, kBlocking }; // Creates a Unix domain socket and starts listening. If |socket_name| // starts with a '@', an abstract socket will be created (Linux/Android only). // Returns always an instance. In case of failure (e.g., another socket // with the same name is already listening) the returned socket will have // is_listening() == false and last_error() will contain the failure reason. static std::unique_ptr<UnixSocket> Listen(const std::string& socket_name, EventListener*, base::TaskRunner*); // Attaches to a pre-existing socket. The socket must have been created in // SOCK_STREAM mode and the caller must have called bind() on it. static std::unique_ptr<UnixSocket> Listen(base::ScopedFile socket_fd, EventListener*, base::TaskRunner*); // Creates a Unix domain socket and connects to the listening endpoint. // Returns always an instance. EventListener::OnConnect(bool success) will // be called always, whether the connection succeeded or not. static std::unique_ptr<UnixSocket> Connect(const std::string& socket_name, EventListener*, base::TaskRunner*); // Creates a Unix domain socket and binds it to |socket_name| (see comment // of Listen() above for the format). This file descriptor is suitable to be // passed to Listen(ScopedFile, ...). Returns the file descriptor, or -1 in // case of failure. static base::ScopedFile CreateAndBind(const std::string& socket_name); // This class gives the hard guarantee that no callback is called on the // passed EventListener immediately after the object has been destroyed. // Any queued callback will be silently dropped. ~UnixSocket(); // Shuts down the current connection, if any. If the socket was Listen()-ing, // stops listening. The socket goes back to kNotInitialized state, so it can // be reused with Listen() or Connect(). void Shutdown(bool notify); // Returns true is the message was queued, false if there was no space in the // output buffer, in which case the client should retry or give up. // If any other error happens the socket will be shutdown and // EventListener::OnDisconnect() will be called. // If the socket is not connected, Send() will just return false. // Does not append a null string terminator to msg in any case. bool Send(const void* msg, size_t len, int send_fd = -1, BlockingMode blocking = BlockingMode::kNonBlocking); bool Send(const std::string& msg); // Returns the number of bytes (<= |len|) written in |msg| or 0 if there // is no data in the buffer to read or an error occurs (in which case a // EventListener::OnDisconnect() will follow). // If the ScopedFile pointer is not null and a FD is received, it moves the // received FD into that. If a FD is received but the ScopedFile pointer is // null, the FD will be automatically closed. size_t Receive(void* msg, size_t len, base::ScopedFile* = nullptr); // Only for tests. This is slower than Receive() as it requires a heap // allocation and a copy for the std::string. Guarantees that the returned // string is null terminated even if the underlying message sent by the peer // is not. std::string ReceiveString(size_t max_length = 1024); bool is_connected() const { return state_ == State::kConnected; } bool is_listening() const { return state_ == State::kListening; } int fd() const { return fd_.get(); } int last_error() const { return last_error_; } // User ID of the peer, as returned by the kernel. If the client disconnects // and the socket goes into the kDisconnected state, it retains the uid of // the last peer. uid_t peer_uid() const { PERFETTO_DCHECK(!is_listening() && peer_uid_ != kInvalidUid); return peer_uid_; } private: UnixSocket(EventListener*, base::TaskRunner*); UnixSocket(EventListener*, base::TaskRunner*, base::ScopedFile, State); UnixSocket(const UnixSocket&) = delete; UnixSocket& operator=(const UnixSocket&) = delete; // Called once by the corresponding public static factory methods. void DoConnect(const std::string& socket_name); void ReadPeerCredentials(); void SetBlockingIO(bool is_blocking); void OnEvent(); void NotifyConnectionState(bool success); base::ScopedFile fd_; State state_ = State::kDisconnected; int last_error_ = 0; uid_t peer_uid_ = kInvalidUid; EventListener* event_listener_; base::TaskRunner* task_runner_; base::WeakPtrFactory<UnixSocket> weak_ptr_factory_; }; } // namespace ipc } // namespace perfetto #endif // SRC_IPC_UNIX_SOCKET_H_