Merge "Add StdioLogger for command-line tools."
diff --git a/adb/Android.bp b/adb/Android.bp
index 99de54e..1f41e4f 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -122,6 +122,7 @@
     "sysdeps_test.cpp",
     "sysdeps/stat_test.cpp",
     "transport_test.cpp",
+    "types_test.cpp",
 ]
 
 cc_library_host_static {
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 76ca19a..f8a54c6 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -885,9 +885,8 @@
     }
 #else /* !defined(_WIN32) */
     // set up a pipe so the child can tell us when it is ready.
-    // fd[0] will be parent's end, and the child will write on fd[1]
-    int fd[2];
-    if (pipe(fd)) {
+    unique_fd pipe_read, pipe_write;
+    if (!Pipe(&pipe_read, &pipe_write)) {
         fprintf(stderr, "pipe failed in launch_server, errno: %d\n", errno);
         return -1;
     }
@@ -899,11 +898,10 @@
 
     if (pid == 0) {
         // child side of the fork
-
-        adb_close(fd[0]);
+        pipe_read.reset();
 
         char reply_fd[30];
-        snprintf(reply_fd, sizeof(reply_fd), "%d", fd[1]);
+        snprintf(reply_fd, sizeof(reply_fd), "%d", pipe_write.get());
         // child process
         int result = execl(path.c_str(), "adb", "-L", socket_spec.c_str(), "fork-server", "server",
                            "--reply-fd", reply_fd, NULL);
@@ -913,10 +911,10 @@
         // parent side of the fork
         char temp[3] = {};
         // wait for the "OK\n" message
-        adb_close(fd[1]);
-        int ret = adb_read(fd[0], temp, 3);
+        pipe_write.reset();
+        int ret = adb_read(pipe_read.get(), temp, 3);
         int saved_errno = errno;
-        adb_close(fd[0]);
+        pipe_read.reset();
         if (ret < 0) {
             fprintf(stderr, "could not read ok from ADB Server, errno = %d\n", saved_errno);
             return -1;
diff --git a/adb/adb_listeners.cpp b/adb/adb_listeners.cpp
index fecf452..ea5a44e 100644
--- a/adb/adb_listeners.cpp
+++ b/adb/adb_listeners.cpp
@@ -42,7 +42,7 @@
     alistener(const std::string& _local_name, const std::string& _connect_to);
     ~alistener();
 
-    fdevent fde;
+    fdevent* fde = nullptr;
     int fd = -1;
 
     std::string local_name;
@@ -60,7 +60,7 @@
 
 alistener::~alistener() {
     // Closes the corresponding fd.
-    fdevent_remove(&fde);
+    fdevent_destroy(fde);
 
     if (transport) {
         transport->RemoveDisconnect(&disconnect);
@@ -222,11 +222,11 @@
 
     close_on_exec(listener->fd);
     if (listener->connect_to == "*smartsocket*") {
-        fdevent_install(&listener->fde, listener->fd, ss_listener_event_func, listener.get());
+        listener->fde = fdevent_create(listener->fd, ss_listener_event_func, listener.get());
     } else {
-        fdevent_install(&listener->fde, listener->fd, listener_event_func, listener.get());
+        listener->fde = fdevent_create(listener->fd, listener_event_func, listener.get());
     }
-    fdevent_set(&listener->fde, FDE_READ);
+    fdevent_set(listener->fde, FDE_READ);
 
     listener->transport = transport;
 
diff --git a/adb/adb_unique_fd.h b/adb/adb_unique_fd.h
index 34c1bbc..7d2354d 100644
--- a/adb/adb_unique_fd.h
+++ b/adb/adb_unique_fd.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <unistd.h>
+
 #include <android-base/unique_fd.h>
 
 // Helper to automatically close an FD when it goes out of scope.
@@ -24,3 +26,42 @@
 };
 
 using unique_fd = android::base::unique_fd_impl<AdbCloser>;
+
+#if !defined(_WIN32)
+inline bool Pipe(unique_fd* read, unique_fd* write, int flags = 0) {
+    int pipefd[2];
+#if !defined(__APPLE__)
+    if (pipe2(pipefd, flags) != 0) {
+        return false;
+    }
+#else
+    // Darwin doesn't have pipe2. Implement it ourselves.
+    if (flags != 0 && (flags & ~(O_CLOEXEC | O_NONBLOCK)) != 0) {
+        errno = EINVAL;
+        return false;
+    }
+
+    if (pipe(pipefd) != 0) {
+        return false;
+    }
+
+    if (flags & O_CLOEXEC) {
+        if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 ||
+            fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) {
+            PLOG(FATAL) << "failed to set FD_CLOEXEC on newly created pipe";
+        }
+    }
+
+    if (flags & O_NONBLOCK) {
+        if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 ||
+            fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) {
+            PLOG(FATAL) << "failed to set O_NONBLOCK  on newly created pipe";
+        }
+    }
+#endif
+
+    read->reset(pipefd[0]);
+    write->reset(pipefd[1]);
+    return true;
+}
+#endif
diff --git a/adb/benchmark_device.py b/adb/benchmark_device.py
new file mode 100755
index 0000000..00c2315
--- /dev/null
+++ b/adb/benchmark_device.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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.
+#
+
+import os
+import statistics
+import time
+
+import adb
+
+def lock_min(device):
+    device.shell_nocheck(["""
+        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
+            echo userspace > $x/scaling_governor
+            cat $x/scaling_min_freq > $x/scaling_setspeed
+        done
+    """])
+
+def lock_max(device):
+    device.shell_nocheck(["""
+        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
+            echo userspace > $x/scaling_governor
+            cat $x/scaling_max_freq > $x/scaling_setspeed
+        done
+    """])
+
+def unlock(device):
+    device.shell_nocheck(["""
+        for x in /sys/devices/system/cpu/cpu?/cpufreq; do
+            echo ondemand > $x/scaling_governor
+            echo sched > $x/scaling_governor
+            echo schedutil > $x/scaling_governor
+        done
+    """])
+
+def harmonic_mean(xs):
+    return 1.0 / statistics.mean([1.0 / x for x in xs])
+
+def analyze(name, speeds):
+    median = statistics.median(speeds)
+    mean = harmonic_mean(speeds)
+    stddev = statistics.stdev(speeds)
+    msg = "%s: %d runs: median %.2f MiB/s, mean %.2f MiB/s, stddev: %.2f MiB/s"
+    print(msg % (name, len(speeds), median, mean, stddev))
+
+def benchmark_push(device=None, file_size_mb=100):
+    if device == None:
+        device = adb.get_device()
+
+    lock_max(device)
+
+    remote_path = "/dev/null"
+    local_path = "/tmp/adb_benchmark_temp"
+
+    with open(local_path, "wb") as f:
+        f.truncate(file_size_mb * 1024 * 1024)
+
+    speeds = list()
+    for _ in range(0, 5):
+        begin = time.time()
+        device.push(local=local_path, remote=remote_path)
+        end = time.time()
+        speeds.append(file_size_mb / float(end - begin))
+
+    analyze("push %dMiB" % file_size_mb, speeds)
+
+def benchmark_pull(device=None, file_size_mb=100):
+    if device == None:
+        device = adb.get_device()
+
+    lock_max(device)
+
+    remote_path = "/data/local/tmp/adb_benchmark_temp"
+    local_path = "/tmp/adb_benchmark_temp"
+
+    device.shell(["dd", "if=/dev/zero", "of=" + remote_path, "bs=1m",
+                  "count=" + str(file_size_mb)])
+    speeds = list()
+    for _ in range(0, 5):
+        begin = time.time()
+        device.pull(remote=remote_path, local=local_path)
+        end = time.time()
+        speeds.append(file_size_mb / float(end - begin))
+
+    analyze("pull %dMiB" % file_size_mb, speeds)
+
+def benchmark_shell(device=None, file_size_mb=100):
+    if device == None:
+        device = adb.get_device()
+
+    lock_max(device)
+
+    speeds = list()
+    for _ in range(0, 5):
+        begin = time.time()
+        device.shell(["dd", "if=/dev/zero", "bs=1m",
+                      "count=" + str(file_size_mb)])
+        end = time.time()
+        speeds.append(file_size_mb / float(end - begin))
+
+    analyze("shell %dMiB" % file_size_mb, speeds)
+
+def main():
+    benchmark_pull()
+
+if __name__ == "__main__":
+    main()
diff --git a/adb/client/transport_mdns.cpp b/adb/client/transport_mdns.cpp
index 3603f09..283fac5 100644
--- a/adb/client/transport_mdns.cpp
+++ b/adb/client/transport_mdns.cpp
@@ -35,7 +35,7 @@
 #include "sysdeps.h"
 
 static DNSServiceRef service_ref;
-static fdevent service_ref_fde;
+static fdevent* service_ref_fde;
 
 // Use adb_DNSServiceRefSockFD() instead of calling DNSServiceRefSockFD()
 // directly so that the socket is put through the appropriate compatibility
@@ -68,27 +68,26 @@
     }
 
     virtual ~AsyncServiceRef() {
-        if (! initialized_) {
+        if (!initialized_) {
             return;
         }
 
         DNSServiceRefDeallocate(sdRef_);
-        fdevent_remove(&fde_);
+        fdevent_destroy(fde_);
     }
 
   protected:
     DNSServiceRef sdRef_;
 
     void Initialize() {
-        fdevent_install(&fde_, adb_DNSServiceRefSockFD(sdRef_),
-                        pump_service_ref, &sdRef_);
-        fdevent_set(&fde_, FDE_READ);
+        fde_ = fdevent_create(adb_DNSServiceRefSockFD(sdRef_), pump_service_ref, &sdRef_);
+        fdevent_set(fde_, FDE_READ);
         initialized_ = true;
     }
 
   private:
-    bool initialized_;
-    fdevent fde_;
+    bool initialized_ = false;
+    fdevent* fde_;
 };
 
 class ResolvedService : public AsyncServiceRef {
@@ -252,14 +251,12 @@
     if (errorCode != kDNSServiceErr_NoError) {
         D("Got error %d during mDNS browse.", errorCode);
         DNSServiceRefDeallocate(sdRef);
-        fdevent_remove(&service_ref_fde);
+        fdevent_destroy(service_ref_fde);
         return;
     }
 
-    auto discovered = new DiscoveredService(interfaceIndex, serviceName,
-                                            regtype, domain);
-
-    if (! discovered->Initialized()) {
+    auto discovered = new DiscoveredService(interfaceIndex, serviceName, regtype, domain);
+    if (!discovered->Initialized()) {
         delete discovered;
     }
 }
@@ -274,9 +271,9 @@
     }
 
     fdevent_run_on_main_thread([]() {
-        fdevent_install(&service_ref_fde, adb_DNSServiceRefSockFD(service_ref), pump_service_ref,
-                        &service_ref);
-        fdevent_set(&service_ref_fde, FDE_READ);
+        service_ref_fde =
+            fdevent_create(adb_DNSServiceRefSockFD(service_ref), pump_service_ref, &service_ref);
+        fdevent_set(service_ref_fde, FDE_READ);
     });
 }
 
diff --git a/adb/daemon/auth.cpp b/adb/daemon/auth.cpp
index 3fd2b31..f0c3629 100644
--- a/adb/daemon/auth.cpp
+++ b/adb/daemon/auth.cpp
@@ -35,8 +35,8 @@
 #include <openssl/rsa.h>
 #include <openssl/sha.h>
 
-static fdevent listener_fde;
-static fdevent framework_fde;
+static fdevent* listener_fde = nullptr;
+static fdevent* framework_fde = nullptr;
 static int framework_fd = -1;
 
 static void usb_disconnected(void* unused, atransport* t);
@@ -106,8 +106,10 @@
 
 static void framework_disconnected() {
     LOG(INFO) << "Framework disconnect";
-    fdevent_remove(&framework_fde);
-    framework_fd = -1;
+    if (framework_fde) {
+        fdevent_destroy(framework_fde);
+        framework_fd = -1;
+    }
 }
 
 static void adbd_auth_event(int fd, unsigned events, void*) {
@@ -168,8 +170,8 @@
     }
 
     framework_fd = s;
-    fdevent_install(&framework_fde, framework_fd, adbd_auth_event, nullptr);
-    fdevent_add(&framework_fde, FDE_READ);
+    framework_fde = fdevent_create(framework_fd, adbd_auth_event, nullptr);
+    fdevent_add(framework_fde, FDE_READ);
 
     if (needs_retry) {
         needs_retry = false;
@@ -198,8 +200,8 @@
         return;
     }
 
-    fdevent_install(&listener_fde, fd, adbd_auth_listener, NULL);
-    fdevent_add(&listener_fde, FDE_READ);
+    listener_fde = fdevent_create(fd, adbd_auth_listener, NULL);
+    fdevent_add(listener_fde, FDE_READ);
 }
 
 void send_auth_request(atransport* t) {
diff --git a/adb/daemon/jdwp_service.cpp b/adb/daemon/jdwp_service.cpp
index 3a3f399..367695d 100644
--- a/adb/daemon/jdwp_service.cpp
+++ b/adb/daemon/jdwp_service.cpp
@@ -139,8 +139,6 @@
             fatal("could not create fdevent for new JDWP process");
         }
 
-        this->fde->state |= FDE_DONT_CLOSE;
-
         /* start by waiting for the PID */
         fdevent_add(this->fde, FDE_READ);
     }
@@ -148,7 +146,6 @@
     ~JdwpProcess() {
         if (this->socket >= 0) {
             adb_shutdown(this->socket);
-            adb_close(this->socket);
             this->socket = -1;
         }
 
diff --git a/adb/fdevent.cpp b/adb/fdevent.cpp
index cf441cf..1b7758c 100644
--- a/adb/fdevent.cpp
+++ b/adb/fdevent.cpp
@@ -57,7 +57,7 @@
 
   explicit PollNode(fdevent* fde) : fde(fde) {
       memset(&pollfd, 0, sizeof(pollfd));
-      pollfd.fd = fde->fd;
+      pollfd.fd = fde->fd.get();
 
 #if defined(__linux__)
       // Always enable POLLRDHUP, so the host server can take action when some clients disconnect.
@@ -111,37 +111,22 @@
     if (fde->state & FDE_ERROR) {
         state += "E";
     }
-    if (fde->state & FDE_DONT_CLOSE) {
-        state += "D";
-    }
-    return android::base::StringPrintf("(fdevent %d %s)", fde->fd, state.c_str());
-}
-
-fdevent* fdevent_create(int fd, fd_func func, void* arg) {
-    check_main_thread();
-    fdevent *fde = (fdevent*) malloc(sizeof(fdevent));
-    if(fde == 0) return 0;
-    fdevent_install(fde, fd, func, arg);
-    fde->state |= FDE_CREATED;
-    return fde;
-}
-
-void fdevent_destroy(fdevent* fde) {
-    check_main_thread();
-    if(fde == 0) return;
-    if(!(fde->state & FDE_CREATED)) {
-        LOG(FATAL) << "destroying fde not created by fdevent_create(): " << dump_fde(fde);
-    }
-    fdevent_remove(fde);
-    free(fde);
+    return android::base::StringPrintf("(fdevent %d %s)", fde->fd.get(), state.c_str());
 }
 
 void fdevent_install(fdevent* fde, int fd, fd_func func, void* arg) {
     check_main_thread();
     CHECK_GE(fd, 0);
     memset(fde, 0, sizeof(fdevent));
+}
+
+fdevent* fdevent_create(int fd, fd_func func, void* arg) {
+    check_main_thread();
+    CHECK_GE(fd, 0);
+
+    fdevent* fde = new fdevent();
     fde->state = FDE_ACTIVE;
-    fde->fd = fd;
+    fde->fd.reset(fd);
     fde->func = func;
     fde->arg = arg;
     if (!set_file_block_mode(fd, false)) {
@@ -150,30 +135,35 @@
         // to handle it.
         LOG(ERROR) << "failed to set non-blocking mode for fd " << fd;
     }
-    auto pair = g_poll_node_map.emplace(fde->fd, PollNode(fde));
+    auto pair = g_poll_node_map.emplace(fde->fd.get(), PollNode(fde));
     CHECK(pair.second) << "install existing fd " << fd;
-    D("fdevent_install %s", dump_fde(fde).c_str());
+
+    fde->state |= FDE_CREATED;
+    return fde;
 }
 
-void fdevent_remove(fdevent* fde) {
+void fdevent_destroy(fdevent* fde) {
     check_main_thread();
-    D("fdevent_remove %s", dump_fde(fde).c_str());
+    if (fde == 0) return;
+    if (!(fde->state & FDE_CREATED)) {
+        LOG(FATAL) << "destroying fde not created by fdevent_create(): " << dump_fde(fde);
+    }
+
     if (fde->state & FDE_ACTIVE) {
-        g_poll_node_map.erase(fde->fd);
+        g_poll_node_map.erase(fde->fd.get());
         if (fde->state & FDE_PENDING) {
             g_pending_list.remove(fde);
         }
-        if (!(fde->state & FDE_DONT_CLOSE)) {
-            adb_close(fde->fd);
-            fde->fd = -1;
-        }
+        fde->fd.reset();
         fde->state = 0;
         fde->events = 0;
     }
+
+    delete fde;
 }
 
 static void fdevent_update(fdevent* fde, unsigned events) {
-    auto it = g_poll_node_map.find(fde->fd);
+    auto it = g_poll_node_map.find(fde->fd.get());
     CHECK(it != g_poll_node_map.end());
     PollNode& node = it->second;
     if (events & FDE_READ) {
@@ -272,7 +262,7 @@
             auto it = g_poll_node_map.find(pollfd.fd);
             CHECK(it != g_poll_node_map.end());
             fdevent* fde = it->second.fde;
-            CHECK_EQ(fde->fd, pollfd.fd);
+            CHECK_EQ(fde->fd.get(), pollfd.fd);
             fde->events |= events;
             D("%s got events %x", dump_fde(fde).c_str(), events);
             fde->state |= FDE_PENDING;
@@ -287,7 +277,7 @@
     CHECK(fde->state & FDE_PENDING);
     fde->state &= (~FDE_PENDING);
     D("fdevent_call_fdfunc %s", dump_fde(fde).c_str());
-    fde->func(fde->fd, events, fde->arg);
+    fde->func(fde->fd.get(), events, fde->arg);
 }
 
 static void fdevent_run_flush() EXCLUDES(run_queue_mutex) {
diff --git a/adb/fdevent.h b/adb/fdevent.h
index 896400a..69c4072 100644
--- a/adb/fdevent.h
+++ b/adb/fdevent.h
@@ -22,28 +22,27 @@
 
 #include <functional>
 
+#include "adb_unique_fd.h"
+
 /* events that may be observed */
 #define FDE_READ              0x0001
 #define FDE_WRITE             0x0002
 #define FDE_ERROR             0x0004
 
-/* features that may be set (via the events set/add/del interface) */
-#define FDE_DONT_CLOSE        0x0080
-
 typedef void (*fd_func)(int fd, unsigned events, void *userdata);
 
 struct fdevent {
-    fdevent *next;
-    fdevent *prev;
+    fdevent* next = nullptr;
+    fdevent* prev = nullptr;
 
-    int fd;
-    int force_eof;
+    unique_fd fd;
+    int force_eof = 0;
 
-    uint16_t state;
-    uint16_t events;
+    uint16_t state = 0;
+    uint16_t events = 0;
 
-    fd_func func;
-    void *arg;
+    fd_func func = nullptr;
+    void* arg = nullptr;
 };
 
 /* Allocate and initialize a new fdevent object
@@ -57,15 +56,6 @@
 */
 void fdevent_destroy(fdevent *fde);
 
-/* Initialize an fdevent object that was externally allocated
-*/
-void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg);
-
-/* Uninitialize an fdevent object that was initialized by
-** fdevent_install()
-*/
-void fdevent_remove(fdevent *item);
-
 /* Change which events should cause notifications
 */
 void fdevent_set(fdevent *fde, unsigned events);
diff --git a/adb/fdevent_test.cpp b/adb/fdevent_test.cpp
index 2f0ff18..0cb2439 100644
--- a/adb/fdevent_test.cpp
+++ b/adb/fdevent_test.cpp
@@ -31,14 +31,14 @@
 class FdHandler {
   public:
     FdHandler(int read_fd, int write_fd) : read_fd_(read_fd), write_fd_(write_fd) {
-        fdevent_install(&read_fde_, read_fd_, FdEventCallback, this);
-        fdevent_add(&read_fde_, FDE_READ);
-        fdevent_install(&write_fde_, write_fd_, FdEventCallback, this);
+        read_fde_ = fdevent_create(read_fd_, FdEventCallback, this);
+        fdevent_add(read_fde_, FDE_READ);
+        write_fde_ = fdevent_create(write_fd_, FdEventCallback, this);
     }
 
     ~FdHandler() {
-        fdevent_remove(&read_fde_);
-        fdevent_remove(&write_fde_);
+        fdevent_destroy(read_fde_);
+        fdevent_destroy(write_fde_);
     }
 
   private:
@@ -50,7 +50,7 @@
             char c;
             ASSERT_EQ(1, adb_read(fd, &c, 1));
             handler->queue_.push(c);
-            fdevent_add(&handler->write_fde_, FDE_WRITE);
+            fdevent_add(handler->write_fde_, FDE_WRITE);
         }
         if (events & FDE_WRITE) {
             ASSERT_EQ(fd, handler->write_fd_);
@@ -59,7 +59,7 @@
             handler->queue_.pop();
             ASSERT_EQ(1, adb_write(fd, &c, 1));
             if (handler->queue_.empty()) {
-              fdevent_del(&handler->write_fde_, FDE_WRITE);
+                fdevent_del(handler->write_fde_, FDE_WRITE);
             }
         }
     }
@@ -67,8 +67,8 @@
   private:
     const int read_fd_;
     const int write_fd_;
-    fdevent read_fde_;
-    fdevent write_fde_;
+    fdevent* read_fde_;
+    fdevent* write_fde_;
     std::queue<char> queue_;
 };
 
@@ -137,7 +137,7 @@
 }
 
 struct InvalidFdArg {
-    fdevent fde;
+    fdevent* fde;
     unsigned expected_events;
     size_t* happened_event_count;
 };
@@ -145,7 +145,7 @@
 static void InvalidFdEventCallback(int, unsigned events, void* userdata) {
     InvalidFdArg* arg = reinterpret_cast<InvalidFdArg*>(userdata);
     ASSERT_EQ(arg->expected_events, events);
-    fdevent_remove(&arg->fde);
+    fdevent_destroy(arg->fde);
     if (++*(arg->happened_event_count) == 2) {
         fdevent_terminate_loop();
     }
@@ -157,15 +157,15 @@
     InvalidFdArg read_arg;
     read_arg.expected_events = FDE_READ | FDE_ERROR;
     read_arg.happened_event_count = &happened_event_count;
-    fdevent_install(&read_arg.fde, INVALID_READ_FD, InvalidFdEventCallback, &read_arg);
-    fdevent_add(&read_arg.fde, FDE_READ);
+    read_arg.fde = fdevent_create(INVALID_READ_FD, InvalidFdEventCallback, &read_arg);
+    fdevent_add(read_arg.fde, FDE_READ);
 
     const int INVALID_WRITE_FD = std::numeric_limits<int>::max();
     InvalidFdArg write_arg;
     write_arg.expected_events = FDE_READ | FDE_ERROR;
     write_arg.happened_event_count = &happened_event_count;
-    fdevent_install(&write_arg.fde, INVALID_WRITE_FD, InvalidFdEventCallback, &write_arg);
-    fdevent_add(&write_arg.fde, FDE_WRITE);
+    write_arg.fde = fdevent_create(INVALID_WRITE_FD, InvalidFdEventCallback, &write_arg);
+    fdevent_add(write_arg.fde, FDE_WRITE);
     fdevent_loop();
 }
 
diff --git a/adb/socket.h b/adb/socket.h
index e0fd87f..0905aab 100644
--- a/adb/socket.h
+++ b/adb/socket.h
@@ -58,11 +58,11 @@
      * us to our fd event system.  For remote asockets
      * these fields are not used.
      */
-    fdevent fde = {};
-    int fd = 0;
+    fdevent* fde = nullptr;
+    int fd = -1;
 
     // queue of data waiting to be written
-    std::deque<Range> packet_queue;
+    IOVector packet_queue;
 
     std::string smart_socket_data;
 
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index e567ff4..de3215d 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -113,24 +113,23 @@
 };
 
 static SocketFlushResult local_socket_flush_incoming(asocket* s) {
-    while (!s->packet_queue.empty()) {
-        Range& r = s->packet_queue.front();
-
-        int rc = adb_write(s->fd, r.data(), r.size());
-        if (rc == static_cast<int>(r.size())) {
-            s->packet_queue.pop_front();
+    if (!s->packet_queue.empty()) {
+        std::vector<adb_iovec> iov = s->packet_queue.iovecs();
+        ssize_t rc = adb_writev(s->fd, iov.data(), iov.size());
+        if (rc > 0 && static_cast<size_t>(rc) == s->packet_queue.size()) {
+            s->packet_queue.clear();
         } else if (rc > 0) {
-            r.drop_front(rc);
-            fdevent_add(&s->fde, FDE_WRITE);
+            // TODO: Implement a faster drop_front?
+            s->packet_queue.take_front(rc);
+            fdevent_add(s->fde, FDE_WRITE);
             return SocketFlushResult::TryAgain;
         } else if (rc == -1 && errno == EAGAIN) {
-            fdevent_add(&s->fde, FDE_WRITE);
+            fdevent_add(s->fde, FDE_WRITE);
             return SocketFlushResult::TryAgain;
         } else {
             // We failed to write, but it's possible that we can still read from the socket.
             // Give that a try before giving up.
             s->has_write_error = true;
-            break;
         }
     }
 
@@ -140,7 +139,7 @@
         return SocketFlushResult::Destroyed;
     }
 
-    fdevent_del(&s->fde, FDE_WRITE);
+    fdevent_del(s->fde, FDE_WRITE);
     return SocketFlushResult::Completed;
 }
 
@@ -173,7 +172,7 @@
         break;
     }
     D("LS(%d): fd=%d post avail loop. r=%d is_eof=%d forced_eof=%d", s->id, s->fd, r, is_eof,
-      s->fde.force_eof);
+      s->fde->force_eof);
 
     if (avail != max_payload && s->peer) {
         data.resize(max_payload - avail);
@@ -200,13 +199,13 @@
             ** we disable notification of READs.  They'll
             ** be enabled again when we get a call to ready()
             */
-            fdevent_del(&s->fde, FDE_READ);
+            fdevent_del(s->fde, FDE_READ);
         }
     }
 
     // Don't allow a forced eof if data is still there.
-    if ((s->fde.force_eof && !r) || is_eof) {
-        D(" closing because is_eof=%d r=%d s->fde.force_eof=%d", is_eof, r, s->fde.force_eof);
+    if ((s->fde->force_eof && !r) || is_eof) {
+        D(" closing because is_eof=%d r=%d s->fde.force_eof=%d", is_eof, r, s->fde->force_eof);
         s->close(s);
         return false;
     }
@@ -217,8 +216,7 @@
 static int local_socket_enqueue(asocket* s, apacket::payload_type data) {
     D("LS(%d): enqueue %zu", s->id, data.size());
 
-    Range r(std::move(data));
-    s->packet_queue.push_back(std::move(r));
+    s->packet_queue.append(std::move(data));
     switch (local_socket_flush_incoming(s)) {
         case SocketFlushResult::Destroyed:
             return -1;
@@ -236,19 +234,19 @@
 static void local_socket_ready(asocket* s) {
     /* far side is ready for data, pay attention to
        readable events */
-    fdevent_add(&s->fde, FDE_READ);
+    fdevent_add(s->fde, FDE_READ);
 }
 
 // be sure to hold the socket list lock when calling this
 static void local_socket_destroy(asocket* s) {
     int exit_on_close = s->exit_on_close;
 
-    D("LS(%d): destroying fde.fd=%d", s->id, s->fde.fd);
+    D("LS(%d): destroying fde.fd=%d", s->id, s->fd);
 
     /* IMPORTANT: the remove closes the fd
     ** that belongs to this socket
     */
-    fdevent_remove(&s->fde);
+    fdevent_destroy(s->fde);
 
     remove_socket(s);
     delete s;
@@ -290,11 +288,11 @@
     */
     D("LS(%d): closing", s->id);
     s->closing = 1;
-    fdevent_del(&s->fde, FDE_READ);
+    fdevent_del(s->fde, FDE_READ);
     remove_socket(s);
     D("LS(%d): put on socket_closing_list fd=%d", s->id, s->fd);
     local_socket_closing_list.push_back(s);
-    CHECK_EQ(FDE_WRITE, s->fde.state & FDE_WRITE);
+    CHECK_EQ(FDE_WRITE, s->fde->state & FDE_WRITE);
 }
 
 static void local_socket_event_func(int fd, unsigned ev, void* _s) {
@@ -343,7 +341,7 @@
     s->close = local_socket_close;
     install_local_socket(s);
 
-    fdevent_install(&s->fde, fd, local_socket_event_func, s);
+    s->fde = fdevent_create(fd, local_socket_event_func, s);
     D("LS(%d): created (fd=%d)", s->id, s->fd);
     return s;
 }
@@ -622,7 +620,7 @@
     D("SS(%d): enqueue %zu", s->id, data.size());
 
     if (s->smart_socket_data.empty()) {
-        // TODO: Make this a BlockChain?
+        // TODO: Make this an IOVector?
         s->smart_socket_data.assign(data.begin(), data.end());
     } else {
         std::copy(data.begin(), data.end(), std::back_inserter(s->smart_socket_data));
diff --git a/adb/transport.cpp b/adb/transport.cpp
index beec13a..7db9bf2 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -427,7 +427,7 @@
 
 static int transport_registration_send = -1;
 static int transport_registration_recv = -1;
-static fdevent transport_registration_fde;
+static fdevent* transport_registration_fde;
 
 #if ADB_HOST
 
@@ -698,10 +698,9 @@
     transport_registration_send = s[0];
     transport_registration_recv = s[1];
 
-    fdevent_install(&transport_registration_fde, transport_registration_recv,
-                    transport_registration_func, 0);
-
-    fdevent_set(&transport_registration_fde, FDE_READ);
+    transport_registration_fde =
+        fdevent_create(transport_registration_recv, transport_registration_func, 0);
+    fdevent_set(transport_registration_fde, FDE_READ);
 }
 
 void kick_all_transports() {
diff --git a/adb/types.h b/adb/types.h
index dd3e063..c6b3f07 100644
--- a/adb/types.h
+++ b/adb/types.h
@@ -17,11 +17,15 @@
 #pragma once
 
 #include <algorithm>
+#include <deque>
+#include <type_traits>
 #include <utility>
+#include <vector>
 
 #include <android-base/logging.h>
 
 #include "sysdeps/memory.h"
+#include "sysdeps/uio.h"
 
 // Essentially std::vector<char>, except without zero initialization or reallocation.
 struct Block {
@@ -130,34 +134,205 @@
     payload_type payload;
 };
 
-struct Range {
-    explicit Range(apacket::payload_type data) : data_(std::move(data)) {}
+struct IOVector {
+    using value_type = char;
+    using block_type = Block;
+    using size_type = size_t;
 
-    Range(const Range& copy) = delete;
-    Range& operator=(const Range& copy) = delete;
+    IOVector() {}
 
-    Range(Range&& move) = default;
-    Range& operator=(Range&& move) = default;
+    explicit IOVector(std::unique_ptr<block_type> block) {
+        append(std::move(block));
+    }
 
-    size_t size() const { return data_.size() - begin_offset_ - end_offset_; };
+    IOVector(const IOVector& copy) = delete;
+    IOVector(IOVector&& move) : IOVector() {
+        *this = std::move(move);
+    }
+
+    IOVector& operator=(const IOVector& copy) = delete;
+    IOVector& operator=(IOVector&& move) {
+        chain_ = std::move(move.chain_);
+        chain_length_ = move.chain_length_;
+        begin_offset_ = move.begin_offset_;
+        end_offset_ = move.end_offset_;
+
+        move.chain_.clear();
+        move.chain_length_ = 0;
+        move.begin_offset_ = 0;
+        move.end_offset_ = 0;
+
+        return *this;
+    }
+
+    size_type size() const { return chain_length_ - begin_offset_ - end_offset_; }
     bool empty() const { return size() == 0; }
 
-    void drop_front(size_t n) {
-        CHECK_GE(size(), n);
-        begin_offset_ += n;
+    void clear() {
+        chain_length_ = 0;
+        begin_offset_ = 0;
+        end_offset_ = 0;
+        chain_.clear();
     }
 
-    void drop_end(size_t n) {
-        CHECK_GE(size(), n);
-        end_offset_ += n;
+    // Split the first |len| bytes out of this chain into its own.
+    IOVector take_front(size_type len) {
+        IOVector head;
+
+        if (len == 0) {
+            return head;
+        }
+        CHECK_GE(size(), len);
+
+        std::shared_ptr<const block_type> first_block = chain_.front();
+        CHECK_GE(first_block->size(), begin_offset_);
+        head.append_shared(std::move(first_block));
+        head.begin_offset_ = begin_offset_;
+
+        while (head.size() < len) {
+            pop_front_block();
+            CHECK(!chain_.empty());
+
+            head.append_shared(chain_.front());
+        }
+
+        if (head.size() == len) {
+            // Head takes full ownership of the last block it took.
+            head.end_offset_ = 0;
+            begin_offset_ = 0;
+            pop_front_block();
+        } else {
+            // Head takes partial ownership of the last block it took.
+            size_t bytes_taken = head.size() - len;
+            head.end_offset_ = bytes_taken;
+            CHECK_GE(chain_.front()->size(), bytes_taken);
+            begin_offset_ = chain_.front()->size() - bytes_taken;
+        }
+
+        return head;
     }
 
-    char* data() { return &data_[0] + begin_offset_; }
+    // Add a nonempty block to the chain.
+    // The end of the chain must be a complete block (i.e. end_offset_ == 0).
+    void append(std::unique_ptr<const block_type> block) {
+        CHECK_NE(0ULL, block->size());
+        CHECK_EQ(0ULL, end_offset_);
+        chain_length_ += block->size();
+        chain_.emplace_back(std::move(block));
+    }
 
-    apacket::payload_type::iterator begin() { return data_.begin() + begin_offset_; }
-    apacket::payload_type::iterator end() { return data_.end() - end_offset_; }
+    void append(block_type&& block) { append(std::make_unique<block_type>(std::move(block))); }
 
-    apacket::payload_type data_;
+    void trim_front() {
+        if (begin_offset_ == 0) {
+            return;
+        }
+
+        const block_type* first_block = chain_.front().get();
+        auto copy = std::make_unique<block_type>(first_block->size() - begin_offset_);
+        memcpy(copy->data(), first_block->data() + begin_offset_, copy->size());
+        chain_.front() = std::move(copy);
+
+        chain_length_ -= begin_offset_;
+        begin_offset_ = 0;
+    }
+
+  private:
+    // append, except takes a shared_ptr.
+    // Private to prevent exterior mutation of blocks.
+    void append_shared(std::shared_ptr<const block_type> block) {
+        CHECK_NE(0ULL, block->size());
+        CHECK_EQ(0ULL, end_offset_);
+        chain_length_ += block->size();
+        chain_.emplace_back(std::move(block));
+    }
+
+    // Drop the front block from the chain, and update chain_length_ appropriately.
+    void pop_front_block() {
+        chain_length_ -= chain_.front()->size();
+        begin_offset_ = 0;
+        chain_.pop_front();
+    }
+
+    // Iterate over the blocks with a callback with an operator()(const char*, size_t).
+    template <typename Fn>
+    void iterate_blocks(Fn&& callback) const {
+        if (chain_.size() == 0) {
+            return;
+        }
+
+        for (size_t i = 0; i < chain_.size(); ++i) {
+            const std::shared_ptr<const block_type>& block = chain_.at(i);
+            const char* begin = block->data();
+            size_t length = block->size();
+
+            // Note that both of these conditions can be true if there's only one block.
+            if (i == 0) {
+                CHECK_GE(block->size(), begin_offset_);
+                begin += begin_offset_;
+                length -= begin_offset_;
+            }
+
+            if (i == chain_.size() - 1) {
+                CHECK_GE(length, end_offset_);
+                length -= end_offset_;
+            }
+
+            callback(begin, length);
+        }
+    }
+
+  public:
+    // Copy all of the blocks into a single block.
+    template <typename CollectionType = block_type>
+    CollectionType coalesce() const {
+        CollectionType result;
+        if (size() == 0) {
+            return result;
+        }
+
+        result.resize(size());
+
+        size_t offset = 0;
+        iterate_blocks([&offset, &result](const char* data, size_t len) {
+            memcpy(&result[offset], data, len);
+            offset += len;
+        });
+
+        return result;
+    }
+
+    template <typename FunctionType>
+    auto coalesced(FunctionType&& f) const ->
+        typename std::result_of<FunctionType(const char*, size_t)>::type {
+        if (chain_.size() == 1) {
+            // If we only have one block, we can use it directly.
+            return f(chain_.front()->data() + begin_offset_, size());
+        } else {
+            // Otherwise, copy to a single block.
+            auto data = coalesce();
+            return f(data.data(), data.size());
+        }
+    }
+
+    // Get a list of iovecs that can be used to write out all of the blocks.
+    std::vector<adb_iovec> iovecs() const {
+        std::vector<adb_iovec> result;
+        iterate_blocks([&result](const char* data, size_t len) {
+            adb_iovec iov;
+            iov.iov_base = const_cast<char*>(data);
+            iov.iov_len = len;
+            result.emplace_back(iov);
+        });
+
+        return result;
+    }
+
+  private:
+    // Total length of all of the blocks in the chain.
+    size_t chain_length_ = 0;
+
     size_t begin_offset_ = 0;
     size_t end_offset_ = 0;
+    std::deque<std::shared_ptr<const block_type>> chain_;
 };
diff --git a/adb/types_test.cpp b/adb/types_test.cpp
new file mode 100644
index 0000000..31ab90a
--- /dev/null
+++ b/adb/types_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 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 <gtest/gtest.h>
+
+#include "sysdeps/memory.h"
+#include "types.h"
+
+static std::unique_ptr<IOVector::block_type> create_block(const std::string& string) {
+    return std::make_unique<IOVector::block_type>(string.begin(), string.end());
+}
+
+static std::unique_ptr<IOVector::block_type> create_block(char value, size_t len) {
+    auto block = std::make_unique<IOVector::block_type>();
+    block->resize(len);
+    memset(&(*block)[0], value, len);
+    return block;
+}
+
+template <typename T>
+static std::unique_ptr<IOVector::block_type> copy_block(T&& block) {
+    auto copy = std::make_unique<IOVector::block_type>();
+    copy->assign(block->begin(), block->end());
+    return copy;
+}
+
+TEST(IOVector, empty) {
+    // Empty IOVector.
+    IOVector bc;
+    CHECK_EQ(0ULL, bc.coalesce().size());
+}
+
+TEST(IOVector, single_block) {
+    // A single block.
+    auto block = create_block('x', 100);
+    IOVector bc;
+    bc.append(copy_block(block));
+    ASSERT_EQ(100ULL, bc.size());
+    auto coalesced = bc.coalesce();
+    ASSERT_EQ(*block, coalesced);
+}
+
+TEST(IOVector, single_block_split) {
+    // One block split.
+    IOVector bc;
+    bc.append(create_block("foobar"));
+    IOVector foo = bc.take_front(3);
+    ASSERT_EQ(3ULL, foo.size());
+    ASSERT_EQ(3ULL, bc.size());
+    ASSERT_EQ(*create_block("foo"), foo.coalesce());
+    ASSERT_EQ(*create_block("bar"), bc.coalesce());
+}
+
+TEST(IOVector, aligned_split) {
+    IOVector bc;
+    bc.append(create_block("foo"));
+    bc.append(create_block("bar"));
+    bc.append(create_block("baz"));
+    ASSERT_EQ(9ULL, bc.size());
+
+    IOVector foo = bc.take_front(3);
+    ASSERT_EQ(3ULL, foo.size());
+    ASSERT_EQ(*create_block("foo"), foo.coalesce());
+
+    IOVector bar = bc.take_front(3);
+    ASSERT_EQ(3ULL, bar.size());
+    ASSERT_EQ(*create_block("bar"), bar.coalesce());
+
+    IOVector baz = bc.take_front(3);
+    ASSERT_EQ(3ULL, baz.size());
+    ASSERT_EQ(*create_block("baz"), baz.coalesce());
+
+    ASSERT_EQ(0ULL, bc.size());
+}
+
+TEST(IOVector, misaligned_split) {
+    IOVector bc;
+    bc.append(create_block("foo"));
+    bc.append(create_block("bar"));
+    bc.append(create_block("baz"));
+    bc.append(create_block("qux"));
+    bc.append(create_block("quux"));
+
+    // Aligned left, misaligned right, across multiple blocks.
+    IOVector foob = bc.take_front(4);
+    ASSERT_EQ(4ULL, foob.size());
+    ASSERT_EQ(*create_block("foob"), foob.coalesce());
+
+    // Misaligned left, misaligned right, in one block.
+    IOVector a = bc.take_front(1);
+    ASSERT_EQ(1ULL, a.size());
+    ASSERT_EQ(*create_block("a"), a.coalesce());
+
+    // Misaligned left, misaligned right, across two blocks.
+    IOVector rba = bc.take_front(3);
+    ASSERT_EQ(3ULL, rba.size());
+    ASSERT_EQ(*create_block("rba"), rba.coalesce());
+
+    // Misaligned left, misaligned right, across three blocks.
+    IOVector zquxquu = bc.take_front(7);
+    ASSERT_EQ(7ULL, zquxquu.size());
+    ASSERT_EQ(*create_block("zquxquu"), zquxquu.coalesce());
+
+    ASSERT_EQ(1ULL, bc.size());
+    ASSERT_EQ(*create_block("x"), bc.coalesce());
+}
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 9463cc9..6493262 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -507,7 +507,8 @@
 static std::string make_temporary_directory() {
     std::string result(make_temporary_template());
     if (mkdtemp(&result[0]) == nullptr) {
-        die("unable to create temporary directory: %s", strerror(errno));
+        die("unable to create temporary directory with template %s: %s",
+            result.c_str(), strerror(errno));
     }
     return result;
 }
@@ -516,7 +517,8 @@
     std::string path_template(make_temporary_template());
     int fd = mkstemp(&path_template[0]);
     if (fd == -1) {
-        die("failed to create temporary file for %s: %s\n", what, strerror(errno));
+        die("failed to create temporary file for %s with template %s: %s\n",
+            path_template.c_str(), what, strerror(errno));
     }
     unlink(path_template.c_str());
     return fd;
diff --git a/init/Android.bp b/init/Android.bp
index a31c5a5..63f3fca 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -100,6 +100,7 @@
         "capabilities.cpp",
         "descriptors.cpp",
         "devices.cpp",
+        "epoll.cpp",
         "firmware_handler.cpp",
         "import_parser.cpp",
         "init.cpp",
diff --git a/init/epoll.cpp b/init/epoll.cpp
new file mode 100644
index 0000000..4bca09e
--- /dev/null
+++ b/init/epoll.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 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 "epoll.h"
+
+#include <sys/epoll.h>
+
+#include <chrono>
+#include <functional>
+#include <map>
+
+namespace android {
+namespace init {
+
+Epoll::Epoll() {}
+
+Result<Success> Epoll::Open() {
+    if (epoll_fd_ >= 0) return Success();
+    epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC));
+
+    if (epoll_fd_ == -1) {
+        return ErrnoError() << "epoll_create1 failed";
+    }
+    return Success();
+}
+
+Result<Success> Epoll::RegisterHandler(int fd, std::function<void()> handler) {
+    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(handler));
+    if (!inserted) {
+        return Error() << "Cannot specify two epoll handlers for a given FD";
+    }
+    epoll_event ev;
+    ev.events = EPOLLIN;
+    // std::map's iterators do not get invalidated until erased, so we use the
+    // pointer to the std::function in the map directly for epoll_ctl.
+    ev.data.ptr = reinterpret_cast<void*>(&it->second);
+    if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev) == -1) {
+        Result<Success> result = ErrnoError() << "epoll_ctl failed to add fd";
+        epoll_handlers_.erase(fd);
+        return result;
+    }
+    return Success();
+}
+
+Result<Success> Epoll::UnregisterHandler(int fd) {
+    if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr) == -1) {
+        return ErrnoError() << "epoll_ctl failed to remove fd";
+    }
+    if (epoll_handlers_.erase(fd) != 1) {
+        return Error() << "Attempting to remove epoll handler for FD without an existing handler";
+    }
+    return Success();
+}
+
+Result<Success> Epoll::Wait(std::optional<std::chrono::milliseconds> timeout) {
+    int timeout_ms = -1;
+    if (timeout && timeout->count() < INT_MAX) {
+        timeout_ms = timeout->count();
+    }
+    epoll_event ev;
+    auto nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd_, &ev, 1, timeout_ms));
+    if (nr == -1) {
+        return ErrnoError() << "epoll_wait failed";
+    } else if (nr == 1) {
+        std::invoke(*reinterpret_cast<std::function<void()>*>(ev.data.ptr));
+    }
+    return Success();
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/epoll.h b/init/epoll.h
new file mode 100644
index 0000000..85a791c
--- /dev/null
+++ b/init/epoll.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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 _INIT_EPOLL_H
+#define _INIT_EPOLL_H
+
+#include <chrono>
+#include <functional>
+#include <map>
+#include <optional>
+
+#include <android-base/unique_fd.h>
+
+#include "result.h"
+
+namespace android {
+namespace init {
+
+class Epoll {
+  public:
+    Epoll();
+
+    Result<Success> Open();
+    Result<Success> RegisterHandler(int fd, std::function<void()> handler);
+    Result<Success> UnregisterHandler(int fd);
+    Result<Success> Wait(std::optional<std::chrono::milliseconds> timeout);
+
+  private:
+    android::base::unique_fd epoll_fd_;
+    std::map<int, std::function<void()>> epoll_handlers_;
+};
+
+}  // namespace init
+}  // namespace android
+
+#endif
diff --git a/init/init.cpp b/init/init.cpp
index 645184b..fd9a90c 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -24,7 +24,6 @@
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/epoll.h>
 #include <sys/mount.h>
 #include <sys/signalfd.h>
 #include <sys/sysmacros.h>
@@ -48,6 +47,7 @@
 #include <selinux/android.h>
 
 #include "action_parser.h"
+#include "epoll.h"
 #include "import_parser.h"
 #include "init_first_stage.h"
 #include "keychords.h"
@@ -61,6 +61,7 @@
 #include "util.h"
 #include "watchdogd.h"
 
+using namespace std::chrono_literals;
 using namespace std::string_literals;
 
 using android::base::boot_clock;
@@ -79,7 +80,6 @@
 
 std::string default_console = "/dev/console";
 
-static int epoll_fd = -1;
 static int signal_fd = -1;
 
 static std::unique_ptr<Timer> waiting_for_prop(nullptr);
@@ -131,34 +131,6 @@
     }
 }
 
-static std::map<int, std::function<void()>> epoll_handlers;
-
-void register_epoll_handler(int fd, std::function<void()> handler) {
-    auto[it, inserted] = epoll_handlers.emplace(fd, std::move(handler));
-    if (!inserted) {
-        LOG(ERROR) << "Cannot specify two epoll handlers for a given FD";
-        return;
-    }
-    epoll_event ev;
-    ev.events = EPOLLIN;
-    // std::map's iterators do not get invalidated until erased, so we use the pointer to the
-    // std::function in the map directly for epoll_ctl.
-    ev.data.ptr = reinterpret_cast<void*>(&it->second);
-    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
-        PLOG(ERROR) << "epoll_ctl failed to add fd";
-        epoll_handlers.erase(fd);
-    }
-}
-
-void unregister_epoll_handler(int fd) {
-    if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, nullptr) == -1) {
-        PLOG(ERROR) << "epoll_ctl failed to remove fd";
-    }
-    if (epoll_handlers.erase(fd) != 1) {
-        LOG(ERROR) << "Attempting to remove epoll handler for FD without an existing handler";
-    }
-}
-
 bool start_waiting_for_property(const char *name, const char *value)
 {
     if (waiting_for_prop) {
@@ -343,11 +315,6 @@
     return Success();
 }
 
-static Result<Success> KeychordInitAction(const BuiltinArguments& args) {
-    KeychordInit();
-    return Success();
-}
-
 static Result<Success> console_init_action(const BuiltinArguments& args) {
     std::string console = GetProperty("ro.boot.console", "");
     if (!console.empty()) {
@@ -550,7 +517,7 @@
     }
 }
 
-static void InstallSignalFdHandler() {
+static void InstallSignalFdHandler(Epoll* epoll) {
     // Applying SA_NOCLDSTOP to a defaulted SIGCHLD handler prevents the signalfd from receiving
     // SIGCHLD when a child process stops or continues (b/77867680#comment9).
     const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };
@@ -581,7 +548,9 @@
         PLOG(FATAL) << "failed to create signalfd";
     }
 
-    register_epoll_handler(signal_fd, HandleSignalFd);
+    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result) {
+        LOG(FATAL) << result.error();
+    }
 }
 
 int main(int argc, char** argv) {
@@ -727,16 +696,16 @@
     SelabelInitialize();
     SelinuxRestoreContext();
 
-    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
-    if (epoll_fd == -1) {
-        PLOG(FATAL) << "epoll_create1 failed";
+    Epoll epoll;
+    if (auto result = epoll.Open(); !result) {
+        PLOG(FATAL) << result.error();
     }
 
-    InstallSignalFdHandler();
+    InstallSignalFdHandler(&epoll);
 
     property_load_boot_defaults();
     export_oem_lock_status();
-    start_property_service();
+    StartPropertyService(&epoll);
     set_usb_controller();
 
     const BuiltinFunctionMap function_map;
@@ -761,7 +730,12 @@
     am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
     am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
-    am.QueueBuiltinAction(KeychordInitAction, "KeychordInit");
+    am.QueueBuiltinAction(
+        [&epoll](const BuiltinArguments& args) -> Result<Success> {
+            KeychordInit(&epoll);
+            return Success();
+        },
+        "KeychordInit");
     am.QueueBuiltinAction(console_init_action, "console_init");
 
     // Trigger all the boot actions to get us started.
@@ -784,7 +758,7 @@
 
     while (true) {
         // By default, sleep until something happens.
-        int epoll_timeout_ms = -1;
+        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
 
         if (do_shutdown && !shutting_down) {
             do_shutdown = false;
@@ -802,23 +776,18 @@
 
                 // If there's a process that needs restarting, wake up in time for that.
                 if (next_process_restart_time) {
-                    epoll_timeout_ms = std::chrono::ceil<std::chrono::milliseconds>(
-                                           *next_process_restart_time - boot_clock::now())
-                                           .count();
-                    if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
+                    epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
+                        *next_process_restart_time - boot_clock::now());
+                    if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
                 }
             }
 
             // If there's more work to do, wake up again immediately.
-            if (am.HasMoreCommands()) epoll_timeout_ms = 0;
+            if (am.HasMoreCommands()) epoll_timeout = 0ms;
         }
 
-        epoll_event ev;
-        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
-        if (nr == -1) {
-            PLOG(ERROR) << "epoll_wait failed";
-        } else if (nr == 1) {
-            std::invoke(*reinterpret_cast<std::function<void()>*>(ev.data.ptr));
+        if (auto result = epoll.Wait(epoll_timeout); !result) {
+            LOG(ERROR) << result.error();
         }
     }
 
diff --git a/init/init.h b/init/init.h
index e7c4d8d..6c82fa1 100644
--- a/init/init.h
+++ b/init/init.h
@@ -43,9 +43,6 @@
 
 void property_changed(const std::string& name, const std::string& value);
 
-void register_epoll_handler(int fd, std::function<void()> handler);
-void unregister_epoll_handler(int fd);
-
 bool start_waiting_for_property(const char *name, const char *value);
 
 void DumpState();
diff --git a/init/keychords.cpp b/init/keychords.cpp
index 293736d..418cdeb 100644
--- a/init/keychords.cpp
+++ b/init/keychords.cpp
@@ -36,6 +36,7 @@
 #include <android-base/properties.h>
 
 #include "init.h"
+#include "service.h"
 
 namespace android {
 namespace init {
@@ -43,6 +44,7 @@
 namespace {
 
 int keychords_count;
+Epoll* epoll;
 
 struct KeychordEntry {
     const std::vector<int> keycodes;
@@ -214,7 +216,7 @@
         keychord_current |= mask & available & set;
         KeychordLambdaCheck();
     }
-    register_epoll_handler(fd, [fd]() { KeychordLambdaHandler(fd); });
+    epoll->RegisterHandler(fd, [fd]() { KeychordLambdaHandler(fd); });
     return true;
 }
 
@@ -236,7 +238,7 @@
     auto it = keychord_registration.find(device);
     if (it == keychord_registration.end()) return;
     auto fd = (*it).second;
-    unregister_epoll_handler(fd);
+    epoll->UnregisterHandler(fd);
     keychord_registration.erase(it);
     ::close(fd);
 }
@@ -294,7 +296,7 @@
         }
     }
 
-    if (inotify_fd >= 0) register_epoll_handler(inotify_fd, InotifyHandler);
+    if (inotify_fd >= 0) epoll->RegisterHandler(inotify_fd, InotifyHandler);
 }
 
 void AddServiceKeycodes(Service* svc) {
@@ -309,7 +311,8 @@
 
 }  // namespace
 
-void KeychordInit() {
+void KeychordInit(Epoll* init_epoll) {
+    epoll = init_epoll;
     for (const auto& service : ServiceList::GetInstance()) {
         AddServiceKeycodes(service.get());
     }
diff --git a/init/keychords.h b/init/keychords.h
index 689a3b5..f3aecbb 100644
--- a/init/keychords.h
+++ b/init/keychords.h
@@ -17,12 +17,12 @@
 #ifndef _INIT_KEYCHORDS_H_
 #define _INIT_KEYCHORDS_H_
 
-#include "service.h"
+#include "epoll.h"
 
 namespace android {
 namespace init {
 
-void KeychordInit();
+void KeychordInit(Epoll* init_epoll);
 
 }  // namespace init
 }  // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 47e45ef..741fde0 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -56,6 +56,7 @@
 #include <selinux/label.h>
 #include <selinux/selinux.h>
 
+#include "epoll.h"
 #include "init.h"
 #include "persistent_properties.h"
 #include "property_type.h"
@@ -371,6 +372,7 @@
 
             int result = TEMP_FAILURE_RETRY(recv(socket_, data, bytes_left, MSG_DONTWAIT));
             if (result <= 0) {
+                PLOG(ERROR) << "sys_prop: recv error";
                 return false;
             }
 
@@ -378,6 +380,10 @@
             data += result;
         }
 
+        if (bytes_left != 0) {
+            LOG(ERROR) << "sys_prop: recv data is not properly obtained.";
+        }
+
         return bytes_left == 0;
     }
 
@@ -808,7 +814,7 @@
     selinux_android_restorecon(kPropertyInfosPath, 0);
 }
 
-void start_property_service() {
+void StartPropertyService(Epoll* epoll) {
     selinux_callback cb;
     cb.func_audit = SelinuxAuditCallback;
     selinux_set_callback(SELINUX_CB_AUDIT, cb);
@@ -823,7 +829,9 @@
 
     listen(property_set_fd, 8);
 
-    register_epoll_handler(property_set_fd, handle_property_set_fd);
+    if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
+        PLOG(FATAL) << result.error();
+    }
 }
 
 }  // namespace init
diff --git a/init/property_service.h b/init/property_service.h
index 29eaaa9..4a354c2 100644
--- a/init/property_service.h
+++ b/init/property_service.h
@@ -21,6 +21,8 @@
 
 #include <string>
 
+#include "epoll.h"
+
 namespace android {
 namespace init {
 
@@ -40,7 +42,7 @@
 void property_load_boot_defaults(void);
 void load_persist_props(void);
 void load_system_props(void);
-void start_property_service(void);
+void StartPropertyService(Epoll* epoll);
 
 }  // namespace init
 }  // namespace android
diff --git a/liblog/Android.bp b/liblog/Android.bp
index 2d5a5db..1bd796a 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -84,6 +84,7 @@
         android_arm: {
             // TODO: This is to work around b/24465209. Remove after root cause is fixed
             ldflags: ["-Wl,--hash-style=both"],
+            use_clang_lld: false,
         },
         windows: {
             srcs: ["uio.c"],
diff --git a/liblog/logprint.c b/liblog/logprint.c
index a2839bf..7937cb1 100644
--- a/liblog/logprint.c
+++ b/liblog/logprint.c
@@ -1632,8 +1632,10 @@
     prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), "\x1B[38;5;%dm",
                          colorFromPri(entry->priority));
     prefixLen = MIN(prefixLen, sizeof(prefixBuf));
-    suffixLen = snprintf(suffixBuf, sizeof(suffixBuf), "\x1B[0m");
-    suffixLen = MIN(suffixLen, sizeof(suffixBuf));
+
+    const char suffixContents[] = "\x1B[0m";
+    strcpy(suffixBuf, suffixContents);
+    suffixLen = strlen(suffixContents);
   }
 
   char uid[16];
diff --git a/libstats/Android.bp b/libstats/Android.bp
new file mode 100644
index 0000000..d58f294
--- /dev/null
+++ b/libstats/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2018 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.
+//
+
+// ==========================================================
+// Native library to write stats log to statsd socket
+// ==========================================================
+cc_library_static {
+    name: "libstatssocket",
+    srcs: [
+        "stats_event_list.c",
+        "statsd_writer.c",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DLIBLOG_LOG_TAG=1006",
+        "-DWRITE_TO_STATSD=1",
+        "-DWRITE_TO_LOGD=0",
+    ],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "liblog",
+    ],
+}
diff --git a/libstats/OWNERS b/libstats/OWNERS
new file mode 100644
index 0000000..ed06fbc
--- /dev/null
+++ b/libstats/OWNERS
@@ -0,0 +1,4 @@
+bookatz@google.com
+joeo@google.com
+yaochen@google.com
+yanglu@google.com
diff --git a/libstats/include/stats_event_list.h b/libstats/include/stats_event_list.h
new file mode 100644
index 0000000..5d174ae
--- /dev/null
+++ b/libstats/include/stats_event_list.h
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2018, 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 ANDROID_STATS_LOG_STATS_EVENT_LIST_H
+#define ANDROID_STATS_LOG_STATS_EVENT_LIST_H
+
+#include <log/log_event_list.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void reset_log_context(android_log_context ctx);
+int write_to_logger(android_log_context context, log_id_t id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __cplusplus
+/**
+ * A copy of android_log_event_list class.
+ *
+ * android_log_event_list is going to be deprecated soon, so copy it here to
+ * avoid creating dependency on upstream code. TODO(b/78304629): Rewrite this
+ * code.
+ */
+class stats_event_list {
+  private:
+    android_log_context ctx;
+    int ret;
+
+    stats_event_list(const stats_event_list&) = delete;
+    void operator=(const stats_event_list&) = delete;
+
+  public:
+    explicit stats_event_list(int tag) : ret(0) {
+        ctx = create_android_logger(static_cast<uint32_t>(tag));
+    }
+    explicit stats_event_list(log_msg& log_msg) : ret(0) {
+        ctx = create_android_log_parser(log_msg.msg() + sizeof(uint32_t),
+                                        log_msg.entry.len - sizeof(uint32_t));
+    }
+    ~stats_event_list() { android_log_destroy(&ctx); }
+
+    int close() {
+        int retval = android_log_destroy(&ctx);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return retval;
+    }
+
+    /* To allow above C calls to use this class as parameter */
+    operator android_log_context() const { return ctx; }
+
+    /* return errors or transmit status */
+    int status() const { return ret; }
+
+    int begin() {
+        int retval = android_log_write_list_begin(ctx);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret;
+    }
+    int end() {
+        int retval = android_log_write_list_end(ctx);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret;
+    }
+
+    stats_event_list& operator<<(int32_t value) {
+        int retval = android_log_write_int32(ctx, value);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return *this;
+    }
+
+    stats_event_list& operator<<(uint32_t value) {
+        int retval = android_log_write_int32(ctx, static_cast<int32_t>(value));
+        if (retval < 0) {
+            ret = retval;
+        }
+        return *this;
+    }
+
+    stats_event_list& operator<<(bool value) {
+        int retval = android_log_write_int32(ctx, value ? 1 : 0);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return *this;
+    }
+
+    stats_event_list& operator<<(int64_t value) {
+        int retval = android_log_write_int64(ctx, value);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return *this;
+    }
+
+    stats_event_list& operator<<(uint64_t value) {
+        int retval = android_log_write_int64(ctx, static_cast<int64_t>(value));
+        if (retval < 0) {
+            ret = retval;
+        }
+        return *this;
+    }
+
+    stats_event_list& operator<<(const char* value) {
+        int retval = android_log_write_string8(ctx, value);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return *this;
+    }
+
+#if defined(_USING_LIBCXX)
+    stats_event_list& operator<<(const std::string& value) {
+        int retval = android_log_write_string8_len(ctx, value.data(), value.length());
+        if (retval < 0) {
+            ret = retval;
+        }
+        return *this;
+    }
+#endif
+
+    stats_event_list& operator<<(float value) {
+        int retval = android_log_write_float32(ctx, value);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return *this;
+    }
+
+    int write(log_id_t id = LOG_ID_EVENTS) {
+        /* facilitate -EBUSY retry */
+        if ((ret == -EBUSY) || (ret > 0)) {
+            ret = 0;
+        }
+        int retval = write_to_logger(ctx, id);
+        /* existing errors trump transmission errors */
+        if (!ret) {
+            ret = retval;
+        }
+        return ret;
+    }
+
+    /*
+     * Append<Type> methods removes any integer promotion
+     * confusion, and adds access to string with length.
+     * Append methods are also added for all types for
+     * convenience.
+     */
+
+    bool AppendInt(int32_t value) {
+        int retval = android_log_write_int32(ctx, value);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret >= 0;
+    }
+
+    bool AppendLong(int64_t value) {
+        int retval = android_log_write_int64(ctx, value);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret >= 0;
+    }
+
+    bool AppendString(const char* value) {
+        int retval = android_log_write_string8(ctx, value);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret >= 0;
+    }
+
+    bool AppendString(const char* value, size_t len) {
+        int retval = android_log_write_string8_len(ctx, value, len);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret >= 0;
+    }
+
+#if defined(_USING_LIBCXX)
+    bool AppendString(const std::string& value) {
+        int retval = android_log_write_string8_len(ctx, value.data(), value.length());
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret;
+    }
+
+    bool Append(const std::string& value) {
+        int retval = android_log_write_string8_len(ctx, value.data(), value.length());
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret;
+    }
+#endif
+
+    bool AppendFloat(float value) {
+        int retval = android_log_write_float32(ctx, value);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret >= 0;
+    }
+
+    template <typename Tvalue>
+    bool Append(Tvalue value) {
+        *this << value;
+        return ret >= 0;
+    }
+
+    bool Append(const char* value, size_t len) {
+        int retval = android_log_write_string8_len(ctx, value, len);
+        if (retval < 0) {
+            ret = retval;
+        }
+        return ret >= 0;
+    }
+
+    android_log_list_element read() { return android_log_read_next(ctx); }
+    android_log_list_element peek() { return android_log_peek_next(ctx); }
+};
+
+#endif
+#endif  // ANDROID_STATS_LOG_STATS_EVENT_LIST_H
diff --git a/libstats/stats_event_list.c b/libstats/stats_event_list.c
new file mode 100644
index 0000000..3d746db
--- /dev/null
+++ b/libstats/stats_event_list.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018, 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 "include/stats_event_list.h"
+
+#include <string.h>
+#include "statsd_writer.h"
+
+#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t))
+
+typedef struct {
+    uint32_t tag;
+    unsigned pos;                                    /* Read/write position into buffer */
+    unsigned count[ANDROID_MAX_LIST_NEST_DEPTH + 1]; /* Number of elements   */
+    unsigned list[ANDROID_MAX_LIST_NEST_DEPTH + 1];  /* pos for list counter */
+    unsigned list_nest_depth;
+    unsigned len; /* Length or raw buffer. */
+    bool overflow;
+    bool list_stop; /* next call decrement list_nest_depth and issue a stop */
+    enum {
+        kAndroidLoggerRead = 1,
+        kAndroidLoggerWrite = 2,
+    } read_write_flag;
+    uint8_t storage[LOGGER_ENTRY_MAX_PAYLOAD];
+} android_log_context_internal;
+
+extern struct android_log_transport_write statsdLoggerWrite;
+
+static int __write_to_statsd_init(struct iovec* vec, size_t nr);
+static int (*write_to_statsd)(struct iovec* vec, size_t nr) = __write_to_statsd_init;
+
+// Similar to create_android_logger(), but instead of allocation a new buffer,
+// this function resets the buffer for resuse.
+void reset_log_context(android_log_context ctx) {
+    if (!ctx) {
+        return;
+    }
+    android_log_context_internal* context = (android_log_context_internal*)(ctx);
+    uint32_t tag = context->tag;
+    memset(context, 0, sizeof(android_log_context_internal));
+
+    context->tag = tag;
+    context->read_write_flag = kAndroidLoggerWrite;
+    size_t needed = sizeof(uint8_t) + sizeof(uint8_t);
+    if ((context->pos + needed) > MAX_EVENT_PAYLOAD) {
+        context->overflow = true;
+    }
+    /* Everything is a list */
+    context->storage[context->pos + 0] = EVENT_TYPE_LIST;
+    context->list[0] = context->pos + 1;
+    context->pos += needed;
+}
+
+int stats_write_list(android_log_context ctx) {
+    android_log_context_internal* context;
+    const char* msg;
+    ssize_t len;
+
+    context = (android_log_context_internal*)(ctx);
+    if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
+        return -EBADF;
+    }
+
+    if (context->list_nest_depth) {
+        return -EIO;
+    }
+
+    /* NB: if there was overflow, then log is truncated. Nothing reported */
+    context->storage[1] = context->count[0];
+    len = context->len = context->pos;
+    msg = (const char*)context->storage;
+    /* it's not a list */
+    if (context->count[0] <= 1) {
+        len -= sizeof(uint8_t) + sizeof(uint8_t);
+        if (len < 0) {
+            len = 0;
+        }
+        msg += sizeof(uint8_t) + sizeof(uint8_t);
+    }
+
+    struct iovec vec[2];
+    vec[0].iov_base = &context->tag;
+    vec[0].iov_len = sizeof(context->tag);
+    vec[1].iov_base = (void*)msg;
+    vec[1].iov_len = len;
+    return write_to_statsd(vec, 2);
+}
+
+int write_to_logger(android_log_context ctx, log_id_t id) {
+    int retValue = 0;
+
+    if (WRITE_TO_LOGD) {
+        retValue = android_log_write_list(ctx, id);
+    }
+
+    if (WRITE_TO_STATSD) {
+        // log_event_list's cast operator is overloaded.
+        int ret = stats_write_list(ctx);
+        // In debugging phase, we may write to both logd and statsd. Prefer to
+        // return statsd socket write error code here.
+        if (ret < 0) {
+            retValue = ret;
+        }
+    }
+
+    return retValue;
+}
+
+/* log_init_lock assumed */
+static int __write_to_statsd_initialize_locked() {
+    if (!statsdLoggerWrite.open || ((*statsdLoggerWrite.open)() < 0)) {
+        if (statsdLoggerWrite.close) {
+            (*statsdLoggerWrite.close)();
+            return -ENODEV;
+        }
+    }
+    return 1;
+}
+
+static int __write_to_stats_daemon(struct iovec* vec, size_t nr) {
+    int save_errno;
+    struct timespec ts;
+    size_t len, i;
+
+    for (len = i = 0; i < nr; ++i) {
+        len += vec[i].iov_len;
+    }
+    if (!len) {
+        return -EINVAL;
+    }
+
+    save_errno = errno;
+    clock_gettime(CLOCK_REALTIME, &ts);
+
+    int ret = (int)(*statsdLoggerWrite.write)(&ts, vec, nr);
+    errno = save_errno;
+    return ret;
+}
+
+static int __write_to_statsd_init(struct iovec* vec, size_t nr) {
+    int ret, save_errno = errno;
+
+    statsd_writer_init_lock();
+
+    if (write_to_statsd == __write_to_statsd_init) {
+        ret = __write_to_statsd_initialize_locked();
+        if (ret < 0) {
+            statsd_writer_init_unlock();
+            errno = save_errno;
+            return ret;
+        }
+
+        write_to_statsd = __write_to_stats_daemon;
+    }
+
+    statsd_writer_init_unlock();
+
+    ret = write_to_statsd(vec, nr);
+    errno = save_errno;
+    return ret;
+}
diff --git a/libstats/statsd_writer.c b/libstats/statsd_writer.c
new file mode 100644
index 0000000..9953bba
--- /dev/null
+++ b/libstats/statsd_writer.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2018, 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 "statsd_writer.h"
+
+#include <cutils/sockets.h>
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
+#include <stdarg.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <time.h>
+#include <unistd.h>
+
+/* branchless on many architectures. */
+#define min(x, y) ((y) ^ (((x) ^ (y)) & -((x) < (y))))
+
+static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
+
+void statsd_writer_init_lock() {
+    /*
+     * If we trigger a signal handler in the middle of locked activity and the
+     * signal handler logs a message, we could get into a deadlock state.
+     */
+    pthread_mutex_lock(&log_init_lock);
+}
+
+int statd_writer_trylock() {
+    return pthread_mutex_trylock(&log_init_lock);
+}
+
+void statsd_writer_init_unlock() {
+    pthread_mutex_unlock(&log_init_lock);
+}
+
+static int statsdAvailable();
+static int statsdOpen();
+static void statsdClose();
+static int statsdWrite(struct timespec* ts, struct iovec* vec, size_t nr);
+
+struct android_log_transport_write statsdLoggerWrite = {
+    .name = "statsd",
+    .sock = -EBADF,
+    .available = statsdAvailable,
+    .open = statsdOpen,
+    .close = statsdClose,
+    .write = statsdWrite,
+};
+
+/* log_init_lock assumed */
+static int statsdOpen() {
+    int i, ret = 0;
+
+    i = atomic_load(&statsdLoggerWrite.sock);
+    if (i < 0) {
+        int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
+        if (sock < 0) {
+            ret = -errno;
+        } else {
+            struct sockaddr_un un;
+            memset(&un, 0, sizeof(struct sockaddr_un));
+            un.sun_family = AF_UNIX;
+            strcpy(un.sun_path, "/dev/socket/statsdw");
+
+            if (TEMP_FAILURE_RETRY(
+                    connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) {
+                ret = -errno;
+                switch (ret) {
+                    case -ENOTCONN:
+                    case -ECONNREFUSED:
+                    case -ENOENT:
+                        i = atomic_exchange(&statsdLoggerWrite.sock, ret);
+                    /* FALLTHRU */
+                    default:
+                        break;
+                }
+                close(sock);
+            } else {
+                ret = atomic_exchange(&statsdLoggerWrite.sock, sock);
+                if ((ret >= 0) && (ret != sock)) {
+                    close(ret);
+                }
+                ret = 0;
+            }
+        }
+    }
+
+    return ret;
+}
+
+static void __statsdClose(int negative_errno) {
+    int sock = atomic_exchange(&statsdLoggerWrite.sock, negative_errno);
+    if (sock >= 0) {
+        close(sock);
+    }
+}
+
+static void statsdClose() {
+    __statsdClose(-EBADF);
+}
+
+static int statsdAvailable() {
+    if (atomic_load(&statsdLoggerWrite.sock) < 0) {
+        if (access("/dev/socket/statsdw", W_OK) == 0) {
+            return 0;
+        }
+        return -EBADF;
+    }
+    return 1;
+}
+
+static int statsdWrite(struct timespec* ts, struct iovec* vec, size_t nr) {
+    ssize_t ret;
+    int sock;
+    static const unsigned headerLength = 1;
+    struct iovec newVec[nr + headerLength];
+    android_log_header_t header;
+    size_t i, payloadSize;
+    static atomic_int dropped;
+
+    sock = atomic_load(&statsdLoggerWrite.sock);
+    if (sock < 0) switch (sock) {
+            case -ENOTCONN:
+            case -ECONNREFUSED:
+            case -ENOENT:
+                break;
+            default:
+                return -EBADF;
+        }
+    /*
+     *  struct {
+     *      // what we provide to socket
+     *      android_log_header_t header;
+     *      // caller provides
+     *      union {
+     *          struct {
+     *              char     prio;
+     *              char     payload[];
+     *          } string;
+     *          struct {
+     *              uint32_t tag
+     *              char     payload[];
+     *          } binary;
+     *      };
+     *  };
+     */
+
+    header.tid = gettid();
+    header.realtime.tv_sec = ts->tv_sec;
+    header.realtime.tv_nsec = ts->tv_nsec;
+
+    newVec[0].iov_base = (unsigned char*)&header;
+    newVec[0].iov_len = sizeof(header);
+
+    // If we dropped events before, try to tell statsd.
+    if (sock >= 0) {
+        int32_t snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
+        if (snapshot) {
+            android_log_event_int_t buffer;
+            header.id = LOG_ID_STATS;
+            buffer.header.tag = htole32(LIBLOG_LOG_TAG);
+            buffer.payload.type = EVENT_TYPE_INT;
+            buffer.payload.data = htole32(snapshot);
+
+            newVec[headerLength].iov_base = &buffer;
+            newVec[headerLength].iov_len = sizeof(buffer);
+
+            ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
+            if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
+                atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
+            }
+        }
+    }
+
+    header.id = LOG_ID_STATS;
+
+    for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
+        newVec[i].iov_base = vec[i - headerLength].iov_base;
+        payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;
+
+        if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
+            newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
+            if (newVec[i].iov_len) {
+                ++i;
+            }
+            break;
+        }
+    }
+
+    /*
+     * The write below could be lost, but will never block.
+     *
+     * ENOTCONN occurs if statsd has died.
+     * ENOENT occurs if statsd is not running and socket is missing.
+     * ECONNREFUSED occurs if we can not reconnect to statsd.
+     * EAGAIN occurs if statsd is overloaded.
+     */
+    if (sock < 0) {
+        ret = sock;
+    } else {
+        ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i));
+        if (ret < 0) {
+            ret = -errno;
+        }
+    }
+    switch (ret) {
+        case -ENOTCONN:
+        case -ECONNREFUSED:
+        case -ENOENT:
+            if (statd_writer_trylock()) {
+                return ret; /* in a signal handler? try again when less stressed
+                             */
+            }
+            __statsdClose(ret);
+            ret = statsdOpen();
+            statsd_writer_init_unlock();
+
+            if (ret < 0) {
+                return ret;
+            }
+
+            ret = TEMP_FAILURE_RETRY(writev(atomic_load(&statsdLoggerWrite.sock), newVec, i));
+            if (ret < 0) {
+                ret = -errno;
+            }
+        /* FALLTHRU */
+        default:
+            break;
+    }
+
+    if (ret > (ssize_t)sizeof(header)) {
+        ret -= sizeof(header);
+    } else if (ret == -EAGAIN) {
+        atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
+    }
+
+    return ret;
+}
diff --git a/libstats/statsd_writer.h b/libstats/statsd_writer.h
new file mode 100644
index 0000000..82e14e0
--- /dev/null
+++ b/libstats/statsd_writer.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018, 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 ANDROID_STATS_LOG_STATS_WRITER_H
+#define ANDROID_STATS_LOG_STATS_WRITER_H
+
+#include <pthread.h>
+#include <stdatomic.h>
+#include <sys/socket.h>
+
+/**
+ * Internal lock should not be exposed. This is bad design.
+ * TODO: rewrite it in c++ code and encapsulate the functionality in a
+ * StatsdWriter class.
+ */
+void statsd_writer_init_lock();
+int statsd_writer_init_trylock();
+void statsd_writer_init_unlock();
+
+struct android_log_transport_write {
+    const char* name; /* human name to describe the transport */
+    atomic_int sock;
+    int (*available)(); /* Does not cause resources to be taken */
+    int (*open)();      /* can be called multiple times, reusing current resources */
+    void (*close)();    /* free up resources */
+    /* write log to transport, returns number of bytes propagated, or -errno */
+    int (*write)(struct timespec* ts, struct iovec* vec, size_t nr);
+};
+
+#endif  // ANDROID_STATS_LOG_STATS_WRITER_H
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 82f2e73..a6bf730 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -220,6 +220,15 @@
         "libbase",
         "liblzma",
     ],
+    target: {
+        // Always disable optimizations for host to make it easier to debug.
+        host: {
+            cflags: [
+                "-O0",
+                "-g",
+            ],
+        },
+    },
 }
 
 cc_binary {
diff --git a/libunwindstack/tools/unwind_for_offline.cpp b/libunwindstack/tools/unwind_for_offline.cpp
index 589731d..640992f 100644
--- a/libunwindstack/tools/unwind_for_offline.cpp
+++ b/libunwindstack/tools/unwind_for_offline.cpp
@@ -69,7 +69,7 @@
 bool SaveRegs(unwindstack::Regs* regs) {
   std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("regs.txt", "w+"), &fclose);
   if (fp == nullptr) {
-    printf("Failed to create file regs.txt.\n");
+    perror("Failed to create file regs.txt");
     return false;
   }
   regs->IterateRegisters([&fp](const char* name, uint64_t value) {
@@ -102,13 +102,14 @@
 
     std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(file_name.c_str(), "w+"), &fclose);
     if (fp == nullptr) {
-      printf("Failed to create stack.data.\n");
+      perror("Failed to create stack.data");
       return false;
     }
 
     size_t bytes = fwrite(&sp_start, 1, sizeof(sp_start), fp.get());
     if (bytes != sizeof(sp_start)) {
-      perror("Failed to write all data.");
+      printf("Failed to write sp_start data: sizeof(sp_start) %zu, written %zu\n", sizeof(sp_start),
+             bytes);
       return false;
     }
 
@@ -141,7 +142,7 @@
 
   std::unique_ptr<FILE, decltype(&fclose)> output(fopen(cur_name.c_str(), "w+"), &fclose);
   if (output == nullptr) {
-    printf("Cannot create %s\n", cur_name.c_str());
+    perror((std::string("Cannot create ") + cur_name).c_str());
     return false;
   }
 
@@ -160,13 +161,14 @@
 bool CopyElfFromFile(map_info_t* info) {
   std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(info->name.c_str(), "r"), &fclose);
   if (fp == nullptr) {
+    perror((std::string("Cannot open ") + info->name).c_str());
     return false;
   }
 
   std::string cur_name = basename(info->name.c_str());
   std::unique_ptr<FILE, decltype(&fclose)> output(fopen(cur_name.c_str(), "w+"), &fclose);
   if (output == nullptr) {
-    printf("Cannot create file %s\n", cur_name.c_str());
+    perror((std::string("Cannot create file " + cur_name)).c_str());
     return false;
   }
   std::vector<uint8_t> buffer(10000);
@@ -265,7 +267,7 @@
 
   std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("maps.txt", "w+"), &fclose);
   if (fp == nullptr) {
-    printf("Failed to create maps.txt.\n");
+    perror("Failed to create maps.txt");
     return false;
   }
 
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index 80711bc..1cfef34 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -472,47 +472,50 @@
         return;
     }
 
-    if (params.oomadj >= 900) {
-        soft_limit_mult = 0;
-    } else if (params.oomadj >= 800) {
-        soft_limit_mult = 0;
-    } else if (params.oomadj >= 700) {
-        soft_limit_mult = 0;
-    } else if (params.oomadj >= 600) {
-        // Launcher should be perceptible, don't kill it.
-        params.oomadj = 200;
-        soft_limit_mult = 1;
-    } else if (params.oomadj >= 500) {
-        soft_limit_mult = 0;
-    } else if (params.oomadj >= 400) {
-        soft_limit_mult = 0;
-    } else if (params.oomadj >= 300) {
-        soft_limit_mult = 1;
-    } else if (params.oomadj >= 200) {
-        soft_limit_mult = 2;
-    } else if (params.oomadj >= 100) {
-        soft_limit_mult = 10;
-    } else if (params.oomadj >=   0) {
-        soft_limit_mult = 20;
-    } else {
-        // Persistent processes will have a large
-        // soft limit 512MB.
-        soft_limit_mult = 64;
+    if (low_ram_device) {
+        if (params.oomadj >= 900) {
+            soft_limit_mult = 0;
+        } else if (params.oomadj >= 800) {
+            soft_limit_mult = 0;
+        } else if (params.oomadj >= 700) {
+            soft_limit_mult = 0;
+        } else if (params.oomadj >= 600) {
+            // Launcher should be perceptible, don't kill it.
+            params.oomadj = 200;
+            soft_limit_mult = 1;
+        } else if (params.oomadj >= 500) {
+            soft_limit_mult = 0;
+        } else if (params.oomadj >= 400) {
+            soft_limit_mult = 0;
+        } else if (params.oomadj >= 300) {
+            soft_limit_mult = 1;
+        } else if (params.oomadj >= 200) {
+            soft_limit_mult = 2;
+        } else if (params.oomadj >= 100) {
+            soft_limit_mult = 10;
+        } else if (params.oomadj >=   0) {
+            soft_limit_mult = 20;
+        } else {
+            // Persistent processes will have a large
+            // soft limit 512MB.
+            soft_limit_mult = 64;
+        }
+
+        snprintf(path, sizeof(path), MEMCG_SYSFS_PATH
+                 "apps/uid_%d/pid_%d/memory.soft_limit_in_bytes",
+                 params.uid, params.pid);
+        snprintf(val, sizeof(val), "%d", soft_limit_mult * EIGHT_MEGA);
+
+        /*
+         * system_server process has no memcg under /dev/memcg/apps but should be
+         * registered with lmkd. This is the best way so far to identify it.
+         */
+        is_system_server = (params.oomadj == SYSTEM_ADJ &&
+                            (pwdrec = getpwnam("system")) != NULL &&
+                            params.uid == pwdrec->pw_uid);
+        writefilestring(path, val, !is_system_server);
     }
 
-    snprintf(path, sizeof(path), MEMCG_SYSFS_PATH "apps/uid_%d/pid_%d/memory.soft_limit_in_bytes",
-             params.uid, params.pid);
-    snprintf(val, sizeof(val), "%d", soft_limit_mult * EIGHT_MEGA);
-
-    /*
-     * system_server process has no memcg under /dev/memcg/apps but should be
-     * registered with lmkd. This is the best way so far to identify it.
-     */
-    is_system_server = (params.oomadj == SYSTEM_ADJ &&
-                        (pwdrec = getpwnam("system")) != NULL &&
-                        params.uid == pwdrec->pw_uid);
-    writefilestring(path, val, !is_system_server);
-
     procp = pid_lookup(params.pid);
     if (!procp) {
             procp = malloc(sizeof(struct proc));
@@ -1150,8 +1153,15 @@
             }
         }
 
-        if (min_score_adj == OOM_SCORE_ADJ_MAX + 1)
+        if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
+            if (debug_process_killing) {
+                ALOGI("Ignore %s memory pressure event "
+                      "(free memory=%ldkB, cache=%ldkB, limit=%ldkB)",
+                      level_name[level], other_free * page_k, other_file * page_k,
+                      (long)lowmem_minfree[lowmem_targets_size - 1] * page_k);
+            }
             return;
+        }
 
         /* Free up enough pages to push over the highest minfree level */
         pages_to_free = lowmem_minfree[lowmem_targets_size - 1] -