Merge "Include vendor gralloc0 flags in gralloc1 conversion." into oc-dev
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 024bc3d..4783d6e 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -163,6 +163,7 @@
             srcs: [
                 "client/debuggerd_client_test.cpp",
                 "debuggerd_test.cpp",
+                "tombstoned_client.cpp",
                 "util.cpp"
             ],
         },
@@ -176,7 +177,8 @@
     ],
 
     static_libs: [
-        "libdebuggerd"
+        "libdebuggerd",
+        "libc_logging",
     ],
 
     local_include_dirs: [
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index b9fb512..224444f 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -65,24 +65,25 @@
   auto time_left = [timeout_ms, &end]() { return end - std::chrono::steady_clock::now(); };
   auto set_timeout = [timeout_ms, &time_left](int sockfd) {
     if (timeout_ms <= 0) {
-      return true;
+      return -1;
     }
 
     auto remaining = time_left();
     if (remaining < decltype(remaining)::zero()) {
-      return false;
+      LOG(ERROR) << "timeout expired";
+      return -1;
     }
     struct timeval timeout;
     populate_timeval(&timeout, remaining);
 
     if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0) {
-      return false;
+      return -1;
     }
     if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0) {
-      return false;
+      return -1;
     }
 
-    return true;
+    return sockfd;
   };
 
   sockfd.reset(socket(AF_LOCAL, SOCK_SEQPACKET, 0));
@@ -91,12 +92,7 @@
     return false;
   }
 
-  if (!set_timeout(sockfd)) {
-    PLOG(ERROR) << "libdebugger_client: failed to set timeout";
-    return false;
-  }
-
-  if (socket_local_client_connect(sockfd.get(), kTombstonedInterceptSocketName,
+  if (socket_local_client_connect(set_timeout(sockfd.get()), kTombstonedInterceptSocketName,
                                   ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET) == -1) {
     PLOG(ERROR) << "libdebuggerd_client: failed to connect to tombstoned";
     return false;
@@ -115,21 +111,35 @@
     return false;
   }
 
-  if (send_fd(sockfd.get(), &req, sizeof(req), std::move(pipe_write)) != sizeof(req)) {
+  if (send_fd(set_timeout(sockfd), &req, sizeof(req), std::move(pipe_write)) != sizeof(req)) {
     PLOG(ERROR) << "libdebuggerd_client: failed to send output fd to tombstoned";
     return false;
   }
 
+  // Check to make sure we've successfully registered.
+  InterceptResponse response;
+  ssize_t rc =
+      TEMP_FAILURE_RETRY(recv(set_timeout(sockfd.get()), &response, sizeof(response), MSG_TRUNC));
+  if (rc == 0) {
+    LOG(ERROR) << "libdebuggerd_client: failed to read response from tombstoned: timeout reached?";
+    return false;
+  } else if (rc != sizeof(response)) {
+    LOG(ERROR)
+        << "libdebuggerd_client: received packet of unexpected length from tombstoned: expected "
+        << sizeof(response) << ", received " << rc;
+    return false;
+  }
+
+  if (response.status != InterceptStatus::kRegistered) {
+    LOG(ERROR) << "libdebuggerd_client: unexpected registration response: "
+               << static_cast<int>(response.status);
+    return false;
+  }
+
   bool backtrace = dump_type == kDebuggerdBacktrace;
   send_signal(pid, backtrace);
 
-  if (!set_timeout(sockfd)) {
-    PLOG(ERROR) << "libdebuggerd_client: failed to set timeout";
-    return false;
-  }
-
-  InterceptResponse response;
-  ssize_t rc = TEMP_FAILURE_RETRY(recv(sockfd.get(), &response, sizeof(response), MSG_TRUNC));
+  rc = TEMP_FAILURE_RETRY(recv(set_timeout(sockfd.get()), &response, sizeof(response), MSG_TRUNC));
   if (rc == 0) {
     LOG(ERROR) << "libdebuggerd_client: failed to read response from tombstoned: timeout reached?";
     return false;
@@ -140,7 +150,7 @@
     return false;
   }
 
-  if (response.success != 1) {
+  if (response.status != InterceptStatus::kStarted) {
     response.error_message[sizeof(response.error_message) - 1] = '\0';
     LOG(ERROR) << "libdebuggerd_client: tombstoned reported failure: " << response.error_message;
     return false;
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 1a27f3f..1befcb1 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -36,6 +36,7 @@
 #include <cutils/sockets.h>
 #include <debuggerd/handler.h>
 #include <debuggerd/protocol.h>
+#include <debuggerd/tombstoned.h>
 #include <debuggerd/util.h>
 #include <gtest/gtest.h>
 
@@ -77,6 +78,54 @@
     }                                                                           \
   } while (0)
 
+static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd) {
+  intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName,
+                                          ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
+  if (intercept_fd->get() == -1) {
+    FAIL() << "failed to contact tombstoned: " << strerror(errno);
+  }
+
+  InterceptRequest req = {.pid = target_pid};
+
+  unique_fd output_pipe_write;
+  if (!Pipe(output_fd, &output_pipe_write)) {
+    FAIL() << "failed to create output pipe: " << strerror(errno);
+  }
+
+  std::string pipe_size_str;
+  int pipe_buffer_size;
+  if (!android::base::ReadFileToString("/proc/sys/fs/pipe-max-size", &pipe_size_str)) {
+    FAIL() << "failed to read /proc/sys/fs/pipe-max-size: " << strerror(errno);
+  }
+
+  pipe_size_str = android::base::Trim(pipe_size_str);
+
+  if (!android::base::ParseInt(pipe_size_str.c_str(), &pipe_buffer_size, 0)) {
+    FAIL() << "failed to parse pipe max size";
+  }
+
+  if (fcntl(output_fd->get(), F_SETPIPE_SZ, pipe_buffer_size) != pipe_buffer_size) {
+    FAIL() << "failed to set pipe size: " << strerror(errno);
+  }
+
+  if (send_fd(intercept_fd->get(), &req, sizeof(req), std::move(output_pipe_write)) != sizeof(req)) {
+    FAIL() << "failed to send output fd to tombstoned: " << strerror(errno);
+  }
+
+  InterceptResponse response;
+  ssize_t rc = TEMP_FAILURE_RETRY(read(intercept_fd->get(), &response, sizeof(response)));
+  if (rc == -1) {
+    FAIL() << "failed to read response from tombstoned: " << strerror(errno);
+  } else if (rc == 0) {
+    FAIL() << "failed to read response from tombstoned (EOF)";
+  } else if (rc != sizeof(response)) {
+    FAIL() << "received packet of unexpected length from tombstoned: expected " << sizeof(response)
+           << ", received " << rc;
+  }
+
+  ASSERT_EQ(InterceptStatus::kRegistered, response.status);
+}
+
 class CrasherTest : public ::testing::Test {
  public:
   pid_t crasher_pid = -1;
@@ -118,38 +167,7 @@
     FAIL() << "crasher hasn't been started";
   }
 
-  intercept_fd.reset(socket_local_client(kTombstonedInterceptSocketName,
-                                         ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
-  if (intercept_fd == -1) {
-    FAIL() << "failed to contact tombstoned: " << strerror(errno);
-  }
-
-  InterceptRequest req = {.pid = crasher_pid };
-
-  unique_fd output_pipe_write;
-  if (!Pipe(output_fd, &output_pipe_write)) {
-    FAIL() << "failed to create output pipe: " << strerror(errno);
-  }
-
-  std::string pipe_size_str;
-  int pipe_buffer_size;
-  if (!android::base::ReadFileToString("/proc/sys/fs/pipe-max-size", &pipe_size_str)) {
-    FAIL() << "failed to read /proc/sys/fs/pipe-max-size: " << strerror(errno);
-  }
-
-  pipe_size_str = android::base::Trim(pipe_size_str);
-
-  if (!android::base::ParseInt(pipe_size_str.c_str(), &pipe_buffer_size, 0)) {
-    FAIL() << "failed to parse pipe max size";
-  }
-
-  if (fcntl(output_fd->get(), F_SETPIPE_SZ, pipe_buffer_size) != pipe_buffer_size) {
-    FAIL() << "failed to set pipe size: " << strerror(errno);
-  }
-
-  if (send_fd(intercept_fd.get(), &req, sizeof(req), std::move(output_pipe_write)) != sizeof(req)) {
-    FAIL() << "failed to send output fd to tombstoned: " << strerror(errno);
-  }
+  tombstoned_intercept(crasher_pid, &this->intercept_fd, output_fd);
 }
 
 void CrasherTest::FinishIntercept(int* result) {
@@ -165,7 +183,7 @@
     FAIL() << "received packet of unexpected length from tombstoned: expected " << sizeof(response)
            << ", received " << rc;
   } else {
-    *result = response.success;
+    *result = response.status == InterceptStatus::kStarted ? 1 : 0;
   }
 }
 
@@ -508,3 +526,97 @@
     ASSERT_EQ(0, WEXITSTATUS(status));
   }
 }
+
+TEST(tombstoned, no_notify) {
+  // Do this a few times.
+  for (int i = 0; i < 3; ++i) {
+    pid_t pid = 123'456'789 + i;
+
+    unique_fd intercept_fd, output_fd;
+    tombstoned_intercept(pid, &intercept_fd, &output_fd);
+
+    {
+      unique_fd tombstoned_socket, input_fd;
+      ASSERT_TRUE(tombstoned_connect(pid, &tombstoned_socket, &input_fd));
+      ASSERT_TRUE(android::base::WriteFully(input_fd.get(), &pid, sizeof(pid)));
+    }
+
+    pid_t read_pid;
+    ASSERT_TRUE(android::base::ReadFully(output_fd.get(), &read_pid, sizeof(read_pid)));
+    ASSERT_EQ(read_pid, pid);
+  }
+}
+
+TEST(tombstoned, stress) {
+  // Spawn threads to simultaneously do a bunch of failing dumps and a bunch of successful dumps.
+  static constexpr int kDumpCount = 100;
+
+  std::atomic<bool> start(false);
+  std::vector<std::thread> threads;
+  threads.emplace_back([&start]() {
+    while (!start) {
+      continue;
+    }
+
+    // Use a way out of range pid, to avoid stomping on an actual process.
+    pid_t pid_base = 1'000'000;
+
+    for (int dump = 0; dump < kDumpCount; ++dump) {
+      pid_t pid = pid_base + dump;
+
+      unique_fd intercept_fd, output_fd;
+      tombstoned_intercept(pid, &intercept_fd, &output_fd);
+
+      // Pretend to crash, and then immediately close the socket.
+      unique_fd sockfd(socket_local_client(kTombstonedCrashSocketName,
+                                           ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
+      if (sockfd == -1) {
+        FAIL() << "failed to connect to tombstoned: " << strerror(errno);
+      }
+      TombstonedCrashPacket packet = {};
+      packet.packet_type = CrashPacketType::kDumpRequest;
+      packet.packet.dump_request.pid = pid;
+      if (TEMP_FAILURE_RETRY(write(sockfd, &packet, sizeof(packet))) != sizeof(packet)) {
+        FAIL() << "failed to write to tombstoned: " << strerror(errno);
+      }
+
+      continue;
+    }
+  });
+
+  threads.emplace_back([&start]() {
+    while (!start) {
+      continue;
+    }
+
+    // Use a way out of range pid, to avoid stomping on an actual process.
+    pid_t pid_base = 2'000'000;
+
+    for (int dump = 0; dump < kDumpCount; ++dump) {
+      pid_t pid = pid_base + dump;
+
+      unique_fd intercept_fd, output_fd;
+      tombstoned_intercept(pid, &intercept_fd, &output_fd);
+
+      {
+        unique_fd tombstoned_socket, input_fd;
+        ASSERT_TRUE(tombstoned_connect(pid, &tombstoned_socket, &input_fd));
+        ASSERT_TRUE(android::base::WriteFully(input_fd.get(), &pid, sizeof(pid)));
+        tombstoned_notify_completion(tombstoned_socket.get());
+      }
+
+      // TODO: Fix the race that requires this sleep.
+      std::this_thread::sleep_for(50ms);
+
+      pid_t read_pid;
+      ASSERT_TRUE(android::base::ReadFully(output_fd.get(), &read_pid, sizeof(read_pid)));
+      ASSERT_EQ(read_pid, pid);
+    }
+  });
+
+  start = true;
+
+  for (std::thread& thread : threads) {
+    thread.join();
+  }
+}
diff --git a/debuggerd/include/debuggerd/protocol.h b/debuggerd/include/debuggerd/protocol.h
index bb2ab0d..0756876 100644
--- a/debuggerd/include/debuggerd/protocol.h
+++ b/debuggerd/include/debuggerd/protocol.h
@@ -56,8 +56,14 @@
   int32_t pid;
 };
 
+enum class InterceptStatus : uint8_t {
+  kFailed,
+  kStarted,
+  kRegistered,
+};
+
 // Sent either immediately upon failure, or when the intercept has been used.
 struct InterceptResponse {
-  uint8_t success;          // 0 or 1
+  InterceptStatus status;
   char error_message[127];  // always null-terminated
 };
diff --git a/debuggerd/tombstoned/intercept_manager.cpp b/debuggerd/tombstoned/intercept_manager.cpp
index 789260d..dff942c 100644
--- a/debuggerd/tombstoned/intercept_manager.cpp
+++ b/debuggerd/tombstoned/intercept_manager.cpp
@@ -105,6 +105,7 @@
     // We trust the other side, so only do minimal validity checking.
     if (intercept_request.pid <= 0 || intercept_request.pid > std::numeric_limits<pid_t>::max()) {
       InterceptResponse response = {};
+      response.status = InterceptStatus::kFailed;
       snprintf(response.error_message, sizeof(response.error_message), "invalid pid %" PRId32,
                intercept_request.pid);
       TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response)));
@@ -113,9 +114,10 @@
 
     intercept->intercept_pid = intercept_request.pid;
 
-    // Register the intercept with the InterceptManager.
+    // Check if it's already registered.
     if (intercept_manager->intercepts.count(intercept_request.pid) > 0) {
       InterceptResponse response = {};
+      response.status = InterceptStatus::kFailed;
       snprintf(response.error_message, sizeof(response.error_message),
                "pid %" PRId32 " already intercepted", intercept_request.pid);
       TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response)));
@@ -123,6 +125,15 @@
       goto fail;
     }
 
+    // Let the other side know that the intercept has been registered, now that we know we can't
+    // fail. tombstoned is single threaded, so this isn't racy.
+    InterceptResponse response = {};
+    response.status = InterceptStatus::kRegistered;
+    if (TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response))) == -1) {
+      PLOG(WARNING) << "failed to notify interceptor of registration";
+      goto fail;
+    }
+
     intercept->output_fd = std::move(rcv_fd);
     intercept_manager->intercepts[intercept_request.pid] = std::unique_ptr<Intercept>(intercept);
     intercept->registered = true;
@@ -174,7 +185,7 @@
 
   LOG(INFO) << "found intercept fd " << intercept->output_fd.get() << " for pid " << pid;
   InterceptResponse response = {};
-  response.success = 1;
+  response.status = InterceptStatus::kStarted;
   TEMP_FAILURE_RETRY(write(intercept->sockfd, &response, sizeof(response)));
   *out_fd = std::move(intercept->output_fd);
 
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index 6754508..2248a21 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -126,9 +126,7 @@
   return result;
 }
 
-static void dequeue_request(Crash* crash) {
-  ++num_concurrent_dumps;
-
+static void perform_request(Crash* crash) {
   unique_fd output_fd;
   if (!intercept_manager->GetIntercept(crash->crash_pid, &output_fd)) {
     output_fd = get_tombstone_fd();
@@ -153,12 +151,22 @@
                  crash_completed_cb, crash);
     event_add(crash->crash_event, &timeout);
   }
+
+  ++num_concurrent_dumps;
   return;
 
 fail:
   delete crash;
 }
 
+static void dequeue_requests() {
+  while (!queued_requests.empty() && num_concurrent_dumps < kMaxConcurrentDumps) {
+    Crash* next_crash = queued_requests.front();
+    queued_requests.pop_front();
+    perform_request(next_crash);
+  }
+}
+
 static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int,
                             void*) {
   event_base* base = evconnlistener_get_base(listener);
@@ -207,7 +215,7 @@
     LOG(INFO) << "enqueueing crash request for pid " << crash->crash_pid;
     queued_requests.push_back(crash);
   } else {
-    dequeue_request(crash);
+    perform_request(crash);
   }
 
   return;
@@ -247,11 +255,7 @@
   delete crash;
 
   // If there's something queued up, let them proceed.
-  if (!queued_requests.empty()) {
-    Crash* next_crash = queued_requests.front();
-    queued_requests.pop_front();
-    dequeue_request(next_crash);
-  }
+  dequeue_requests();
 }
 
 int main(int, char* []) {
diff --git a/fs_mgr/fs_mgr_avb.cpp b/fs_mgr/fs_mgr_avb.cpp
index a762038..7512eb9 100644
--- a/fs_mgr/fs_mgr_avb.cpp
+++ b/fs_mgr/fs_mgr_avb.cpp
@@ -390,10 +390,12 @@
             continue;
         }
 
-        // Ensures that hashtree descriptor is either in /vbmeta or in
+        // Ensures that hashtree descriptor is in /vbmeta or /boot or in
         // the same partition for verity setup.
         std::string vbmeta_partition_name(verify_data.vbmeta_images[i].partition_name);
-        if (vbmeta_partition_name != "vbmeta" && vbmeta_partition_name != partition_name) {
+        if (vbmeta_partition_name != "vbmeta" &&
+            vbmeta_partition_name != "boot" &&  // for legacy device to append top-level vbmeta
+            vbmeta_partition_name != partition_name) {
             LWARNING << "Skip vbmeta image at " << verify_data.vbmeta_images[i].partition_name
                      << " for partition: " << partition_name.c_str();
             continue;
diff --git a/fs_mgr/fs_mgr_avb_ops.cpp b/fs_mgr/fs_mgr_avb_ops.cpp
index 2c14c9b..8e49663 100644
--- a/fs_mgr/fs_mgr_avb_ops.cpp
+++ b/fs_mgr/fs_mgr_avb_ops.cpp
@@ -39,7 +39,28 @@
 #include "fs_mgr_avb_ops.h"
 #include "fs_mgr_priv.h"
 
-static struct fstab* fs_mgr_fstab = nullptr;
+static std::string fstab_by_name_prefix;
+
+static std::string extract_by_name_prefix(struct fstab* fstab) {
+    // In AVB, we can assume that there's an entry for the /misc mount
+    // point in the fstab file and use that to get the device file for
+    // the misc partition. The device needs not to have an actual /misc
+    // partition. Then returns the prefix by removing the trailing "misc":
+    //
+    //    - /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+    //    - /dev/block/platform/soc.0/7824900.sdhci/by-name/
+
+    struct fstab_rec* fstab_entry = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+    if (fstab_entry == nullptr) {
+        LERROR << "/misc mount point not found in fstab";
+        return "";
+    }
+
+    std::string full_path(fstab_entry->blk_device);
+    size_t end_slash = full_path.find_last_of("/");
+
+    return full_path.substr(0, end_slash + 1);
+}
 
 static AvbIOResult read_from_partition(AvbOps* ops ATTRIBUTE_UNUSED, const char* partition,
                                        int64_t offset, size_t num_bytes, void* buffer,
@@ -49,30 +70,14 @@
     // for partitions having 'slotselect' optin in fstab file, but it
     // won't be appended to the mount point.
     //
-    // In AVB, we can assume that there's an entry for the /misc mount
-    // point and use that to get the device file for the misc partition.
-    // From there we'll assume that a by-name scheme is used
-    // so we can just replace the trailing "misc" by the given
-    // |partition|, e.g.
+    // Appends |partition| to the fstab_by_name_prefix, which is obtained
+    // by removing the trailing "misc" from the device file of /misc mount
+    // point. e.g.,
     //
-    //    - /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+    //    - /dev/block/platform/soc.0/7824900.sdhci/by-name/ ->
     //    - /dev/block/platform/soc.0/7824900.sdhci/by-name/system_a
 
-    struct fstab_rec* fstab_entry = fs_mgr_get_entry_for_mount_point(fs_mgr_fstab, "/misc");
-
-    if (fstab_entry == nullptr) {
-        LERROR << "/misc mount point not found in fstab";
-        return AVB_IO_RESULT_ERROR_IO;
-    }
-
-    std::string partition_name(partition);
-    std::string path(fstab_entry->blk_device);
-    // Replaces the last field of device file if it's not misc.
-    if (!android::base::StartsWith(partition_name, "misc")) {
-        size_t end_slash = path.find_last_of("/");
-        std::string by_name_prefix(path.substr(0, end_slash + 1));
-        path = by_name_prefix + partition_name;
-    }
+    std::string path = fstab_by_name_prefix + partition;
 
     // Ensures the device path (a symlink created by init) is ready to
     // access. fs_mgr_test_access() will test a few iterations if the
@@ -168,8 +173,8 @@
 AvbOps* fs_mgr_dummy_avb_ops_new(struct fstab* fstab) {
     AvbOps* ops;
 
-    // Assigns the fstab to the static variable for later use.
-    fs_mgr_fstab = fstab;
+    fstab_by_name_prefix = extract_by_name_prefix(fstab);
+    if (fstab_by_name_prefix.empty()) return nullptr;
 
     ops = (AvbOps*)calloc(1, sizeof(AvbOps));
     if (ops == nullptr) {
diff --git a/fs_mgr/fs_mgr_boot_config.cpp b/fs_mgr/fs_mgr_boot_config.cpp
index 5b2f218..cffa6ce 100644
--- a/fs_mgr/fs_mgr_boot_config.cpp
+++ b/fs_mgr/fs_mgr_boot_config.cpp
@@ -56,7 +56,7 @@
             return true;
         }
 
-        LERROR << "Error finding '" << key << "' in device tree";
+        LINFO << "Error finding '" << key << "' in device tree";
     }
 
     return false;
diff --git a/init/Android.mk b/init/Android.mk
index b52c949..1ca88d7 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -16,6 +16,14 @@
     -DREBOOT_BOOTLOADER_ON_PANIC=0
 endif
 
+ifneq (,$(filter eng,$(TARGET_BUILD_VARIANT)))
+init_options += \
+    -DSHUTDOWN_ZERO_TIMEOUT=1
+else
+init_options += \
+    -DSHUTDOWN_ZERO_TIMEOUT=0
+endif
+
 init_options += -DLOG_UEVENTS=0
 
 init_cflags += \
diff --git a/init/README.md b/init/README.md
index 024d559..e66ade2 100644
--- a/init/README.md
+++ b/init/README.md
@@ -286,11 +286,6 @@
 `copy <src> <dst>`
 > Copies a file. Similar to write, but useful for binary/large
   amounts of data.
-  Regarding to the src file, copying from symbol link file and world-writable
-  or group-writable files are not allowed.
-  Regarding to the dst file, the default mode created is 0600 if it does not
-  exist. And it will be truncated if dst file is a normal regular file and
-  already exists.
 
 `domainname <name>`
 > Set the domain name.
@@ -311,6 +306,12 @@
   groups can be provided. No other commands will be run until this one
   finishes. _seclabel_ can be a - to denote default. Properties are expanded
   within _argument_.
+  Init halts executing commands until the forked process exits.
+
+`exec_start <service>`
+> Start service a given service and halt processing of additional init commands
+  until it returns.  It functions similarly to the `exec` command, but uses an
+  existing service definition instead of providing them as arguments.
 
 `export <name> <value>`
 > Set the environment variable _name_ equal to _value_ in the
diff --git a/init/action.cpp b/init/action.cpp
index 1bba0f2..2ccf0bc 100644
--- a/init/action.cpp
+++ b/init/action.cpp
@@ -18,14 +18,14 @@
 
 #include <errno.h>
 
-#include <android-base/strings.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 
 #include "builtins.h"
 #include "error.h"
 #include "init_parser.h"
 #include "log.h"
-#include "property_service.h"
 #include "util.h"
 
 using android::base::Join;
@@ -219,9 +219,8 @@
                 found = true;
             }
         } else {
-            std::string prop_val = property_get(trigger_name.c_str());
-            if (prop_val.empty() || (trigger_value != "*" &&
-                                     trigger_value != prop_val)) {
+            std::string prop_val = android::base::GetProperty(trigger_name, "");
+            if (prop_val.empty() || (trigger_value != "*" && trigger_value != prop_val)) {
                 return false;
             }
         }
diff --git a/init/bootchart.cpp b/init/bootchart.cpp
index 4a9c32e..beabea1 100644
--- a/init/bootchart.cpp
+++ b/init/bootchart.cpp
@@ -16,8 +16,6 @@
 
 #include "bootchart.h"
 
-#include "property_service.h"
-
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -39,6 +37,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 
 using android::base::StringPrintf;
@@ -72,7 +71,7 @@
   utsname uts;
   if (uname(&uts) == -1) return;
 
-  std::string fingerprint = property_get("ro.build.fingerprint");
+  std::string fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
   if (fingerprint.empty()) return;
 
   std::string kernel_cmdline;
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 95f1aa0..75b3c61 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -45,16 +45,16 @@
 #include <selinux/selinux.h>
 #include <selinux/label.h>
 
-#include <fs_mgr.h>
 #include <android-base/file.h>
 #include <android-base/parseint.h>
-#include <android-base/strings.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <bootloader_message/bootloader_message.h>
-#include <cutils/partition_utils.h>
 #include <cutils/android_reboot.h>
 #include <ext4_utils/ext4_crypt.h>
 #include <ext4_utils/ext4_crypt_init_extensions.h>
+#include <fs_mgr.h>
 #include <logwrap/logwrap.h>
 
 #include "action.h"
@@ -167,19 +167,11 @@
 }
 
 static int do_exec(const std::vector<std::string>& args) {
-    Service* svc = ServiceManager::GetInstance().MakeExecOneshotService(args);
-    if (!svc) {
-        return -1;
-    }
-    if (!start_waiting_for_exec()) {
-        return -1;
-    }
-    if (!svc->Start()) {
-        stop_waiting_for_exec();
-        ServiceManager::GetInstance().RemoveService(*svc);
-        return -1;
-    }
-    return 0;
+    return ServiceManager::GetInstance().Exec(args) ? 0 : -1;
+}
+
+static int do_exec_start(const std::vector<std::string>& args) {
+    return ServiceManager::GetInstance().ExecStart(args[1]) ? 0 : -1;
 }
 
 static int do_export(const std::vector<std::string>& args) {
@@ -710,11 +702,51 @@
 }
 
 static int do_copy(const std::vector<std::string>& args) {
-    std::string data;
-    if (read_file(args[1].c_str(), &data)) {
-        return write_file(args[2].c_str(), data.data()) ? 0 : 1;
+    char* buffer = NULL;
+    int rc = 0;
+    int fd1 = -1, fd2 = -1;
+    struct stat info;
+    int brtw, brtr;
+    char* p;
+
+    if (stat(args[1].c_str(), &info) < 0) return -1;
+
+    if ((fd1 = open(args[1].c_str(), O_RDONLY | O_CLOEXEC)) < 0) goto out_err;
+
+    if ((fd2 = open(args[2].c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0660)) < 0)
+        goto out_err;
+
+    if (!(buffer = (char*)malloc(info.st_size))) goto out_err;
+
+    p = buffer;
+    brtr = info.st_size;
+    while (brtr) {
+        rc = read(fd1, p, brtr);
+        if (rc < 0) goto out_err;
+        if (rc == 0) break;
+        p += rc;
+        brtr -= rc;
     }
-    return 1;
+
+    p = buffer;
+    brtw = info.st_size;
+    while (brtw) {
+        rc = write(fd2, p, brtw);
+        if (rc < 0) goto out_err;
+        if (rc == 0) break;
+        p += rc;
+        brtw -= rc;
+    }
+
+    rc = 0;
+    goto out;
+out_err:
+    rc = -1;
+out:
+    if (buffer) free(buffer);
+    if (fd1 >= 0) close(fd1);
+    if (fd2 >= 0) close(fd2);
+    return rc;
 }
 
 static int do_chown(const std::vector<std::string>& args) {
@@ -880,16 +912,21 @@
 }
 
 static bool is_file_crypto() {
-    std::string value = property_get("ro.crypto.type");
-    return value == "file";
+    return android::base::GetProperty("ro.crypto.type", "") == "file";
 }
 
 static int do_installkey(const std::vector<std::string>& args) {
     if (!is_file_crypto()) {
         return 0;
     }
-    return e4crypt_create_device_key(args[1].c_str(),
-                                     do_installkeys_ensure_dir_exists);
+    auto unencrypted_dir = args[1] + e4crypt_unencrypted_folder;
+    if (do_installkeys_ensure_dir_exists(unencrypted_dir.c_str())) {
+        PLOG(ERROR) << "Failed to create " << unencrypted_dir;
+        return -1;
+    }
+    std::vector<std::string> exec_args = {"exec", "/system/bin/vdc", "--wait", "cryptfs",
+                                          "enablefilecrypto"};
+    return do_exec(exec_args);
 }
 
 static int do_init_user0(const std::vector<std::string>& args) {
@@ -898,6 +935,7 @@
 
 BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
     constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
+    // clang-format off
     static const Map builtin_functions = {
         {"bootchart",               {1,     1,    do_bootchart}},
         {"chmod",                   {2,     2,    do_chmod}},
@@ -910,6 +948,7 @@
         {"domainname",              {1,     1,    do_domainname}},
         {"enable",                  {1,     1,    do_enable}},
         {"exec",                    {1,     kMax, do_exec}},
+        {"exec_start",              {1,     1,    do_exec_start}},
         {"export",                  {2,     2,    do_export}},
         {"hostname",                {1,     1,    do_hostname}},
         {"ifup",                    {1,     1,    do_ifup}},
@@ -943,5 +982,6 @@
         {"wait_for_prop",           {2,     2,    do_wait_for_prop}},
         {"write",                   {2,     2,    do_write}},
     };
+    // clang-format on
     return builtin_functions;
 }
diff --git a/init/init.cpp b/init/init.cpp
index 23448d6..a1d9f1b 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -41,13 +41,10 @@
 #include <selinux/android.h>
 
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <cutils/fs.h>
-#include <cutils/iosched_policy.h>
-#include <cutils/list.h>
-#include <cutils/sockets.h>
 #include <libavb/libavb.h>
 #include <private/android_filesystem_config.h>
 
@@ -72,6 +69,7 @@
 #include "util.h"
 #include "watchdogd.h"
 
+using android::base::GetProperty;
 using android::base::StringPrintf;
 
 struct selabel_handle *sehandle;
@@ -86,8 +84,6 @@
 
 const char *ENV[32];
 
-static std::unique_ptr<Timer> waiting_for_exec(nullptr);
-
 static int epoll_fd = -1;
 
 static std::unique_ptr<Timer> waiting_for_prop(nullptr);
@@ -135,29 +131,12 @@
     return -1;
 }
 
-bool start_waiting_for_exec()
-{
-    if (waiting_for_exec) {
-        return false;
-    }
-    waiting_for_exec.reset(new Timer());
-    return true;
-}
-
-void stop_waiting_for_exec()
-{
-    if (waiting_for_exec) {
-        LOG(INFO) << "Wait for exec took " << *waiting_for_exec;
-        waiting_for_exec.reset();
-    }
-}
-
 bool start_waiting_for_property(const char *name, const char *value)
 {
     if (waiting_for_prop) {
         return false;
     }
-    if (property_get(name) != value) {
+    if (GetProperty(name, "") != value) {
         // Current property value is not equal to expected value
         wait_prop_name = name;
         wait_prop_value = value;
@@ -445,7 +424,7 @@
 
 static int console_init_action(const std::vector<std::string>& args)
 {
-    std::string console = property_get("ro.boot.console");
+    std::string console = GetProperty("ro.boot.console", "");
     if (!console.empty()) {
         default_console = "/dev/" + console;
     }
@@ -469,11 +448,11 @@
 }
 
 static void export_oem_lock_status() {
-    if (property_get("ro.oem_unlock_supported") != "1") {
+    if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
         return;
     }
 
-    std::string value = property_get("ro.boot.verifiedbootstate");
+    std::string value = GetProperty("ro.boot.verifiedbootstate", "");
 
     if (!value.empty()) {
         property_set("ro.boot.flash.locked", value == "orange" ? "0" : "1");
@@ -494,7 +473,7 @@
         { "ro.boot.revision",   "ro.revision",   "0", },
     };
     for (size_t i = 0; i < arraysize(prop_map); i++) {
-        std::string value = property_get(prop_map[i].src_prop);
+        std::string value = GetProperty(prop_map[i].src_prop, "");
         property_set(prop_map[i].dst_prop, (!value.empty()) ? value.c_str() : prop_map[i].default_value);
     }
 }
@@ -896,6 +875,34 @@
     }
 }
 
+// The files and directories that were created before initial sepolicy load
+// need to have their security context restored to the proper value.
+// This must happen before /dev is populated by ueventd.
+static void selinux_restore_context() {
+    LOG(INFO) << "Running restorecon...";
+    restorecon("/dev");
+    restorecon("/dev/kmsg");
+    restorecon("/dev/socket");
+    restorecon("/dev/random");
+    restorecon("/dev/urandom");
+    restorecon("/dev/__properties__");
+
+    restorecon("/file_contexts.bin");
+    restorecon("/plat_file_contexts");
+    restorecon("/nonplat_file_contexts");
+    restorecon("/plat_property_contexts");
+    restorecon("/nonplat_property_contexts");
+    restorecon("/plat_seapp_contexts");
+    restorecon("/nonplat_seapp_contexts");
+    restorecon("/plat_service_contexts");
+    restorecon("/nonplat_service_contexts");
+    restorecon("/sepolicy");
+
+    restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
+    restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
+    restorecon("/dev/device-mapper");
+}
+
 // Set the UDC controller for the ConfigFS USB Gadgets.
 // Read the UDC controller in use from "/sys/class/udc".
 // In case of multiple UDC controllers select the first one.
@@ -1234,22 +1241,7 @@
 
     // Now set up SELinux for second stage.
     selinux_initialize(false);
-
-    // These directories were necessarily created before initial policy load
-    // and therefore need their security context restored to the proper value.
-    // This must happen before /dev is populated by ueventd.
-    LOG(INFO) << "Running restorecon...";
-    restorecon("/dev");
-    restorecon("/dev/kmsg");
-    restorecon("/dev/socket");
-    restorecon("/dev/random");
-    restorecon("/dev/urandom");
-    restorecon("/dev/__properties__");
-    restorecon("/plat_property_contexts");
-    restorecon("/nonplat_property_contexts");
-    restorecon("/sys", SELINUX_ANDROID_RESTORECON_RECURSE);
-    restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
-    restorecon("/dev/device-mapper");
+    selinux_restore_context();
 
     epoll_fd = epoll_create1(EPOLL_CLOEXEC);
     if (epoll_fd == -1) {
@@ -1271,7 +1263,7 @@
     parser.AddSectionParser("service",std::make_unique<ServiceParser>());
     parser.AddSectionParser("on", std::make_unique<ActionParser>());
     parser.AddSectionParser("import", std::make_unique<ImportParser>());
-    std::string bootscript = property_get("ro.boot.init_rc");
+    std::string bootscript = GetProperty("ro.boot.init_rc", "");
     if (bootscript.empty()) {
         parser.ParseConfig("/init.rc");
         parser.set_is_system_etc_init_loaded(
@@ -1311,7 +1303,7 @@
     am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
 
     // Don't mount filesystems or start core system services in charger mode.
-    std::string bootmode = property_get("ro.bootmode");
+    std::string bootmode = GetProperty("ro.bootmode", "");
     if (bootmode == "charger") {
         am.QueueEventTrigger("charger");
     } else {
@@ -1325,10 +1317,10 @@
         // By default, sleep until something happens.
         int epoll_timeout_ms = -1;
 
-        if (!(waiting_for_exec || waiting_for_prop)) {
+        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
             am.ExecuteOneCommand();
         }
-        if (!(waiting_for_exec || waiting_for_prop)) {
+        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
             restart_processes();
 
             // If there's a process that needs restarting, wake up in time for that.
diff --git a/init/init.h b/init/init.h
index b4d25fb..fe850ef 100644
--- a/init/init.h
+++ b/init/init.h
@@ -32,10 +32,6 @@
 
 int add_environment(const char* key, const char* val);
 
-bool start_waiting_for_exec();
-
-void stop_waiting_for_exec();
-
 bool start_waiting_for_property(const char *name, const char *value);
 
 #endif  /* _INIT_INIT_H */
diff --git a/init/keychords.cpp b/init/keychords.cpp
index 3dbb2f0..5801ea8 100644
--- a/init/keychords.cpp
+++ b/init/keychords.cpp
@@ -23,9 +23,10 @@
 #include <linux/keychord.h>
 #include <unistd.h>
 
+#include <android-base/properties.h>
+
 #include "init.h"
 #include "log.h"
-#include "property_service.h"
 #include "service.h"
 
 static struct input_keychord *keychords = 0;
@@ -74,7 +75,7 @@
     }
 
     // Only handle keychords if adb is enabled.
-    std::string adb_enabled = property_get("init.svc.adbd");
+    std::string adb_enabled = android::base::GetProperty("init.svc.adbd", "");
     if (adb_enabled == "running") {
         Service* svc = ServiceManager::GetInstance().FindServiceByKeychord(id);
         if (svc) {
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 983e684..a4d8b5f 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -30,10 +30,6 @@
 #include <memory>
 #include <vector>
 
-#include <cutils/misc.h>
-#include <cutils/sockets.h>
-#include <cutils/multiuser.h>
-
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
@@ -118,12 +114,6 @@
     return check_mac_perms(ctl_name, sctx, cr);
 }
 
-std::string property_get(const char* name) {
-    char value[PROP_VALUE_MAX] = {0};
-    __system_property_get(name, value);
-    return value;
-}
-
 static void write_persistent_property(const char *name, const char *value)
 {
     char tempPath[PATH_MAX];
@@ -592,10 +582,7 @@
 
 static void load_override_properties() {
     if (ALLOW_LOCAL_PROP_OVERRIDE) {
-        std::string debuggable = property_get("ro.debuggable");
-        if (debuggable == "1") {
-            load_properties_from_file("/data/local.prop", NULL);
-        }
+        load_properties_from_file("/data/local.prop", NULL);
     }
 }
 
diff --git a/init/property_service.h b/init/property_service.h
index 5d59473..994da63 100644
--- a/init/property_service.h
+++ b/init/property_service.h
@@ -32,7 +32,6 @@
 void load_persist_props(void);
 void load_system_props(void);
 void start_property_service(void);
-std::string property_get(const char* name);
 uint32_t property_set(const std::string& name, const std::string& value);
 bool is_legal_property_name(const std::string& name);
 
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 261a437..e34abdb 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -32,17 +32,15 @@
 
 #include <android-base/file.h>
 #include <android-base/macros.h>
-#include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <bootloader_message/bootloader_message.h>
 #include <cutils/android_reboot.h>
-#include <cutils/partition_utils.h>
 #include <fs_mgr.h>
 #include <logwrap/logwrap.h>
 
 #include "log.h"
-#include "property_service.h"
 #include "reboot.h"
 #include "service.h"
 #include "util.h"
@@ -342,12 +340,16 @@
         abort();
     }
 
-    std::string timeout = property_get("ro.build.shutdown_timeout");
     /* TODO update default waiting time based on usage data */
-    unsigned int shutdownTimeout = 10;  // default value
-    if (android::base::ParseUint(timeout, &shutdownTimeout)) {
-        LOG(INFO) << "ro.build.shutdown_timeout set:" << shutdownTimeout;
+    constexpr unsigned int shutdownTimeoutDefault = 10;
+    unsigned int shutdownTimeout = shutdownTimeoutDefault;
+    if (SHUTDOWN_ZERO_TIMEOUT) {  // eng build
+        shutdownTimeout = 0;
+    } else {
+        shutdownTimeout =
+            android::base::GetUintProperty("ro.build.shutdown_timeout", shutdownTimeoutDefault);
     }
+    LOG(INFO) << "Shutdown timeout: " << shutdownTimeout;
 
     static const constexpr char* shutdown_critical_services[] = {"vold", "watchdogd"};
     for (const char* name : shutdown_critical_services) {
diff --git a/init/service.cpp b/init/service.cpp
index c6ef838..3db34db 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -34,6 +34,7 @@
 
 #include <android-base/file.h>
 #include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <system/thread_defs.h>
@@ -191,8 +192,8 @@
 }
 
 void Service::NotifyStateChange(const std::string& new_state) const {
-    if ((flags_ & SVC_EXEC) != 0) {
-        // 'exec' commands don't have properties tracking their state.
+    if ((flags_ & SVC_TEMPORARY) != 0) {
+        // Services created by 'exec' are temporary and don't have properties tracking their state.
         return;
     }
 
@@ -210,7 +211,13 @@
     LOG(INFO) << "Sending signal " << signal
               << " to service '" << name_
               << "' (pid " << pid_ << ") process group...";
-    if (killProcessGroup(uid_, pid_, signal) == -1) {
+    int r;
+    if (signal == SIGTERM) {
+        r = killProcessGroupOnce(uid_, pid_, signal);
+    } else {
+        r = killProcessGroup(uid_, pid_, signal);
+    }
+    if (r == -1) {
         PLOG(ERROR) << "killProcessGroup(" << uid_ << ", " << pid_ << ", " << signal << ") failed";
     }
     if (kill(-pid_, signal) == -1) {
@@ -259,7 +266,7 @@
     }
 }
 
-bool Service::Reap() {
+void Service::Reap() {
     if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
         KillProcessGroup(SIGKILL);
     }
@@ -270,7 +277,10 @@
 
     if (flags_ & SVC_EXEC) {
         LOG(INFO) << "SVC_EXEC pid " << pid_ << " finished...";
-        return true;
+    }
+
+    if (flags_ & SVC_TEMPORARY) {
+        return;
     }
 
     pid_ = 0;
@@ -285,7 +295,7 @@
     // Disabled and reset processes do not get restarted automatically.
     if (flags_ & (SVC_DISABLED | SVC_RESET))  {
         NotifyStateChange("stopped");
-        return false;
+        return;
     }
 
     // If we crash > 4 times in 4 minutes, reboot into recovery.
@@ -309,7 +319,7 @@
     onrestart_.ExecuteAllCommands();
 
     NotifyStateChange("restarting");
-    return false;
+    return;
 }
 
 void Service::DumpState() const {
@@ -577,6 +587,18 @@
     return (this->*parser)(args, err);
 }
 
+bool Service::ExecStart(std::unique_ptr<Timer>* exec_waiter) {
+    flags_ |= SVC_EXEC | SVC_ONESHOT;
+
+    exec_waiter->reset(new Timer);
+
+    if (!Start()) {
+        exec_waiter->reset();
+        return false;
+    }
+    return true;
+}
+
 bool Service::Start() {
     // Starting a service removes it from the disabled or reset state and
     // immediately takes it out of the restarting state if it was in there.
@@ -657,7 +679,7 @@
         if (iter == writepid_files_.end()) {
             // There were no "writepid" instructions for cpusets, check if the system default
             // cpuset is specified to be used for the process.
-            std::string default_cpuset = property_get("ro.cpuset.default");
+            std::string default_cpuset = android::base::GetProperty("ro.cpuset.default", "");
             if (!default_cpuset.empty()) {
                 // Make sure the cpuset name starts and ends with '/'.
                 // A single '/' means the 'root' cpuset.
@@ -863,6 +885,35 @@
     services_.emplace_back(std::move(service));
 }
 
+bool ServiceManager::Exec(const std::vector<std::string>& args) {
+    Service* svc = MakeExecOneshotService(args);
+    if (!svc) {
+        LOG(ERROR) << "Could not create exec service";
+        return false;
+    }
+    if (!svc->ExecStart(&exec_waiter_)) {
+        LOG(ERROR) << "Could not start exec service";
+        ServiceManager::GetInstance().RemoveService(*svc);
+        return false;
+    }
+    return true;
+}
+
+bool ServiceManager::ExecStart(const std::string& name) {
+    Service* svc = FindServiceByName(name);
+    if (!svc) {
+        LOG(ERROR) << "ExecStart(" << name << "): Service not found";
+        return false;
+    }
+    if (!svc->ExecStart(&exec_waiter_)) {
+        LOG(ERROR) << "ExecStart(" << name << "): Could not start Service";
+        return false;
+    }
+    return true;
+}
+
+bool ServiceManager::IsWaitingForExec() const { return exec_waiter_ != nullptr; }
+
 Service* ServiceManager::MakeExecOneshotService(const std::vector<std::string>& args) {
     // Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS...
     // SECLABEL can be a - to denote default
@@ -886,7 +937,7 @@
 
     exec_count_++;
     std::string name = StringPrintf("exec %d (%s)", exec_count_, str_args[0].c_str());
-    unsigned flags = SVC_EXEC | SVC_ONESHOT;
+    unsigned flags = SVC_EXEC | SVC_ONESHOT | SVC_TEMPORARY;
     CapSet no_capabilities;
     unsigned namespace_flags = 0;
 
@@ -1026,8 +1077,13 @@
         return true;
     }
 
-    if (svc->Reap()) {
-        stop_waiting_for_exec();
+    svc->Reap();
+
+    if (svc->flags() & SVC_EXEC) {
+        LOG(INFO) << "Wait for exec took " << *exec_waiter_;
+        exec_waiter_.reset();
+    }
+    if (svc->flags() & SVC_TEMPORARY) {
         RemoveService(*svc);
     }
 
diff --git a/init/service.h b/init/service.h
index 9a9046b..f08a03f 100644
--- a/init/service.h
+++ b/init/service.h
@@ -44,10 +44,13 @@
 #define SVC_RC_DISABLED 0x080     // Remember if the disabled flag was set in the rc script.
 #define SVC_RESTART 0x100         // Use to safely restart (stop, wait, start) a service.
 #define SVC_DISABLED_START 0x200  // A start was requested but it was disabled at the time.
-#define SVC_EXEC 0x400            // This synthetic service corresponds to an 'exec'.
+#define SVC_EXEC 0x400  // This service was started by either 'exec' or 'exec_start' and stops
+                        // init from processing more commands until it completes
 
 #define SVC_SHUTDOWN_CRITICAL 0x800  // This service is critical for shutdown and
                                      // should not be killed during shutdown
+#define SVC_TEMPORARY 0x1000  // This service was started by 'exec' and should be removed from the
+                              // service list once it is reaped.
 
 #define NR_SVC_SUPP_GIDS 12    // twelve supplementary groups
 
@@ -72,6 +75,7 @@
 
     bool IsRunning() { return (flags_ & SVC_RUNNING) != 0; }
     bool ParseLine(const std::vector<std::string>& args, std::string* err);
+    bool ExecStart(std::unique_ptr<Timer>* exec_waiter);
     bool Start();
     bool StartIfNotDisabled();
     bool Enable();
@@ -80,7 +84,7 @@
     void Terminate();
     void Restart();
     void RestartIfNeeded(time_t* process_needs_restart_at);
-    bool Reap();
+    void Reap();
     void DumpState() const;
     void SetShutdownCritical() { flags_ |= SVC_SHUTDOWN_CRITICAL; }
     bool IsShutdownCritical() const { return (flags_ & SVC_SHUTDOWN_CRITICAL) != 0; }
@@ -178,6 +182,9 @@
 
     void AddService(std::unique_ptr<Service> service);
     Service* MakeExecOneshotService(const std::vector<std::string>& args);
+    bool Exec(const std::vector<std::string>& args);
+    bool ExecStart(const std::string& name);
+    bool IsWaitingForExec() const;
     Service* FindServiceByName(const std::string& name) const;
     Service* FindServiceByPid(pid_t pid) const;
     Service* FindServiceByKeychord(int keychord_id) const;
@@ -198,6 +205,8 @@
     bool ReapOneProcess();
 
     static int exec_count_; // Every service needs a unique name.
+    std::unique_ptr<Timer> exec_waiter_;
+
     std::vector<std::unique_ptr<Service>> services_;
 };
 
diff --git a/init/signal_handler.cpp b/init/signal_handler.cpp
index 1041b82..5e3acac 100644
--- a/init/signal_handler.cpp
+++ b/init/signal_handler.cpp
@@ -24,8 +24,6 @@
 #include <unistd.h>
 
 #include <android-base/stringprintf.h>
-#include <cutils/list.h>
-#include <cutils/sockets.h>
 
 #include "action.h"
 #include "init.h"
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 915afbd..f27be64 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -26,6 +26,7 @@
 
 #include <sys/types.h>
 
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <selinux/selinux.h>
 
@@ -34,7 +35,6 @@
 #include "util.h"
 #include "devices.h"
 #include "ueventd_parser.h"
-#include "property_service.h"
 
 int ueventd_main(int argc, char **argv)
 {
@@ -71,7 +71,7 @@
      * TODO: cleanup platform ueventd.rc to remove vendor specific
      * device node entries (b/34968103)
      */
-    std::string hardware = property_get("ro.hardware");
+    std::string hardware = android::base::GetProperty("ro.hardware", "");
     ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware.c_str()).c_str());
 
     device_init();
diff --git a/init/util.cpp b/init/util.cpp
index 0ba9800..73d97ed 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -38,6 +38,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
@@ -48,7 +49,6 @@
 
 #include "init.h"
 #include "log.h"
-#include "property_service.h"
 #include "reboot.h"
 #include "util.h"
 
@@ -185,8 +185,8 @@
 }
 
 bool write_file(const char* path, const char* content) {
-    android::base::unique_fd fd(TEMP_FAILURE_RETRY(
-        open(path, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0600)));
+    android::base::unique_fd fd(
+        TEMP_FAILURE_RETRY(open(path, O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC, 0600)));
     if (fd == -1) {
         PLOG(ERROR) << "write_file: Unable to open '" << path << "'";
         return false;
@@ -395,7 +395,7 @@
             return false;
         }
 
-        std::string prop_val = property_get(prop_name.c_str());
+        std::string prop_val = android::base::GetProperty(prop_name, "");
         if (prop_val.empty()) {
             if (def_val.empty()) {
                 LOG(ERROR) << "property '" << prop_name << "' doesn't exist while expanding '" << src << "'";
diff --git a/init/util_test.cpp b/init/util_test.cpp
index 4e82e76..24c75c4 100644
--- a/init/util_test.cpp
+++ b/init/util_test.cpp
@@ -17,15 +17,9 @@
 #include "util.h"
 
 #include <errno.h>
-#include <fcntl.h>
-
-#include <sys/stat.h>
 
 #include <gtest/gtest.h>
 
-#include <android-base/stringprintf.h>
-#include <android-base/test_utils.h>
-
 TEST(util, read_file_ENOENT) {
   std::string s("hello");
   errno = 0;
@@ -34,35 +28,6 @@
   EXPECT_EQ("", s); // s was cleared.
 }
 
-TEST(util, read_file_group_writeable) {
-    std::string s("hello");
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
-    EXPECT_TRUE(write_file(tf.path, s.c_str())) << strerror(errno);
-    EXPECT_NE(-1, fchmodat(AT_FDCWD, tf.path, 0620, AT_SYMLINK_NOFOLLOW)) << strerror(errno);
-    EXPECT_FALSE(read_file(tf.path, &s)) << strerror(errno);
-    EXPECT_EQ("", s);  // s was cleared.
-}
-
-TEST(util, read_file_world_writeable) {
-    std::string s("hello");
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
-    EXPECT_TRUE(write_file(tf.path, s.c_str())) << strerror(errno);
-    EXPECT_NE(-1, fchmodat(AT_FDCWD, tf.path, 0602, AT_SYMLINK_NOFOLLOW)) << strerror(errno);
-    EXPECT_FALSE(read_file(tf.path, &s)) << strerror(errno);
-    EXPECT_EQ("", s);  // s was cleared.
-}
-
-TEST(util, read_file_symbol_link) {
-    std::string s("hello");
-    errno = 0;
-    // lrwxrwxrwx 1 root root 13 1970-01-01 00:00 charger -> /sbin/healthd
-    EXPECT_FALSE(read_file("/charger", &s));
-    EXPECT_EQ(ELOOP, errno);
-    EXPECT_EQ("", s);  // s was cleared.
-}
-
 TEST(util, read_file_success) {
   std::string s("hello");
   EXPECT_TRUE(read_file("/proc/version", &s));
@@ -72,42 +37,6 @@
   EXPECT_STREQ("Linux", s.c_str());
 }
 
-TEST(util, write_file_not_exist) {
-    std::string s("hello");
-    std::string s2("hello");
-    TemporaryDir test_dir;
-    std::string path = android::base::StringPrintf("%s/does-not-exist", test_dir.path);
-    EXPECT_TRUE(write_file(path.c_str(), s.c_str()));
-    EXPECT_TRUE(read_file(path.c_str(), &s2));
-    EXPECT_EQ(s, s2);
-    struct stat sb;
-    int fd = open(path.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
-    EXPECT_NE(-1, fd);
-    EXPECT_EQ(0, fstat(fd, &sb));
-    EXPECT_NE(0u, sb.st_mode & S_IRUSR);
-    EXPECT_NE(0u, sb.st_mode & S_IWUSR);
-    EXPECT_EQ(0u, sb.st_mode & S_IXUSR);
-    EXPECT_EQ(0u, sb.st_mode & S_IRGRP);
-    EXPECT_EQ(0u, sb.st_mode & S_IWGRP);
-    EXPECT_EQ(0u, sb.st_mode & S_IXGRP);
-    EXPECT_EQ(0u, sb.st_mode & S_IROTH);
-    EXPECT_EQ(0u, sb.st_mode & S_IWOTH);
-    EXPECT_EQ(0u, sb.st_mode & S_IXOTH);
-    EXPECT_EQ(0, unlink(path.c_str()));
-}
-
-TEST(util, write_file_exist) {
-    std::string s2("");
-    TemporaryFile tf;
-    ASSERT_TRUE(tf.fd != -1);
-    EXPECT_TRUE(write_file(tf.path, "1hello1")) << strerror(errno);
-    EXPECT_TRUE(read_file(tf.path, &s2));
-    EXPECT_STREQ("1hello1", s2.c_str());
-    EXPECT_TRUE(write_file(tf.path, "2hello2"));
-    EXPECT_TRUE(read_file(tf.path, &s2));
-    EXPECT_STREQ("2hello2", s2.c_str());
-}
-
 TEST(util, decode_uid) {
   EXPECT_EQ(0U, decode_uid("root"));
   EXPECT_EQ(UINT_MAX, decode_uid("toot"));
diff --git a/libappfuse/FuseAppLoop.cc b/libappfuse/FuseAppLoop.cc
index a31880e..b6bc191 100644
--- a/libappfuse/FuseAppLoop.cc
+++ b/libappfuse/FuseAppLoop.cc
@@ -16,205 +16,232 @@
 
 #include "libappfuse/FuseAppLoop.h"
 
+#include <sys/eventfd.h>
 #include <sys/stat.h>
 
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 
+#include "libappfuse/EpollController.h"
+
 namespace android {
 namespace fuse {
 
 namespace {
 
-void HandleLookUp(FuseBuffer* buffer, FuseAppLoopCallback* callback) {
-  // AppFuse does not support directory structure now.
-  // It can lookup only files under the mount point.
-  if (buffer->request.header.nodeid != FUSE_ROOT_ID) {
-    LOG(ERROR) << "Nodeid is not FUSE_ROOT_ID.";
-    buffer->response.Reset(0, -ENOENT, buffer->request.header.unique);
-    return;
-  }
-
-  // Ensure that the filename ends with 0.
-  const size_t filename_length =
-      buffer->request.header.len - sizeof(fuse_in_header);
-  if (buffer->request.lookup_name[filename_length - 1] != 0) {
-    LOG(ERROR) << "File name does not end with 0.";
-    buffer->response.Reset(0, -ENOENT, buffer->request.header.unique);
-    return;
-  }
-
-  const uint64_t inode =
-      static_cast<uint64_t>(atol(buffer->request.lookup_name));
-  if (inode == 0 || inode == LONG_MAX) {
-    LOG(ERROR) << "Invalid filename";
-    buffer->response.Reset(0, -ENOENT, buffer->request.header.unique);
-    return;
-  }
-
-  const int64_t size = callback->OnGetSize(inode);
-  if (size < 0) {
-    buffer->response.Reset(0, size, buffer->request.header.unique);
-    return;
-  }
-
-  buffer->response.Reset(sizeof(fuse_entry_out), 0,
-                         buffer->request.header.unique);
-  buffer->response.entry_out.nodeid = inode;
-  buffer->response.entry_out.attr_valid = 10;
-  buffer->response.entry_out.entry_valid = 10;
-  buffer->response.entry_out.attr.ino = inode;
-  buffer->response.entry_out.attr.mode = S_IFREG | 0777;
-  buffer->response.entry_out.attr.size = size;
-}
-
-void HandleGetAttr(FuseBuffer* buffer, FuseAppLoopCallback* callback) {
-  const uint64_t nodeid = buffer->request.header.nodeid;
-  int64_t size;
-  uint32_t mode;
-  if (nodeid == FUSE_ROOT_ID) {
-    size = 0;
-    mode = S_IFDIR | 0777;
-  } else {
-    size = callback->OnGetSize(buffer->request.header.nodeid);
-    if (size < 0) {
-      buffer->response.Reset(0, size, buffer->request.header.unique);
-      return;
+bool HandleLookUp(FuseAppLoop* loop, FuseBuffer* buffer, FuseAppLoopCallback* callback) {
+    // AppFuse does not support directory structure now.
+    // It can lookup only files under the mount point.
+    if (buffer->request.header.nodeid != FUSE_ROOT_ID) {
+        LOG(ERROR) << "Nodeid is not FUSE_ROOT_ID.";
+        return loop->ReplySimple(buffer->request.header.unique, -ENOENT);
     }
-    mode = S_IFREG | 0777;
-  }
 
-  buffer->response.Reset(sizeof(fuse_attr_out), 0,
-                         buffer->request.header.unique);
-  buffer->response.attr_out.attr_valid = 10;
-  buffer->response.attr_out.attr.ino = nodeid;
-  buffer->response.attr_out.attr.mode = mode;
-  buffer->response.attr_out.attr.size = size;
+    // Ensure that the filename ends with 0.
+    const size_t filename_length = buffer->request.header.len - sizeof(fuse_in_header);
+    if (buffer->request.lookup_name[filename_length - 1] != 0) {
+        LOG(ERROR) << "File name does not end with 0.";
+        return loop->ReplySimple(buffer->request.header.unique, -ENOENT);
+    }
+
+    const uint64_t inode = static_cast<uint64_t>(atol(buffer->request.lookup_name));
+    if (inode == 0 || inode == LONG_MAX) {
+        LOG(ERROR) << "Invalid filename";
+        return loop->ReplySimple(buffer->request.header.unique, -ENOENT);
+    }
+
+    callback->OnLookup(buffer->request.header.unique, inode);
+    return true;
 }
 
-void HandleOpen(FuseBuffer* buffer, FuseAppLoopCallback* callback) {
-  const int32_t file_handle = callback->OnOpen(buffer->request.header.nodeid);
-  if (file_handle < 0) {
-    buffer->response.Reset(0, file_handle, buffer->request.header.unique);
-    return;
-  }
-  buffer->response.Reset(sizeof(fuse_open_out), kFuseSuccess,
-                         buffer->request.header.unique);
-  buffer->response.open_out.fh = file_handle;
+bool HandleGetAttr(FuseAppLoop* loop, FuseBuffer* buffer, FuseAppLoopCallback* callback) {
+    if (buffer->request.header.nodeid == FUSE_ROOT_ID) {
+        return loop->ReplyGetAttr(buffer->request.header.unique, buffer->request.header.nodeid, 0,
+                                  S_IFDIR | 0777);
+    } else {
+        callback->OnGetAttr(buffer->request.header.unique, buffer->request.header.nodeid);
+        return true;
+    }
 }
 
-void HandleFsync(FuseBuffer* buffer, FuseAppLoopCallback* callback) {
-  buffer->response.Reset(0, callback->OnFsync(buffer->request.header.nodeid),
-                         buffer->request.header.unique);
+bool HandleRead(FuseAppLoop* loop, FuseBuffer* buffer, FuseAppLoopCallback* callback) {
+    if (buffer->request.read_in.size > kFuseMaxRead) {
+        return loop->ReplySimple(buffer->request.header.unique, -EINVAL);
+    }
+
+    callback->OnRead(buffer->request.header.unique, buffer->request.header.nodeid,
+                     buffer->request.read_in.offset, buffer->request.read_in.size);
+    return true;
 }
 
-void HandleRelease(FuseBuffer* buffer, FuseAppLoopCallback* callback) {
-  buffer->response.Reset(0, callback->OnRelease(buffer->request.header.nodeid),
-                         buffer->request.header.unique);
+bool HandleWrite(FuseAppLoop* loop, FuseBuffer* buffer, FuseAppLoopCallback* callback) {
+    if (buffer->request.write_in.size > kFuseMaxWrite) {
+        return loop->ReplySimple(buffer->request.header.unique, -EINVAL);
+    }
+
+    callback->OnWrite(buffer->request.header.unique, buffer->request.header.nodeid,
+                      buffer->request.write_in.offset, buffer->request.write_in.size,
+                      buffer->request.write_data);
+    return true;
 }
 
-void HandleRead(FuseBuffer* buffer, FuseAppLoopCallback* callback) {
-  const uint64_t unique = buffer->request.header.unique;
-  const uint64_t nodeid = buffer->request.header.nodeid;
-  const uint64_t offset = buffer->request.read_in.offset;
-  const uint32_t size = buffer->request.read_in.size;
+bool HandleMessage(FuseAppLoop* loop, FuseBuffer* buffer, int fd, FuseAppLoopCallback* callback) {
+    if (!buffer->request.Read(fd)) {
+        return false;
+    }
 
-  if (size > kFuseMaxRead) {
-    buffer->response.Reset(0, -EINVAL, buffer->request.header.unique);
-    return;
-  }
+    const uint32_t opcode = buffer->request.header.opcode;
+    LOG(VERBOSE) << "Read a fuse packet, opcode=" << opcode;
+    switch (opcode) {
+        case FUSE_FORGET:
+            // Do not reply to FUSE_FORGET.
+            return true;
 
-  const int32_t read_size = callback->OnRead(nodeid, offset, size,
-                                             buffer->response.read_data);
-  if (read_size < 0) {
-    buffer->response.Reset(0, read_size, buffer->request.header.unique);
-    return;
-  }
+        case FUSE_LOOKUP:
+            return HandleLookUp(loop, buffer, callback);
 
-  buffer->response.ResetHeader(read_size, kFuseSuccess, unique);
-}
+        case FUSE_GETATTR:
+            return HandleGetAttr(loop, buffer, callback);
 
-void HandleWrite(FuseBuffer* buffer, FuseAppLoopCallback* callback) {
-  const uint64_t unique = buffer->request.header.unique;
-  const uint64_t nodeid = buffer->request.header.nodeid;
-  const uint64_t offset = buffer->request.write_in.offset;
-  const uint32_t size = buffer->request.write_in.size;
+        case FUSE_OPEN:
+            callback->OnOpen(buffer->request.header.unique, buffer->request.header.nodeid);
+            return true;
 
-  if (size > kFuseMaxWrite) {
-    buffer->response.Reset(0, -EINVAL, buffer->request.header.unique);
-    return;
-  }
+        case FUSE_READ:
+            return HandleRead(loop, buffer, callback);
 
-  const int32_t write_size = callback->OnWrite(nodeid, offset, size,
-                                               buffer->request.write_data);
-  if (write_size < 0) {
-    buffer->response.Reset(0, write_size, buffer->request.header.unique);
-    return;
-  }
+        case FUSE_WRITE:
+            return HandleWrite(loop, buffer, callback);
 
-  buffer->response.Reset(sizeof(fuse_write_out), kFuseSuccess, unique);
-  buffer->response.write_out.size = write_size;
+        case FUSE_RELEASE:
+            callback->OnRelease(buffer->request.header.unique, buffer->request.header.nodeid);
+            return true;
+
+        case FUSE_FSYNC:
+            callback->OnFsync(buffer->request.header.unique, buffer->request.header.nodeid);
+            return true;
+
+        default:
+            buffer->HandleNotImpl();
+            return buffer->response.Write(fd);
+    }
 }
 
 } // namespace
 
-bool StartFuseAppLoop(int raw_fd, FuseAppLoopCallback* callback) {
-  base::unique_fd fd(raw_fd);
-  FuseBuffer buffer;
+FuseAppLoopCallback::~FuseAppLoopCallback() = default;
 
-  LOG(DEBUG) << "Start fuse loop.";
-  while (callback->IsActive()) {
-    if (!buffer.request.Read(fd)) {
-      return false;
+FuseAppLoop::FuseAppLoop(base::unique_fd&& fd) : fd_(std::move(fd)) {}
+
+void FuseAppLoop::Break() {
+    const int64_t value = 1;
+    if (write(break_fd_, &value, sizeof(value)) == -1) {
+        PLOG(ERROR) << "Failed to send a break event";
+    }
+}
+
+bool FuseAppLoop::ReplySimple(uint64_t unique, int32_t result) {
+    if (result == -ENOSYS) {
+        // We should not return -ENOSYS because the kernel stops delivering FUSE
+        // command after receiving -ENOSYS as a result for the command.
+        result = -EBADF;
+    }
+    FuseSimpleResponse response;
+    response.Reset(0, result, unique);
+    return response.Write(fd_);
+}
+
+bool FuseAppLoop::ReplyLookup(uint64_t unique, uint64_t inode, int64_t size) {
+    FuseSimpleResponse response;
+    response.Reset(sizeof(fuse_entry_out), 0, unique);
+    response.entry_out.nodeid = inode;
+    response.entry_out.attr_valid = 10;
+    response.entry_out.entry_valid = 10;
+    response.entry_out.attr.ino = inode;
+    response.entry_out.attr.mode = S_IFREG | 0777;
+    response.entry_out.attr.size = size;
+    return response.Write(fd_);
+}
+
+bool FuseAppLoop::ReplyGetAttr(uint64_t unique, uint64_t inode, int64_t size, int mode) {
+    CHECK(mode == (S_IFREG | 0777) || mode == (S_IFDIR | 0777));
+    FuseSimpleResponse response;
+    response.Reset(sizeof(fuse_attr_out), 0, unique);
+    response.attr_out.attr_valid = 10;
+    response.attr_out.attr.ino = inode;
+    response.attr_out.attr.mode = mode;
+    response.attr_out.attr.size = size;
+    return response.Write(fd_);
+}
+
+bool FuseAppLoop::ReplyOpen(uint64_t unique, uint64_t fh) {
+    FuseSimpleResponse response;
+    response.Reset(sizeof(fuse_open_out), kFuseSuccess, unique);
+    response.open_out.fh = fh;
+    return response.Write(fd_);
+}
+
+bool FuseAppLoop::ReplyWrite(uint64_t unique, uint32_t size) {
+    CHECK(size <= kFuseMaxWrite);
+    FuseSimpleResponse response;
+    response.Reset(sizeof(fuse_write_out), kFuseSuccess, unique);
+    response.write_out.size = size;
+    return response.Write(fd_);
+}
+
+bool FuseAppLoop::ReplyRead(uint64_t unique, uint32_t size, const void* data) {
+    CHECK(size <= kFuseMaxRead);
+    FuseSimpleResponse response;
+    response.ResetHeader(size, kFuseSuccess, unique);
+    return response.WriteWithBody(fd_, sizeof(FuseResponse), data);
+}
+
+void FuseAppLoop::Start(FuseAppLoopCallback* callback) {
+    break_fd_.reset(eventfd(/* initval */ 0, EFD_CLOEXEC));
+    if (break_fd_.get() == -1) {
+        PLOG(ERROR) << "Failed to open FD for break event";
+        return;
     }
 
-    const uint32_t opcode = buffer.request.header.opcode;
-    LOG(VERBOSE) << "Read a fuse packet, opcode=" << opcode;
-    switch (opcode) {
-      case FUSE_FORGET:
-        // Do not reply to FUSE_FORGET.
-        continue;
-
-      case FUSE_LOOKUP:
-        HandleLookUp(&buffer, callback);
-        break;
-
-      case FUSE_GETATTR:
-        HandleGetAttr(&buffer, callback);
-        break;
-
-      case FUSE_OPEN:
-        HandleOpen(&buffer, callback);
-        break;
-
-      case FUSE_READ:
-        HandleRead(&buffer, callback);
-        break;
-
-      case FUSE_WRITE:
-        HandleWrite(&buffer, callback);
-        break;
-
-      case FUSE_RELEASE:
-        HandleRelease(&buffer, callback);
-        break;
-
-      case FUSE_FSYNC:
-        HandleFsync(&buffer, callback);
-        break;
-
-      default:
-        buffer.HandleNotImpl();
-        break;
+    base::unique_fd epoll_fd(epoll_create1(EPOLL_CLOEXEC));
+    if (epoll_fd.get() == -1) {
+        PLOG(ERROR) << "Failed to open FD for epoll";
+        return;
     }
 
-    if (!buffer.response.Write(fd)) {
-      LOG(ERROR) << "Failed to write a response to the device.";
-      return false;
-    }
-  }
+    int last_event;
+    int break_event;
 
-  return true;
+    std::unique_ptr<EpollController> epoll_controller(new EpollController(std::move(epoll_fd)));
+    if (!epoll_controller->AddFd(fd_, EPOLLIN, &last_event)) {
+        return;
+    }
+    if (!epoll_controller->AddFd(break_fd_, EPOLLIN, &break_event)) {
+        return;
+    }
+
+    last_event = 0;
+    break_event = 0;
+
+    FuseBuffer buffer;
+    while (true) {
+        if (!epoll_controller->Wait(1)) {
+            break;
+        }
+        last_event = 0;
+        *reinterpret_cast<int*>(epoll_controller->events()[0].data.ptr) =
+            epoll_controller->events()[0].events;
+
+        if (break_event != 0 || (last_event & ~EPOLLIN) != 0) {
+            break;
+        }
+
+        if (!HandleMessage(this, &buffer, fd_, callback)) {
+            break;
+        }
+    }
+
+    LOG(VERBOSE) << "FuseAppLoop exit";
 }
 
 }  // namespace fuse
diff --git a/libappfuse/FuseBuffer.cc b/libappfuse/FuseBuffer.cc
index 5bc5497..b42a049 100644
--- a/libappfuse/FuseBuffer.cc
+++ b/libappfuse/FuseBuffer.cc
@@ -34,6 +34,8 @@
 namespace fuse {
 namespace {
 
+constexpr useconds_t kRetrySleepForWriting = 1000;  // 1 ms
+
 template <typename T>
 bool CheckHeaderLength(const FuseMessage<T>* self, const char* name, size_t max_size) {
     const auto& header = static_cast<const T*>(self)->header;
@@ -91,28 +93,35 @@
     const char* const buf = reinterpret_cast<const char*>(self);
     const auto& header = static_cast<const T*>(self)->header;
 
-    int result;
-    if (sockflag) {
-        CHECK(data == nullptr);
-        result = TEMP_FAILURE_RETRY(send(fd, buf, header.len, sockflag));
-    } else if (data) {
-        const struct iovec vec[] = {{const_cast<char*>(buf), sizeof(header)},
-                                    {const_cast<void*>(data), header.len - sizeof(header)}};
-        result = TEMP_FAILURE_RETRY(writev(fd, vec, arraysize(vec)));
-    } else {
-        result = TEMP_FAILURE_RETRY(write(fd, buf, header.len));
-    }
-
-    if (result == -1) {
-        if (errno == EAGAIN) {
-            return ResultOrAgain::kAgain;
+    while (true) {
+        int result;
+        if (sockflag) {
+            CHECK(data == nullptr);
+            result = TEMP_FAILURE_RETRY(send(fd, buf, header.len, sockflag));
+        } else if (data) {
+            const struct iovec vec[] = {{const_cast<char*>(buf), sizeof(header)},
+                                        {const_cast<void*>(data), header.len - sizeof(header)}};
+            result = TEMP_FAILURE_RETRY(writev(fd, vec, arraysize(vec)));
+        } else {
+            result = TEMP_FAILURE_RETRY(write(fd, buf, header.len));
         }
-        PLOG(ERROR) << "Failed to write a FUSE message";
-        return ResultOrAgain::kFailure;
+        if (result == -1) {
+            switch (errno) {
+                case ENOBUFS:
+                    // When returning ENOBUFS, epoll still reports the FD is writable. Just usleep
+                    // and retry again.
+                    usleep(kRetrySleepForWriting);
+                    continue;
+                case EAGAIN:
+                    return ResultOrAgain::kAgain;
+                default:
+                    PLOG(ERROR) << "Failed to write a FUSE message";
+                    return ResultOrAgain::kFailure;
+            }
+        }
+        CHECK(static_cast<uint32_t>(result) == header.len);
+        return ResultOrAgain::kSuccess;
     }
-
-    CHECK(static_cast<uint32_t>(result) == header.len);
-    return ResultOrAgain::kSuccess;
 }
 }
 
@@ -161,7 +170,7 @@
 template <typename T>
 bool FuseMessage<T>::WriteWithBody(int fd, size_t max_size, const void* data) const {
     CHECK(data != nullptr);
-    return WriteInternal<T>(this, fd, 0, data, max_size) == ResultOrAgain::kSuccess;
+    return WriteInternal(this, fd, 0, data, max_size) == ResultOrAgain::kSuccess;
 }
 
 template <typename T>
diff --git a/libappfuse/include/libappfuse/FuseAppLoop.h b/libappfuse/include/libappfuse/FuseAppLoop.h
index c3edfcc..f2ef2b5 100644
--- a/libappfuse/include/libappfuse/FuseAppLoop.h
+++ b/libappfuse/include/libappfuse/FuseAppLoop.h
@@ -17,23 +17,51 @@
 #ifndef ANDROID_LIBAPPFUSE_FUSEAPPLOOP_H_
 #define ANDROID_LIBAPPFUSE_FUSEAPPLOOP_H_
 
+#include <memory>
+#include <mutex>
+
+#include <android-base/unique_fd.h>
+
 #include "libappfuse/FuseBuffer.h"
 
 namespace android {
 namespace fuse {
 
+class EpollController;
+
 class FuseAppLoopCallback {
  public:
-  virtual bool IsActive() = 0;
-  virtual int64_t OnGetSize(uint64_t inode) = 0;
-  virtual int32_t OnFsync(uint64_t inode) = 0;
-  virtual int32_t OnWrite(
-      uint64_t inode, uint64_t offset, uint32_t size, const void* data) = 0;
-  virtual int32_t OnRead(
-      uint64_t inode, uint64_t offset, uint32_t size, void* data) = 0;
-  virtual int32_t OnOpen(uint64_t inode) = 0;
-  virtual int32_t OnRelease(uint64_t inode) = 0;
-  virtual ~FuseAppLoopCallback() = default;
+   virtual void OnLookup(uint64_t unique, uint64_t inode) = 0;
+   virtual void OnGetAttr(uint64_t unique, uint64_t inode) = 0;
+   virtual void OnFsync(uint64_t unique, uint64_t inode) = 0;
+   virtual void OnWrite(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size,
+                        const void* data) = 0;
+   virtual void OnRead(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size) = 0;
+   virtual void OnOpen(uint64_t unique, uint64_t inode) = 0;
+   virtual void OnRelease(uint64_t unique, uint64_t inode) = 0;
+   virtual ~FuseAppLoopCallback();
+};
+
+class FuseAppLoop final {
+  public:
+    FuseAppLoop(base::unique_fd&& fd);
+
+    void Start(FuseAppLoopCallback* callback);
+    void Break();
+
+    bool ReplySimple(uint64_t unique, int32_t result);
+    bool ReplyLookup(uint64_t unique, uint64_t inode, int64_t size);
+    bool ReplyGetAttr(uint64_t unique, uint64_t inode, int64_t size, int mode);
+    bool ReplyOpen(uint64_t unique, uint64_t fh);
+    bool ReplyWrite(uint64_t unique, uint32_t size);
+    bool ReplyRead(uint64_t unique, uint32_t size, const void* data);
+
+  private:
+    base::unique_fd fd_;
+    base::unique_fd break_fd_;
+
+    // Lock for multi-threading.
+    std::mutex mutex_;
 };
 
 bool StartFuseAppLoop(int fd, FuseAppLoopCallback* callback);
diff --git a/libappfuse/tests/FuseAppLoopTest.cc b/libappfuse/tests/FuseAppLoopTest.cc
index 64dd813..98e3665 100644
--- a/libappfuse/tests/FuseAppLoopTest.cc
+++ b/libappfuse/tests/FuseAppLoopTest.cc
@@ -23,6 +23,9 @@
 #include <gtest/gtest.h>
 #include <thread>
 
+#include "libappfuse/EpollController.h"
+#include "libappfuse/FuseBridgeLoop.h"
+
 namespace android {
 namespace fuse {
 namespace {
@@ -37,82 +40,61 @@
 class Callback : public FuseAppLoopCallback {
  public:
   std::vector<CallbackRequest> requests;
+  FuseAppLoop* loop;
 
-  bool IsActive() override {
-    return true;
+  void OnGetAttr(uint64_t seq, uint64_t inode) override {
+      EXPECT_NE(FUSE_ROOT_ID, static_cast<int>(inode));
+      EXPECT_TRUE(loop->ReplyGetAttr(seq, inode, kTestFileSize, S_IFREG | 0777));
   }
 
-  int64_t OnGetSize(uint64_t inode) override {
-    if (inode == FUSE_ROOT_ID) {
-      return 0;
-    } else {
-      return kTestFileSize;
-    }
+  void OnLookup(uint64_t unique, uint64_t inode) override {
+      EXPECT_NE(FUSE_ROOT_ID, static_cast<int>(inode));
+      EXPECT_TRUE(loop->ReplyLookup(unique, inode, kTestFileSize));
   }
 
-  int32_t OnFsync(uint64_t inode) override {
-    requests.push_back({
-      .code = FUSE_FSYNC,
-      .inode = inode
-    });
-    return 0;
+  void OnFsync(uint64_t seq, uint64_t inode) override {
+      requests.push_back({.code = FUSE_FSYNC, .inode = inode});
+      loop->ReplySimple(seq, 0);
   }
 
-  int32_t OnWrite(uint64_t inode,
-                  uint64_t offset ATTRIBUTE_UNUSED,
-                  uint32_t size ATTRIBUTE_UNUSED,
-                  const void* data ATTRIBUTE_UNUSED) override {
-    requests.push_back({
-      .code = FUSE_WRITE,
-      .inode = inode
-    });
-    return 0;
+  void OnWrite(uint64_t seq, uint64_t inode, uint64_t offset ATTRIBUTE_UNUSED,
+               uint32_t size ATTRIBUTE_UNUSED, const void* data ATTRIBUTE_UNUSED) override {
+      requests.push_back({.code = FUSE_WRITE, .inode = inode});
+      loop->ReplyWrite(seq, 0);
   }
 
-  int32_t OnRead(uint64_t inode,
-                 uint64_t offset ATTRIBUTE_UNUSED,
-                 uint32_t size ATTRIBUTE_UNUSED,
-                 void* data ATTRIBUTE_UNUSED) override {
-    requests.push_back({
-      .code = FUSE_READ,
-      .inode = inode
-    });
-    return 0;
+  void OnRead(uint64_t seq, uint64_t inode, uint64_t offset ATTRIBUTE_UNUSED,
+              uint32_t size ATTRIBUTE_UNUSED) override {
+      requests.push_back({.code = FUSE_READ, .inode = inode});
+      loop->ReplySimple(seq, 0);
   }
 
-  int32_t OnOpen(uint64_t inode) override {
-    requests.push_back({
-      .code = FUSE_OPEN,
-      .inode = inode
-    });
-    return 0;
+  void OnOpen(uint64_t seq, uint64_t inode) override {
+      requests.push_back({.code = FUSE_OPEN, .inode = inode});
+      loop->ReplyOpen(seq, inode);
   }
 
-  int32_t OnRelease(uint64_t inode) override {
-    requests.push_back({
-      .code = FUSE_RELEASE,
-      .inode = inode
-    });
-    return 0;
+  void OnRelease(uint64_t seq, uint64_t inode) override {
+      requests.push_back({.code = FUSE_RELEASE, .inode = inode});
+      loop->ReplySimple(seq, 0);
   }
 };
 
 class FuseAppLoopTest : public ::testing::Test {
- private:
-  std::thread thread_;
-
  protected:
-  base::unique_fd sockets_[2];
-  Callback callback_;
-  FuseRequest request_;
-  FuseResponse response_;
+   std::thread thread_;
+   base::unique_fd sockets_[2];
+   Callback callback_;
+   FuseRequest request_;
+   FuseResponse response_;
+   std::unique_ptr<FuseAppLoop> loop_;
 
-  void SetUp() override {
-    base::SetMinimumLogSeverity(base::VERBOSE);
-    ASSERT_TRUE(SetupMessageSockets(&sockets_));
-    thread_ = std::thread([this] {
-      StartFuseAppLoop(sockets_[1].release(), &callback_);
-    });
+   void SetUp() override {
+       base::SetMinimumLogSeverity(base::VERBOSE);
+       ASSERT_TRUE(SetupMessageSockets(&sockets_));
+       loop_.reset(new FuseAppLoop(std::move(sockets_[1])));
+       callback_.loop = loop_.get();
+       thread_ = std::thread([this] { loop_->Start(&callback_); });
   }
 
   void CheckCallback(
@@ -300,5 +282,18 @@
   CheckCallback(sizeof(fuse_write_in), FUSE_WRITE, sizeof(fuse_write_out));
 }
 
+TEST_F(FuseAppLoopTest, Break) {
+    // Ensure that the loop started.
+    request_.Reset(sizeof(fuse_open_in), FUSE_OPEN, 1);
+    request_.header.nodeid = 10;
+    ASSERT_TRUE(request_.Write(sockets_[0]));
+    ASSERT_TRUE(response_.Read(sockets_[0]));
+
+    loop_->Break();
+    if (thread_.joinable()) {
+        thread_.join();
+    }
+}
+
 }  // namespace fuse
 }  // namespace android
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 11bd8cc..47f6ff3 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -24,6 +24,8 @@
 
 int killProcessGroup(uid_t uid, int initialPid, int signal);
 
+int killProcessGroupOnce(uid_t uid, int initialPid, int signal);
+
 int createProcessGroup(uid_t uid, int initialPid);
 
 void removeAllProcessGroups(void);
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index eb66727..1572cb3 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -252,8 +252,7 @@
     }
 }
 
-static int killProcessGroupOnce(uid_t uid, int initialPid, int signal)
-{
+static int doKillProcessGroupOnce(uid_t uid, int initialPid, int signal) {
     int processes = 0;
     struct ctx ctx;
     pid_t pid;
@@ -282,13 +281,11 @@
     return processes;
 }
 
-int killProcessGroup(uid_t uid, int initialPid, int signal)
-{
+static int killProcessGroup(uid_t uid, int initialPid, int signal, int retry) {
     std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
 
-    int retry = 40;
     int processes;
-    while ((processes = killProcessGroupOnce(uid, initialPid, signal)) > 0) {
+    while ((processes = doKillProcessGroupOnce(uid, initialPid, signal)) > 0) {
         LOG(VERBOSE) << "killed " << processes << " processes for processgroup " << initialPid;
         if (retry > 0) {
             std::this_thread::sleep_for(5ms);
@@ -313,6 +310,14 @@
     }
 }
 
+int killProcessGroup(uid_t uid, int initialPid, int signal) {
+    return killProcessGroup(uid, initialPid, signal, 40 /*maxRetry*/);
+}
+
+int killProcessGroupOnce(uid_t uid, int initialPid, int signal) {
+    return killProcessGroup(uid, initialPid, signal, 0 /*maxRetry*/);
+}
+
 static bool mkdirAndChown(const char *path, mode_t mode, uid_t uid, gid_t gid)
 {
     if (mkdir(path, mode) == -1 && errno != EEXIST) {
diff --git a/libutils/RefBase.cpp b/libutils/RefBase.cpp
index 0d98db9..24737b9 100644
--- a/libutils/RefBase.cpp
+++ b/libutils/RefBase.cpp
@@ -760,6 +760,4 @@
     ref->mRefs->renameWeakRefId(old_id, new_id);
 }
 
-VirtualLightRefBase::~VirtualLightRefBase() {}
-
 }; // namespace android
diff --git a/libutils/include/utils/LightRefBase.h b/libutils/include/utils/LightRefBase.h
new file mode 100644
index 0000000..65257ed
--- /dev/null
+++ b/libutils/include/utils/LightRefBase.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+/*
+ * See documentation in RefBase.h
+ */
+
+#include <atomic>
+
+#include <sys/types.h>
+
+namespace android {
+
+class ReferenceRenamer;
+
+template <class T>
+class LightRefBase
+{
+public:
+    inline LightRefBase() : mCount(0) { }
+    inline void incStrong(__attribute__((unused)) const void* id) const {
+        mCount.fetch_add(1, std::memory_order_relaxed);
+    }
+    inline void decStrong(__attribute__((unused)) const void* id) const {
+        if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
+            std::atomic_thread_fence(std::memory_order_acquire);
+            delete static_cast<const T*>(this);
+        }
+    }
+    //! DEBUGGING ONLY: Get current strong ref count.
+    inline int32_t getStrongCount() const {
+        return mCount.load(std::memory_order_relaxed);
+    }
+
+    typedef LightRefBase<T> basetype;
+
+protected:
+    inline ~LightRefBase() { }
+
+private:
+    friend class ReferenceMover;
+    inline static void renameRefs(size_t /*n*/, const ReferenceRenamer& /*renamer*/) { }
+    inline static void renameRefId(T* /*ref*/, const void* /*old_id*/ , const void* /*new_id*/) { }
+
+private:
+    mutable std::atomic<int32_t> mCount;
+};
+
+
+// This is a wrapper around LightRefBase that simply enforces a virtual
+// destructor to eliminate the template requirement of LightRefBase
+class VirtualLightRefBase : public LightRefBase<VirtualLightRefBase> {
+public:
+    virtual ~VirtualLightRefBase() = default;
+};
+
+}; // namespace android
diff --git a/libutils/include/utils/RefBase.h b/libutils/include/utils/RefBase.h
index a61ea58..223b666 100644
--- a/libutils/include/utils/RefBase.h
+++ b/libutils/include/utils/RefBase.h
@@ -177,6 +177,9 @@
 #include <stdlib.h>
 #include <string.h>
 
+// LightRefBase used to be declared in this header, so we have to include it
+#include <utils/LightRefBase.h>
+
 #include <utils/StrongPointer.h>
 #include <utils/TypeHelpers.h>
 
@@ -216,7 +219,7 @@
 
 class ReferenceRenamer {
 protected:
-    // destructor is purposedly not virtual so we avoid code overhead from
+    // destructor is purposely not virtual so we avoid code overhead from
     // subclasses; we have to make it protected to guarantee that it
     // cannot be called from this base class (and to make strict compilers
     // happy).
@@ -246,13 +249,13 @@
     {
     public:
         RefBase*            refBase() const;
-        
+
         void                incWeak(const void* id);
         void                decWeak(const void* id);
-        
+
         // acquires a strong reference if there is already one.
         bool                attemptIncStrong(const void* id);
-        
+
         // acquires a weak reference if there is already one.
         // This is not always safe. see ProcessState.cpp and BpBinder.cpp
         // for proper use.
@@ -268,12 +271,12 @@
         // enable -- enable/disable tracking
         // retain -- when tracking is enable, if true, then we save a stack trace
         //           for each reference and dereference; when retain == false, we
-        //           match up references and dereferences and keep only the 
+        //           match up references and dereferences and keep only the
         //           outstanding ones.
-        
+
         void                trackMe(bool enable, bool retain);
     };
-    
+
             weakref_type*   createWeak(const void* id) const;
             
             weakref_type*   getWeakRefs() const;
@@ -345,54 +348,12 @@
 
 // ---------------------------------------------------------------------------
 
-template <class T>
-class LightRefBase
-{
-public:
-    inline LightRefBase() : mCount(0) { }
-    inline void incStrong(__attribute__((unused)) const void* id) const {
-        mCount.fetch_add(1, std::memory_order_relaxed);
-    }
-    inline void decStrong(__attribute__((unused)) const void* id) const {
-        if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
-            std::atomic_thread_fence(std::memory_order_acquire);
-            delete static_cast<const T*>(this);
-        }
-    }
-    //! DEBUGGING ONLY: Get current strong ref count.
-    inline int32_t getStrongCount() const {
-        return mCount.load(std::memory_order_relaxed);
-    }
-
-    typedef LightRefBase<T> basetype;
-
-protected:
-    inline ~LightRefBase() { }
-
-private:
-    friend class ReferenceMover;
-    inline static void renameRefs(size_t /*n*/, const ReferenceRenamer& /*renamer*/) { }
-    inline static void renameRefId(T* /*ref*/, const void* /*old_id*/ , const void* /*new_id*/) { }
-
-private:
-    mutable std::atomic<int32_t> mCount;
-};
-
-// This is a wrapper around LightRefBase that simply enforces a virtual
-// destructor to eliminate the template requirement of LightRefBase
-class VirtualLightRefBase : public LightRefBase<VirtualLightRefBase> {
-public:
-    virtual ~VirtualLightRefBase();
-};
-
-// ---------------------------------------------------------------------------
-
 template <typename T>
 class wp
 {
 public:
     typedef typename RefBase::weakref_type weakref_type;
-    
+
     inline wp() : m_ptr(0) { }
 
     wp(T* other);  // NOLINT(implicit)
@@ -403,31 +364,31 @@
     template<typename U> wp(const wp<U>& other);  // NOLINT(implicit)
 
     ~wp();
-    
+
     // Assignment
 
     wp& operator = (T* other);
     wp& operator = (const wp<T>& other);
     wp& operator = (const sp<T>& other);
-    
+
     template<typename U> wp& operator = (U* other);
     template<typename U> wp& operator = (const wp<U>& other);
     template<typename U> wp& operator = (const sp<U>& other);
-    
+
     void set_object_and_refs(T* other, weakref_type* refs);
 
     // promotion to sp
-    
+
     sp<T> promote() const;
 
     // Reset
-    
+
     void clear();
 
     // Accessors
-    
+
     inline  weakref_type* get_refs() const { return m_refs; }
-    
+
     inline  T* unsafe_get() const { return m_ptr; }
 
     // Operators
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index 1f52d04..bdbe725 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -109,11 +109,11 @@
 
 LogBuffer::LogBuffer(LastLogTimes* times)
     : monotonic(android_log_clockid() == CLOCK_MONOTONIC), mTimes(*times) {
-    pthread_mutex_init(&mLogElementsLock, NULL);
+    pthread_mutex_init(&mLogElementsLock, nullptr);
 
     log_id_for_each(i) {
-        lastLoggedElements[i] = NULL;
-        droppedElements[i] = NULL;
+        lastLoggedElements[i] = nullptr;
+        droppedElements[i] = nullptr;
     }
 
     init();
@@ -198,7 +198,7 @@
         new LogBufferElement(log_id, realtime, uid, pid, tid, msg, len);
     if (log_id != LOG_ID_SECURITY) {
         int prio = ANDROID_LOG_INFO;
-        const char* tag = NULL;
+        const char* tag = nullptr;
         if (log_id == LOG_ID_EVENTS) {
             tag = tagToName(elem->getTag());
         } else {
@@ -224,24 +224,24 @@
         //
         // State Init
         //     incoming:
-        //         dropped = NULL
-        //         currentLast = NULL;
+        //         dropped = nullptr
+        //         currentLast = nullptr;
         //         elem = incoming message
         //     outgoing:
-        //         dropped = NULL -> State 0
+        //         dropped = nullptr -> State 0
         //         currentLast = copy of elem
         //         log elem
         // State 0
         //     incoming:
         //         count = 0
-        //         dropped = NULL
+        //         dropped = nullptr
         //         currentLast = copy of last message
         //         elem = incoming message
         //     outgoing: if match != DIFFERENT
         //         dropped = copy of first identical message -> State 1
         //         currentLast = reference to elem
         //     break: if match == DIFFERENT
-        //         dropped = NULL -> State 0
+        //         dropped = nullptr -> State 0
         //         delete copy of last message (incoming currentLast)
         //         currentLast = copy of elem
         //         log elem
@@ -268,7 +268,7 @@
         //             currentLast = reference to elem, sum liblog.
         //     break: if match == DIFFERENT
         //         delete dropped
-        //         dropped = NULL -> State 0
+        //         dropped = nullptr -> State 0
         //         log reference to last held-back (currentLast)
         //         currentLast = copy of elem
         //         log elem
@@ -287,7 +287,7 @@
         //         currentLast = reference to elem
         //     break: if match == DIFFERENT
         //         log dropped (chatty message)
-        //         dropped = NULL -> State 0
+        //         dropped = nullptr -> State 0
         //         log reference to last held-back (currentLast)
         //         currentLast = copy of elem
         //         log elem
@@ -352,7 +352,7 @@
             } else {           // State 1
                 delete dropped;
             }
-            droppedElements[log_id] = NULL;
+            droppedElements[log_id] = nullptr;
             log(currentLast);  // report last message in the series
         } else {               // State 0
             delete currentLast;
@@ -656,7 +656,7 @@
 // mLogElementsLock must be held when this function is called.
 //
 bool LogBuffer::prune(log_id_t id, unsigned long pruneRows, uid_t caller_uid) {
-    LogTimeEntry* oldest = NULL;
+    LogTimeEntry* oldest = nullptr;
     bool busy = false;
     bool clearAll = pruneRows == ULONG_MAX;
 
@@ -1078,9 +1078,11 @@
     return retval;
 }
 
-log_time LogBuffer::flushTo(
-    SocketClient* reader, const log_time& start, bool privileged, bool security,
-    int (*filter)(const LogBufferElement* element, void* arg), void* arg) {
+log_time LogBuffer::flushTo(SocketClient* reader, const log_time& start,
+                            pid_t* lastTid, bool privileged, bool security,
+                            int (*filter)(const LogBufferElement* element,
+                                          void* arg),
+                            void* arg) {
     LogBufferElementCollection::iterator it;
     uid_t uid = reader->getUid();
 
@@ -1109,9 +1111,6 @@
     }
 
     log_time max = start;
-    // Help detect if the valid message before is from the same source so
-    // we can differentiate chatty filter types.
-    pid_t lastTid[LOG_ID_MAX] = { 0 };
 
     for (; it != mLogElements.end(); ++it) {
         LogBufferElement* element = *it;
@@ -1139,14 +1138,17 @@
             }
         }
 
-        bool sameTid = lastTid[element->getLogId()] == element->getTid();
-        // Dropped (chatty) immediately following a valid log from the
-        // same source in the same log buffer indicates we have a
-        // multiple identical squash.  chatty that differs source
-        // is due to spam filter.  chatty to chatty of different
-        // source is also due to spam filter.
-        lastTid[element->getLogId()] =
-            (element->getDropped() && !sameTid) ? 0 : element->getTid();
+        bool sameTid = false;
+        if (lastTid) {
+            sameTid = lastTid[element->getLogId()] == element->getTid();
+            // Dropped (chatty) immediately following a valid log from the
+            // same source in the same log buffer indicates we have a
+            // multiple identical squash.  chatty that differs source
+            // is due to spam filter.  chatty to chatty of different
+            // source is also due to spam filter.
+            lastTid[element->getLogId()] =
+                (element->getDropped() && !sameTid) ? 0 : element->getTid();
+        }
 
         pthread_mutex_unlock(&mLogElementsLock);
 
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index fcf6b9a..19d11cb 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -115,11 +115,15 @@
 
     int log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
             const char* msg, unsigned short len);
+    // lastTid is an optional context to help detect if the last previous
+    // valid message was from the same source so we can differentiate chatty
+    // filter types (identical or expired)
     log_time flushTo(SocketClient* writer, const log_time& start,
+                     pid_t* lastTid,  // &lastTid[LOG_ID_MAX] or nullptr
                      bool privileged, bool security,
                      int (*filter)(const LogBufferElement* element,
-                                   void* arg) = NULL,
-                     void* arg = NULL);
+                                   void* arg) = nullptr,
+                     void* arg = nullptr);
 
     bool clear(log_id_t id, uid_t uid = AID_ROOT);
     unsigned long getSize(log_id_t id);
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index 81356fe..04a620c 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -235,7 +235,9 @@
     }
     iovec[1].iov_len = entry.len;
 
-    log_time retval = reader->sendDatav(iovec, 2) ? FLUSH_ERROR : mRealTime;
+    log_time retval = reader->sendDatav(iovec, 1 + (entry.len != 0))
+                          ? FLUSH_ERROR
+                          : mRealTime;
 
     if (buffer) free(buffer);
 
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
index 620d4d0..af19279 100644
--- a/logd/LogReader.cpp
+++ b/logd/LogReader.cpp
@@ -182,7 +182,7 @@
         } logFindStart(pid, logMask, sequence,
                        logbuf().isMonotonic() && android::isMonotonic(start));
 
-        logbuf().flushTo(cli, sequence, FlushCommand::hasReadLogs(cli),
+        logbuf().flushTo(cli, sequence, nullptr, FlushCommand::hasReadLogs(cli),
                          FlushCommand::hasSecurityLogs(cli),
                          logFindStart.callback, &logFindStart);
 
diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp
index 04e531f..ccc550a 100644
--- a/logd/LogTimes.cpp
+++ b/logd/LogTimes.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <errno.h>
+#include <string.h>
 #include <sys/prctl.h>
 
 #include <private/android_logger.h>
@@ -47,7 +48,8 @@
       mEnd(log_time(android_log_clockid())) {
     mTimeout.tv_sec = timeout / NS_PER_SEC;
     mTimeout.tv_nsec = timeout % NS_PER_SEC;
-    pthread_cond_init(&threadTriggeredCondition, NULL);
+    memset(mLastTid, 0, sizeof(mLastTid));
+    pthread_cond_init(&threadTriggeredCondition, nullptr);
     cleanSkip_Locked();
 }
 
@@ -98,7 +100,7 @@
             it++;
         }
 
-        me->mClient = NULL;
+        me->mClient = nullptr;
         reader.release(client);
     }
 
@@ -122,7 +124,7 @@
     SocketClient* client = me->mClient;
     if (!client) {
         me->error();
-        return NULL;
+        return nullptr;
     }
 
     LogBuffer& logbuf = me->mReader.logbuf();
@@ -151,12 +153,12 @@
         unlock();
 
         if (me->mTail) {
-            logbuf.flushTo(client, start, privileged, security, FilterFirstPass,
-                           me);
+            logbuf.flushTo(client, start, nullptr, privileged, security,
+                           FilterFirstPass, me);
             me->leadingDropped = true;
         }
-        start = logbuf.flushTo(client, start, privileged, security,
-                               FilterSecondPass, me);
+        start = logbuf.flushTo(client, start, me->mLastTid, privileged,
+                               security, FilterSecondPass, me);
 
         lock();
 
@@ -182,7 +184,7 @@
 
     pthread_cleanup_pop(true);
 
-    return NULL;
+    return nullptr;
 }
 
 // A first pass to count the number of elements
@@ -281,7 +283,5 @@
 }
 
 void LogTimeEntry::cleanSkip_Locked(void) {
-    for (log_id_t i = LOG_ID_MIN; i < LOG_ID_MAX; i = (log_id_t)(i + 1)) {
-        skipAhead[i] = 0;
-    }
+    memset(skipAhead, 0, sizeof(skipAhead));
 }
diff --git a/logd/LogTimes.h b/logd/LogTimes.h
index 9a3ddab..ec8252e 100644
--- a/logd/LogTimes.h
+++ b/logd/LogTimes.h
@@ -44,6 +44,7 @@
     const unsigned int mLogMask;
     const pid_t mPid;
     unsigned int skipAhead[LOG_ID_MAX];
+    pid_t mLastTid[LOG_ID_MAX];
     unsigned long mCount;
     unsigned long mTail;
     unsigned long mIndex;
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 77b173d..28406c8 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -599,7 +599,7 @@
 
 on nonencrypted
     # A/B update verifier that marks a successful boot.
-    exec - root cache -- /system/bin/update_verifier nonencrypted
+    exec_start update_verifier_nonencrypted
     class_start main
     class_start late_start
 
@@ -622,12 +622,12 @@
 
 on property:vold.decrypt=trigger_restart_min_framework
     # A/B update verifier that marks a successful boot.
-    exec - root cache -- /system/bin/update_verifier trigger_restart_min_framework
+    exec_start update_verifier
     class_start main
 
 on property:vold.decrypt=trigger_restart_framework
     # A/B update verifier that marks a successful boot.
-    exec - root cache -- /system/bin/update_verifier trigger_restart_framework
+    exec_start update_verifier
     class_start main
     class_start late_start
 
diff --git a/storaged/README.properties b/storaged/README.properties
index 70e6026..2d8397f 100644
--- a/storaged/README.properties
+++ b/storaged/README.properties
@@ -1,6 +1,5 @@
 ro.storaged.event.interval    # interval storaged scans for IO stats, in seconds
 ro.storaged.event.perf_check  # check for time spent in event loop, in microseconds
 ro.storaged.disk_stats_pub    # interval storaged publish disk stats, in seconds
-ro.storaged.emmc_info_pub     # interval storaged publish emmc info, in seconds
 ro.storaged.uid_io.interval   # interval storaged checks Per UID IO usage, in seconds
 ro.storaged.uid_io.threshold  # Per UID IO usage limit, in bytes
diff --git a/storaged/include/storaged.h b/storaged/include/storaged.h
index bd1391c..b6a0850 100644
--- a/storaged/include/storaged.h
+++ b/storaged/include/storaged.h
@@ -230,7 +230,6 @@
 // Periodic chores intervals in seconds
 #define DEFAULT_PERIODIC_CHORES_INTERVAL_UNIT ( 60 )
 #define DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH ( 3600 )
-#define DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH ( 86400 )
 #define DEFAULT_PERIODIC_CHORES_INTERVAL_UID_IO ( 3600 )
 #define DEFAULT_PERIODIC_CHORES_INTERVAL_UID_IO_LIMIT (300)
 
@@ -240,7 +239,6 @@
 struct storaged_config {
     int periodic_chores_interval_unit;
     int periodic_chores_interval_disk_stats_publish;
-    int periodic_chores_interval_emmc_info_publish;
     int periodic_chores_interval_uid_io;
     bool proc_uid_io_available;      // whether uid_io is accessible
     bool diskstats_available;   // whether diskstats is accessible
@@ -253,7 +251,6 @@
     storaged_config mConfig;
     disk_stats_publisher mDiskStats;
     disk_stats_monitor mDsm;
-    storage_info_t *info = nullptr;
     uid_monitor mUidm;
     time_t mStarttime;
 public:
@@ -264,9 +261,6 @@
     void pause(void) {
         sleep(mConfig.periodic_chores_interval_unit);
     }
-    void set_storage_info(storage_info_t *storage_info) {
-        info = storage_info;
-    }
 
     time_t get_starttime(void) {
         return mStarttime;
diff --git a/storaged/include/storaged_info.h b/storaged/include/storaged_info.h
index cb5b8a8..913c814 100644
--- a/storaged/include/storaged_info.h
+++ b/storaged/include/storaged_info.h
@@ -24,43 +24,42 @@
 
 using namespace std;
 
-// two characters in string for each byte
-struct str_hex {
-    char str[2];
-};
-
 class storage_info_t {
 protected:
     FRIEND_TEST(storaged_test, storage_info_t);
-    uint8_t eol;                    // pre-eol (end of life) information
-    uint8_t lifetime_a;             // device life time estimation (type A)
-    uint8_t lifetime_b;             // device life time estimation (type B)
+    uint16_t eol;                   // pre-eol (end of life) information
+    uint16_t lifetime_a;            // device life time estimation (type A)
+    uint16_t lifetime_b;            // device life time estimation (type B)
     string version;                 // version string
-public:
     void publish();
+public:
+    storage_info_t() : eol(0), lifetime_a(0), lifetime_b(0) {}
     virtual ~storage_info_t() {}
-    virtual bool init() = 0;
-    virtual bool update() = 0;
+    virtual bool report() = 0;
 };
 
 class emmc_info_t : public storage_info_t {
 private:
-    // minimum size of a ext_csd file
-    const int EXT_CSD_FILE_MIN_SIZE = 1024;
-    // List of interesting offsets
-    const size_t EXT_CSD_REV_IDX = 192 * sizeof(str_hex);
-    const size_t EXT_PRE_EOL_INFO_IDX = 267 * sizeof(str_hex);
-    const size_t EXT_DEVICE_LIFE_TIME_EST_A_IDX = 268 * sizeof(str_hex);
-    const size_t EXT_DEVICE_LIFE_TIME_EST_B_IDX = 269 * sizeof(str_hex);
-
-    const char* ext_csd_file = "/d/mmc0/mmc0:0001/ext_csd";
-    const char* emmc_ver_str[8] = {
-        "4.0", "4.1", "4.2", "4.3", "Obsolete", "4.41", "4.5", "5.0"
+    const string emmc_sysfs = "/sys/bus/mmc/devices/mmc0:0001/";
+    const string emmc_debugfs = "/d/mmc0/mmc0:0001/ext_csd";
+    const char* emmc_ver_str[9] = {
+        "4.0", "4.1", "4.2", "4.3", "Obsolete", "4.41", "4.5", "5.0", "5.1"
     };
 public:
     virtual ~emmc_info_t() {}
-    bool init();
-    bool update();
+    bool report();
+    bool report_sysfs();
+    bool report_debugfs();
 };
 
+class ufs_info_t : public storage_info_t {
+private:
+    const string health_file = "/sys/devices/soc/624000.ufshc/health";
+public:
+    virtual ~ufs_info_t() {}
+    bool report();
+};
+
+void report_storage_health();
+
 #endif /* _STORAGED_INFO_H_ */
diff --git a/storaged/main.cpp b/storaged/main.cpp
index e25298b..2f2273d 100644
--- a/storaged/main.cpp
+++ b/storaged/main.cpp
@@ -43,7 +43,6 @@
 #include <storaged_utils.h>
 
 storaged_t storaged;
-emmc_info_t emmc_info;
 
 // Function of storaged's main thread
 void* storaged_main(void* s) {
@@ -114,10 +113,7 @@
     }
 
     if (flag_main_service) { // start main thread
-        if (emmc_info.init()) {
-            storaged.set_storage_info(&emmc_info);
-        }
-
+        report_storage_health();
         // Start the main thread of storaged
         pthread_t storaged_main_thread;
         errno = pthread_create(&storaged_main_thread, NULL, storaged_main, &storaged);
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index 88fbb7a..1770922 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -170,6 +170,9 @@
 }
 
 void storaged_t::init_battery_service() {
+    if (!mConfig.proc_uid_io_available)
+        return;
+
     sp<IBatteryPropertiesRegistrar> battery_properties = get_battery_properties_service();
     if (battery_properties == NULL) {
         LOG_TO(SYSTEM, WARNING) << "failed to find batteryproperties service";
@@ -203,9 +206,6 @@
     mConfig.periodic_chores_interval_disk_stats_publish =
         property_get_int32("ro.storaged.disk_stats_pub", DEFAULT_PERIODIC_CHORES_INTERVAL_DISK_STATS_PUBLISH);
 
-    mConfig.periodic_chores_interval_emmc_info_publish =
-        property_get_int32("ro.storaged.emmc_info_pub", DEFAULT_PERIODIC_CHORES_INTERVAL_EMMC_INFO_PUBLISH);
-
     mConfig.periodic_chores_interval_uid_io =
         property_get_int32("ro.storaged.uid_io.interval", DEFAULT_PERIODIC_CHORES_INTERVAL_UID_IO);
 
@@ -221,12 +221,6 @@
         }
     }
 
-    if (info && mTimer &&
-        (mTimer % mConfig.periodic_chores_interval_emmc_info_publish) == 0) {
-        info->update();
-        info->publish();
-    }
-
     if (mConfig.proc_uid_io_available && mTimer &&
             (mTimer % mConfig.periodic_chores_interval_uid_io) == 0) {
          mUidm.report();
diff --git a/storaged/storaged_info.cpp b/storaged/storaged_info.cpp
index 73d611c..434bd74 100644
--- a/storaged/storaged_info.cpp
+++ b/storaged/storaged_info.cpp
@@ -16,83 +16,169 @@
 
 #define LOG_TAG "storaged"
 
+#include <stdio.h>
 #include <string.h>
 
 #include <android-base/file.h>
-#include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <log/log_event_list.h>
 
 #include "storaged.h"
 
 using namespace std;
-using namespace android;
 using namespace android::base;
 
+void report_storage_health()
+{
+    emmc_info_t mmc;
+    ufs_info_t ufs;
+
+    mmc.report();
+    ufs.report();
+}
+
 void storage_info_t::publish()
 {
-    if (eol == 0 && lifetime_a == 0 && lifetime_b == 0) {
-        return;
-    }
-
     android_log_event_list(EVENTLOGTAG_EMMCINFO)
         << version << eol << lifetime_a << lifetime_b
         << LOG_ID_EVENTS;
 }
 
-bool emmc_info_t::init()
+bool emmc_info_t::report()
+{
+    if (!report_sysfs() && !report_debugfs())
+        return false;
+
+    publish();
+    return true;
+}
+
+bool emmc_info_t::report_sysfs()
 {
     string buffer;
-    if (!ReadFileToString(ext_csd_file, &buffer) ||
-        buffer.length() < (size_t)EXT_CSD_FILE_MIN_SIZE) {
+    uint16_t rev = 0;
+
+    if (!ReadFileToString(emmc_sysfs + "rev", &buffer)) {
         return false;
     }
 
-    string ver_str = buffer.substr(EXT_CSD_REV_IDX, sizeof(str_hex));
-    uint8_t ext_csd_rev;
-    if (!ParseUint(ver_str, &ext_csd_rev)) {
-        LOG_TO(SYSTEM, ERROR) << "Failure on parsing EXT_CSD_REV.";
+    if (sscanf(buffer.c_str(), "0x%hx", &rev) < 1 ||
+        rev < 7 || rev > ARRAY_SIZE(emmc_ver_str)) {
         return false;
     }
 
     version = "emmc ";
-    version += (ext_csd_rev < ARRAY_SIZE(emmc_ver_str)) ?
-                emmc_ver_str[ext_csd_rev] : "Unknown";
+    version += emmc_ver_str[rev];
 
-    if (ext_csd_rev < 7) {
+    if (!ReadFileToString(emmc_sysfs + "pre_eol_info", &buffer)) {
         return false;
     }
 
-    return update();
-}
-
-bool emmc_info_t::update()
-{
-    string buffer;
-    if (!ReadFileToString(ext_csd_file, &buffer) ||
-        buffer.length() < (size_t)EXT_CSD_FILE_MIN_SIZE) {
+    if (sscanf(buffer.c_str(), "%hx", &eol) < 1 || eol == 0) {
         return false;
     }
 
-    string str = buffer.substr(EXT_PRE_EOL_INFO_IDX, sizeof(str_hex));
-    if (!ParseUint(str, &eol)) {
-        LOG_TO(SYSTEM, ERROR) << "Failure on parsing EXT_PRE_EOL_INFO.";
+    if (!ReadFileToString(emmc_sysfs + "life_time", &buffer)) {
         return false;
     }
 
-    str = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_A_IDX, sizeof(str_hex));
-    if (!ParseUint(str, &lifetime_a)) {
-        LOG_TO(SYSTEM, ERROR)
-            << "Failure on parsing EXT_DEVICE_LIFE_TIME_EST_TYP_A.";
-        return false;
-    }
-
-    str = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_B_IDX, sizeof(str_hex));
-    if (!ParseUint(str, &lifetime_b)) {
-        LOG_TO(SYSTEM, ERROR)
-            << "Failure on parsing EXT_DEVICE_LIFE_TIME_EST_TYP_B.";
+    if (sscanf(buffer.c_str(), "0x%hx 0x%hx", &lifetime_a, &lifetime_b) < 2 ||
+        (lifetime_a == 0 && lifetime_b == 0)) {
         return false;
     }
 
     return true;
 }
+
+const size_t EXT_CSD_FILE_MIN_SIZE = 1024;
+/* 2 characters in string for each byte */
+const size_t EXT_CSD_REV_IDX = 192 * 2;
+const size_t EXT_PRE_EOL_INFO_IDX = 267 * 2;
+const size_t EXT_DEVICE_LIFE_TIME_EST_A_IDX = 268 * 2;
+const size_t EXT_DEVICE_LIFE_TIME_EST_B_IDX = 269 * 2;
+
+bool emmc_info_t::report_debugfs()
+{
+    string buffer;
+    uint16_t rev = 0;
+
+    if (!ReadFileToString(emmc_debugfs, &buffer) ||
+        buffer.length() < (size_t)EXT_CSD_FILE_MIN_SIZE) {
+        return false;
+    }
+
+    string str = buffer.substr(EXT_CSD_REV_IDX, 2);
+    if (!ParseUint(str, &rev) ||
+        rev < 7 || rev > ARRAY_SIZE(emmc_ver_str)) {
+        return false;
+    }
+
+    version = "emmc ";
+    version += emmc_ver_str[rev];
+
+    str = buffer.substr(EXT_PRE_EOL_INFO_IDX, 2);
+    if (!ParseUint(str, &eol)) {
+        return false;
+    }
+
+    str = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_A_IDX, 2);
+    if (!ParseUint(str, &lifetime_a)) {
+        return false;
+    }
+
+    str = buffer.substr(EXT_DEVICE_LIFE_TIME_EST_B_IDX, 2);
+    if (!ParseUint(str, &lifetime_b)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool ufs_info_t::report()
+{
+    string buffer;
+    if (!ReadFileToString(health_file, &buffer)) {
+        return false;
+    }
+
+    vector<string> lines = Split(buffer, "\n");
+    if (lines.empty()) {
+        return false;
+    }
+
+    char rev[8];
+    if (sscanf(lines[0].c_str(), "ufs version: 0x%7s\n", rev) < 1) {
+        return false;
+    }
+
+    version = "ufs " + string(rev);
+
+    for (size_t i = 1; i < lines.size(); i++) {
+        char token[32];
+        uint16_t val;
+        int ret;
+        if ((ret = sscanf(lines[i].c_str(),
+                   "Health Descriptor[Byte offset 0x%*d]: %31s = 0x%hx",
+                   token, &val)) < 2) {
+            continue;
+        }
+
+        if (string(token) == "bPreEOLInfo") {
+            eol = val;
+        } else if (string(token) == "bDeviceLifeTimeEstA") {
+            lifetime_a = val;
+        } else if (string(token) == "bDeviceLifeTimeEstB") {
+            lifetime_b = val;
+        }
+    }
+
+    if (eol == 0 || (lifetime_a == 0 && lifetime_b == 0)) {
+        return false;
+    }
+
+    publish();
+    return true;
+}
+
diff --git a/storaged/tests/storaged_test.cpp b/storaged/tests/storaged_test.cpp
index e335cad..b103ac1 100644
--- a/storaged/tests/storaged_test.cpp
+++ b/storaged/tests/storaged_test.cpp
@@ -29,7 +29,6 @@
 
 #define MMC_DISK_STATS_PATH "/sys/block/mmcblk0/stat"
 #define SDA_DISK_STATS_PATH "/sys/block/sda/stat"
-#define EMMC_EXT_CSD_PATH "/d/mmc0/mmc0:0001/ext_csd"
 
 static void pause(uint32_t sec) {
     const char* path = "/cache/test";
@@ -58,13 +57,8 @@
 const char* DISK_STATS_PATH;
 TEST(storaged_test, retvals) {
     struct disk_stats stats;
-    emmc_info_t info;
     memset(&stats, 0, sizeof(struct disk_stats));
 
-    if (info.init()) {
-        EXPECT_TRUE(info.update());
-    }
-
     if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
         DISK_STATS_PATH = MMC_DISK_STATS_PATH;
     } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) {
@@ -127,20 +121,6 @@
     }
 }
 
-TEST(storaged_test, storage_info_t) {
-    emmc_info_t info;
-
-    if (access(EMMC_EXT_CSD_PATH, R_OK) >= 0) {
-        int ret = info.init();
-        if (ret) {
-            EXPECT_TRUE(info.version.empty());
-            ASSERT_TRUE(info.update());
-            // update should put something in info.
-            EXPECT_TRUE(info.eol || info.lifetime_a || info.lifetime_b);
-        }
-    }
-}
-
 static double mean(std::deque<uint32_t> nums) {
     double sum = 0.0;
     for (uint32_t i : nums) {