| /* | |
| * Copyright (C) 2007 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. | |
| */ | |
| #define LOG_TAG "mq" | |
| #include <assert.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <pthread.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/socket.h> | |
| #include <sys/types.h> | |
| #include <sys/un.h> | |
| #include <sys/uio.h> | |
| #include <cutils/array.h> | |
| #include <cutils/hashmap.h> | |
| #include <cutils/selector.h> | |
| #include "loghack.h" | |
| #include "buffer.h" | |
| /** Number of dead peers to remember. */ | |
| #define PEER_HISTORY (16) | |
| typedef struct sockaddr SocketAddress; | |
| typedef struct sockaddr_un UnixAddress; | |
| /** | |
| * Process/user/group ID. We don't use ucred directly because it's only | |
| * available on Linux. | |
| */ | |
| typedef struct { | |
| pid_t pid; | |
| uid_t uid; | |
| gid_t gid; | |
| } Credentials; | |
| /** Listens for bytes coming from remote peers. */ | |
| typedef void BytesListener(Credentials credentials, char* bytes, size_t size); | |
| /** Listens for the deaths of remote peers. */ | |
| typedef void DeathListener(pid_t pid); | |
| /** Types of packets. */ | |
| typedef enum { | |
| /** Request for a connection to another peer. */ | |
| CONNECTION_REQUEST, | |
| /** A connection to another peer. */ | |
| CONNECTION, | |
| /** Reports a failed connection attempt. */ | |
| CONNECTION_ERROR, | |
| /** A generic packet of bytes. */ | |
| BYTES, | |
| } PacketType; | |
| typedef enum { | |
| /** Reading a packet header. */ | |
| READING_HEADER, | |
| /** Waiting for a connection from the master. */ | |
| ACCEPTING_CONNECTION, | |
| /** Reading bytes. */ | |
| READING_BYTES, | |
| } InputState; | |
| /** A packet header. */ | |
| // TODO: Use custom headers for master->peer, peer->master, peer->peer. | |
| typedef struct { | |
| PacketType type; | |
| union { | |
| /** Packet size. Used for BYTES. */ | |
| size_t size; | |
| /** Credentials. Used for CONNECTION and CONNECTION_REQUEST. */ | |
| Credentials credentials; | |
| }; | |
| } Header; | |
| /** A packet which will be sent to a peer. */ | |
| typedef struct OutgoingPacket OutgoingPacket; | |
| struct OutgoingPacket { | |
| /** Packet header. */ | |
| Header header; | |
| union { | |
| /** Connection to peer. Used with CONNECTION. */ | |
| int socket; | |
| /** Buffer of bytes. Used with BYTES. */ | |
| Buffer* bytes; | |
| }; | |
| /** Frees all resources associated with this packet. */ | |
| void (*free)(OutgoingPacket* packet); | |
| /** Optional context. */ | |
| void* context; | |
| /** Next packet in the queue. */ | |
| OutgoingPacket* nextPacket; | |
| }; | |
| /** Represents a remote peer. */ | |
| typedef struct PeerProxy PeerProxy; | |
| /** Local peer state. You typically have one peer per process. */ | |
| typedef struct { | |
| /** This peer's PID. */ | |
| pid_t pid; | |
| /** | |
| * Map from pid to peer proxy. The peer has a peer proxy for each remote | |
| * peer it's connected to. | |
| * | |
| * Acquire mutex before use. | |
| */ | |
| Hashmap* peerProxies; | |
| /** Manages I/O. */ | |
| Selector* selector; | |
| /** Used to synchronize operations with the selector thread. */ | |
| pthread_mutex_t mutex; | |
| /** Is this peer the master? */ | |
| bool master; | |
| /** Peer proxy for the master. */ | |
| PeerProxy* masterProxy; | |
| /** Listens for packets from remote peers. */ | |
| BytesListener* onBytes; | |
| /** Listens for deaths of remote peers. */ | |
| DeathListener* onDeath; | |
| /** Keeps track of recently dead peers. Requires mutex. */ | |
| pid_t deadPeers[PEER_HISTORY]; | |
| size_t deadPeerCursor; | |
| } Peer; | |
| struct PeerProxy { | |
| /** Credentials of the remote process. */ | |
| Credentials credentials; | |
| /** Keeps track of data coming in from the remote peer. */ | |
| InputState inputState; | |
| Buffer* inputBuffer; | |
| PeerProxy* connecting; | |
| /** File descriptor for this peer. */ | |
| SelectableFd* fd; | |
| /** | |
| * Queue of packets to be written out to the remote peer. | |
| * | |
| * Requires mutex. | |
| */ | |
| // TODO: Limit queue length. | |
| OutgoingPacket* currentPacket; | |
| OutgoingPacket* lastPacket; | |
| /** Used to write outgoing header. */ | |
| Buffer outgoingHeader; | |
| /** True if this is the master's proxy. */ | |
| bool master; | |
| /** Reference back to the local peer. */ | |
| Peer* peer; | |
| /** | |
| * Used in master only. Maps this peer proxy to other peer proxies to | |
| * which the peer has been connected to. Maps pid to PeerProxy. Helps | |
| * keep track of which connections we've sent to whom. | |
| */ | |
| Hashmap* connections; | |
| }; | |
| /** Server socket path. */ | |
| static const char* MASTER_PATH = "/master.peer"; | |
| /** Credentials of the master peer. */ | |
| static const Credentials MASTER_CREDENTIALS = {0, 0, 0}; | |
| /** Creates a peer proxy and adds it to the peer proxy map. */ | |
| static PeerProxy* peerProxyCreate(Peer* peer, Credentials credentials); | |
| /** Sets the non-blocking flag on a descriptor. */ | |
| static void setNonBlocking(int fd) { | |
| int flags; | |
| if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { | |
| LOG_ALWAYS_FATAL("fcntl() error: %s", strerror(errno)); | |
| } | |
| if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { | |
| LOG_ALWAYS_FATAL("fcntl() error: %s", strerror(errno)); | |
| } | |
| } | |
| /** Closes a fd and logs a warning if the close fails. */ | |
| static void closeWithWarning(int fd) { | |
| int result = close(fd); | |
| if (result == -1) { | |
| ALOGW("close() error: %s", strerror(errno)); | |
| } | |
| } | |
| /** Hashes pid_t keys. */ | |
| static int pidHash(void* key) { | |
| pid_t* pid = (pid_t*) key; | |
| return (int) (*pid); | |
| } | |
| /** Compares pid_t keys. */ | |
| static bool pidEquals(void* keyA, void* keyB) { | |
| pid_t* a = (pid_t*) keyA; | |
| pid_t* b = (pid_t*) keyB; | |
| return *a == *b; | |
| } | |
| /** Gets the master address. Not thread safe. */ | |
| static UnixAddress* getMasterAddress() { | |
| static UnixAddress masterAddress; | |
| static bool initialized = false; | |
| if (initialized == false) { | |
| masterAddress.sun_family = AF_LOCAL; | |
| strcpy(masterAddress.sun_path, MASTER_PATH); | |
| initialized = true; | |
| } | |
| return &masterAddress; | |
| } | |
| /** Gets exclusive access to the peer for this thread. */ | |
| static void peerLock(Peer* peer) { | |
| pthread_mutex_lock(&peer->mutex); | |
| } | |
| /** Releases exclusive access to the peer. */ | |
| static void peerUnlock(Peer* peer) { | |
| pthread_mutex_unlock(&peer->mutex); | |
| } | |
| /** Frees a simple, i.e. header-only, outgoing packet. */ | |
| static void outgoingPacketFree(OutgoingPacket* packet) { | |
| ALOGD("Freeing outgoing packet."); | |
| free(packet); | |
| } | |
| /** | |
| * Prepare to read a new packet from the peer. | |
| */ | |
| static void peerProxyExpectHeader(PeerProxy* peerProxy) { | |
| peerProxy->inputState = READING_HEADER; | |
| bufferPrepareForRead(peerProxy->inputBuffer, sizeof(Header)); | |
| } | |
| /** Sets up the buffer for the outgoing header. */ | |
| static void peerProxyPrepareOutgoingHeader(PeerProxy* peerProxy) { | |
| peerProxy->outgoingHeader.data | |
| = (char*) &(peerProxy->currentPacket->header); | |
| peerProxy->outgoingHeader.size = sizeof(Header); | |
| bufferPrepareForWrite(&peerProxy->outgoingHeader); | |
| } | |
| /** Adds a packet to the end of the queue. Callers must have the mutex. */ | |
| static void peerProxyEnqueueOutgoingPacket(PeerProxy* peerProxy, | |
| OutgoingPacket* newPacket) { | |
| newPacket->nextPacket = NULL; // Just in case. | |
| if (peerProxy->currentPacket == NULL) { | |
| // The queue is empty. | |
| peerProxy->currentPacket = newPacket; | |
| peerProxy->lastPacket = newPacket; | |
| peerProxyPrepareOutgoingHeader(peerProxy); | |
| } else { | |
| peerProxy->lastPacket->nextPacket = newPacket; | |
| } | |
| } | |
| /** Takes the peer lock and enqueues the given packet. */ | |
| static void peerProxyLockAndEnqueueOutgoingPacket(PeerProxy* peerProxy, | |
| OutgoingPacket* newPacket) { | |
| Peer* peer = peerProxy->peer; | |
| peerLock(peer); | |
| peerProxyEnqueueOutgoingPacket(peerProxy, newPacket); | |
| peerUnlock(peer); | |
| } | |
| /** | |
| * Frees current packet and moves to the next one. Returns true if there is | |
| * a next packet or false if the queue is empty. | |
| */ | |
| static bool peerProxyNextPacket(PeerProxy* peerProxy) { | |
| Peer* peer = peerProxy->peer; | |
| peerLock(peer); | |
| OutgoingPacket* current = peerProxy->currentPacket; | |
| if (current == NULL) { | |
| // The queue is already empty. | |
| peerUnlock(peer); | |
| return false; | |
| } | |
| OutgoingPacket* next = current->nextPacket; | |
| peerProxy->currentPacket = next; | |
| current->nextPacket = NULL; | |
| current->free(current); | |
| if (next == NULL) { | |
| // The queue is empty. | |
| peerProxy->lastPacket = NULL; | |
| peerUnlock(peer); | |
| return false; | |
| } else { | |
| peerUnlock(peer); | |
| peerProxyPrepareOutgoingHeader(peerProxy); | |
| // TODO: Start writing next packet? It would reduce the number of | |
| // system calls, but we could also starve other peers. | |
| return true; | |
| } | |
| } | |
| /** | |
| * Checks whether a peer died recently. | |
| */ | |
| static bool peerIsDead(Peer* peer, pid_t pid) { | |
| size_t i; | |
| for (i = 0; i < PEER_HISTORY; i++) { | |
| pid_t deadPeer = peer->deadPeers[i]; | |
| if (deadPeer == 0) { | |
| return false; | |
| } | |
| if (deadPeer == pid) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Cleans up connection information. | |
| */ | |
| static bool peerProxyRemoveConnection(void* key, void* value, void* context) { | |
| PeerProxy* deadPeer = (PeerProxy*) context; | |
| PeerProxy* otherPeer = (PeerProxy*) value; | |
| hashmapRemove(otherPeer->connections, &(deadPeer->credentials.pid)); | |
| return true; | |
| } | |
| /** | |
| * Called when the peer dies. | |
| */ | |
| static void peerProxyKill(PeerProxy* peerProxy, bool errnoIsSet) { | |
| if (errnoIsSet) { | |
| ALOGI("Peer %d died. errno: %s", peerProxy->credentials.pid, | |
| strerror(errno)); | |
| } else { | |
| ALOGI("Peer %d died.", peerProxy->credentials.pid); | |
| } | |
| // If we lost the master, we're up a creek. We can't let this happen. | |
| if (peerProxy->master) { | |
| LOG_ALWAYS_FATAL("Lost connection to master."); | |
| } | |
| Peer* localPeer = peerProxy->peer; | |
| pid_t pid = peerProxy->credentials.pid; | |
| peerLock(localPeer); | |
| // Remember for awhile that the peer died. | |
| localPeer->deadPeers[localPeer->deadPeerCursor] | |
| = peerProxy->credentials.pid; | |
| localPeer->deadPeerCursor++; | |
| if (localPeer->deadPeerCursor == PEER_HISTORY) { | |
| localPeer->deadPeerCursor = 0; | |
| } | |
| // Remove from peer map. | |
| hashmapRemove(localPeer->peerProxies, &pid); | |
| // External threads can no longer get to this peer proxy, so we don't | |
| // need the lock anymore. | |
| peerUnlock(localPeer); | |
| // Remove the fd from the selector. | |
| if (peerProxy->fd != NULL) { | |
| peerProxy->fd->remove = true; | |
| } | |
| // Clear outgoing packet queue. | |
| while (peerProxyNextPacket(peerProxy)) {} | |
| bufferFree(peerProxy->inputBuffer); | |
| // This only applies to the master. | |
| if (peerProxy->connections != NULL) { | |
| // We can't leave these other maps pointing to freed memory. | |
| hashmapForEach(peerProxy->connections, &peerProxyRemoveConnection, | |
| peerProxy); | |
| hashmapFree(peerProxy->connections); | |
| } | |
| // Invoke death listener. | |
| localPeer->onDeath(pid); | |
| // Free the peer proxy itself. | |
| free(peerProxy); | |
| } | |
| static void peerProxyHandleError(PeerProxy* peerProxy, char* functionName) { | |
| if (errno == EINTR) { | |
| // Log interruptions but otherwise ignore them. | |
| ALOGW("%s() interrupted.", functionName); | |
| } else if (errno == EAGAIN) { | |
| ALOGD("EWOULDBLOCK"); | |
| // Ignore. | |
| } else { | |
| ALOGW("Error returned by %s().", functionName); | |
| peerProxyKill(peerProxy, true); | |
| } | |
| } | |
| /** | |
| * Buffers output sent to a peer. May be called multiple times until the entire | |
| * buffer is filled. Returns true when the buffer is empty. | |
| */ | |
| static bool peerProxyWriteFromBuffer(PeerProxy* peerProxy, Buffer* outgoing) { | |
| ssize_t size = bufferWrite(outgoing, peerProxy->fd->fd); | |
| if (size < 0) { | |
| peerProxyHandleError(peerProxy, "write"); | |
| return false; | |
| } else { | |
| return bufferWriteComplete(outgoing); | |
| } | |
| } | |
| /** Writes packet bytes to peer. */ | |
| static void peerProxyWriteBytes(PeerProxy* peerProxy) { | |
| Buffer* buffer = peerProxy->currentPacket->bytes; | |
| if (peerProxyWriteFromBuffer(peerProxy, buffer)) { | |
| ALOGD("Bytes written."); | |
| peerProxyNextPacket(peerProxy); | |
| } | |
| } | |
| /** Sends a socket to the peer. */ | |
| static void peerProxyWriteConnection(PeerProxy* peerProxy) { | |
| int socket = peerProxy->currentPacket->socket; | |
| // Why does sending and receiving fds have to be such a PITA? | |
| struct msghdr msg; | |
| struct iovec iov[1]; | |
| union { | |
| struct cmsghdr cm; | |
| char control[CMSG_SPACE(sizeof(int))]; | |
| } control_un; | |
| struct cmsghdr *cmptr; | |
| msg.msg_control = control_un.control; | |
| msg.msg_controllen = sizeof(control_un.control); | |
| cmptr = CMSG_FIRSTHDR(&msg); | |
| cmptr->cmsg_len = CMSG_LEN(sizeof(int)); | |
| cmptr->cmsg_level = SOL_SOCKET; | |
| cmptr->cmsg_type = SCM_RIGHTS; | |
| // Store the socket in the message. | |
| *((int *) CMSG_DATA(cmptr)) = peerProxy->currentPacket->socket; | |
| msg.msg_name = NULL; | |
| msg.msg_namelen = 0; | |
| iov[0].iov_base = ""; | |
| iov[0].iov_len = 1; | |
| msg.msg_iov = iov; | |
| msg.msg_iovlen = 1; | |
| ssize_t result = sendmsg(peerProxy->fd->fd, &msg, 0); | |
| if (result < 0) { | |
| peerProxyHandleError(peerProxy, "sendmsg"); | |
| } else { | |
| // Success. Queue up the next packet. | |
| peerProxyNextPacket(peerProxy); | |
| } | |
| } | |
| /** | |
| * Writes some outgoing data. | |
| */ | |
| static void peerProxyWrite(SelectableFd* fd) { | |
| // TODO: Try to write header and body with one system call. | |
| PeerProxy* peerProxy = (PeerProxy*) fd->data; | |
| OutgoingPacket* current = peerProxy->currentPacket; | |
| if (current == NULL) { | |
| // We have nothing left to write. | |
| return; | |
| } | |
| // Write the header. | |
| Buffer* outgoingHeader = &peerProxy->outgoingHeader; | |
| bool headerWritten = bufferWriteComplete(outgoingHeader); | |
| if (!headerWritten) { | |
| ALOGD("Writing header..."); | |
| headerWritten = peerProxyWriteFromBuffer(peerProxy, outgoingHeader); | |
| if (headerWritten) { | |
| ALOGD("Header written."); | |
| } | |
| } | |
| // Write body. | |
| if (headerWritten) { | |
| PacketType type = current->header.type; | |
| switch (type) { | |
| case CONNECTION: | |
| peerProxyWriteConnection(peerProxy); | |
| break; | |
| case BYTES: | |
| peerProxyWriteBytes(peerProxy); | |
| break; | |
| case CONNECTION_REQUEST: | |
| case CONNECTION_ERROR: | |
| // These packets consist solely of a header. | |
| peerProxyNextPacket(peerProxy); | |
| break; | |
| default: | |
| LOG_ALWAYS_FATAL("Unknown packet type: %d", type); | |
| } | |
| } | |
| } | |
| /** | |
| * Sets up a peer proxy's fd before we try to select() it. | |
| */ | |
| static void peerProxyBeforeSelect(SelectableFd* fd) { | |
| ALOGD("Before select..."); | |
| PeerProxy* peerProxy = (PeerProxy*) fd->data; | |
| peerLock(peerProxy->peer); | |
| bool hasPackets = peerProxy->currentPacket != NULL; | |
| peerUnlock(peerProxy->peer); | |
| if (hasPackets) { | |
| ALOGD("Packets found. Setting onWritable()."); | |
| fd->onWritable = &peerProxyWrite; | |
| } else { | |
| // We have nothing to write. | |
| fd->onWritable = NULL; | |
| } | |
| } | |
| /** Prepare to read bytes from the peer. */ | |
| static void peerProxyExpectBytes(PeerProxy* peerProxy, Header* header) { | |
| ALOGD("Expecting %d bytes.", header->size); | |
| peerProxy->inputState = READING_BYTES; | |
| if (bufferPrepareForRead(peerProxy->inputBuffer, header->size) == -1) { | |
| ALOGW("Couldn't allocate memory for incoming data. Size: %u", | |
| (unsigned int) header->size); | |
| // TODO: Ignore the packet and log a warning? | |
| peerProxyKill(peerProxy, false); | |
| } | |
| } | |
| /** | |
| * Gets a peer proxy for the given ID. Creates a peer proxy if necessary. | |
| * Sends a connection request to the master if desired. | |
| * | |
| * Returns NULL if an error occurs. Sets errno to EHOSTDOWN if the peer died | |
| * or ENOMEM if memory couldn't be allocated. | |
| */ | |
| static PeerProxy* peerProxyGetOrCreate(Peer* peer, pid_t pid, | |
| bool requestConnection) { | |
| if (pid == peer->pid) { | |
| errno = EINVAL; | |
| return NULL; | |
| } | |
| if (peerIsDead(peer, pid)) { | |
| errno = EHOSTDOWN; | |
| return NULL; | |
| } | |
| PeerProxy* peerProxy = hashmapGet(peer->peerProxies, &pid); | |
| if (peerProxy != NULL) { | |
| return peerProxy; | |
| } | |
| // If this is the master peer, we already know about all peers. | |
| if (peer->master) { | |
| errno = EHOSTDOWN; | |
| return NULL; | |
| } | |
| // Try to create a peer proxy. | |
| Credentials credentials; | |
| credentials.pid = pid; | |
| // Fake gid and uid until we have the real thing. The real creds are | |
| // filled in by masterProxyExpectConnection(). These fake creds will | |
| // never be exposed to the user. | |
| credentials.uid = 0; | |
| credentials.gid = 0; | |
| // Make sure we can allocate the connection request packet. | |
| OutgoingPacket* packet = NULL; | |
| if (requestConnection) { | |
| packet = calloc(1, sizeof(OutgoingPacket)); | |
| if (packet == NULL) { | |
| errno = ENOMEM; | |
| return NULL; | |
| } | |
| packet->header.type = CONNECTION_REQUEST; | |
| packet->header.credentials = credentials; | |
| packet->free = &outgoingPacketFree; | |
| } | |
| peerProxy = peerProxyCreate(peer, credentials); | |
| if (peerProxy == NULL) { | |
| free(packet); | |
| errno = ENOMEM; | |
| return NULL; | |
| } else { | |
| // Send a connection request to the master. | |
| if (requestConnection) { | |
| PeerProxy* masterProxy = peer->masterProxy; | |
| peerProxyEnqueueOutgoingPacket(masterProxy, packet); | |
| } | |
| return peerProxy; | |
| } | |
| } | |
| /** | |
| * Switches the master peer proxy into a state where it's waiting for a | |
| * connection from the master. | |
| */ | |
| static void masterProxyExpectConnection(PeerProxy* masterProxy, | |
| Header* header) { | |
| // TODO: Restructure things so we don't need this check. | |
| // Verify that this really is the master. | |
| if (!masterProxy->master) { | |
| ALOGW("Non-master process %d tried to send us a connection.", | |
| masterProxy->credentials.pid); | |
| // Kill off the evil peer. | |
| peerProxyKill(masterProxy, false); | |
| return; | |
| } | |
| masterProxy->inputState = ACCEPTING_CONNECTION; | |
| Peer* localPeer = masterProxy->peer; | |
| // Create a peer proxy so we have somewhere to stash the creds. | |
| // See if we already have a proxy set up. | |
| pid_t pid = header->credentials.pid; | |
| peerLock(localPeer); | |
| PeerProxy* peerProxy = peerProxyGetOrCreate(localPeer, pid, false); | |
| if (peerProxy == NULL) { | |
| ALOGW("Peer proxy creation failed: %s", strerror(errno)); | |
| } else { | |
| // Fill in full credentials. | |
| peerProxy->credentials = header->credentials; | |
| } | |
| peerUnlock(localPeer); | |
| // Keep track of which peer proxy we're accepting a connection for. | |
| masterProxy->connecting = peerProxy; | |
| } | |
| /** | |
| * Reads input from a peer process. | |
| */ | |
| static void peerProxyRead(SelectableFd* fd); | |
| /** Sets up fd callbacks. */ | |
| static void peerProxySetFd(PeerProxy* peerProxy, SelectableFd* fd) { | |
| peerProxy->fd = fd; | |
| fd->data = peerProxy; | |
| fd->onReadable = &peerProxyRead; | |
| fd->beforeSelect = &peerProxyBeforeSelect; | |
| // Make the socket non-blocking. | |
| setNonBlocking(fd->fd); | |
| } | |
| /** | |
| * Accepts a connection sent by the master proxy. | |
| */ | |
| static void masterProxyAcceptConnection(PeerProxy* masterProxy) { | |
| struct msghdr msg; | |
| struct iovec iov[1]; | |
| ssize_t size; | |
| char ignored; | |
| int incomingFd; | |
| // TODO: Reuse code which writes the connection. Who the heck designed | |
| // this API anyway? | |
| union { | |
| struct cmsghdr cm; | |
| char control[CMSG_SPACE(sizeof(int))]; | |
| } control_un; | |
| struct cmsghdr *cmptr; | |
| msg.msg_control = control_un.control; | |
| msg.msg_controllen = sizeof(control_un.control); | |
| msg.msg_name = NULL; | |
| msg.msg_namelen = 0; | |
| // We sent 1 byte of data so we can detect EOF. | |
| iov[0].iov_base = &ignored; | |
| iov[0].iov_len = 1; | |
| msg.msg_iov = iov; | |
| msg.msg_iovlen = 1; | |
| size = recvmsg(masterProxy->fd->fd, &msg, 0); | |
| if (size < 0) { | |
| if (errno == EINTR) { | |
| // Log interruptions but otherwise ignore them. | |
| ALOGW("recvmsg() interrupted."); | |
| return; | |
| } else if (errno == EAGAIN) { | |
| // Keep waiting for the connection. | |
| return; | |
| } else { | |
| LOG_ALWAYS_FATAL("Error reading connection from master: %s", | |
| strerror(errno)); | |
| } | |
| } else if (size == 0) { | |
| // EOF. | |
| LOG_ALWAYS_FATAL("Received EOF from master."); | |
| } | |
| // Extract fd from message. | |
| if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL | |
| && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) { | |
| if (cmptr->cmsg_level != SOL_SOCKET) { | |
| LOG_ALWAYS_FATAL("Expected SOL_SOCKET."); | |
| } | |
| if (cmptr->cmsg_type != SCM_RIGHTS) { | |
| LOG_ALWAYS_FATAL("Expected SCM_RIGHTS."); | |
| } | |
| incomingFd = *((int*) CMSG_DATA(cmptr)); | |
| } else { | |
| LOG_ALWAYS_FATAL("Expected fd."); | |
| } | |
| // The peer proxy this connection is for. | |
| PeerProxy* peerProxy = masterProxy->connecting; | |
| if (peerProxy == NULL) { | |
| ALOGW("Received connection for unknown peer."); | |
| closeWithWarning(incomingFd); | |
| } else { | |
| Peer* peer = masterProxy->peer; | |
| SelectableFd* selectableFd = selectorAdd(peer->selector, incomingFd); | |
| if (selectableFd == NULL) { | |
| ALOGW("Error adding fd to selector for %d.", | |
| peerProxy->credentials.pid); | |
| closeWithWarning(incomingFd); | |
| peerProxyKill(peerProxy, false); | |
| } | |
| peerProxySetFd(peerProxy, selectableFd); | |
| } | |
| peerProxyExpectHeader(masterProxy); | |
| } | |
| /** | |
| * Frees an outgoing packet containing a connection. | |
| */ | |
| static void outgoingPacketFreeSocket(OutgoingPacket* packet) { | |
| closeWithWarning(packet->socket); | |
| outgoingPacketFree(packet); | |
| } | |
| /** | |
| * Connects two known peers. | |
| */ | |
| static void masterConnectPeers(PeerProxy* peerA, PeerProxy* peerB) { | |
| int sockets[2]; | |
| int result = socketpair(AF_LOCAL, SOCK_STREAM, 0, sockets); | |
| if (result == -1) { | |
| ALOGW("socketpair() error: %s", strerror(errno)); | |
| // TODO: Send CONNECTION_FAILED packets to peers. | |
| return; | |
| } | |
| OutgoingPacket* packetA = calloc(1, sizeof(OutgoingPacket)); | |
| OutgoingPacket* packetB = calloc(1, sizeof(OutgoingPacket)); | |
| if (packetA == NULL || packetB == NULL) { | |
| free(packetA); | |
| free(packetB); | |
| ALOGW("malloc() error. Failed to tell process %d that process %d is" | |
| " dead.", peerA->credentials.pid, peerB->credentials.pid); | |
| return; | |
| } | |
| packetA->header.type = CONNECTION; | |
| packetB->header.type = CONNECTION; | |
| packetA->header.credentials = peerB->credentials; | |
| packetB->header.credentials = peerA->credentials; | |
| packetA->socket = sockets[0]; | |
| packetB->socket = sockets[1]; | |
| packetA->free = &outgoingPacketFreeSocket; | |
| packetB->free = &outgoingPacketFreeSocket; | |
| peerLock(peerA->peer); | |
| peerProxyEnqueueOutgoingPacket(peerA, packetA); | |
| peerProxyEnqueueOutgoingPacket(peerB, packetB); | |
| peerUnlock(peerA->peer); | |
| } | |
| /** | |
| * Informs a peer that the peer they're trying to connect to couldn't be | |
| * found. | |
| */ | |
| static void masterReportConnectionError(PeerProxy* peerProxy, | |
| Credentials credentials) { | |
| OutgoingPacket* packet = calloc(1, sizeof(OutgoingPacket)); | |
| if (packet == NULL) { | |
| ALOGW("malloc() error. Failed to tell process %d that process %d is" | |
| " dead.", peerProxy->credentials.pid, credentials.pid); | |
| return; | |
| } | |
| packet->header.type = CONNECTION_ERROR; | |
| packet->header.credentials = credentials; | |
| packet->free = &outgoingPacketFree; | |
| peerProxyLockAndEnqueueOutgoingPacket(peerProxy, packet); | |
| } | |
| /** | |
| * Handles a request to be connected to another peer. | |
| */ | |
| static void masterHandleConnectionRequest(PeerProxy* peerProxy, | |
| Header* header) { | |
| Peer* master = peerProxy->peer; | |
| pid_t targetPid = header->credentials.pid; | |
| if (!hashmapContainsKey(peerProxy->connections, &targetPid)) { | |
| // We haven't connected these peers yet. | |
| PeerProxy* targetPeer | |
| = (PeerProxy*) hashmapGet(master->peerProxies, &targetPid); | |
| if (targetPeer == NULL) { | |
| // Unknown process. | |
| masterReportConnectionError(peerProxy, header->credentials); | |
| } else { | |
| masterConnectPeers(peerProxy, targetPeer); | |
| } | |
| } | |
| // This packet is complete. Get ready for the next one. | |
| peerProxyExpectHeader(peerProxy); | |
| } | |
| /** | |
| * The master told us this peer is dead. | |
| */ | |
| static void masterProxyHandleConnectionError(PeerProxy* masterProxy, | |
| Header* header) { | |
| Peer* peer = masterProxy->peer; | |
| // Look up the peer proxy. | |
| pid_t pid = header->credentials.pid; | |
| PeerProxy* peerProxy = NULL; | |
| peerLock(peer); | |
| peerProxy = hashmapGet(peer->peerProxies, &pid); | |
| peerUnlock(peer); | |
| if (peerProxy != NULL) { | |
| ALOGI("Couldn't connect to %d.", pid); | |
| peerProxyKill(peerProxy, false); | |
| } else { | |
| ALOGW("Peer proxy for %d not found. This shouldn't happen.", pid); | |
| } | |
| peerProxyExpectHeader(masterProxy); | |
| } | |
| /** | |
| * Handles a packet header. | |
| */ | |
| static void peerProxyHandleHeader(PeerProxy* peerProxy, Header* header) { | |
| switch (header->type) { | |
| case CONNECTION_REQUEST: | |
| masterHandleConnectionRequest(peerProxy, header); | |
| break; | |
| case CONNECTION: | |
| masterProxyExpectConnection(peerProxy, header); | |
| break; | |
| case CONNECTION_ERROR: | |
| masterProxyHandleConnectionError(peerProxy, header); | |
| break; | |
| case BYTES: | |
| peerProxyExpectBytes(peerProxy, header); | |
| break; | |
| default: | |
| ALOGW("Invalid packet type from %d: %d", peerProxy->credentials.pid, | |
| header->type); | |
| peerProxyKill(peerProxy, false); | |
| } | |
| } | |
| /** | |
| * Buffers input sent by peer. May be called multiple times until the entire | |
| * buffer is filled. Returns true when the buffer is full. | |
| */ | |
| static bool peerProxyBufferInput(PeerProxy* peerProxy) { | |
| Buffer* in = peerProxy->inputBuffer; | |
| ssize_t size = bufferRead(in, peerProxy->fd->fd); | |
| if (size < 0) { | |
| peerProxyHandleError(peerProxy, "read"); | |
| return false; | |
| } else if (size == 0) { | |
| // EOF. | |
| ALOGI("EOF"); | |
| peerProxyKill(peerProxy, false); | |
| return false; | |
| } else if (bufferReadComplete(in)) { | |
| // We're done! | |
| return true; | |
| } else { | |
| // Continue reading. | |
| return false; | |
| } | |
| } | |
| /** | |
| * Reads input from a peer process. | |
| */ | |
| static void peerProxyRead(SelectableFd* fd) { | |
| ALOGD("Reading..."); | |
| PeerProxy* peerProxy = (PeerProxy*) fd->data; | |
| int state = peerProxy->inputState; | |
| Buffer* in = peerProxy->inputBuffer; | |
| switch (state) { | |
| case READING_HEADER: | |
| if (peerProxyBufferInput(peerProxy)) { | |
| ALOGD("Header read."); | |
| // We've read the complete header. | |
| Header* header = (Header*) in->data; | |
| peerProxyHandleHeader(peerProxy, header); | |
| } | |
| break; | |
| case READING_BYTES: | |
| ALOGD("Reading bytes..."); | |
| if (peerProxyBufferInput(peerProxy)) { | |
| ALOGD("Bytes read."); | |
| // We have the complete packet. Notify bytes listener. | |
| peerProxy->peer->onBytes(peerProxy->credentials, | |
| in->data, in->size); | |
| // Get ready for the next packet. | |
| peerProxyExpectHeader(peerProxy); | |
| } | |
| break; | |
| case ACCEPTING_CONNECTION: | |
| masterProxyAcceptConnection(peerProxy); | |
| break; | |
| default: | |
| LOG_ALWAYS_FATAL("Unknown state: %d", state); | |
| } | |
| } | |
| static PeerProxy* peerProxyCreate(Peer* peer, Credentials credentials) { | |
| PeerProxy* peerProxy = calloc(1, sizeof(PeerProxy)); | |
| if (peerProxy == NULL) { | |
| return NULL; | |
| } | |
| peerProxy->inputBuffer = bufferCreate(sizeof(Header)); | |
| if (peerProxy->inputBuffer == NULL) { | |
| free(peerProxy); | |
| return NULL; | |
| } | |
| peerProxy->peer = peer; | |
| peerProxy->credentials = credentials; | |
| // Initial state == expecting a header. | |
| peerProxyExpectHeader(peerProxy); | |
| // Add this proxy to the map. Make sure the key points to the stable memory | |
| // inside of the peer proxy itself. | |
| pid_t* pid = &(peerProxy->credentials.pid); | |
| hashmapPut(peer->peerProxies, pid, peerProxy); | |
| return peerProxy; | |
| } | |
| /** Accepts a connection to the master peer. */ | |
| static void masterAcceptConnection(SelectableFd* listenerFd) { | |
| // Accept connection. | |
| int socket = accept(listenerFd->fd, NULL, NULL); | |
| if (socket == -1) { | |
| ALOGW("accept() error: %s", strerror(errno)); | |
| return; | |
| } | |
| ALOGD("Accepted connection as fd %d.", socket); | |
| // Get credentials. | |
| Credentials credentials; | |
| struct ucred ucredentials; | |
| socklen_t credentialsSize = sizeof(struct ucred); | |
| int result = getsockopt(socket, SOL_SOCKET, SO_PEERCRED, | |
| &ucredentials, &credentialsSize); | |
| // We might want to verify credentialsSize. | |
| if (result == -1) { | |
| ALOGW("getsockopt() error: %s", strerror(errno)); | |
| closeWithWarning(socket); | |
| return; | |
| } | |
| // Copy values into our own structure so we know we have the types right. | |
| credentials.pid = ucredentials.pid; | |
| credentials.uid = ucredentials.uid; | |
| credentials.gid = ucredentials.gid; | |
| ALOGI("Accepted connection from process %d.", credentials.pid); | |
| Peer* masterPeer = (Peer*) listenerFd->data; | |
| peerLock(masterPeer); | |
| // Make sure we don't already have a connection from that process. | |
| PeerProxy* peerProxy | |
| = hashmapGet(masterPeer->peerProxies, &credentials.pid); | |
| if (peerProxy != NULL) { | |
| peerUnlock(masterPeer); | |
| ALOGW("Alread connected to process %d.", credentials.pid); | |
| closeWithWarning(socket); | |
| return; | |
| } | |
| // Add connection to the selector. | |
| SelectableFd* socketFd = selectorAdd(masterPeer->selector, socket); | |
| if (socketFd == NULL) { | |
| peerUnlock(masterPeer); | |
| ALOGW("malloc() failed."); | |
| closeWithWarning(socket); | |
| return; | |
| } | |
| // Create a peer proxy. | |
| peerProxy = peerProxyCreate(masterPeer, credentials); | |
| peerUnlock(masterPeer); | |
| if (peerProxy == NULL) { | |
| ALOGW("malloc() failed."); | |
| socketFd->remove = true; | |
| closeWithWarning(socket); | |
| } | |
| peerProxy->connections = hashmapCreate(10, &pidHash, &pidEquals); | |
| peerProxySetFd(peerProxy, socketFd); | |
| } | |
| /** | |
| * Creates the local peer. | |
| */ | |
| static Peer* peerCreate() { | |
| Peer* peer = calloc(1, sizeof(Peer)); | |
| if (peer == NULL) { | |
| LOG_ALWAYS_FATAL("malloc() error."); | |
| } | |
| peer->peerProxies = hashmapCreate(10, &pidHash, &pidEquals); | |
| peer->selector = selectorCreate(); | |
| pthread_mutexattr_t attributes; | |
| if (pthread_mutexattr_init(&attributes) != 0) { | |
| LOG_ALWAYS_FATAL("pthread_mutexattr_init() error."); | |
| } | |
| if (pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE) != 0) { | |
| LOG_ALWAYS_FATAL("pthread_mutexattr_settype() error."); | |
| } | |
| if (pthread_mutex_init(&peer->mutex, &attributes) != 0) { | |
| LOG_ALWAYS_FATAL("pthread_mutex_init() error."); | |
| } | |
| peer->pid = getpid(); | |
| return peer; | |
| } | |
| /** The local peer. */ | |
| static Peer* localPeer; | |
| /** Frees a packet of bytes. */ | |
| static void outgoingPacketFreeBytes(OutgoingPacket* packet) { | |
| ALOGD("Freeing outgoing packet."); | |
| bufferFree(packet->bytes); | |
| free(packet); | |
| } | |
| /** | |
| * Sends a packet of bytes to a remote peer. Returns 0 on success. | |
| * | |
| * Returns -1 if an error occurs. Sets errno to ENOMEM if memory couldn't be | |
| * allocated. Sets errno to EHOSTDOWN if the peer died recently. Sets errno | |
| * to EINVAL if pid is the same as the local pid. | |
| */ | |
| int peerSendBytes(pid_t pid, const char* bytes, size_t size) { | |
| Peer* peer = localPeer; | |
| assert(peer != NULL); | |
| OutgoingPacket* packet = calloc(1, sizeof(OutgoingPacket)); | |
| if (packet == NULL) { | |
| errno = ENOMEM; | |
| return -1; | |
| } | |
| Buffer* copy = bufferCreate(size); | |
| if (copy == NULL) { | |
| free(packet); | |
| errno = ENOMEM; | |
| return -1; | |
| } | |
| // Copy data. | |
| memcpy(copy->data, bytes, size); | |
| copy->size = size; | |
| packet->bytes = copy; | |
| packet->header.type = BYTES; | |
| packet->header.size = size; | |
| packet->free = outgoingPacketFreeBytes; | |
| bufferPrepareForWrite(packet->bytes); | |
| peerLock(peer); | |
| PeerProxy* peerProxy = peerProxyGetOrCreate(peer, pid, true); | |
| if (peerProxy == NULL) { | |
| // The peer is already dead or we couldn't alloc memory. Either way, | |
| // errno is set. | |
| peerUnlock(peer); | |
| packet->free(packet); | |
| return -1; | |
| } else { | |
| peerProxyEnqueueOutgoingPacket(peerProxy, packet); | |
| peerUnlock(peer); | |
| selectorWakeUp(peer->selector); | |
| return 0; | |
| } | |
| } | |
| /** Keeps track of how to free shared bytes. */ | |
| typedef struct { | |
| void (*free)(void* context); | |
| void* context; | |
| } SharedBytesFreer; | |
| /** Frees shared bytes. */ | |
| static void outgoingPacketFreeSharedBytes(OutgoingPacket* packet) { | |
| SharedBytesFreer* sharedBytesFreer | |
| = (SharedBytesFreer*) packet->context; | |
| sharedBytesFreer->free(sharedBytesFreer->context); | |
| free(sharedBytesFreer); | |
| free(packet); | |
| } | |
| /** | |
| * Sends a packet of bytes to a remote peer without copying the bytes. Calls | |
| * free() with context after the bytes have been sent. | |
| * | |
| * Returns -1 if an error occurs. Sets errno to ENOMEM if memory couldn't be | |
| * allocated. Sets errno to EHOSTDOWN if the peer died recently. Sets errno | |
| * to EINVAL if pid is the same as the local pid. | |
| */ | |
| int peerSendSharedBytes(pid_t pid, char* bytes, size_t size, | |
| void (*free)(void* context), void* context) { | |
| Peer* peer = localPeer; | |
| assert(peer != NULL); | |
| OutgoingPacket* packet = calloc(1, sizeof(OutgoingPacket)); | |
| if (packet == NULL) { | |
| errno = ENOMEM; | |
| return -1; | |
| } | |
| Buffer* wrapper = bufferWrap(bytes, size, size); | |
| if (wrapper == NULL) { | |
| free(packet); | |
| errno = ENOMEM; | |
| return -1; | |
| } | |
| SharedBytesFreer* sharedBytesFreer = malloc(sizeof(SharedBytesFreer)); | |
| if (sharedBytesFreer == NULL) { | |
| free(packet); | |
| free(wrapper); | |
| errno = ENOMEM; | |
| return -1; | |
| } | |
| sharedBytesFreer->free = free; | |
| sharedBytesFreer->context = context; | |
| packet->bytes = wrapper; | |
| packet->context = sharedBytesFreer; | |
| packet->header.type = BYTES; | |
| packet->header.size = size; | |
| packet->free = &outgoingPacketFreeSharedBytes; | |
| bufferPrepareForWrite(packet->bytes); | |
| peerLock(peer); | |
| PeerProxy* peerProxy = peerProxyGetOrCreate(peer, pid, true); | |
| if (peerProxy == NULL) { | |
| // The peer is already dead or we couldn't alloc memory. Either way, | |
| // errno is set. | |
| peerUnlock(peer); | |
| packet->free(packet); | |
| return -1; | |
| } else { | |
| peerProxyEnqueueOutgoingPacket(peerProxy, packet); | |
| peerUnlock(peer); | |
| selectorWakeUp(peer->selector); | |
| return 0; | |
| } | |
| } | |
| /** | |
| * Starts the master peer. The master peer differs from other peers in that | |
| * it is responsible for connecting the other peers. You can only have one | |
| * master peer. | |
| * | |
| * Goes into an I/O loop and does not return. | |
| */ | |
| void masterPeerInitialize(BytesListener* bytesListener, | |
| DeathListener* deathListener) { | |
| // Create and bind socket. | |
| int listenerSocket = socket(AF_LOCAL, SOCK_STREAM, 0); | |
| if (listenerSocket == -1) { | |
| LOG_ALWAYS_FATAL("socket() error: %s", strerror(errno)); | |
| } | |
| unlink(MASTER_PATH); | |
| int result = bind(listenerSocket, (SocketAddress*) getMasterAddress(), | |
| sizeof(UnixAddress)); | |
| if (result == -1) { | |
| LOG_ALWAYS_FATAL("bind() error: %s", strerror(errno)); | |
| } | |
| ALOGD("Listener socket: %d", listenerSocket); | |
| // Queue up to 16 connections. | |
| result = listen(listenerSocket, 16); | |
| if (result != 0) { | |
| LOG_ALWAYS_FATAL("listen() error: %s", strerror(errno)); | |
| } | |
| // Make socket non-blocking. | |
| setNonBlocking(listenerSocket); | |
| // Create the peer for this process. Fail if we already have one. | |
| if (localPeer != NULL) { | |
| LOG_ALWAYS_FATAL("Peer is already initialized."); | |
| } | |
| localPeer = peerCreate(); | |
| if (localPeer == NULL) { | |
| LOG_ALWAYS_FATAL("malloc() failed."); | |
| } | |
| localPeer->master = true; | |
| localPeer->onBytes = bytesListener; | |
| localPeer->onDeath = deathListener; | |
| // Make listener socket selectable. | |
| SelectableFd* listenerFd = selectorAdd(localPeer->selector, listenerSocket); | |
| if (listenerFd == NULL) { | |
| LOG_ALWAYS_FATAL("malloc() error."); | |
| } | |
| listenerFd->data = localPeer; | |
| listenerFd->onReadable = &masterAcceptConnection; | |
| } | |
| /** | |
| * Starts a local peer. | |
| * | |
| * Goes into an I/O loop and does not return. | |
| */ | |
| void peerInitialize(BytesListener* bytesListener, | |
| DeathListener* deathListener) { | |
| // Connect to master peer. | |
| int masterSocket = socket(AF_LOCAL, SOCK_STREAM, 0); | |
| if (masterSocket == -1) { | |
| LOG_ALWAYS_FATAL("socket() error: %s", strerror(errno)); | |
| } | |
| int result = connect(masterSocket, (SocketAddress*) getMasterAddress(), | |
| sizeof(UnixAddress)); | |
| if (result != 0) { | |
| LOG_ALWAYS_FATAL("connect() error: %s", strerror(errno)); | |
| } | |
| // Create the peer for this process. Fail if we already have one. | |
| if (localPeer != NULL) { | |
| LOG_ALWAYS_FATAL("Peer is already initialized."); | |
| } | |
| localPeer = peerCreate(); | |
| if (localPeer == NULL) { | |
| LOG_ALWAYS_FATAL("malloc() failed."); | |
| } | |
| localPeer->onBytes = bytesListener; | |
| localPeer->onDeath = deathListener; | |
| // Make connection selectable. | |
| SelectableFd* masterFd = selectorAdd(localPeer->selector, masterSocket); | |
| if (masterFd == NULL) { | |
| LOG_ALWAYS_FATAL("malloc() error."); | |
| } | |
| // Create a peer proxy for the master peer. | |
| PeerProxy* masterProxy = peerProxyCreate(localPeer, MASTER_CREDENTIALS); | |
| if (masterProxy == NULL) { | |
| LOG_ALWAYS_FATAL("malloc() error."); | |
| } | |
| peerProxySetFd(masterProxy, masterFd); | |
| masterProxy->master = true; | |
| localPeer->masterProxy = masterProxy; | |
| } | |
| /** Starts the master peer I/O loop. Doesn't return. */ | |
| void peerLoop() { | |
| assert(localPeer != NULL); | |
| // Start selector. | |
| selectorLoop(localPeer->selector); | |
| } | |