#ifndef ANDROID_PDX_SERVICE_H_ #define ANDROID_PDX_SERVICE_H_ #include #include #include #include #include #include #include #include #include #include #include #include "pdx/channel_handle.h" #include "pdx/file_handle.h" #include "pdx/message_reader.h" #include "pdx/message_writer.h" #include "pdx/service_endpoint.h" namespace android { namespace pdx { class Service; namespace opcodes { /* * Reserved message opcodes used by libpdx. The reserved opcodes start at the * max positive signed integer for the system and go down. * In contrast, service opcodes start at zero and go up. This scheme leaves * most of the positive integer space for services, a tiny fraction of the * positive integer space for the framework, and the entire negative integer * space for the kernel. */ #define PDX_OPCODE(name, n) name = ((-1U >> 1) - (n)) // 0x7fff..ffff - n enum { // System message sent when a new client channel is open. CHANNEL_OPEN = -1, // System message sent when a channel is closed. CHANNEL_CLOSE = -2, // Request the service to reload system properties. PDX_OPCODE(REPORT_SYSPROP_CHANGE, 0), // Request the service to dump state and return it in a text buffer. PDX_OPCODE(DUMP_STATE, 1), }; } // namespace opcodes /* * Base class of service-side per-channel context classes. */ class Channel : public std::enable_shared_from_this { public: Channel() {} virtual ~Channel() {} /* * Accessors to the pid of the last active client. */ pid_t GetActiveProcessId() const { return client_pid_; } void SetActiveProcessId(pid_t pid) { client_pid_ = pid; } /* * Utility to get a shared_ptr reference from the channel context pointer. */ static std::shared_ptr GetFromMessageInfo(const MessageInfo& info); private: pid_t client_pid_ = 0; }; /* * Message class represents an RPC message, and implicitly the blocked sender * waiting for a response. Every message should get a reply, at some point * (unless the endpoint is closed), to prevent clients from blocking * indefinitely. In order to enforce this and prevent leaking message ids, * Message automatically replies with an error to the client on destruction, * unless one of two things happens: * * 1. The service calls one of the reply methods before the Message is * destroyed. * 2. The responsibility for the message is moved to another instance of * Message, using either move construction or move assignment. * * The second case is useful for services that need to delay responding to a * sender until a later time. In this situation the service can move the * Message to another instance in a suitable data structure for later use. The * moved-to Message then takes on the same behavior and responsibilities * described above. */ class Message : public OutputResourceMapper, public InputResourceMapper { public: Message(); explicit Message(const MessageInfo& info); ~Message(); /* * Message objects support move construction and assignment. */ Message(Message&& other) noexcept; Message& operator=(Message&& other) noexcept; /* * Read/write payload, in either single buffer or iovec form. */ Status ReadVector(const iovec* vector, size_t vector_length); Status Read(void* buffer, size_t length); Status WriteVector(const iovec* vector, size_t vector_length); Status Write(const void* buffer, size_t length); template inline Status ReadVector(const iovec (&vector)[N]) { return ReadVector(vector, N); } template inline Status WriteVector(const iovec (&vector)[N]) { return WriteVector(vector, N); } // Helper functions to read/write all requested bytes, and return EIO if not // all were read/written. Status ReadVectorAll(const iovec* vector, size_t vector_length); Status WriteVectorAll(const iovec* vector, size_t vector_length); inline Status ReadAll(void* buffer, size_t length) { Status status = Read(buffer, length); if (status && status.get() < length) status.SetError(EIO); Status ret; ret.PropagateError(status); return ret; } inline Status WriteAll(const void* buffer, size_t length) { Status status = Write(buffer, length); if (status && status.get() < length) status.SetError(EIO); Status ret; ret.PropagateError(status); return ret; } template inline Status ReadVectorAll(const iovec (&vector)[N]) { return ReadVectorAll(vector, N); } template inline Status WriteVectorAll(const iovec (&vector)[N]) { return WriteVectorAll(vector, N); } // OutputResourceMapper Status PushFileHandle(const LocalHandle& handle) override; Status PushFileHandle(const BorrowedHandle& handle) override; Status PushFileHandle(const RemoteHandle& handle) override; Status PushChannelHandle( const LocalChannelHandle& handle) override; Status PushChannelHandle( const BorrowedChannelHandle& handle) override; Status PushChannelHandle( const RemoteChannelHandle& handle) override; // InputResourceMapper bool GetFileHandle(FileReference ref, LocalHandle* handle) override; bool GetChannelHandle(ChannelReference ref, LocalChannelHandle* handle) override; /* * Various ways to reply to a message. */ Status Reply(int return_code); Status ReplyError(unsigned int error); Status ReplyFileDescriptor(unsigned int fd); Status Reply(const LocalHandle& handle); Status Reply(const BorrowedHandle& handle); Status Reply(const RemoteHandle& handle); Status Reply(const LocalChannelHandle& handle); Status Reply(const BorrowedChannelHandle& handle); Status Reply(const RemoteChannelHandle& handle); template inline Status Reply(const Status& status) { return status ? Reply(status.get()) : ReplyError(status.error()); } inline Status Reply(const Status& status) { return status ? Reply(0) : ReplyError(status.error()); } /* * Update the channel event bits with the given clear and set masks. */ Status ModifyChannelEvents(int clear_mask, int set_mask); /* * Create a new channel and push it as a file descriptor to the client. See * Service::PushChannel() for a detail description of this method's operation. */ Status PushChannel( int flags, const std::shared_ptr& channel, int* channel_id); /* * Create a new channel and push it as a file descriptor to the client. See * Service::PushChannel() for a detail description of this method's operation. */ Status PushChannel( Service* service, int flags, const std::shared_ptr& channel, int* channel_id); /* * Check whether the |ref| is a reference to channel to this service. * If the channel reference in question is valid, the Channel object is * returned in |channel| when non-nullptr. * * Return values: * channel_id - id of the channel if the |ref| is a valid reference to * this service's channel. * Errors: * EOPNOTSUPP - the file descriptor is not a channel or is a channel to * another service. * EBADF - the file descriptor is invalid. * FAULT - |channel_id| or |channel| are non-nullptr and point to invalid * memory addresses. * EINVAL - the value of |ref| is invalid or the message id for this * message is no longer valid. */ Status CheckChannel(ChannelReference ref, std::shared_ptr* channel) const; /* * Overload of CheckChannel() that checks whether the channel reference is for * a channel to the service |service|. */ Status CheckChannel(const Service* service, ChannelReference ref, std::shared_ptr* channel) const; /* * Overload of CheckChannel() that automatically converts to shared pointers * to types derived from Channel. */ template Status CheckChannel(ChannelReference ref, std::shared_ptr* channel) const { std::shared_ptr base_pointer; const Status ret = CheckChannel(ref, channel ? &base_pointer : nullptr); if (channel) *channel = std::static_pointer_cast(base_pointer); return ret; } template Status CheckChannel(const Service* service, ChannelReference ref, std::shared_ptr* channel) const { std::shared_ptr base_pointer; const Status ret = CheckChannel(service, ref, channel ? &base_pointer : nullptr); if (channel) *channel = std::static_pointer_cast(base_pointer); return ret; } /* * MessageInfo accessors. */ pid_t GetProcessId() const; pid_t GetThreadId() const; uid_t GetEffectiveUserId() const; gid_t GetEffectiveGroupId() const; int GetChannelId() const; int GetMessageId() const; int GetOp() const; int GetFlags() const; size_t GetSendLength() const; size_t GetReceiveLength() const; size_t GetFileDescriptorCount() const; /* * Impulses are asynchronous and cannot be replied to. All impulses have this * invalid message id. */ enum { IMPULSE_MESSAGE_ID = -1 }; /* * Returns true if this Message describes an asynchronous "impulse" message. */ bool IsImpulse() const { return GetMessageId() == IMPULSE_MESSAGE_ID; } /* * Returns a pointer to the impulse payload. Impulses are a maximum of 32 * bytes in size and the start of the impulse payload is guaranteed to be * 8-byte aligned. Use GetSendLength() to determine the payload size. */ const std::uint8_t* ImpulseBegin() const; /* * Returns one byte past the end of the impulse payload, as conventional for * STL iterators. */ const std::uint8_t* ImpulseEnd() const; /* * Get/set the Channel object for the channel associated * with this message. It is up to the caller to synchronize * these in multi-threaded services. */ std::shared_ptr GetChannel() const; Status SetChannel(const std::shared_ptr& channnel); /* * Get the Channel object for the channel associated with this message, * automatically converted to the desired subclass of Channel. */ template std::shared_ptr GetChannel() const { return std::static_pointer_cast(GetChannel()); } /* * Gets the service this message was received on. Returns nullptr if the * service was destroyed. */ std::shared_ptr GetService() const; /* * Raw access to the MessageInfo for this message. */ const MessageInfo& GetInfo() const; bool replied() const { return replied_; } bool IsChannelExpired() const { return channel_.expired(); } bool IsServiceExpired() const { return service_.expired(); } /* * Returns true if the message is non-empty; that is the message can be * replied to using this instance. */ explicit operator bool() const { return !replied_; } const void* GetState() const { return state_; } void* GetState() { return state_; } private: friend class Service; Message(const Message&) = delete; void operator=(const Message&) = delete; void Destroy(); std::weak_ptr service_; std::weak_ptr channel_; MessageInfo info_; void* state_{nullptr}; bool replied_; }; // Base class for RPC services. class Service : public std::enable_shared_from_this { public: Service(const std::string& name, std::unique_ptr endpoint); virtual ~Service(); /* * Utility to get a shared_ptr reference from the service context pointer. */ static std::shared_ptr GetFromMessageInfo(const MessageInfo& info); /* * Returns whether initialization was successful. Subclasses that override * this must call this base method and AND the results with their own. This * method is not intended to do any initialization work itself, only to * signal success or failure. */ virtual bool IsInitialized() const; /* * Called by defaultHandleMessage in response to a CHANNEL_OPEN message. * This gives subclasses of Service a convenient hook to create per-channel * context in the form of a Channel subclass. * * The Channel instance returned by this is used to set the channel context * pointer for the channel that was just opened. */ virtual std::shared_ptr OnChannelOpen(Message& message); /* * Called by defaultHandleMessage in response to a CHANNEL_CLOSE message. * This give subclasses of Service a convenient hook to clean up per-channel * context. */ virtual void OnChannelClose(Message& message, const std::shared_ptr& channel); /* * Set the channel context for the given channel. This keeps a reference to * the Channel object until the channel is closed or another call replaces * the current value. */ Status SetChannel(int channel_id, const std::shared_ptr& channel); /* * Get the channel context for the given channel id. This method should be * used sparingly because of the performance characteristics of the underlying * map; it is intended for limited, non-critical path access from outside of * message dispatch. In most cases lookup by id should be unnecessary in a * properly designed service; Message::GetChannel() should be used instead * whenever an operation is in the context of a message. * * Again, if you lookup a channel context object for a service by id in a * message handling path for the same service, you're probably doing something * wrong. */ std::shared_ptr GetChannel(int channel_id) const; /* * Get a snapshot of the active channels for this service. This is the * preferred way to access the set of channels because it avoids potential * deadlocks and race conditions that may occur when operating on the channel * map directly. However, it is more expensive than direct iteration because * of dynamic memory allocation and shared pointer reference costs. * * Automatically converts returned items to shared pointers of the type * std::shared_ptr, where C is the subclass of Channel used by the service. */ template std::vector> GetChannels() const { std::lock_guard autolock(channels_mutex_); std::vector> items; items.reserve(channels_.size()); for (const auto& pair : channels_) { items.push_back(std::static_pointer_cast(pair.second)); } return items; } /* * Close a channel, signaling the client file object and freeing the channel * id. Once closed, the client side of the channel always returns the error * ESHUTDOWN and signals the poll/epoll events POLLHUP and POLLFREE. * * The internal reference to the Channel instance associated with the channel * is removed, which may result in the Channel object being freed. * * OnChannelClosed is not called in response to this method call. */ Status CloseChannel(int channel_id); /* * Update the event bits for the given channel (given by id), using the * given clear and set masks. * * This is useful for asynchronously signaling events that clients may be * waiting for using select/poll/epoll. */ Status ModifyChannelEvents(int channel_id, int clear_mask, int set_mask); /* * Create a new channel and push it as a file descriptor to the process * sending the |message|. |flags| may be set to O_NONBLOCK and/or * O_CLOEXEC to control the initial behavior of the new file descriptor (the * sending process may change these later using fcntl()). The internal Channel * instance associated with this channel is set to |channel|, which may be * nullptr. The new channel id allocated for this channel is returned in * |channel_id|, which may also be nullptr if not needed. * * On success, returns the remote channel handle for the new channel in the * sending process' handle space. This MUST be returned to the sender via * Message::Reply(), Message::Write(), or Message::WriteVector(). * * On error, returns an errno code describing the cause of the error. * * Service::OnChannelCreate() is not called in response to the creation of the * new channel. */ Status PushChannel( Message* message, int flags, const std::shared_ptr& channel, int* channel_id); /* * Check whether the |ref| is a reference to a channel to this service. * If the channel reference in question is valid, the Channel object is * returned in |channel| when non-nullptr. * * Return values: * channel_id - id of the channel if the channel reference. * Errors: * EOPNOTSUPP - the file descriptor is not a channel or is a channel to * another service. * EBADF - the file descriptor is invalid. * FAULT - |channel_id| or |channel| are non-nullptr and point to invalid * memory addresses. * EINVAL - the value of |ref| is invalid or the message id for this * message is no longer valid. */ Status CheckChannel(const Message* message, ChannelReference ref, std::shared_ptr* channel) const; /* * Overload of CheckChannel() that automatically converts to shared pointers * of types derived from Channel. */ template Status CheckChannel(const Message* message, ChannelReference ref, std::shared_ptr* channel) const { std::shared_ptr base_pointer; const Status ret = CheckChannel(message, ref, channel ? &base_pointer : nullptr); if (channel) *channel = std::static_pointer_cast(base_pointer); return ret; } /* * Handle a message. Subclasses override this to receive messages and decide * how to dispatch them. * * The default implementation simply calls defaultHandleMessage(). * Subclasses should call the same for any unrecognized message opcodes. */ virtual Status HandleMessage(Message& message); /* * Handle an asynchronous message. Subclasses override this to receive * asynchronous "impulse" messages. Impulses have a limited-size payload that * is transferred upfront with the message description. */ virtual void HandleImpulse(Message& impulse); /* * The default message handler. It is important that all messages * (eventually) get a reply. This method should be called by subclasses for * any unrecognized opcodes or otherwise unhandled messages to prevent * erroneous requests from blocking indefinitely. * * Provides default handling of CHANNEL_OPEN and CHANNEL_CLOSE, calling * OnChannelOpen() and OnChannelClose(), respectively. * * For all other message opcodes, this method replies with ENOTSUP. */ Status DefaultHandleMessage(Message& message); /* * Called when system properties have changed. Subclasses should implement * this method if they need to handle when system properties change. */ virtual void OnSysPropChange(); /* * Get the endpoint for the service. */ Endpoint* endpoint() const { return endpoint_.get(); } /* * Cancels the endpoint, unblocking any receiver threads waiting in * ReceiveAndDispatch(). */ Status Cancel(); /* * Iterator type for Channel map iterators. */ using ChannelIterator = std::unordered_map>::iterator; /* * Iterates over the Channel map and performs the action given by |action| on * each channel map item (const ChannelIterator::value_type). * |channels_mutex_| is not held; it is the responsibility of the caller to * ensure serialization between threads that modify or iterate over the * Channel map. */ template void ForEachChannelUnlocked(A action) const { std::for_each(channels_.begin(), channels_.end(), action); } /* * Iterates over the Channel map and performs the action given by |action| on * each channel map item (const ChannelIterator::value_type). * |channels_mutex_| is held to serialize access to the map; care must be * taken to avoid recursively acquiring the mutex, for example, by calling * Service::{GetChannel,SetChannel,CloseChannel,PushChannel}() or * Message::SetChannel() in the action. */ template void ForEachChannel(A action) const { std::lock_guard autolock(channels_mutex_); ForEachChannelUnlocked(action); } /* * Return true if a channel with the given ID exists in the Channel map. */ bool HasChannelId(int channel_id) const { std::lock_guard autolock(channels_mutex_); return channels_.find(channel_id) != channels_.end(); } /* * Subclasses of Service may override this method to provide a text string * describing the state of the service. This method is called by * HandleSystemMessage in response to the standard * DUMP_STATE message. The string returned to the dump state client is * truncated to |max_length| and reflects the maximum size the client can * handle. */ virtual std::string DumpState(size_t max_length); /* * Receives a message on this Service instance's endpoint and dispatches it. * If the endpoint is in blocking mode this call blocks until a message is * received, a signal is delivered to this thread, or the service is canceled. * If the endpoint is in non-blocking mode and a message is not pending this * call returns immediately with ETIMEDOUT. */ Status ReceiveAndDispatch(); private: friend class Message; Status HandleSystemMessage(Message& message); Service(const Service&); void operator=(const Service&) = delete; const std::string name_; std::unique_ptr endpoint_; /* * Maintains references to active channels. */ mutable std::mutex channels_mutex_; std::unordered_map> channels_; }; /* * Utility base class for services. This template handles allocation and * initialization checks, reducing boiler plate code. */ template class ServiceBase : public Service { public: /* * Static service allocation method that check for initialization errors. * If errors are encountered these automatically clean up and return * nullptr. */ template static inline std::shared_ptr Create(Args&&... args) { std::shared_ptr service(new TYPE(std::forward(args)...)); if (service->IsInitialized()) return service; else return nullptr; } protected: /* * Shorthand for subclasses to refer to this base, particularly * to call the base class constructor. */ typedef ServiceBase BASE; ServiceBase(const std::string& name, std::unique_ptr endpoint) : Service(name, std::move(endpoint)) {} }; #ifndef STRINGIFY #define STRINGIFY2(s) #s #define STRINGIFY(s) STRINGIFY2(s) #endif #define PDX_ERROR_PREFIX "[" __FILE__ ":" STRINGIFY(__LINE__) "]" /* * Macros for replying to messages. Error handling can be tedious; * these macros make things a little cleaner. */ #define CHECK_ERROR(cond, error, fmt, ...) \ do { \ if ((cond)) { \ ALOGE(fmt, ##__VA_ARGS__); \ goto error; \ } \ } while (0) #define REPLY_ERROR(message, error, error_label) \ do { \ auto __status = message.ReplyError(error); \ CHECK_ERROR(!__status, error_label, \ PDX_ERROR_PREFIX " Failed to reply to message because: %s\n", \ __status.GetErrorMessage().c_str()); \ goto error_label; \ } while (0) #define REPLY_ERROR_RETURN(message, error, ...) \ do { \ auto __status = message.ReplyError(error); \ ALOGE_IF(!__status, \ PDX_ERROR_PREFIX " Failed to reply to message because: %s", \ __status.GetErrorMessage().c_str()); \ return __VA_ARGS__; \ } while (0) #define REPLY_MESSAGE(message, message_return_code, error_label) \ do { \ auto __status = message.Reply(message_return_code); \ CHECK_ERROR(!__status, error_label, \ PDX_ERROR_PREFIX " Failed to reply to message because: %s\n", \ __status.GetErrorMessage().c_str()); \ goto error_label; \ } while (0) #define REPLY_SUCCESS(message, message_return_code, error_label) \ REPLY_MESSAGE(message, message_return_code, error_label) #define REPLY_MESSAGE_RETURN(message, message_return_code, ...) \ do { \ auto __status = message.Reply(message_return_code); \ ALOGE_IF(!__status, \ PDX_ERROR_PREFIX " Failed to reply to message because: %s", \ __status.GetErrorMessage().c_str()); \ return __VA_ARGS__; \ } while (0) #define REPLY_SUCCESS_RETURN(message, message_return_code, ...) \ REPLY_MESSAGE_RETURN(message, message_return_code, __VA_ARGS__) #define REPLY_FD(message, push_fd, error_label) \ do { \ auto __status = message.ReplyFileDescriptor(push_fd); \ CHECK_ERROR(!__status, error_label, \ PDX_ERROR_PREFIX " Failed to reply to message because: %s\n", \ __status.GetErrorMessage().c_str()); \ goto error_label; \ } while (0) #define REPLY_FD_RETURN(message, push_fd, ...) \ do { \ auto __status = message.ReplyFileDescriptor(push_fd); \ ALOGE_IF(__status < 0, \ PDX_ERROR_PREFIX " Failed to reply to message because: %s", \ __status.GetErrorMessage().c_str()); \ return __VA_ARGS__; \ } while (0) } // namespace pdx } // namespace android #endif // ANDROID_PDX_SERVICE_H_