Pivot source files into top-level src,include and unify test target
See discussion in go/perfetto-build-files .
This is to disambiguate things like
#include "base/logging.h"
when in the chrome tree.
Also this CL unifies the test targets into two monolithic targets:
perfetto_tests and perfetto_benchmarks. This is to avoid ending
up with confusing binary names in the chrome tree (e.g.,
ipc_unittests)
Bug: 68710794
Change-Id: I1768e15b661406052b2be060d7aab0f1e7443a98
diff --git a/src/ipc/unix_socket.cc b/src/ipc/unix_socket.cc
new file mode 100644
index 0000000..e28f3dc
--- /dev/null
+++ b/src/ipc/unix_socket.cc
@@ -0,0 +1,450 @@
+/*
+ * 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.
+ */
+
+#include "src/ipc/unix_socket.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/task_runner.h"
+#include "perfetto/base/utils.h"
+
+#if BUILDFLAG(OS_MACOSX)
+#include <sys/ucred.h>
+#endif
+
+namespace perfetto {
+namespace ipc {
+
+// TODO(primiano): Add ThreadChecker to methods of this class.
+
+namespace {
+
+// MSG_NOSIGNAL is not supported on Mac OS X, but in that case the socket is
+// created with SO_NOSIGPIPE (See InitializeSocket()).
+#if BUILDFLAG(OS_MACOSX)
+constexpr int kNoSigPipe = 0;
+#else
+constexpr int kNoSigPipe = MSG_NOSIGNAL;
+#endif
+
+// Android takes an int instead of socklen_t for the control buffer size.
+#if BUILDFLAG(OS_ANDROID)
+using CBufLenType = size_t;
+#else
+using CBufLenType = socklen_t;
+#endif
+
+bool MakeSockAddr(const std::string& socket_name,
+ sockaddr_un* addr,
+ socklen_t* addr_size) {
+ memset(addr, 0, sizeof(*addr));
+ const size_t name_len = socket_name.size();
+ if (name_len >= sizeof(addr->sun_path)) {
+ errno = ENAMETOOLONG;
+ return false;
+ }
+ memcpy(addr->sun_path, socket_name.data(), name_len);
+ if (addr->sun_path[0] == '@')
+ addr->sun_path[0] = '\0';
+ addr->sun_family = AF_UNIX;
+ *addr_size = static_cast<socklen_t>(
+ __builtin_offsetof(sockaddr_un, sun_path) + name_len + 1);
+ return true;
+}
+
+} // namespace
+
+// static
+std::unique_ptr<UnixSocket> UnixSocket::Listen(const std::string& socket_name,
+ EventListener* event_listener,
+ base::TaskRunner* task_runner) {
+ std::unique_ptr<UnixSocket> sock(new UnixSocket(event_listener, task_runner));
+ sock->DoListen(socket_name);
+ return sock;
+}
+
+// static
+std::unique_ptr<UnixSocket> UnixSocket::Connect(const std::string& socket_name,
+ EventListener* event_listener,
+ base::TaskRunner* task_runner) {
+ std::unique_ptr<UnixSocket> sock(new UnixSocket(event_listener, task_runner));
+ sock->DoConnect(socket_name);
+ return sock;
+}
+
+UnixSocket::UnixSocket(EventListener* event_listener,
+ base::TaskRunner* task_runner)
+ : UnixSocket(event_listener, task_runner, base::ScopedFile()) {}
+
+UnixSocket::UnixSocket(EventListener* event_listener,
+ base::TaskRunner* task_runner,
+ base::ScopedFile adopt_fd)
+ : event_listener_(event_listener),
+ task_runner_(task_runner),
+ weak_ptr_factory_(this) {
+ if (adopt_fd) {
+ // Only in the case of OnNewIncomingConnection().
+ fd_ = std::move(adopt_fd);
+ state_ = State::kConnected;
+ ReadPeerCredentials();
+ } else {
+ fd_.reset(socket(AF_UNIX, SOCK_STREAM, 0));
+ }
+ if (!fd_) {
+ last_error_ = errno;
+ return;
+ }
+
+#if BUILDFLAG(OS_MACOSX)
+ const int no_sigpipe = 1;
+ setsockopt(*fd_, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe));
+#endif
+ // There is no reason why a socket should outlive the process in case of
+ // exec() by default, this is just working around a broken unix design.
+ int fcntl_res = fcntl(*fd_, F_SETFD, FD_CLOEXEC);
+ PERFETTO_CHECK(fcntl_res == 0);
+
+ // Set non-blocking mode.
+ int flags = fcntl(*fd_, F_GETFL, 0);
+ flags |= O_NONBLOCK;
+ fcntl_res = fcntl(fd(), F_SETFL, flags);
+ PERFETTO_CHECK(fcntl_res == 0);
+
+ base::WeakPtr<UnixSocket> weak_ptr = weak_ptr_factory_.GetWeakPtr();
+ task_runner_->AddFileDescriptorWatch(*fd_, [weak_ptr]() {
+ if (weak_ptr)
+ weak_ptr->OnEvent();
+ });
+}
+
+UnixSocket::~UnixSocket() {
+ // The implicit dtor of |weak_ptr_factory_| will no-op pending callbacks.
+ Shutdown();
+}
+
+// Called only by the Listen() static constructor.
+void UnixSocket::DoListen(const std::string& socket_name) {
+ PERFETTO_DCHECK(state_ == State::kDisconnected);
+ if (!fd_)
+ return; // This is the only thing that can gracefully fail in the ctor.
+
+ sockaddr_un addr;
+ socklen_t addr_size;
+ if (!MakeSockAddr(socket_name, &addr, &addr_size)) {
+ last_error_ = errno;
+ return;
+ }
+
+// Android takes an int as 3rd argument of bind() instead of socklen_t.
+#if BUILDFLAG(OS_ANDROID)
+ const int bind_size = static_cast<int>(addr_size);
+#else
+ const socklen_t bind_size = addr_size;
+#endif
+
+ if (bind(*fd_, reinterpret_cast<sockaddr*>(&addr), bind_size)) {
+ last_error_ = errno;
+ PERFETTO_DPLOG("bind()");
+ return;
+ }
+ if (listen(*fd_, SOMAXCONN)) {
+ last_error_ = errno;
+ PERFETTO_DPLOG("listen()");
+ return;
+ }
+
+ last_error_ = 0;
+ state_ = State::kListening;
+}
+
+// Called only by the Connect() static constructor.
+void UnixSocket::DoConnect(const std::string& socket_name) {
+ PERFETTO_DCHECK(state_ == State::kDisconnected);
+
+ // This is the only thing that can gracefully fail in the ctor.
+ if (!fd_)
+ return NotifyConnectionState(false);
+
+ sockaddr_un addr;
+ socklen_t addr_size;
+ if (!MakeSockAddr(socket_name, &addr, &addr_size)) {
+ last_error_ = errno;
+ return NotifyConnectionState(false);
+ }
+
+ int res = PERFETTO_EINTR(
+ connect(*fd_, reinterpret_cast<sockaddr*>(&addr), addr_size));
+ if (res && errno != EINPROGRESS) {
+ last_error_ = errno;
+ return NotifyConnectionState(false);
+ }
+
+ // At this point either |res| == 0 (the connect() succeeded) or started
+ // asynchronously (EINPROGRESS).
+ last_error_ = 0;
+ state_ = State::kConnecting;
+
+ // Even if the socket is non-blocking, connecting to a UNIX socket can be
+ // acknowledged straight away rather than returning EINPROGRESS. In this case
+ // just trigger an OnEvent without waiting for the FD watch. That will poll
+ // the SO_ERROR and evolve the state into either kConnected or kDisconnected.
+ if (res == 0) {
+ base::WeakPtr<UnixSocket> weak_ptr = weak_ptr_factory_.GetWeakPtr();
+ task_runner_->PostTask([weak_ptr]() {
+ if (weak_ptr)
+ weak_ptr->OnEvent();
+ });
+ }
+}
+
+void UnixSocket::ReadPeerCredentials() {
+#if BUILDFLAG(OS_LINUX) || BUILDFLAG(OS_ANDROID)
+ struct ucred user_cred;
+ socklen_t len = sizeof(user_cred);
+ int res = getsockopt(*fd_, SOL_SOCKET, SO_PEERCRED, &user_cred, &len);
+ PERFETTO_CHECK(res == 0);
+ peer_uid_ = user_cred.uid;
+#else
+ struct xucred user_cred;
+ socklen_t len = sizeof(user_cred);
+ int res = getsockopt(*fd_, 0, LOCAL_PEERCRED, &user_cred, &len);
+ PERFETTO_CHECK(res == 0 && user_cred.cr_version == XUCRED_VERSION);
+ peer_uid_ = user_cred.cr_uid;
+#endif
+}
+
+void UnixSocket::OnEvent() {
+ if (state_ == State::kDisconnected)
+ return; // Some spurious event, typically queued just before Shutdown().
+
+ if (state_ == State::kConnected)
+ return event_listener_->OnDataAvailable(this);
+
+ if (state_ == State::kConnecting) {
+ PERFETTO_DCHECK(fd_);
+ int sock_err = EINVAL;
+ socklen_t err_len = sizeof(sock_err);
+ int res = getsockopt(*fd_, SOL_SOCKET, SO_ERROR, &sock_err, &err_len);
+ if (res == 0 && sock_err == EINPROGRESS)
+ return; // Not connected yet, just a spurious FD watch wakeup.
+ if (res == 0 && sock_err == 0) {
+ ReadPeerCredentials();
+ state_ = State::kConnected;
+ return event_listener_->OnConnect(this, true /* connected */);
+ }
+ last_error_ = sock_err;
+ return event_listener_->OnConnect(this, false /* connected */);
+ }
+
+ // New incoming connection.
+ if (state_ == State::kListening) {
+ // There could be more than one incoming connection behind each FD watch
+ // notification. Drain'em all.
+ for (;;) {
+ sockaddr_un cli_addr = {};
+ socklen_t size = sizeof(cli_addr);
+ base::ScopedFile new_fd(PERFETTO_EINTR(
+ accept(*fd_, reinterpret_cast<sockaddr*>(&cli_addr), &size)));
+ if (!new_fd)
+ return;
+ std::unique_ptr<UnixSocket> new_sock(
+ new UnixSocket(event_listener_, task_runner_, std::move(new_fd)));
+ event_listener_->OnNewIncomingConnection(this, std::move(new_sock));
+ }
+ }
+}
+
+bool UnixSocket::Send(const std::string& msg) {
+ return Send(msg.c_str(), msg.size() + 1);
+}
+
+bool UnixSocket::Send(const void* msg, size_t len, int send_fd) {
+ if (state_ != State::kConnected) {
+ errno = last_error_ = ENOTCONN;
+ return false;
+ }
+
+ msghdr msg_hdr = {};
+ iovec iov = {const_cast<void*>(msg), len};
+ msg_hdr.msg_iov = &iov;
+ msg_hdr.msg_iovlen = 1;
+ alignas(cmsghdr) char control_buf[256];
+
+ if (send_fd > -1) {
+ const CBufLenType control_buf_len =
+ static_cast<CBufLenType>(CMSG_SPACE(sizeof(int)));
+ PERFETTO_CHECK(control_buf_len <= sizeof(control_buf));
+ memset(control_buf, 0, sizeof(control_buf));
+ msg_hdr.msg_control = control_buf;
+ msg_hdr.msg_controllen = control_buf_len;
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg_hdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(int));
+ msg_hdr.msg_controllen = cmsg->cmsg_len;
+ }
+
+ const ssize_t sz = PERFETTO_EINTR(sendmsg(*fd_, &msg_hdr, kNoSigPipe));
+ if (sz >= 0) {
+ // There should be no way a non-blocking socket returns < |len|.
+ // If the queueing fails, sendmsg() must return -1 + errno = EWOULDBLOCK.
+ PERFETTO_CHECK(static_cast<size_t>(sz) == len);
+ last_error_ = 0;
+ return true;
+ }
+
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ // A genuine out-of-buffer. The client should retry or give up.
+ // Man pages specify that EAGAIN and EWOULDBLOCK have the same semantic here
+ // and clients should check for both.
+ last_error_ = EAGAIN;
+ return false;
+ }
+
+ // Either the the other endpoint disconnect (ECONNRESET) or some other error
+ // happened.
+ last_error_ = errno;
+ PERFETTO_DPLOG("sendmsg() failed");
+ Shutdown();
+ return false;
+}
+
+void UnixSocket::Shutdown() {
+ base::WeakPtr<UnixSocket> weak_ptr = weak_ptr_factory_.GetWeakPtr();
+ if (state_ == State::kConnected) {
+ task_runner_->PostTask([weak_ptr]() {
+ if (weak_ptr)
+ weak_ptr->event_listener_->OnDisconnect(weak_ptr.get());
+ });
+ } else if (state_ == State::kConnecting) {
+ task_runner_->PostTask([weak_ptr]() {
+ if (weak_ptr)
+ weak_ptr->event_listener_->OnConnect(weak_ptr.get(), false);
+ });
+ }
+ if (fd_) {
+ shutdown(*fd_, SHUT_RDWR);
+ task_runner_->RemoveFileDescriptorWatch(*fd_);
+ fd_.reset();
+ }
+ state_ = State::kDisconnected;
+}
+
+size_t UnixSocket::Receive(void* msg, size_t len, base::ScopedFile* recv_fd) {
+ if (state_ != State::kConnected) {
+ last_error_ = ENOTCONN;
+ return 0;
+ }
+
+ msghdr msg_hdr = {};
+ iovec iov = {msg, len};
+ msg_hdr.msg_iov = &iov;
+ msg_hdr.msg_iovlen = 1;
+ alignas(cmsghdr) char control_buf[256];
+
+ if (recv_fd) {
+ msg_hdr.msg_control = control_buf;
+ msg_hdr.msg_controllen = static_cast<CBufLenType>(CMSG_SPACE(sizeof(int)));
+ PERFETTO_CHECK(msg_hdr.msg_controllen <= sizeof(control_buf));
+ }
+ const ssize_t sz = PERFETTO_EINTR(recvmsg(*fd_, &msg_hdr, kNoSigPipe));
+ if (sz < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ last_error_ = EAGAIN;
+ return 0;
+ }
+ if (sz <= 0) {
+ last_error_ = errno;
+ Shutdown();
+ return 0;
+ }
+ PERFETTO_CHECK(static_cast<size_t>(sz) <= len);
+
+ int* fds = nullptr;
+ uint32_t fds_len = 0;
+
+ if (msg_hdr.msg_controllen > 0) {
+ for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg_hdr); cmsg;
+ cmsg = CMSG_NXTHDR(&msg_hdr, cmsg)) {
+ const size_t payload_len = cmsg->cmsg_len - CMSG_LEN(0);
+ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+ PERFETTO_DCHECK(payload_len % sizeof(int) == 0u);
+ PERFETTO_DCHECK(fds == nullptr);
+ fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ fds_len = static_cast<uint32_t>(payload_len / sizeof(int));
+ }
+ }
+ }
+
+ if (msg_hdr.msg_flags & MSG_TRUNC || msg_hdr.msg_flags & MSG_CTRUNC) {
+ for (size_t i = 0; fds && i < fds_len; ++i)
+ close(fds[i]);
+ last_error_ = EMSGSIZE;
+ Shutdown();
+ return 0;
+ }
+
+ for (size_t i = 0; fds && i < fds_len; ++i) {
+ if (recv_fd && i == 0) {
+ recv_fd->reset(fds[i]);
+ } else {
+ close(fds[i]);
+ }
+ }
+
+ last_error_ = 0;
+ return static_cast<size_t>(sz);
+}
+
+std::string UnixSocket::ReceiveString(size_t max_length) {
+ std::unique_ptr<char[]> buf(new char[max_length + 1]);
+ size_t rsize = Receive(buf.get(), max_length);
+ PERFETTO_CHECK(static_cast<size_t>(rsize) <= max_length);
+ buf[static_cast<size_t>(rsize)] = '\0';
+ return std::string(buf.get());
+}
+
+void UnixSocket::NotifyConnectionState(bool success) {
+ base::WeakPtr<UnixSocket> weak_ptr = weak_ptr_factory_.GetWeakPtr();
+ task_runner_->PostTask([weak_ptr, success]() {
+ if (weak_ptr)
+ weak_ptr->event_listener_->OnConnect(weak_ptr.get(), success);
+ });
+}
+
+UnixSocket::EventListener::~EventListener() {}
+void UnixSocket::EventListener::OnNewIncomingConnection(
+ UnixSocket*,
+ std::unique_ptr<UnixSocket>) {}
+void UnixSocket::EventListener::OnConnect(UnixSocket*, bool) {}
+void UnixSocket::EventListener::OnDisconnect(UnixSocket*) {}
+void UnixSocket::EventListener::OnDataAvailable(UnixSocket*) {}
+
+} // namespace ipc
+} // namespace perfetto