Merge "Do not access ext_csd register in debugfs from storaged"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a3bd44f..cc1978d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,6 +4,9 @@
       "name": "adbd_test"
     },
     {
+      "name": "CtsInitTestCases"
+    },
+    {
       "name": "debuggerd_test"
     },
     {
@@ -13,9 +16,6 @@
       "name": "fs_mgr_vendor_overlay_test"
     },
     {
-      "name": "init_tests"
-    },
-    {
       "name": "libbase_test"
     },
     {
diff --git a/adb/Android.bp b/adb/Android.bp
index 06cfcbf..57872b0 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -142,6 +142,10 @@
     "sysdeps/posix/network.cpp",
 ]
 
+libadb_linux_srcs = [
+    "fdevent/fdevent_epoll.cpp",
+]
+
 libadb_test_srcs = [
     "adb_io_test.cpp",
     "adb_listeners_test.cpp",
@@ -170,12 +174,11 @@
 
     target: {
         linux: {
-            srcs: ["client/usb_linux.cpp"],
+            srcs: ["client/usb_linux.cpp"] + libadb_linux_srcs,
         },
         darwin: {
             srcs: ["client/usb_osx.cpp"],
         },
-
         not_windows: {
             srcs: libadb_posix_srcs,
         },
@@ -342,7 +345,7 @@
     // libminadbd wants both, as it's used to build native tests.
     compile_multilib: "both",
 
-    srcs: libadb_srcs + libadb_posix_srcs + [
+    srcs: libadb_srcs + libadb_linux_srcs + libadb_posix_srcs + [
         "daemon/auth.cpp",
         "daemon/jdwp_service.cpp",
     ],
diff --git a/adb/daemon/usb_ffs.cpp b/adb/daemon/usb_ffs.cpp
index a64ce40..338d776 100644
--- a/adb/daemon/usb_ffs.cpp
+++ b/adb/daemon/usb_ffs.cpp
@@ -299,7 +299,6 @@
         }
         // Signal only when writing the descriptors to ffs
         android::base::SetProperty("sys.usb.ffs.ready", "1");
-        *out_control = std::move(control);
     }
 
     bulk_out.reset(adb_open(USB_FFS_ADB_OUT, O_RDONLY));
@@ -314,6 +313,7 @@
         return false;
     }
 
+    *out_control = std::move(control);
     *out_bulk_in = std::move(bulk_in);
     *out_bulk_out = std::move(bulk_out);
     return true;
diff --git a/adb/fdevent/fdevent.cpp b/adb/fdevent/fdevent.cpp
index 28b8f37..562f587 100644
--- a/adb/fdevent/fdevent.cpp
+++ b/adb/fdevent/fdevent.cpp
@@ -26,16 +26,24 @@
 
 #include "adb_utils.h"
 #include "fdevent.h"
+#include "fdevent_epoll.h"
 #include "fdevent_poll.h"
 
+using namespace std::chrono_literals;
+using std::chrono::duration_cast;
+
+void invoke_fde(struct fdevent* fde, unsigned events) {
+    if (auto f = std::get_if<fd_func>(&fde->func)) {
+        (*f)(fde->fd.get(), events, fde->arg);
+    } else if (auto f = std::get_if<fd_func2>(&fde->func)) {
+        (*f)(fde, events, fde->arg);
+    } else {
+        __builtin_unreachable();
+    }
+}
+
 std::string dump_fde(const fdevent* fde) {
     std::string state;
-    if (fde->state & FDE_ACTIVE) {
-        state += "A";
-    }
-    if (fde->state & FDE_PENDING) {
-        state += "P";
-    }
     if (fde->state & FDE_READ) {
         state += "R";
     }
@@ -53,9 +61,11 @@
     CheckMainThread();
     CHECK_GE(fd.get(), 0);
 
+    int fd_num = fd.get();
+
     fdevent* fde = new fdevent();
     fde->id = fdevent_id_++;
-    fde->state = FDE_ACTIVE;
+    fde->state = 0;
     fde->fd = std::move(fd);
     fde->func = func;
     fde->arg = arg;
@@ -66,6 +76,10 @@
         LOG(ERROR) << "failed to set non-blocking mode for fd " << fde->fd.get();
     }
 
+    auto [it, inserted] = this->installed_fdevents_.emplace(fd_num, fde);
+    CHECK(inserted);
+    UNUSED(it);
+
     this->Register(fde);
     return fde;
 }
@@ -78,18 +92,22 @@
 
     this->Unregister(fde);
 
+    auto erased = this->installed_fdevents_.erase(fde->fd.get());
+    CHECK_EQ(1UL, erased);
+
     unique_fd result = std::move(fde->fd);
     delete fde;
     return result;
 }
 
 void fdevent_context::Add(fdevent* fde, unsigned events) {
-    Set(fde, (fde->state & FDE_EVENTMASK) | events);
+    CHECK(!(events & FDE_TIMEOUT));
+    Set(fde, fde->state | events);
 }
 
 void fdevent_context::Del(fdevent* fde, unsigned events) {
     CHECK(!(events & FDE_TIMEOUT));
-    Set(fde, (fde->state & FDE_EVENTMASK) & ~events);
+    Set(fde, fde->state & ~events);
 }
 
 void fdevent_context::SetTimeout(fdevent* fde, std::optional<std::chrono::milliseconds> timeout) {
@@ -98,6 +116,56 @@
     fde->last_active = std::chrono::steady_clock::now();
 }
 
+std::optional<std::chrono::milliseconds> fdevent_context::CalculatePollDuration() {
+    std::optional<std::chrono::milliseconds> result = std::nullopt;
+    auto now = std::chrono::steady_clock::now();
+    CheckMainThread();
+
+    for (const auto& [fd, fde] : this->installed_fdevents_) {
+        UNUSED(fd);
+        auto timeout_opt = fde->timeout;
+        if (timeout_opt) {
+            auto deadline = fde->last_active + *timeout_opt;
+            auto time_left = duration_cast<std::chrono::milliseconds>(deadline - now);
+            if (time_left < 0ms) {
+                time_left = 0ms;
+            }
+
+            if (!result) {
+                result = time_left;
+            } else {
+                result = std::min(*result, time_left);
+            }
+        }
+    }
+
+    return result;
+}
+
+void fdevent_context::HandleEvents(const std::vector<fdevent_event>& events) {
+    for (const auto& event : events) {
+        invoke_fde(event.fde, event.events);
+    }
+    FlushRunQueue();
+}
+
+void fdevent_context::FlushRunQueue() {
+    // We need to be careful around reentrancy here, since a function we call can queue up another
+    // function.
+    while (true) {
+        std::function<void()> fn;
+        {
+            std::lock_guard<std::mutex> lock(this->run_queue_mutex_);
+            if (this->run_queue_.empty()) {
+                break;
+            }
+            fn = std::move(this->run_queue_.front());
+            this->run_queue_.pop_front();
+        }
+        fn();
+    }
+}
+
 void fdevent_context::CheckMainThread() {
     if (main_thread_id_) {
         CHECK_EQ(*main_thread_id_, android::base::GetThreadId());
@@ -118,25 +186,16 @@
     Interrupt();
 }
 
-void fdevent_context::FlushRunQueue() {
-    // We need to be careful around reentrancy here, since a function we call can queue up another
-    // function.
-    while (true) {
-        std::function<void()> fn;
-        {
-            std::lock_guard<std::mutex> lock(this->run_queue_mutex_);
-            if (this->run_queue_.empty()) {
-                break;
-            }
-            fn = this->run_queue_.front();
-            this->run_queue_.pop_front();
-        }
-        fn();
-    }
+static std::unique_ptr<fdevent_context> fdevent_create_context() {
+#if defined(__linux__)
+    return std::make_unique<fdevent_context_epoll>();
+#else
+    return std::make_unique<fdevent_context_poll>();
+#endif
 }
 
 static auto& g_ambient_fdevent_context =
-        *new std::unique_ptr<fdevent_context>(new fdevent_context_poll());
+        *new std::unique_ptr<fdevent_context>(fdevent_create_context());
 
 static fdevent_context* fdevent_get_ambient() {
     return g_ambient_fdevent_context.get();
@@ -197,5 +256,5 @@
 }
 
 void fdevent_reset() {
-    g_ambient_fdevent_context.reset(new fdevent_context_poll());
+    g_ambient_fdevent_context = fdevent_create_context();
 }
diff --git a/adb/fdevent/fdevent.h b/adb/fdevent/fdevent.h
index 2424252..86814d7 100644
--- a/adb/fdevent/fdevent.h
+++ b/adb/fdevent/fdevent.h
@@ -26,6 +26,7 @@
 #include <functional>
 #include <mutex>
 #include <optional>
+#include <unordered_map>
 #include <variant>
 
 #include <android-base/thread_annotations.h>
@@ -38,19 +39,19 @@
 #define FDE_ERROR 0x0004
 #define FDE_TIMEOUT 0x0008
 
-// Internal states.
-#define FDE_EVENTMASK  0x00ff
-#define FDE_STATEMASK  0xff00
-
-#define FDE_ACTIVE     0x0100
-#define FDE_PENDING    0x0200
+struct fdevent;
 
 typedef void (*fd_func)(int fd, unsigned events, void *userdata);
 typedef void (*fd_func2)(struct fdevent* fde, unsigned events, void* userdata);
 
-struct fdevent;
+void invoke_fde(struct fdevent* fde, unsigned events);
 std::string dump_fde(const fdevent* fde);
 
+struct fdevent_event {
+    fdevent* fde;
+    unsigned events;
+};
+
 struct fdevent_context {
   public:
     virtual ~fdevent_context() = default;
@@ -59,14 +60,13 @@
     fdevent* Create(unique_fd fd, std::variant<fd_func, fd_func2> func, void* arg);
 
     // Deallocate an fdevent object, returning the file descriptor that was owned by it.
+    // Note that this calls Set, which is a virtual method, so destructors that call this must be
+    // final.
     unique_fd Destroy(fdevent* fde);
 
   protected:
-    // Register an fdevent that is being created by Create with the fdevent_context.
-    virtual void Register(fdevent* fde) = 0;
-
-    // Unregister an fdevent that is being destroyed by Destroy with the fdevent_context.
-    virtual void Unregister(fdevent* fde) = 0;
+    virtual void Register(fdevent*) {}
+    virtual void Unregister(fdevent*) {}
 
   public:
     // Change which events should cause notifications.
@@ -80,6 +80,15 @@
     // trigger repeatedly every |timeout| ms.
     void SetTimeout(fdevent* fde, std::optional<std::chrono::milliseconds> timeout);
 
+  protected:
+    std::optional<std::chrono::milliseconds> CalculatePollDuration();
+    void HandleEvents(const std::vector<fdevent_event>& events);
+
+  private:
+    // Run all pending functions enqueued via Run().
+    void FlushRunQueue() EXCLUDES(run_queue_mutex_);
+
+  public:
     // Loop until TerminateLoop is called, handling events.
     // Implementations should call FlushRunQueue on every iteration, and check the value of
     // terminate_loop_ to determine whether to stop.
@@ -100,12 +109,12 @@
     // Interrupt the run loop.
     virtual void Interrupt() = 0;
 
-    // Run all pending functions enqueued via Run().
-    void FlushRunQueue() EXCLUDES(run_queue_mutex_);
-
     std::optional<uint64_t> main_thread_id_ = std::nullopt;
     std::atomic<bool> terminate_loop_ = false;
 
+  protected:
+    std::unordered_map<int, fdevent*> installed_fdevents_;
+
   private:
     uint64_t fdevent_id_ = 0;
     std::mutex run_queue_mutex_;
@@ -119,7 +128,6 @@
     int force_eof = 0;
 
     uint16_t state = 0;
-    uint16_t events = 0;
     std::optional<std::chrono::milliseconds> timeout;
     std::chrono::steady_clock::time_point last_active;
 
diff --git a/adb/fdevent/fdevent_epoll.cpp b/adb/fdevent/fdevent_epoll.cpp
new file mode 100644
index 0000000..e3d1674
--- /dev/null
+++ b/adb/fdevent/fdevent_epoll.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fdevent_epoll.h"
+
+#if defined(__linux__)
+
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+
+#include <android-base/logging.h>
+#include <android-base/threads.h>
+
+#include "adb_unique_fd.h"
+#include "fdevent.h"
+
+static void fdevent_interrupt(int fd, unsigned, void*) {
+    uint64_t buf;
+    ssize_t rc = TEMP_FAILURE_RETRY(adb_read(fd, &buf, sizeof(buf)));
+    if (rc == -1) {
+        PLOG(FATAL) << "failed to read from fdevent interrupt fd";
+    }
+}
+
+fdevent_context_epoll::fdevent_context_epoll() {
+    epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC));
+
+    unique_fd interrupt_fd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
+    if (interrupt_fd == -1) {
+        PLOG(FATAL) << "failed to create fdevent interrupt eventfd";
+    }
+
+    unique_fd interrupt_fd_dup(fcntl(interrupt_fd.get(), F_DUPFD_CLOEXEC, 3));
+    if (interrupt_fd_dup == -1) {
+        PLOG(FATAL) << "failed to dup fdevent interrupt eventfd";
+    }
+
+    this->interrupt_fd_ = std::move(interrupt_fd_dup);
+    fdevent* fde = this->Create(std::move(interrupt_fd), fdevent_interrupt, nullptr);
+    CHECK(fde != nullptr);
+    this->Add(fde, FDE_READ);
+}
+
+fdevent_context_epoll::~fdevent_context_epoll() {
+    // Destroy calls virtual methods, but this class is final, so that's okay.
+    this->Destroy(this->interrupt_fde_);
+}
+
+static epoll_event calculate_epoll_event(fdevent* fde) {
+    epoll_event result;
+    result.events = 0;
+    if (fde->state & FDE_READ) {
+        result.events |= EPOLLIN;
+    }
+    if (fde->state & FDE_WRITE) {
+        result.events |= EPOLLOUT;
+    }
+    if (fde->state & FDE_ERROR) {
+        result.events |= EPOLLERR;
+    }
+    result.events |= EPOLLRDHUP;
+    result.data.ptr = fde;
+    return result;
+}
+
+void fdevent_context_epoll::Register(fdevent* fde) {
+    epoll_event ev = calculate_epoll_event(fde);
+    if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_ADD, fde->fd.get(), &ev) != 0) {
+        PLOG(FATAL) << "failed to register fd " << fde->fd.get() << " with epoll";
+    }
+}
+
+void fdevent_context_epoll::Unregister(fdevent* fde) {
+    if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_DEL, fde->fd.get(), nullptr) != 0) {
+        PLOG(FATAL) << "failed to unregister fd " << fde->fd.get() << " with epoll";
+    }
+}
+
+void fdevent_context_epoll::Set(fdevent* fde, unsigned events) {
+    unsigned previous_state = fde->state;
+    fde->state = events;
+
+    // If the state is the same, or only differed by FDE_TIMEOUT, we don't need to modify epoll.
+    if ((previous_state & ~FDE_TIMEOUT) == (events & ~FDE_TIMEOUT)) {
+        return;
+    }
+
+    epoll_event ev = calculate_epoll_event(fde);
+    if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_MOD, fde->fd.get(), &ev) != 0) {
+        PLOG(FATAL) << "failed to modify fd " << fde->fd.get() << " with epoll";
+    }
+}
+
+void fdevent_context_epoll::Loop() {
+    main_thread_id_ = android::base::GetThreadId();
+
+    std::vector<fdevent_event> fde_events;
+    std::vector<epoll_event> epoll_events;
+    epoll_events.resize(this->installed_fdevents_.size());
+
+    while (true) {
+        if (terminate_loop_) {
+            break;
+        }
+
+        int rc = -1;
+        while (rc == -1) {
+            std::optional<std::chrono::milliseconds> timeout = CalculatePollDuration();
+            int timeout_ms;
+            if (!timeout) {
+                timeout_ms = -1;
+            } else {
+                timeout_ms = timeout->count();
+            }
+
+            rc = epoll_wait(epoll_fd_.get(), epoll_events.data(), epoll_events.size(), timeout_ms);
+            if (rc == -1 && errno != EINTR) {
+                PLOG(FATAL) << "epoll_wait failed";
+            }
+        }
+
+        auto post_poll = std::chrono::steady_clock::now();
+        std::unordered_map<fdevent*, unsigned> event_map;
+        for (int i = 0; i < rc; ++i) {
+            fdevent* fde = static_cast<fdevent*>(epoll_events[i].data.ptr);
+
+            unsigned events = 0;
+            if (epoll_events[i].events & EPOLLIN) {
+                CHECK(fde->state & FDE_READ);
+                events |= FDE_READ;
+            }
+            if (epoll_events[i].events & EPOLLOUT) {
+                CHECK(fde->state & FDE_WRITE);
+                events |= FDE_WRITE;
+            }
+            if (epoll_events[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) {
+                // We fake a read, as the rest of the code assumes that errors will
+                // be detected at that point.
+                events |= FDE_READ | FDE_ERROR;
+            }
+
+            event_map[fde] = events;
+        }
+
+        for (const auto& [fd, fde] : installed_fdevents_) {
+            unsigned events = 0;
+            if (auto it = event_map.find(fde); it != event_map.end()) {
+                events = it->second;
+            }
+
+            if (events == 0) {
+                if (fde->timeout) {
+                    auto deadline = fde->last_active + *fde->timeout;
+                    if (deadline < post_poll) {
+                        events |= FDE_TIMEOUT;
+                    }
+                }
+            }
+
+            if (events != 0) {
+                LOG(DEBUG) << dump_fde(fde) << " got events " << std::hex << std::showbase
+                           << events;
+                fde_events.push_back({fde, events});
+                fde->last_active = post_poll;
+            }
+        }
+        this->HandleEvents(std::move(fde_events));
+        fde_events.clear();
+    }
+
+    main_thread_id_.reset();
+}
+
+size_t fdevent_context_epoll::InstalledCount() {
+    // We always have an installed fde for interrupt.
+    return this->installed_fdevents_.size() - 1;
+}
+
+void fdevent_context_epoll::Interrupt() {
+    uint64_t i = 1;
+    ssize_t rc = TEMP_FAILURE_RETRY(adb_write(this->interrupt_fd_, &i, sizeof(i)));
+    if (rc != sizeof(i)) {
+        PLOG(FATAL) << "failed to write to fdevent interrupt eventfd";
+    }
+}
+
+#endif  // defined(__linux__)
diff --git a/adb/fdevent/fdevent_epoll.h b/adb/fdevent/fdevent_epoll.h
new file mode 100644
index 0000000..684fa32
--- /dev/null
+++ b/adb/fdevent/fdevent_epoll.h
@@ -0,0 +1,61 @@
+#pragma once
+
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#if defined(__linux__)
+
+#include "sysdeps.h"
+
+#include <sys/epoll.h>
+
+#include <deque>
+#include <list>
+#include <mutex>
+#include <unordered_map>
+
+#include <android-base/thread_annotations.h>
+
+#include "adb_unique_fd.h"
+#include "fdevent.h"
+
+struct fdevent_context_epoll final : public fdevent_context {
+    fdevent_context_epoll();
+    virtual ~fdevent_context_epoll();
+
+    virtual void Register(fdevent* fde) final;
+    virtual void Unregister(fdevent* fde) final;
+
+    virtual void Set(fdevent* fde, unsigned events) final;
+
+    virtual void Loop() final;
+    size_t InstalledCount() final;
+
+  protected:
+    virtual void Interrupt() final;
+
+  public:
+    // All operations to fdevent should happen only in the main thread.
+    // That's why we don't need a lock for fdevent.
+    std::unordered_map<int, fdevent*> epoll_node_map_;
+    std::list<fdevent*> pending_list_;
+
+    unique_fd epoll_fd_;
+    unique_fd interrupt_fd_;
+    fdevent* interrupt_fde_ = nullptr;
+};
+
+#endif  // defined(__linux__)
diff --git a/adb/fdevent/fdevent_poll.cpp b/adb/fdevent/fdevent_poll.cpp
index 75ea081..cc4a7a1 100644
--- a/adb/fdevent/fdevent_poll.cpp
+++ b/adb/fdevent/fdevent_poll.cpp
@@ -75,60 +75,14 @@
 }
 
 fdevent_context_poll::~fdevent_context_poll() {
+    // Destroy calls virtual methods, but this class is final, so that's okay.
     this->Destroy(this->interrupt_fde_);
 }
 
-void fdevent_context_poll::Register(fdevent* fde) {
-    auto pair = poll_node_map_.emplace(fde->fd.get(), PollNode(fde));
-    CHECK(pair.second) << "install existing fd " << fde->fd.get();
-}
-
-void fdevent_context_poll::Unregister(fdevent* fde) {
-    if (fde->state & FDE_ACTIVE) {
-        poll_node_map_.erase(fde->fd.get());
-
-        if (fde->state & FDE_PENDING) {
-            pending_list_.remove(fde);
-        }
-        fde->state = 0;
-        fde->events = 0;
-    }
-}
-
 void fdevent_context_poll::Set(fdevent* fde, unsigned events) {
     CheckMainThread();
-    events &= FDE_EVENTMASK;
-    if ((fde->state & FDE_EVENTMASK) == events) {
-        return;
-    }
-    CHECK(fde->state & FDE_ACTIVE);
-
-    auto it = poll_node_map_.find(fde->fd.get());
-    CHECK(it != poll_node_map_.end());
-    PollNode& node = it->second;
-    if (events & FDE_READ) {
-        node.pollfd.events |= POLLIN;
-    } else {
-        node.pollfd.events &= ~POLLIN;
-    }
-
-    if (events & FDE_WRITE) {
-        node.pollfd.events |= POLLOUT;
-    } else {
-        node.pollfd.events &= ~POLLOUT;
-    }
-    fde->state = (fde->state & FDE_STATEMASK) | events;
-
+    fde->state = events;
     D("fdevent_set: %s, events = %u", dump_fde(fde).c_str(), events);
-
-    if (fde->state & FDE_PENDING) {
-        // If we are pending, make sure we don't signal an event that is no longer wanted.
-        fde->events &= events;
-        if (fde->events == 0) {
-            pending_list_.remove(fde);
-            fde->state &= ~FDE_PENDING;
-        }
-    }
 }
 
 static std::string dump_pollfds(const std::vector<adb_pollfd>& pollfds) {
@@ -146,204 +100,94 @@
     return result;
 }
 
-static std::optional<std::chrono::milliseconds> calculate_timeout(fdevent_context_poll* ctx) {
-    std::optional<std::chrono::milliseconds> result = std::nullopt;
-    auto now = std::chrono::steady_clock::now();
-    ctx->CheckMainThread();
-
-    for (const auto& [fd, pollnode] : ctx->poll_node_map_) {
-        UNUSED(fd);
-        auto timeout_opt = pollnode.fde->timeout;
-        if (timeout_opt) {
-            auto deadline = pollnode.fde->last_active + *timeout_opt;
-            auto time_left = std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now);
-            if (time_left < std::chrono::milliseconds::zero()) {
-                time_left = std::chrono::milliseconds::zero();
-            }
-
-            if (!result) {
-                result = time_left;
-            } else {
-                result = std::min(*result, time_left);
-            }
-        }
-    }
-
-    return result;
-}
-
-static void fdevent_process(fdevent_context_poll* ctx) {
-    std::vector<adb_pollfd> pollfds;
-    for (const auto& pair : ctx->poll_node_map_) {
-        pollfds.push_back(pair.second.pollfd);
-    }
-    CHECK_GT(pollfds.size(), 0u);
-    D("poll(), pollfds = %s", dump_pollfds(pollfds).c_str());
-
-    auto timeout = calculate_timeout(ctx);
-    int timeout_ms;
-    if (!timeout) {
-        timeout_ms = -1;
-    } else {
-        timeout_ms = timeout->count();
-    }
-
-    int ret = adb_poll(&pollfds[0], pollfds.size(), timeout_ms);
-    if (ret == -1) {
-        PLOG(ERROR) << "poll(), ret = " << ret;
-        return;
-    }
-
-    auto post_poll = std::chrono::steady_clock::now();
-
-    for (const auto& pollfd : pollfds) {
-        if (pollfd.revents != 0) {
-            D("for fd %d, revents = %x", pollfd.fd, pollfd.revents);
-        }
-        unsigned events = 0;
-        if (pollfd.revents & POLLIN) {
-            events |= FDE_READ;
-        }
-        if (pollfd.revents & POLLOUT) {
-            events |= FDE_WRITE;
-        }
-        if (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
-            // We fake a read, as the rest of the code assumes that errors will
-            // be detected at that point.
-            events |= FDE_READ | FDE_ERROR;
-        }
-#if defined(__linux__)
-        if (pollfd.revents & POLLRDHUP) {
-            events |= FDE_READ | FDE_ERROR;
-        }
-#endif
-        auto it = ctx->poll_node_map_.find(pollfd.fd);
-        CHECK(it != ctx->poll_node_map_.end());
-        fdevent* fde = it->second.fde;
-
-        if (events == 0) {
-            // Check for timeout.
-            if (fde->timeout) {
-                auto deadline = fde->last_active + *fde->timeout;
-                if (deadline < post_poll) {
-                    events |= FDE_TIMEOUT;
-                }
-            }
-        }
-
-        if (events != 0) {
-            CHECK_EQ(fde->fd.get(), pollfd.fd);
-            fde->events |= events;
-            fde->last_active = post_poll;
-            D("%s got events %x", dump_fde(fde).c_str(), events);
-            fde->state |= FDE_PENDING;
-            ctx->pending_list_.push_back(fde);
-        }
-    }
-}
-
-template <class T>
-struct always_false : std::false_type {};
-
-static void fdevent_call_fdfunc(fdevent* fde) {
-    unsigned events = fde->events;
-    fde->events = 0;
-    CHECK(fde->state & FDE_PENDING);
-    fde->state &= (~FDE_PENDING);
-    D("fdevent_call_fdfunc %s", dump_fde(fde).c_str());
-    std::visit(
-            [&](auto&& f) {
-                using F = std::decay_t<decltype(f)>;
-                if constexpr (std::is_same_v<fd_func, F>) {
-                    f(fde->fd.get(), events, fde->arg);
-                } else if constexpr (std::is_same_v<fd_func2, F>) {
-                    f(fde, events, fde->arg);
-                } else {
-                    static_assert(always_false<F>::value, "non-exhaustive visitor");
-                }
-            },
-            fde->func);
-}
-
-static void fdevent_check_spin(fdevent_context_poll* ctx, uint64_t cycle) {
-    // Check to see if we're spinning because we forgot about an fdevent
-    // by keeping track of how long fdevents have been continuously pending.
-    struct SpinCheck {
-        fdevent* fde;
-        android::base::boot_clock::time_point timestamp;
-        uint64_t cycle;
-    };
-
-    // TODO: Move this into the base fdevent_context.
-    static auto& g_continuously_pending = *new std::unordered_map<uint64_t, SpinCheck>();
-    static auto last_cycle = android::base::boot_clock::now();
-
-    auto now = android::base::boot_clock::now();
-    if (now - last_cycle > 10ms) {
-        // We're not spinning.
-        g_continuously_pending.clear();
-        last_cycle = now;
-        return;
-    }
-    last_cycle = now;
-
-    for (auto* fde : ctx->pending_list_) {
-        auto it = g_continuously_pending.find(fde->id);
-        if (it == g_continuously_pending.end()) {
-            g_continuously_pending[fde->id] =
-                    SpinCheck{.fde = fde, .timestamp = now, .cycle = cycle};
-        } else {
-            it->second.cycle = cycle;
-        }
-    }
-
-    for (auto it = g_continuously_pending.begin(); it != g_continuously_pending.end();) {
-        if (it->second.cycle != cycle) {
-            it = g_continuously_pending.erase(it);
-        } else {
-            // Use an absurdly long window, since all we really care about is
-            // getting a bugreport eventually.
-            if (now - it->second.timestamp > 300s) {
-                LOG(FATAL_WITHOUT_ABORT)
-                        << "detected spin in fdevent: " << dump_fde(it->second.fde);
-#if defined(__linux__)
-                int fd = it->second.fde->fd.get();
-                std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
-                std::string path;
-                if (!android::base::Readlink(fd_path, &path)) {
-                    PLOG(FATAL_WITHOUT_ABORT) << "readlink of fd " << fd << " failed";
-                }
-                LOG(FATAL_WITHOUT_ABORT) << "fd " << fd << " = " << path;
-#endif
-                abort();
-            }
-            ++it;
-        }
-    }
-}
-
 void fdevent_context_poll::Loop() {
     main_thread_id_ = android::base::GetThreadId();
 
-    uint64_t cycle = 0;
     while (true) {
         if (terminate_loop_) {
             break;
         }
 
         D("--- --- waiting for events");
+        std::vector<adb_pollfd> pollfds;
+        for (const auto& [fd, fde] : this->installed_fdevents_) {
+            adb_pollfd pfd;
+            pfd.fd = fd;
+            pfd.events = 0;
+            if (fde->state & FDE_READ) {
+                pfd.events |= POLLIN;
+            }
+            if (fde->state & FDE_WRITE) {
+                pfd.events |= POLLOUT;
+            }
+            if (fde->state & FDE_ERROR) {
+                pfd.events |= POLLERR;
+            }
+#if defined(__linux__)
+            pfd.events |= POLLRDHUP;
+#endif
+            pfd.revents = 0;
+            pollfds.push_back(pfd);
+        }
+        CHECK_GT(pollfds.size(), 0u);
+        D("poll(), pollfds = %s", dump_pollfds(pollfds).c_str());
 
-        fdevent_process(this);
-
-        fdevent_check_spin(this, cycle++);
-
-        while (!pending_list_.empty()) {
-            fdevent* fde = pending_list_.front();
-            pending_list_.pop_front();
-            fdevent_call_fdfunc(fde);
+        std::optional<std::chrono::milliseconds> timeout = CalculatePollDuration();
+        int timeout_ms;
+        if (!timeout) {
+            timeout_ms = -1;
+        } else {
+            timeout_ms = timeout->count();
         }
 
-        this->FlushRunQueue();
+        int ret = adb_poll(pollfds.data(), pollfds.size(), timeout_ms);
+        if (ret == -1) {
+            PLOG(ERROR) << "poll(), ret = " << ret;
+            return;
+        }
+
+        auto post_poll = std::chrono::steady_clock::now();
+        std::vector<fdevent_event> poll_events;
+
+        for (const auto& pollfd : pollfds) {
+            unsigned events = 0;
+            if (pollfd.revents & POLLIN) {
+                events |= FDE_READ;
+            }
+            if (pollfd.revents & POLLOUT) {
+                events |= FDE_WRITE;
+            }
+            if (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
+                // We fake a read, as the rest of the code assumes that errors will
+                // be detected at that point.
+                events |= FDE_READ | FDE_ERROR;
+            }
+#if defined(__linux__)
+            if (pollfd.revents & POLLRDHUP) {
+                events |= FDE_READ | FDE_ERROR;
+            }
+#endif
+
+            auto it = this->installed_fdevents_.find(pollfd.fd);
+            CHECK(it != this->installed_fdevents_.end());
+            fdevent* fde = it->second;
+
+            if (events == 0) {
+                if (fde->timeout) {
+                    auto deadline = fde->last_active + *fde->timeout;
+                    if (deadline < post_poll) {
+                        events |= FDE_TIMEOUT;
+                    }
+                }
+            }
+
+            if (events != 0) {
+                D("%s got events %x", dump_fde(fde).c_str(), events);
+                poll_events.push_back({fde, events});
+                fde->last_active = post_poll;
+            }
+        }
+        this->HandleEvents(std::move(poll_events));
     }
 
     main_thread_id_.reset();
@@ -351,7 +195,7 @@
 
 size_t fdevent_context_poll::InstalledCount() {
     // We always have an installed fde for interrupt.
-    return poll_node_map_.size() - 1;
+    return this->installed_fdevents_.size() - 1;
 }
 
 void fdevent_context_poll::Interrupt() {
diff --git a/adb/fdevent/fdevent_poll.h b/adb/fdevent/fdevent_poll.h
index db08301..98abab2 100644
--- a/adb/fdevent/fdevent_poll.h
+++ b/adb/fdevent/fdevent_poll.h
@@ -44,13 +44,10 @@
   }
 };
 
-struct fdevent_context_poll : public fdevent_context {
+struct fdevent_context_poll final : public fdevent_context {
     fdevent_context_poll();
     virtual ~fdevent_context_poll();
 
-    virtual void Register(fdevent* fde) final;
-    virtual void Unregister(fdevent* fde) final;
-
     virtual void Set(fdevent* fde, unsigned events) final;
 
     virtual void Loop() final;
@@ -61,11 +58,6 @@
     virtual void Interrupt() final;
 
   public:
-    // All operations to fdevent should happen only in the main thread.
-    // That's why we don't need a lock for fdevent.
-    std::unordered_map<int, PollNode> poll_node_map_;
-    std::list<fdevent*> pending_list_;
-
     unique_fd interrupt_fd_;
     fdevent* interrupt_fde_ = nullptr;
 };
diff --git a/adb/fdevent/fdevent_test.cpp b/adb/fdevent/fdevent_test.cpp
index 682f061..e06b3b3 100644
--- a/adb/fdevent/fdevent_test.cpp
+++ b/adb/fdevent/fdevent_test.cpp
@@ -118,8 +118,8 @@
 TEST_F(FdeventTest, smoke) {
     for (bool use_new_callback : {true, false}) {
         fdevent_reset();
-        const size_t PIPE_COUNT = 10;
-        const size_t MESSAGE_LOOP_COUNT = 100;
+        const size_t PIPE_COUNT = 512;
+        const size_t MESSAGE_LOOP_COUNT = 10;
         const std::string MESSAGE = "fdevent_test";
         int fd_pair1[2];
         int fd_pair2[2];
@@ -172,44 +172,6 @@
     }
 }
 
-struct InvalidFdArg {
-    fdevent* fde;
-    unsigned expected_events;
-    size_t* happened_event_count;
-};
-
-static void InvalidFdEventCallback(int, unsigned events, void* userdata) {
-    InvalidFdArg* arg = reinterpret_cast<InvalidFdArg*>(userdata);
-    ASSERT_EQ(arg->expected_events, events);
-    fdevent_destroy(arg->fde);
-    if (++*(arg->happened_event_count) == 2) {
-        fdevent_terminate_loop();
-    }
-}
-
-static void InvalidFdThreadFunc() {
-    const int INVALID_READ_FD = std::numeric_limits<int>::max() - 1;
-    size_t happened_event_count = 0;
-    InvalidFdArg read_arg;
-    read_arg.expected_events = FDE_READ | FDE_ERROR;
-    read_arg.happened_event_count = &happened_event_count;
-    read_arg.fde = fdevent_create(INVALID_READ_FD, InvalidFdEventCallback, &read_arg);
-    fdevent_add(read_arg.fde, FDE_READ);
-
-    const int INVALID_WRITE_FD = std::numeric_limits<int>::max();
-    InvalidFdArg write_arg;
-    write_arg.expected_events = FDE_READ | FDE_ERROR;
-    write_arg.happened_event_count = &happened_event_count;
-    write_arg.fde = fdevent_create(INVALID_WRITE_FD, InvalidFdEventCallback, &write_arg);
-    fdevent_add(write_arg.fde, FDE_WRITE);
-    fdevent_loop();
-}
-
-TEST_F(FdeventTest, invalid_fd) {
-    std::thread thread(InvalidFdThreadFunc);
-    thread.join();
-}
-
 TEST_F(FdeventTest, run_on_main_thread) {
     std::vector<int> vec;
 
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index b08a13b..987f994 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -33,6 +33,7 @@
 // Include this before open/close/unlink are defined as macros below.
 #include <android-base/errors.h>
 #include <android-base/macros.h>
+#include <android-base/off64_t.h>
 #include <android-base/unique_fd.h>
 #include <android-base/utf8.h>
 
diff --git a/debuggerd/tombstoned/tombstoned.rc b/debuggerd/tombstoned/tombstoned.rc
index 53ef01c..b4a1e71 100644
--- a/debuggerd/tombstoned/tombstoned.rc
+++ b/debuggerd/tombstoned/tombstoned.rc
@@ -2,10 +2,10 @@
     user tombstoned
     group system
 
-    # Don't start tombstoned until after the real /data is mounted.
-    class late_start
-
     socket tombstoned_crash seqpacket 0666 system system
     socket tombstoned_intercept seqpacket 0666 system system
     socket tombstoned_java_trace seqpacket 0666 system system
     writepid /dev/cpuset/system-background/tasks
+
+on post-fs-data
+    start tombstoned
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index e01e39b..b3f2d5f 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -56,8 +56,16 @@
     if (!path) {
         return false;
     }
+
+    CreateLogicalPartitionParams params = {
+            .block_device = *path,
+            .metadata_slot = slot_number,
+            .partition_name = partition_name,
+            .force_writable = true,
+            .timeout_ms = 5s,
+    };
     std::string dm_path;
-    if (!CreateLogicalPartition(path->c_str(), slot_number, partition_name, true, 5s, &dm_path)) {
+    if (!CreateLogicalPartition(params, &dm_path)) {
         LOG(ERROR) << "Could not map partition: " << partition_name;
         return false;
     }
diff --git a/fs_mgr/fs_mgr_dm_linear.cpp b/fs_mgr/fs_mgr_dm_linear.cpp
index 04ba0bf..1b85b47 100644
--- a/fs_mgr/fs_mgr_dm_linear.cpp
+++ b/fs_mgr/fs_mgr_dm_linear.cpp
@@ -52,29 +52,36 @@
 using DmTargetZero = android::dm::DmTargetZero;
 using DmTargetLinear = android::dm::DmTargetLinear;
 
-static bool GetPhysicalPartitionDevicePath(const LpMetadata& metadata,
+static bool GetPhysicalPartitionDevicePath(const IPartitionOpener& opener,
+                                           const LpMetadata& metadata,
                                            const LpMetadataBlockDevice& block_device,
-                                           const std::string& super_device,
-                                           std::string* result) {
-    // Note: device-mapper will not accept symlinks, so we must use realpath
-    // here.
-    std::string name = GetBlockDevicePartitionName(block_device);
-    std::string path = "/dev/block/by-name/" + name;
+                                           const std::string& super_device, std::string* result) {
     // If the super device is the source of this block device's metadata,
     // make sure we use the correct super device (and not just "super",
     // which might not exist.)
+    std::string name = GetBlockDevicePartitionName(block_device);
+    std::string dev_string = opener.GetDeviceString(name);
     if (GetMetadataSuperBlockDevice(metadata) == &block_device) {
-        path = super_device;
+        dev_string = opener.GetDeviceString(super_device);
     }
-    if (!android::base::Realpath(path, result)) {
-        PERROR << "realpath: " << path;
-        return false;
+
+    // Note: device-mapper will not accept symlinks, so we must use realpath
+    // here. If the device string is a major:minor sequence, we don't need to
+    // to call Realpath (it would not work anyway).
+    if (android::base::StartsWith(dev_string, "/")) {
+        if (!android::base::Realpath(dev_string, result)) {
+            PERROR << "realpath: " << dev_string;
+            return false;
+        }
+    } else {
+        *result = dev_string;
     }
     return true;
 }
 
-static bool CreateDmTable(const LpMetadata& metadata, const LpMetadataPartition& partition,
-                          const std::string& super_device, DmTable* table) {
+static bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
+                          const LpMetadataPartition& partition, const std::string& super_device,
+                          DmTable* table) {
     uint64_t sector = 0;
     for (size_t i = 0; i < partition.num_extents; i++) {
         const auto& extent = metadata.extents[partition.first_extent_index + i];
@@ -85,12 +92,13 @@
                 break;
             case LP_TARGET_TYPE_LINEAR: {
                 const auto& block_device = metadata.block_devices[extent.target_source];
-                std::string path;
-                if (!GetPhysicalPartitionDevicePath(metadata, block_device, super_device, &path)) {
+                std::string dev_string;
+                if (!GetPhysicalPartitionDevicePath(opener, metadata, block_device, super_device,
+                                                    &dev_string)) {
                     LOG(ERROR) << "Unable to complete device-mapper table, unknown block device";
                     return false;
                 }
-                target = std::make_unique<DmTargetLinear>(sector, extent.num_sectors, path,
+                target = std::make_unique<DmTargetLinear>(sector, extent.num_sectors, dev_string,
                                                           extent.target_data);
                 break;
             }
@@ -109,26 +117,6 @@
     return true;
 }
 
-static bool CreateLogicalPartition(const LpMetadata& metadata, const LpMetadataPartition& partition,
-                                   bool force_writable, const std::chrono::milliseconds& timeout_ms,
-                                   const std::string& super_device, std::string* path) {
-    DeviceMapper& dm = DeviceMapper::Instance();
-
-    DmTable table;
-    if (!CreateDmTable(metadata, partition, super_device, &table)) {
-        return false;
-    }
-    if (force_writable) {
-        table.set_readonly(false);
-    }
-    std::string name = GetPartitionName(partition);
-    if (!dm.CreateDevice(name, table, path, timeout_ms)) {
-        return false;
-    }
-    LINFO << "Created logical partition " << name << " on device " << *path;
-    return true;
-}
-
 bool CreateLogicalPartitions(const std::string& block_device) {
     uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
     auto metadata = ReadMetadata(block_device.c_str(), slot);
@@ -145,13 +133,20 @@
 }
 
 bool CreateLogicalPartitions(const LpMetadata& metadata, const std::string& super_device) {
+    CreateLogicalPartitionParams params = {
+            .block_device = super_device,
+            .metadata = &metadata,
+    };
     for (const auto& partition : metadata.partitions) {
         if (!partition.num_extents) {
             LINFO << "Skipping zero-length logical partition: " << GetPartitionName(partition);
             continue;
         }
-        std::string path;
-        if (!CreateLogicalPartition(metadata, partition, false, {}, super_device, &path)) {
+
+        params.partition = &partition;
+
+        std::string ignore_path;
+        if (!CreateLogicalPartition(params, &ignore_path)) {
             LERROR << "Could not create logical partition: " << GetPartitionName(partition);
             return false;
         }
@@ -159,29 +154,62 @@
     return true;
 }
 
-bool CreateLogicalPartition(const std::string& block_device, const LpMetadata& metadata,
-                            const std::string& partition_name, bool force_writable,
-                            const std::chrono::milliseconds& timeout_ms, std::string* path) {
-    for (const auto& partition : metadata.partitions) {
-        if (GetPartitionName(partition) == partition_name) {
-            return CreateLogicalPartition(metadata, partition, force_writable, timeout_ms,
-                                          block_device, path);
+bool CreateLogicalPartition(const CreateLogicalPartitionParams& params, std::string* path) {
+    const LpMetadata* metadata = params.metadata;
+
+    // Read metadata if needed.
+    std::unique_ptr<LpMetadata> local_metadata;
+    if (!metadata) {
+        if (!params.metadata_slot) {
+            LOG(ERROR) << "Either metadata or a metadata slot must be specified.";
+            return false;
+        }
+        auto slot = *params.metadata_slot;
+        if (local_metadata = ReadMetadata(params.block_device, slot); !local_metadata) {
+            LOG(ERROR) << "Could not read partition table for: " << params.block_device;
+            return false;
+        }
+        metadata = local_metadata.get();
+    }
+
+    // Find the partition by name if needed.
+    const LpMetadataPartition* partition = params.partition;
+    if (!partition) {
+        for (const auto& iter : metadata->partitions) {
+            if (GetPartitionName(iter) == params.partition_name) {
+                partition = &iter;
+                break;
+            }
+        }
+        if (!partition) {
+            LERROR << "Could not find any partition with name: " << params.partition_name;
+            return false;
         }
     }
-    LERROR << "Could not find any partition with name: " << partition_name;
-    return false;
-}
 
-bool CreateLogicalPartition(const std::string& block_device, uint32_t metadata_slot,
-                            const std::string& partition_name, bool force_writable,
-                            const std::chrono::milliseconds& timeout_ms, std::string* path) {
-    auto metadata = ReadMetadata(block_device.c_str(), metadata_slot);
-    if (!metadata) {
-        LOG(ERROR) << "Could not read partition table.";
-        return true;
+    PartitionOpener default_opener;
+    const IPartitionOpener* opener =
+            params.partition_opener ? params.partition_opener : &default_opener;
+
+    DmTable table;
+    if (!CreateDmTable(*opener, *metadata, *partition, params.block_device, &table)) {
+        return false;
     }
-    return CreateLogicalPartition(block_device, *metadata.get(), partition_name, force_writable,
-                                  timeout_ms, path);
+    if (params.force_writable) {
+        table.set_readonly(false);
+    }
+
+    std::string device_name = params.device_name;
+    if (device_name.empty()) {
+        device_name = GetPartitionName(*partition);
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    if (!dm.CreateDevice(device_name, table, path, params.timeout_ms)) {
+        return false;
+    }
+    LINFO << "Created logical partition " << device_name << " on device " << *path;
+    return true;
 }
 
 bool UnmapDevice(const std::string& name) {
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 4ee7db9..d7ea81d 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -133,7 +133,7 @@
     return ret | !rmdir(test_directory.c_str());
 }
 
-// At less than 1% free space return value of false,
+// At less than 1% or 8MB of free space return value of false,
 // means we will try to wrap with overlayfs.
 bool fs_mgr_filesystem_has_space(const std::string& mount_point) {
     // If we have access issues to find out space remaining, return true
@@ -145,9 +145,11 @@
         return true;
     }
 
-    static constexpr int kPercentThreshold = 1;  // 1%
+    static constexpr int kPercentThreshold = 1;                       // 1%
+    static constexpr unsigned long kSizeThreshold = 8 * 1024 * 1024;  // 8MB
 
-    return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100));
+    return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100)) &&
+           (vst.f_bfree * vst.f_bsize) >= kSizeThreshold;
 }
 
 const auto kPhysicalDevice = "/dev/block/by-name/"s;
@@ -959,9 +961,16 @@
     }
 
     if (changed || partition_create) {
-        if (!CreateLogicalPartition(super_device, slot_number, partition_name, true, 10s,
-                                    scratch_device))
+        CreateLogicalPartitionParams params = {
+                .block_device = super_device,
+                .metadata_slot = slot_number,
+                .partition_name = partition_name,
+                .force_writable = true,
+                .timeout_ms = 10s,
+        };
+        if (!CreateLogicalPartition(params, scratch_device)) {
             return false;
+        }
 
         if (change) *change = true;
     }
@@ -1182,11 +1191,15 @@
     if ((mount_point != nullptr) && !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
         auto scratch_device = fs_mgr_overlayfs_scratch_device();
         if (scratch_device.empty()) {
-            auto slot_number = fs_mgr_overlayfs_slot_number();
-            auto super_device = fs_mgr_overlayfs_super_device(slot_number);
-            const auto partition_name = android::base::Basename(kScratchMountPoint);
-            CreateLogicalPartition(super_device, slot_number, partition_name, true, 10s,
-                                   &scratch_device);
+            auto metadata_slot = fs_mgr_overlayfs_slot_number();
+            CreateLogicalPartitionParams params = {
+                    .block_device = fs_mgr_overlayfs_super_device(metadata_slot),
+                    .metadata_slot = metadata_slot,
+                    .partition_name = android::base::Basename(kScratchMountPoint),
+                    .force_writable = true,
+                    .timeout_ms = 10s,
+            };
+            CreateLogicalPartition(params, &scratch_device);
         }
         mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
                                                        fs_mgr_overlayfs_scratch_mount_type());
diff --git a/fs_mgr/include/fs_mgr_dm_linear.h b/fs_mgr/include/fs_mgr_dm_linear.h
index a1dc2dc..8e2fdbb 100644
--- a/fs_mgr/include/fs_mgr_dm_linear.h
+++ b/fs_mgr/include/fs_mgr_dm_linear.h
@@ -29,6 +29,7 @@
 
 #include <chrono>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -49,22 +50,36 @@
 // method for ReadMetadata and CreateLogicalPartitions.
 bool CreateLogicalPartitions(const std::string& block_device);
 
-// Create a block device for a single logical partition, given metadata and
-// the partition name. On success, a path to the partition's block device is
-// returned. If |force_writable| is true, the "readonly" flag will be ignored
-// so the partition can be flashed.
-//
-// If |timeout_ms| is non-zero, then CreateLogicalPartition will block for the
-// given amount of time until the path returned in |path| is available.
-bool CreateLogicalPartition(const std::string& block_device, uint32_t metadata_slot,
-                            const std::string& partition_name, bool force_writable,
-                            const std::chrono::milliseconds& timeout_ms, std::string* path);
+struct CreateLogicalPartitionParams {
+    // Block device of the super partition.
+    std::string block_device;
 
-// Same as above, but with a given metadata object. Care should be taken that
-// the metadata represents a valid partition layout.
-bool CreateLogicalPartition(const std::string& block_device, const LpMetadata& metadata,
-                            const std::string& partition_name, bool force_writable,
-                            const std::chrono::milliseconds& timeout_ms, std::string* path);
+    // If |metadata| is null, the slot will be read using |metadata_slot|.
+    const LpMetadata* metadata = nullptr;
+    std::optional<uint32_t> metadata_slot;
+
+    // If |partition| is not set, it will be found via |partition_name|.
+    const LpMetadataPartition* partition = nullptr;
+    std::string partition_name;
+
+    // Force the device to be read-write even if it was specified as readonly
+    // in the metadata.
+    bool force_writable = false;
+
+    // If |timeout_ms| is non-zero, then CreateLogicalPartition will block for
+    // the given amount of time until the path returned in |path| is available.
+    std::chrono::milliseconds timeout_ms = {};
+
+    // If this is non-empty, it will override the device mapper name (by
+    // default the partition name will be used).
+    std::string device_name;
+
+    // If non-null, this will use the specified IPartitionOpener rather than
+    // the default one.
+    const IPartitionOpener* partition_opener = nullptr;
+};
+
+bool CreateLogicalPartition(const CreateLogicalPartitionParams& params, std::string* path);
 
 // Destroy the block device for a logical partition, by name. If |timeout_ms|
 // is non-zero, then this will block until the device path has been unlinked.
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index a4e0d76..e7a3ff2 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -79,6 +79,13 @@
     return true;
 }
 
+bool DeviceMapper::DeleteDeviceIfExists(const std::string& name) {
+    if (GetState(name) == DmDeviceState::INVALID) {
+        return true;
+    }
+    return DeleteDevice(name);
+}
+
 bool DeviceMapper::DeleteDevice(const std::string& name) {
     struct dm_ioctl io;
     InitIo(&io, name);
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index cf306f3..e25ce7f 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -89,6 +89,7 @@
     // Removes a device mapper device with the given name.
     // Returns 'true' on success, false otherwise.
     bool DeleteDevice(const std::string& name);
+    bool DeleteDeviceIfExists(const std::string& name);
 
     // Fetches and returns the complete state of the underlying device mapper
     // device with given name.
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index 5b377ae..2b11da2 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -69,6 +69,7 @@
     stl: "libc++_static",
     srcs: [
         "builder_test.cpp",
+        "device_test.cpp",
         "io_test.cpp",
         "test_partition_opener.cpp",
         "utility_test.cpp",
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 6dc288e..77c9c28 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -171,6 +171,13 @@
         }
     }
 
+    if (IPropertyFetcher::GetInstance()->GetBoolProperty("ro.virtual_ab.enabled", false)) {
+        if (!UpdateMetadataForInPlaceSnapshot(metadata.get(), source_slot_number,
+                                              target_slot_number)) {
+            return nullptr;
+        }
+    }
+
     return New(*metadata.get(), &opener);
 }
 
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index b3c13e6..ecad7d4 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include <fs_mgr.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
 
+#include "liblp_test.h"
 #include "mock_property_fetcher.h"
 #include "utility.h"
 
@@ -31,7 +31,7 @@
 using ::testing::NiceMock;
 using ::testing::Return;
 
-static void ResetPropertyFetcher() {
+void ResetPropertyFetcher() {
     IPropertyFetcher::OverrideForTesting(std::make_unique<NiceMock<MockPropertyFetcher>>());
 }
 
@@ -49,11 +49,7 @@
     return RUN_ALL_TESTS();
 }
 
-class BuilderTest : public ::testing::Test {
-  public:
-    void SetUp() override { ResetPropertyFetcher(); }
-    void TearDown() override { ResetPropertyFetcher(); }
-};
+class BuilderTest : public LiblpTest {};
 
 TEST_F(BuilderTest, BuildBasic) {
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
@@ -460,23 +456,6 @@
     EXPECT_EQ(builder, nullptr);
 }
 
-TEST_F(BuilderTest, block_device_info) {
-    PartitionOpener opener;
-
-    BlockDeviceInfo device_info;
-    ASSERT_TRUE(opener.GetInfo(fs_mgr_get_super_partition_name(), &device_info));
-
-    // Sanity check that the device doesn't give us some weird inefficient
-    // alignment.
-    ASSERT_EQ(device_info.alignment % LP_SECTOR_SIZE, 0);
-    ASSERT_EQ(device_info.alignment_offset % LP_SECTOR_SIZE, 0);
-    ASSERT_LE(device_info.alignment_offset, INT_MAX);
-    ASSERT_EQ(device_info.logical_block_size % LP_SECTOR_SIZE, 0);
-
-    // Having an alignment offset > alignment doesn't really make sense.
-    ASSERT_LT(device_info.alignment_offset, device_info.alignment);
-}
-
 TEST_F(BuilderTest, UpdateBlockDeviceInfo) {
     BlockDeviceInfo device_info("super", 1024 * 1024, 4096, 1024, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
diff --git a/fs_mgr/liblp/device_test.cpp b/fs_mgr/liblp/device_test.cpp
new file mode 100644
index 0000000..b759fd1
--- /dev/null
+++ b/fs_mgr/liblp/device_test.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/properties.h>
+#include <fs_mgr.h>
+#include <fstab/fstab.h>
+#include <gtest/gtest.h>
+#include <liblp/liblp.h>
+#include <liblp/metadata_format.h>
+#include <liblp/partition_opener.h>
+#include <liblp/property_fetcher.h>
+
+#include "liblp_test.h"
+#include "mock_property_fetcher.h"
+
+using namespace android::fs_mgr;
+using ::testing::Return;
+
+// Compliance test on the actual device with dynamic partitions.
+class DeviceTest : public LiblpTest {
+  public:
+    void SetUp() override {
+        // Read real properties.
+        IPropertyFetcher::OverrideForTesting(std::make_unique<PropertyFetcher>());
+        if (!IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions",
+                                                              false)) {
+            GTEST_SKIP() << "Device doesn't have dynamic partitions enabled, skipping";
+        }
+    }
+};
+
+TEST_F(DeviceTest, BlockDeviceInfo) {
+    PartitionOpener opener;
+    BlockDeviceInfo device_info;
+    ASSERT_TRUE(opener.GetInfo(fs_mgr_get_super_partition_name(), &device_info));
+
+    // Sanity check that the device doesn't give us some weird inefficient
+    // alignment.
+    EXPECT_EQ(device_info.alignment % LP_SECTOR_SIZE, 0);
+    EXPECT_EQ(device_info.alignment_offset % LP_SECTOR_SIZE, 0);
+    EXPECT_LE(device_info.alignment_offset, INT_MAX);
+    EXPECT_EQ(device_info.logical_block_size % LP_SECTOR_SIZE, 0);
+
+    // Having an alignment offset > alignment doesn't really make sense.
+    EXPECT_LT(device_info.alignment_offset, device_info.alignment);
+}
+
+TEST_F(DeviceTest, ReadSuperPartitionCurrentSlot) {
+    auto slot_suffix = fs_mgr_get_slot_suffix();
+    auto slot_number = SlotNumberForSlotSuffix(slot_suffix);
+    auto super_name = fs_mgr_get_super_partition_name(slot_number);
+    auto metadata = ReadMetadata(super_name, slot_number);
+    EXPECT_NE(metadata, nullptr);
+}
+
+TEST_F(DeviceTest, ReadSuperPartitionOtherSlot) {
+    auto other_slot_suffix = fs_mgr_get_other_slot_suffix();
+    if (other_slot_suffix.empty()) {
+        GTEST_SKIP() << "No other slot, skipping";
+    }
+    if (IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions_retrofit",
+                                                         false)) {
+        GTEST_SKIP() << "Device with retrofit dynamic partition may not have metadata at other "
+                     << "slot, skipping";
+    }
+
+    auto other_slot_number = SlotNumberForSlotSuffix(other_slot_suffix);
+    auto other_super_name = fs_mgr_get_super_partition_name(other_slot_number);
+    auto other_metadata = ReadMetadata(other_super_name, other_slot_number);
+    EXPECT_NE(other_metadata, nullptr);
+}
diff --git a/fs_mgr/liblp/include/liblp/partition_opener.h b/fs_mgr/liblp/include/liblp/partition_opener.h
index e506bd5..7c9100b 100644
--- a/fs_mgr/liblp/include/liblp/partition_opener.h
+++ b/fs_mgr/liblp/include/liblp/partition_opener.h
@@ -62,6 +62,11 @@
     // Return block device information about the given named physical partition.
     // The name can be an absolute path if the full path is already known.
     virtual bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const = 0;
+
+    // Return a path that can be used to pass the block device to device-mapper.
+    // This must either result in an absolute path, or a major:minor device
+    // sequence.
+    virtual std::string GetDeviceString(const std::string& partition_name) const = 0;
 };
 
 // Helper class to implement IPartitionOpener. If |partition_name| is not an
@@ -71,6 +76,7 @@
     virtual android::base::unique_fd Open(const std::string& partition_name,
                                           int flags) const override;
     virtual bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const override;
+    virtual std::string GetDeviceString(const std::string& partition_name) const override;
 };
 
 }  // namespace fs_mgr
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 3dace25..4d00944 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -21,13 +21,12 @@
 
 #include <android-base/file.h>
 #include <android-base/unique_fd.h>
-#include <fs_mgr.h>
-#include <fstab/fstab.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
 
 #include "images.h"
+#include "liblp_test.h"
 #include "mock_property_fetcher.h"
 #include "reader.h"
 #include "test_partition_opener.h"
@@ -124,7 +123,7 @@
 }
 
 // Test that our CreateFakeDisk() function works.
-TEST(liblp, CreateFakeDisk) {
+TEST_F(LiblpTest, CreateFakeDisk) {
     unique_fd fd = CreateFakeDisk();
     ASSERT_GE(fd, 0);
 
@@ -140,7 +139,7 @@
 
 // Flashing metadata should not work if the metadata was created for a larger
 // disk than the destination disk.
-TEST(liblp, ExportDiskTooSmall) {
+TEST_F(LiblpTest, ExportDiskTooSmall) {
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize + 4096, 512, 2);
     ASSERT_NE(builder, nullptr);
     unique_ptr<LpMetadata> exported = builder->Export();
@@ -157,7 +156,7 @@
 }
 
 // Test the basics of flashing a partition and reading it back.
-TEST(liblp, FlashAndReadback) {
+TEST_F(LiblpTest, FlashAndReadback) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -207,7 +206,7 @@
 }
 
 // Test that we can update metadata slots without disturbing others.
-TEST(liblp, UpdateAnyMetadataSlot) {
+TEST_F(LiblpTest, UpdateAnyMetadataSlot) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -252,7 +251,7 @@
     }
 }
 
-TEST(liblp, InvalidMetadataSlot) {
+TEST_F(LiblpTest, InvalidMetadataSlot) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -271,7 +270,7 @@
 
 // Test that updating a metadata slot does not allow it to be computed based
 // on mismatching geometry.
-TEST(liblp, NoChangingGeometry) {
+TEST_F(LiblpTest, NoChangingGeometry) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -300,7 +299,7 @@
 }
 
 // Test that changing one bit of metadata is enough to break the checksum.
-TEST(liblp, BitFlipGeometry) {
+TEST_F(LiblpTest, BitFlipGeometry) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -319,7 +318,7 @@
     EXPECT_EQ(metadata->geometry.metadata_slot_count, 2);
 }
 
-TEST(liblp, ReadBackupGeometry) {
+TEST_F(LiblpTest, ReadBackupGeometry) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -339,7 +338,7 @@
     EXPECT_EQ(ReadMetadata(opener, "super", 0), nullptr);
 }
 
-TEST(liblp, ReadBackupMetadata) {
+TEST_F(LiblpTest, ReadBackupMetadata) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -366,7 +365,7 @@
 
 // Test that we don't attempt to write metadata if it would overflow its
 // reserved space.
-TEST(liblp, TooManyPartitions) {
+TEST_F(LiblpTest, TooManyPartitions) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
 
@@ -420,7 +419,7 @@
 }
 
 // Test that we can read and write image files.
-TEST(liblp, ImageFiles) {
+TEST_F(LiblpTest, ImageFiles) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -436,7 +435,7 @@
 }
 
 // Test that we can read images from buffers.
-TEST(liblp, ImageFilesInMemory) {
+TEST_F(LiblpTest, ImageFilesInMemory) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -496,7 +495,7 @@
 
 // Test that an interrupted flash operation on the "primary" copy of metadata
 // is not fatal.
-TEST(liblp, UpdatePrimaryMetadataFailure) {
+TEST_F(LiblpTest, UpdatePrimaryMetadataFailure) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -524,7 +523,7 @@
 
 // Test that an interrupted flash operation on the "backup" copy of metadata
 // is not fatal.
-TEST(liblp, UpdateBackupMetadataFailure) {
+TEST_F(LiblpTest, UpdateBackupMetadataFailure) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -553,7 +552,7 @@
 // Test that an interrupted write *in between* writing metadata will read
 // the correct metadata copy. The primary is always considered newer than
 // the backup.
-TEST(liblp, UpdateMetadataCleanFailure) {
+TEST_F(LiblpTest, UpdateMetadataCleanFailure) {
     unique_fd fd = CreateFlashedDisk();
     ASSERT_GE(fd, 0);
 
@@ -590,7 +589,7 @@
 }
 
 // Test that writing a sparse image can be read back.
-TEST(liblp, FlashSparseImage) {
+TEST_F(LiblpTest, FlashSparseImage) {
     unique_fd fd = CreateFakeDisk();
     ASSERT_GE(fd, 0);
 
@@ -624,7 +623,7 @@
     ASSERT_NE(ReadBackupMetadata(fd.get(), geometry, 0), nullptr);
 }
 
-TEST(liblp, AutoSlotSuffixing) {
+TEST_F(LiblpTest, AutoSlotSuffixing) {
     unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder();
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(AddDefaultPartitions(builder.get()));
@@ -667,7 +666,7 @@
     EXPECT_EQ(metadata->groups[1].flags, 0);
 }
 
-TEST(liblp, UpdateRetrofit) {
+TEST_F(LiblpTest, UpdateRetrofit) {
     ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
             .WillByDefault(Return(true));
 
@@ -699,7 +698,7 @@
     ASSERT_TRUE(updated->extents.empty());
 }
 
-TEST(liblp, UpdateNonRetrofit) {
+TEST_F(LiblpTest, UpdateNonRetrofit) {
     ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
             .WillByDefault(Return(false));
 
@@ -714,19 +713,3 @@
     ASSERT_EQ(updated->block_devices.size(), static_cast<size_t>(1));
     EXPECT_EQ(GetBlockDevicePartitionName(updated->block_devices[0]), "super");
 }
-
-TEST(liblp, ReadSuperPartition) {
-    auto slot_suffix = fs_mgr_get_slot_suffix();
-    auto slot_number = SlotNumberForSlotSuffix(slot_suffix);
-    auto super_name = fs_mgr_get_super_partition_name(slot_number);
-    auto metadata = ReadMetadata(super_name, slot_number);
-    ASSERT_NE(metadata, nullptr);
-
-    if (!slot_suffix.empty()) {
-        auto other_slot_suffix = fs_mgr_get_other_slot_suffix();
-        auto other_slot_number = SlotNumberForSlotSuffix(other_slot_suffix);
-        auto other_super_name = fs_mgr_get_super_partition_name(other_slot_number);
-        auto other_metadata = ReadMetadata(other_super_name, other_slot_number);
-        ASSERT_NE(other_metadata, nullptr);
-    }
-}
diff --git a/fs_mgr/liblp/liblp_test.h b/fs_mgr/liblp/liblp_test.h
new file mode 100644
index 0000000..892f43d
--- /dev/null
+++ b/fs_mgr/liblp/liblp_test.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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
+
+#include <gtest/gtest.h>
+
+#include "mock_property_fetcher.h"
+
+class LiblpTest : public ::testing::Test {
+  public:
+    void SetUp() override { ResetPropertyFetcher(); }
+    void TearDown() override { ResetPropertyFetcher(); }
+};
diff --git a/fs_mgr/liblp/mock_property_fetcher.h b/fs_mgr/liblp/mock_property_fetcher.h
index 0c28710..4612c52 100644
--- a/fs_mgr/liblp/mock_property_fetcher.h
+++ b/fs_mgr/liblp/mock_property_fetcher.h
@@ -45,3 +45,4 @@
 }  // namespace android
 
 android::fs_mgr::MockPropertyFetcher* GetMockedPropertyFetcher();
+void ResetPropertyFetcher();
diff --git a/fs_mgr/liblp/partition_opener.cpp b/fs_mgr/liblp/partition_opener.cpp
index cc4a882..1d4db85 100644
--- a/fs_mgr/liblp/partition_opener.cpp
+++ b/fs_mgr/liblp/partition_opener.cpp
@@ -41,7 +41,21 @@
     if (android::base::StartsWith(path, "/")) {
         return path;
     }
-    return "/dev/block/by-name/" + path;
+
+    auto by_name = "/dev/block/by-name/" + path;
+    if (access(by_name.c_str(), F_OK) != 0) {
+        // If the by-name symlink doesn't exist, as a special case we allow
+        // certain devices to be used as partition names. This can happen if a
+        // Dynamic System Update is installed to an sdcard, which won't be in
+        // the boot device list.
+        //
+        // We whitelist because most devices in /dev/block are not valid for
+        // storing fiemaps.
+        if (android::base::StartsWith(path, "mmcblk")) {
+            return "/dev/block/" + path;
+        }
+    }
+    return by_name;
 }
 
 bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info) {
@@ -100,5 +114,9 @@
     return GetBlockDeviceInfo(path, info);
 }
 
+std::string PartitionOpener::GetDeviceString(const std::string& partition_name) const {
+    return GetPartitionAbsolutePath(partition_name);
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/utility.cpp b/fs_mgr/liblp/utility.cpp
index 338b525..17f05aa 100644
--- a/fs_mgr/liblp/utility.cpp
+++ b/fs_mgr/liblp/utility.cpp
@@ -24,6 +24,10 @@
 #include <sys/ioctl.h>
 #endif
 
+#include <map>
+#include <string>
+#include <vector>
+
 #include <android-base/file.h>
 #include <ext4_utils/ext4_utils.h>
 #include <openssl/sha.h>
@@ -182,6 +186,14 @@
     return true;
 }
 
+bool UpdatePartitionName(LpMetadataPartition* partition, const std::string& name) {
+    if (name.size() > sizeof(partition->name)) {
+        return false;
+    }
+    strncpy(partition->name, name.c_str(), sizeof(partition->name));
+    return true;
+}
+
 bool SetBlockReadonly(int fd, bool readonly) {
 #if defined(__linux__)
     int val = readonly;
@@ -207,5 +219,70 @@
     return base::unique_fd(open(path, flags));
 }
 
+bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number,
+                                      uint32_t target_slot_number) {
+    std::string source_slot_suffix = SlotSuffixForSlotNumber(source_slot_number);
+    std::string target_slot_suffix = SlotSuffixForSlotNumber(target_slot_number);
+
+    // There can be leftover groups with target suffix on retrofit devices.
+    // They are useless now, so delete.
+    std::vector<LpMetadataPartitionGroup*> new_group_ptrs;
+    for (auto& group : metadata->groups) {
+        std::string group_name = GetPartitionGroupName(group);
+        std::string slot_suffix = GetPartitionSlotSuffix(group_name);
+        // Don't add groups with target slot suffix.
+        if (slot_suffix == target_slot_suffix) continue;
+        // Replace source slot suffix with target slot suffix.
+        if (slot_suffix == source_slot_suffix) {
+            std::string new_name = group_name.substr(0, group_name.size() - slot_suffix.size()) +
+                                   target_slot_suffix;
+            if (!UpdatePartitionGroupName(&group, new_name)) {
+                LERROR << "Group name too long: " << new_name;
+                return false;
+            }
+        }
+        new_group_ptrs.push_back(&group);
+    }
+
+    std::vector<LpMetadataPartition*> new_partition_ptrs;
+    for (auto& partition : metadata->partitions) {
+        std::string partition_name = GetPartitionName(partition);
+        std::string slot_suffix = GetPartitionSlotSuffix(partition_name);
+        // Don't add partitions with target slot suffix.
+        if (slot_suffix == target_slot_suffix) continue;
+        // Replace source slot suffix with target slot suffix.
+        if (slot_suffix == source_slot_suffix) {
+            std::string new_name =
+                    partition_name.substr(0, partition_name.size() - slot_suffix.size()) +
+                    target_slot_suffix;
+            if (!UpdatePartitionName(&partition, new_name)) {
+                LERROR << "Partition name too long: " << new_name;
+                return false;
+            }
+        }
+        // Update group index.
+        auto it = std::find(new_group_ptrs.begin(), new_group_ptrs.end(),
+                            &metadata->groups[partition.group_index]);
+        if (it == new_group_ptrs.end()) {
+            LWARN << "Removing partition " << partition_name << " from group "
+                  << GetPartitionGroupName(metadata->groups[partition.group_index])
+                  << "; this partition should not belong to this group!";
+            continue;  // not adding to new_partition_ptrs
+        }
+        partition.group_index = std::distance(new_group_ptrs.begin(), it);
+        new_partition_ptrs.push_back(&partition);
+    }
+
+    std::vector<LpMetadataPartition> new_partitions;
+    for (auto* p : new_partition_ptrs) new_partitions.emplace_back(std::move(*p));
+    metadata->partitions = std::move(new_partitions);
+
+    std::vector<LpMetadataPartitionGroup> new_groups;
+    for (auto* g : new_group_ptrs) new_groups.emplace_back(std::move(*g));
+    metadata->groups = std::move(new_groups);
+
+    return true;
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/utility.h b/fs_mgr/liblp/utility.h
index 96f1717..25ab66b 100644
--- a/fs_mgr/liblp/utility.h
+++ b/fs_mgr/liblp/utility.h
@@ -89,12 +89,17 @@
 // Update names from C++ strings.
 bool UpdateBlockDevicePartitionName(LpMetadataBlockDevice* device, const std::string& name);
 bool UpdatePartitionGroupName(LpMetadataPartitionGroup* group, const std::string& name);
+bool UpdatePartitionName(LpMetadataPartition* partition, const std::string& name);
 
 // Call BLKROSET ioctl on fd so that fd is readonly / read-writable.
 bool SetBlockReadonly(int fd, bool readonly);
 
 ::android::base::unique_fd GetControlFileOrOpen(const char* path, int flags);
 
+// For Virtual A/B updates, modify |metadata| so that it can be written to |target_slot_number|.
+bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number,
+                                      uint32_t target_slot_number);
+
 }  // namespace fs_mgr
 }  // namespace android
 
diff --git a/fs_mgr/liblp/utility_test.cpp b/fs_mgr/liblp/utility_test.cpp
index 15f7fff..cac3989 100644
--- a/fs_mgr/liblp/utility_test.cpp
+++ b/fs_mgr/liblp/utility_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <gtest/gtest.h>
+#include <liblp/builder.h>
 #include <liblp/liblp.h>
 
 #include "utility.h"
@@ -75,3 +76,81 @@
     EXPECT_EQ(GetPartitionSlotSuffix("system_a"), "_a");
     EXPECT_EQ(GetPartitionSlotSuffix("system_b"), "_b");
 }
+
+namespace android {
+namespace fs_mgr {
+// Equality comparison for testing. In reality, equality of device_index doesn't
+// necessary mean equality of the block device.
+bool operator==(const LinearExtent& l, const LinearExtent& r) {
+    return l.device_index() == r.device_index() && l.physical_sector() == r.physical_sector() &&
+           l.end_sector() == r.end_sector();
+}
+}  // namespace fs_mgr
+}  // namespace android
+
+static std::vector<LinearExtent> GetPartitionExtents(Partition* p) {
+    std::vector<LinearExtent> extents;
+    for (auto&& extent : p->extents()) {
+        auto linear_extent = extent->AsLinearExtent();
+        if (!linear_extent) return {};
+        extents.push_back(*linear_extent);
+    }
+    return extents;
+}
+
+TEST(liblp, UpdateMetadataForInPlaceSnapshot) {
+    using std::unique_ptr;
+
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    ASSERT_NE(builder, nullptr);
+
+    ASSERT_TRUE(builder->AddGroup("group_a", 256 * 1024));
+    Partition* system_a = builder->AddPartition("system_a", "group_a", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system_a, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(system_a, 40 * 1024));
+    Partition* vendor_a = builder->AddPartition("vendor_a", "group_a", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(vendor_a, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(vendor_a, 20 * 1024));
+
+    ASSERT_TRUE(builder->AddGroup("group_b", 258 * 1024));
+    Partition* system_b = builder->AddPartition("system_b", "group_b", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system_b, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(system_b, 36 * 1024));
+    Partition* vendor_b = builder->AddPartition("vendor_b", "group_b", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(vendor_b, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(vendor_b, 32 * 1024));
+
+    auto system_a_extents = GetPartitionExtents(system_a);
+    ASSERT_FALSE(system_a_extents.empty());
+
+    auto vendor_a_extents = GetPartitionExtents(vendor_a);
+    ASSERT_FALSE(vendor_a_extents.empty());
+
+    auto metadata = builder->Export();
+    ASSERT_NE(nullptr, metadata);
+
+    ASSERT_TRUE(UpdateMetadataForInPlaceSnapshot(metadata.get(), 0, 1));
+
+    auto new_builder = MetadataBuilder::New(*metadata);
+    ASSERT_NE(nullptr, new_builder);
+
+    EXPECT_EQ(nullptr, new_builder->FindGroup("group_a"));
+    EXPECT_EQ(nullptr, new_builder->FindPartition("system_a"));
+    EXPECT_EQ(nullptr, new_builder->FindPartition("vendor_a"));
+
+    auto group_b = new_builder->FindGroup("group_b");
+    ASSERT_NE(nullptr, group_b);
+    ASSERT_EQ(256 * 1024, group_b->maximum_size());
+
+    auto new_system_b = new_builder->FindPartition("system_b");
+    ASSERT_NE(nullptr, new_system_b);
+    EXPECT_EQ(40 * 1024, new_system_b->size());
+    auto new_system_b_extents = GetPartitionExtents(new_system_b);
+    EXPECT_EQ(system_a_extents, new_system_b_extents);
+
+    auto new_vendor_b = new_builder->FindPartition("vendor_b");
+    ASSERT_NE(nullptr, new_vendor_b);
+    EXPECT_EQ(20 * 1024, new_vendor_b->size());
+    auto new_vendor_b_extents = GetPartitionExtents(new_vendor_b);
+    EXPECT_EQ(vendor_a_extents, new_vendor_b_extents);
+}
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 128a4f9..51f5c50 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -74,6 +74,7 @@
     defaults: ["libsnapshot_defaults"],
     srcs: [
         "snapshot_test.cpp",
+        "test_helpers.cpp",
     ],
     shared_libs: [
         "libbinder",
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 438ec2f..518c619 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -26,9 +26,13 @@
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <fs_mgr_dm_linear.h>
 #include <gtest/gtest.h>
 #include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
+#include <liblp/builder.h>
+
+#include "test_helpers.h"
 
 namespace android {
 namespace snapshot {
@@ -37,25 +41,35 @@
 using android::dm::DeviceMapper;
 using android::dm::DmDeviceState;
 using android::fiemap::IImageManager;
+using android::fs_mgr::BlockDeviceInfo;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::MetadataBuilder;
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 
-class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
-  public:
-    std::string GetGsidDir() const override { return "ota/test"s; }
-    std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
-    std::string GetSlotSuffix() const override { return slot_suffix_; }
-
-    void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; }
-
-  private:
-    std::string slot_suffix_ = "_a";
-};
-
-// These are not reset between each test because it's expensive to reconnect
-// to gsid each time.
+// These are not reset between each test because it's expensive to create
+// these resources (starting+connecting to gsid, zero-filling images).
 std::unique_ptr<SnapshotManager> sm;
 TestDeviceInfo* test_device = nullptr;
+std::string fake_super;
+
+static constexpr uint64_t kSuperSize = 16 * 1024 * 1024;
+
+// Helper to remove stale partitions in fake super.
+void CleanupPartitions() {
+    // These are hardcoded since we might abort in the middle of a test, and
+    // can't recover the in-use list.
+    static std::vector<std::string> kPartitionNames = {
+            "base-device",
+    };
+
+    auto& dm = DeviceMapper::Instance();
+    for (const auto& partition : kPartitionNames) {
+        if (dm.GetState(partition) != DmDeviceState::INVALID) {
+            dm.DeleteDevice(partition);
+        }
+    }
+}
 
 class SnapshotTest : public ::testing::Test {
   public:
@@ -69,6 +83,7 @@
         test_device->set_slot_suffix("_a");
 
         CleanupTestArtifacts();
+        FormatFakeSuper();
 
         ASSERT_TRUE(sm->BeginUpdate());
     }
@@ -96,12 +111,7 @@
             android::base::RemoveFileIfExists(status_file);
         }
 
-        // Remove all images. We hardcode the list of names since this can run
-        // before the test (when cleaning up from a crash).
-        std::vector<std::string> temp_partitions = {"base-device"};
-        for (const auto& partition : temp_partitions) {
-            DeleteBackingImage(image_manager_, partition);
-        }
+        CleanupPartitions();
 
         if (sm->GetUpdateState() != UpdateState::None) {
             auto state_file = sm->GetStateFilePath();
@@ -114,11 +124,48 @@
         return !!lock_;
     }
 
-    bool CreateTempDevice(const std::string& name, uint64_t size, std::string* path) {
-        if (!image_manager_->CreateBackingImage(name, size, false)) {
+    void FormatFakeSuper() {
+        BlockDeviceInfo super_device("super", kSuperSize, 0, 0, 4096);
+        std::vector<BlockDeviceInfo> devices = {super_device};
+
+        auto builder = MetadataBuilder::New(devices, "super", 65536, 2);
+        ASSERT_NE(builder, nullptr);
+
+        auto metadata = builder->Export();
+        ASSERT_NE(metadata, nullptr);
+
+        TestPartitionOpener opener(fake_super);
+        ASSERT_TRUE(FlashPartitionTable(opener, fake_super, *metadata.get()));
+    }
+
+    // If |path| is non-null, the partition will be mapped after creation.
+    bool CreatePartition(const std::string& name, uint64_t size, std::string* path = nullptr) {
+        TestPartitionOpener opener(fake_super);
+        auto builder = MetadataBuilder::New(opener, "super", 0);
+        if (!builder) return false;
+
+        auto partition = builder->AddPartition(name, 0);
+        if (!partition) return false;
+        if (!builder->ResizePartition(partition, size)) {
             return false;
         }
-        return image_manager_->MapImageDevice(name, 10s, path);
+
+        auto metadata = builder->Export();
+        if (!metadata) return false;
+        if (!UpdatePartitionTable(opener, "super", *metadata.get(), 0)) {
+            return false;
+        }
+
+        if (!path) return true;
+
+        CreateLogicalPartitionParams params = {
+                .block_device = fake_super,
+                .metadata = metadata.get(),
+                .partition_name = name,
+                .force_writable = true,
+                .timeout_ms = 10s,
+        };
+        return CreateLogicalPartition(params, path);
     }
 
     void DeleteSnapshotDevice(const std::string& snapshot) {
@@ -130,18 +177,10 @@
         }
     }
 
-    void DeleteBackingImage(IImageManager* manager, const std::string& name) {
-        if (manager->IsImageMapped(name)) {
-            ASSERT_TRUE(manager->UnmapImageDevice(name));
-        }
-        if (manager->BackingImageExists(name)) {
-            ASSERT_TRUE(manager->DeleteBackingImage(name));
-        }
-    }
-
     DeviceMapper& dm_;
     std::unique_ptr<SnapshotManager::LockedFile> lock_;
     android::fiemap::IImageManager* image_manager_ = nullptr;
+    std::string fake_super_;
 };
 
 TEST_F(SnapshotTest, CreateSnapshot) {
@@ -177,7 +216,7 @@
                                    kDeviceSize));
 
     std::string base_device;
-    ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
 
     std::string snap_device;
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
@@ -193,7 +232,7 @@
                                    kSnapshotSize));
 
     std::string base_device;
-    ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
 
     std::string snap_device;
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
@@ -215,7 +254,7 @@
                                    kDeviceSize));
 
     std::string base_device, snap_device;
-    ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
 
     std::string test_string = "This is a test string.";
@@ -270,7 +309,7 @@
                                    kDeviceSize));
 
     std::string base_device, snap_device;
-    ASSERT_TRUE(CreateTempDevice("base-device", kDeviceSize, &base_device));
+    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, 10s, &snap_device));
 
     // Keep an open handle to the cow device. This should cause the merge to
@@ -321,10 +360,14 @@
     ::testing::InitGoogleTest(&argc, argv);
 
     std::vector<std::string> paths = {
+            // clang-format off
             "/data/gsi/ota/test",
+            "/data/gsi/ota/test/super",
             "/metadata/gsi/ota/test",
+            "/metadata/gsi/ota/test/super",
             "/metadata/ota/test",
             "/metadata/ota/test/snapshots",
+            // clang-format on
     };
     for (const auto& path : paths) {
         if (!Mkdir(path)) {
@@ -336,9 +379,38 @@
     test_device = new TestDeviceInfo();
     sm = SnapshotManager::New(test_device);
     if (!sm) {
-        std::cerr << "Could not create snapshot manager";
+        std::cerr << "Could not create snapshot manager\n";
         return 1;
     }
 
-    return RUN_ALL_TESTS();
+    // Use a separate image manager for our fake super partition.
+    auto super_images = IImageManager::Open("ota/test/super", 10s);
+    if (!super_images) {
+        std::cerr << "Could not create image manager\n";
+        return 1;
+    }
+
+    // Clean up previous run.
+    CleanupPartitions();
+    DeleteBackingImage(super_images.get(), "fake-super");
+
+    // Create and map the fake super partition.
+    static constexpr int kImageFlags =
+            IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
+    if (!super_images->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) {
+        std::cerr << "Could not create fake super partition\n";
+        return 1;
+    }
+    if (!super_images->MapImageDevice("fake-super", 10s, &fake_super)) {
+        std::cerr << "Could not map fake super partition\n";
+        return 1;
+    }
+
+    auto result = RUN_ALL_TESTS();
+
+    // Clean up again.
+    CleanupPartitions();
+    DeleteBackingImage(super_images.get(), "fake-super");
+
+    return result;
 }
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
new file mode 100644
index 0000000..17ffa4e
--- /dev/null
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test_helpers.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace snapshot {
+
+using android::fiemap::IImageManager;
+
+void DeleteBackingImage(IImageManager* manager, const std::string& name) {
+    if (manager->IsImageMapped(name)) {
+        ASSERT_TRUE(manager->UnmapImageDevice(name));
+    }
+    if (manager->BackingImageExists(name)) {
+        ASSERT_TRUE(manager->DeleteBackingImage(name));
+    }
+}
+
+android::base::unique_fd TestPartitionOpener::Open(const std::string& partition_name,
+                                                   int flags) const {
+    if (partition_name == "super") {
+        return PartitionOpener::Open(fake_super_path_, flags);
+    }
+    return PartitionOpener::Open(partition_name, flags);
+}
+
+bool TestPartitionOpener::GetInfo(const std::string& partition_name,
+                                  android::fs_mgr::BlockDeviceInfo* info) const {
+    if (partition_name == "super") {
+        return PartitionOpener::GetInfo(fake_super_path_, info);
+    }
+    return PartitionOpener::GetInfo(partition_name, info);
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/test_helpers.h
new file mode 100644
index 0000000..9491be3
--- /dev/null
+++ b/fs_mgr/libsnapshot/test_helpers.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2019 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
+
+#include <string>
+
+#include <libfiemap/image_manager.h>
+#include <liblp/partition_opener.h>
+#include <libsnapshot/snapshot.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace std::string_literals;
+
+class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
+  public:
+    std::string GetGsidDir() const override { return "ota/test"s; }
+    std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
+    std::string GetSlotSuffix() const override { return slot_suffix_; }
+
+    void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; }
+
+  private:
+    std::string slot_suffix_ = "_a";
+};
+
+// Redirect requests for "super" to our fake super partition.
+class TestPartitionOpener final : public android::fs_mgr::PartitionOpener {
+  public:
+    explicit TestPartitionOpener(const std::string& fake_super_path)
+        : fake_super_path_(fake_super_path) {}
+
+    android::base::unique_fd Open(const std::string& partition_name, int flags) const override;
+    bool GetInfo(const std::string& partition_name,
+                 android::fs_mgr::BlockDeviceInfo* info) const override;
+
+  private:
+    std::string fake_super_path_;
+};
+
+// Helper for error-spam-free cleanup.
+void DeleteBackingImage(android::fiemap::IImageManager* manager, const std::string& name);
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 397d8e5..ed6d0e3 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -709,8 +709,9 @@
       EXPECT_EQ "${lval}" "${rval}" ${*}
       return
   fi
-  EXPECT_EQ "${lval}" "${rval}" ||
+  if ! EXPECT_EQ "${lval}" "${rval}"; then
     die "${@}"
+  fi
 }
 
 [ "USAGE: check_ne <lval> <rval> [--warning [message]]
@@ -724,8 +725,9 @@
       EXPECT_NE "${lval}" "${rval}" ${*}
       return
   fi
-  EXPECT_NE "${lval}" "${rval}" ||
+  if ! EXPECT_NE "${lval}" "${rval}"; then
     die "${@}"
+  fi
 }
 
 [ "USAGE: skip_administrative_mounts [data] < /proc/mounts
@@ -847,6 +849,8 @@
 
 # Do something.
 
+# Collect characteristics of the device and report.
+
 D=`get_property ro.serialno`
 [ -n "${D}" ] || D=`get_property ro.boot.serialno`
 [ -z "${D}" -o -n "${ANDROID_SERIAL}" ] || ANDROID_SERIAL=${D}
@@ -869,22 +873,42 @@
 [ -z "${ACTIVE_SLOT}" ] ||
   echo "${BLUE}[     INFO ]${NORMAL} active slot is ${ACTIVE_SLOT}" >&2
 
+# Acquire list of system partitions
+
+PARTITIONS=`adb_su cat /vendor/etc/fstab* |
+              skip_administrative_mounts |
+              sed -n "s@^\([^ ${TAB}/][^ ${TAB}/]*\)[ ${TAB}].*[, ${TAB}]ro[, ${TAB}].*@\1@p" |
+              sort -u |
+              tr '\n' ' '`
+PARTITIONS="${PARTITIONS:-system vendor}"
+# KISS (we do not support sub-mounts for system partitions currently)
+MOUNTS="`for i in ${PARTITIONS}; do
+           echo /${i}
+         done |
+         tr '\n' ' '`"
+echo "${BLUE}[     INFO ]${NORMAL} System Partitions list: ${PARTITIONS}" >&2
+
 # Report existing partition sizes
-adb_sh ls -l /dev/block/by-name/ </dev/null 2>/dev/null |
+adb_sh ls -l /dev/block/by-name/ /dev/block/mapper/ </dev/null 2>/dev/null |
   sed -n 's@.* \([^ ]*\) -> /dev/block/\([^ ]*\)$@\1 \2@p' |
   while read name device; do
-    case ${name} in
-      system_[ab] | system | vendor_[ab] | vendor | super | cache)
-        case ${device} in
-          sd*)
-            device=${device%%[0-9]*}/${device}
-            ;;
-        esac
-        size=`adb_su cat /sys/block/${device}/size 2>/dev/null </dev/null` &&
-          size=`expr ${size} / 2` &&
-          echo "${BLUE}[     INFO ]${NORMAL} partition ${name} device ${device} size ${size}K" >&2
+    [ super = ${name} -o cache = ${name} ] ||
+      (
+        for i in ${PARTITIONS}; do
+          [ ${i} = ${name} -o ${i} = ${name%_[ab]} ] && exit
+        done
+        exit 1
+      ) ||
+      continue
+
+    case ${device} in
+      sd*)
+        device=${device%%[0-9]*}/${device}
         ;;
     esac
+    size=`adb_su cat /sys/block/${device}/size 2>/dev/null </dev/null` &&
+      size=`expr ${size} / 2` &&
+      echo "${BLUE}[     INFO ]${NORMAL} partition ${name} device ${device} size ${size}K" >&2
   done
 
 # If reboot too soon after fresh flash, could trip device update failure logic
@@ -1107,7 +1131,7 @@
 
 # Feed log with selinux denials as baseline before overlays
 adb_unroot
-adb_sh find /system /vendor </dev/null >/dev/null 2>/dev/null
+adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
 adb_root
 
 D=`adb remount 2>&1`
@@ -1199,21 +1223,19 @@
 
 # Check something.
 
-echo "${GREEN}[ RUN      ]${NORMAL} push content to /system and /vendor" >&2
+echo "${GREEN}[ RUN      ]${NORMAL} push content to ${MOUNTS}" >&2
 
 A="Hello World! $(date)"
-echo "${A}" | adb_sh cat - ">/system/hello"
+for i in ${MOUNTS}; do
+  echo "${A}" | adb_sh cat - ">${i}/hello"
+  B="`adb_cat ${i}/hello`" ||
+    die "${i#/} hello"
+  check_eq "${A}" "${B}" ${i} before reboot
+done
 echo "${A}" | adb_sh cat - ">/system/priv-app/hello"
-echo "${A}" | adb_sh cat - ">/vendor/hello"
-B="`adb_cat /system/hello`" ||
-  die "system hello"
-check_eq "${A}" "${B}" /system before reboot
 B="`adb_cat /system/priv-app/hello`" ||
   die "system priv-app hello"
 check_eq "${A}" "${B}" /system/priv-app before reboot
-B="`adb_cat /vendor/hello`" ||
-  die "vendor hello"
-check_eq "${A}" "${B}" /vendor before reboot
 SYSTEM_DEVT=`adb_sh stat --format=%D /system/hello </dev/null`
 VENDOR_DEVT=`adb_sh stat --format=%D /vendor/hello </dev/null`
 SYSTEM_INO=`adb_sh stat --format=%i /system/hello </dev/null`
@@ -1288,24 +1310,23 @@
   echo "${GREEN}[       OK ]${NORMAL} /vendor content correct MAC after reboot" >&2
   # Feed unprivileged log with selinux denials as a result of overlays
   wait_for_screen
-  adb_sh find /system /vendor </dev/null >/dev/null 2>/dev/null
+  adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
 fi
-B="`adb_cat /system/hello`"
-check_eq "${A}" "${B}" /system after reboot
 # If overlayfs has a nested security problem, this will fail.
 B="`adb_ls /system/`" ||
-  dir "adb ls /system"
+  die "adb ls /system"
 [ X"${B}" != X"${B#*priv-app}" ] ||
-  dir "adb ls /system/priv-app"
+  die "adb ls /system/priv-app"
 B="`adb_cat /system/priv-app/hello`"
 check_eq "${A}" "${B}" /system/priv-app after reboot
-echo "${GREEN}[       OK ]${NORMAL} /system content remains after reboot" >&2
 # Only root can read vendor if sepolicy permissions are as expected.
 adb_root ||
   die "adb root"
-B="`adb_cat /vendor/hello`"
-check_eq "${A}" "${B}" vendor after reboot
-echo "${GREEN}[       OK ]${NORMAL} /vendor content remains after reboot" >&2
+for i in ${MOUNTS}; do
+  B="`adb_cat ${i}/hello`"
+  check_eq "${A}" "${B}" ${i#/} after reboot
+  echo "${GREEN}[       OK ]${NORMAL} ${i} content remains after reboot" >&2
+done
 
 check_eq "${SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/hello </dev/null`" system devt after reboot
 check_eq "${VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/hello </dev/null`" vendor devt after reboot
@@ -1316,7 +1337,7 @@
 check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" devt for su after reboot
 
 # Feed log with selinux denials as a result of overlays
-adb_sh find /system /vendor </dev/null >/dev/null 2>/dev/null
+adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
 
 # Check if the updated libc.so is persistent after reboot.
 adb_root &&
@@ -1421,9 +1442,9 @@
   B="`adb_cat /system/hello`"
   check_eq "${A}" "${B}" system after flash vendor
   B="`adb_ls /system/`" ||
-    dir "adb ls /system"
+    die "adb ls /system"
   [ X"${B}" != X"${B#*priv-app}" ] ||
-    dir "adb ls /system/priv-app"
+    die "adb ls /system/priv-app"
   B="`adb_cat /system/priv-app/hello`"
   check_eq "${A}" "${B}" system/priv-app after flash vendor
   adb_root ||
@@ -1476,6 +1497,9 @@
 check_eq "cat: /system/priv-app/hello: No such file or directory" "${B}" after rm
 B="`adb_cat /vendor/hello`"
 check_eq "cat: /vendor/hello: No such file or directory" "${B}" after rm
+for i in ${MOUNTS}; do
+  adb_sh rm ${i}/hello </dev/null 2>/dev/null || true
+done
 
 if ${is_bootloader_fastboot} && [ -n "${scratch_partition}" ]; then
 
diff --git a/init/Android.bp b/init/Android.bp
index 3233cc3..7dfb828 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -128,6 +128,7 @@
         "persistent_properties.cpp",
         "persistent_properties.proto",
         "property_service.cpp",
+        "property_service.proto",
         "property_type.cpp",
         "reboot.cpp",
         "reboot_utils.cpp",
@@ -206,9 +207,18 @@
 // ------------------------------------------------------------------------------
 
 cc_test {
-    name: "init_tests",
+    name: "CtsInitTestCases",
     defaults: ["init_defaults"],
-    compile_multilib: "first",
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
     srcs: [
         "devices_test.cpp",
         "init_test.cpp",
@@ -225,7 +235,12 @@
         "util_test.cpp",
     ],
     static_libs: ["libinit"],
-    test_suites: ["device-tests"],
+
+    test_suites: [
+        "cts",
+        "device-tests",
+        "vts",
+    ],
 }
 
 cc_benchmark {
@@ -269,11 +284,13 @@
     shared_libs: [
         "libcutils",
         "libhidl-gen-utils",
-        "libjsoncpp",
         "liblog",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
     ],
+    header_libs: [
+        "libjsoncpp_headers",
+    ],
     srcs: [
         "action.cpp",
         "action_manager.cpp",
diff --git a/init/AndroidTest.xml b/init/AndroidTest.xml
new file mode 100644
index 0000000..94a02e6
--- /dev/null
+++ b/init/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Config for CTS init test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsInitTestCases->/data/local/tmp/CtsInitTestCases" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="CtsInitTestCases" />
+        <option name="runtime-hint" value="65s" />
+    </test>
+</configuration>
diff --git a/init/builtins.cpp b/init/builtins.cpp
index a2d782b..7076926 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -80,6 +80,7 @@
 using namespace std::literals::string_literals;
 
 using android::base::Basename;
+using android::base::StartsWith;
 using android::base::unique_fd;
 using android::fs_mgr::Fstab;
 using android::fs_mgr::ReadFstabFromFile;
@@ -640,12 +641,7 @@
     if (!ReadFstabFromFile(fstab_file, &fstab)) {
         return Error() << "Could not read fstab";
     }
-
-    auto mount_fstab_return_code =
-            CallFunctionAndHandleProperties(fs_mgr_mount_all, &fstab, mount_mode);
-    if (!mount_fstab_return_code) {
-        return Error() << "Could not call fs_mgr_mount_all(): " << mount_fstab_return_code.error();
-    }
+    auto mount_fstab_return_code = fs_mgr_mount_all(&fstab, mount_mode);
     property_set(prop_name, std::to_string(t.duration().count()));
 
     if (import_rc && SelinuxGetVendorAndroidVersion() <= __ANDROID_API_Q__) {
@@ -656,7 +652,7 @@
     if (queue_event) {
         /* queue_fs_event will queue event based on mount_fstab return code
          * and return processed return code*/
-        auto queue_fs_result = queue_fs_event(*mount_fstab_return_code);
+        auto queue_fs_result = queue_fs_event(mount_fstab_return_code);
         if (!queue_fs_result) {
             return Error() << "queue_fs_event() failed: " << queue_fs_result.error();
         }
@@ -672,13 +668,8 @@
         return Error() << "Could not read fstab";
     }
 
-    auto result = CallFunctionAndHandleProperties(fs_mgr_umount_all, &fstab);
-    if (!result) {
-        return Error() << "Could not call fs_mgr_mount_all() " << result.error();
-    }
-
-    if (*result != 0) {
-        return Error() << "fs_mgr_mount_all() failed: " << *result;
+    if (auto result = fs_mgr_umount_all(&fstab); result != 0) {
+        return Error() << "umount_fstab() failed " << result;
     }
     return {};
 }
@@ -689,19 +680,23 @@
         return Error() << "Could not read fstab '" << args[1] << "'";
     }
 
-    auto result = CallFunctionAndHandleProperties(fs_mgr_swapon_all, fstab);
-    if (!result) {
-        return Error() << "Could not call fs_mgr_swapon_all() " << result.error();
-    }
-
-    if (*result == 0) {
-        return Error() << "fs_mgr_swapon_all() failed.";
+    if (!fs_mgr_swapon_all(fstab)) {
+        return Error() << "fs_mgr_swapon_all() failed";
     }
 
     return {};
 }
 
 static Result<void> do_setprop(const BuiltinArguments& args) {
+    if (StartsWith(args[1], "ctl.")) {
+        return Error()
+               << "Cannot set ctl. properties from init; call the Service functions directly";
+    }
+    if (args[1] == kRestoreconProperty) {
+        return Error() << "Cannot set '" << kRestoreconProperty
+                       << "' from init; use the restorecon builtin directly";
+    }
+
     property_set(args[1], args[2]);
     return {};
 }
@@ -1017,7 +1012,20 @@
 }
 
 static Result<void> do_load_persist_props(const BuiltinArguments& args) {
-    load_persist_props();
+    // Devices with FDE have load_persist_props called twice; the first time when the temporary
+    // /data partition is mounted and then again once /data is truly mounted.  We do not want to
+    // read persistent properties from the temporary /data partition or mark persistent properties
+    // as having been loaded during the first call, so we return in that case.
+    std::string crypto_state = android::base::GetProperty("ro.crypto.state", "");
+    std::string crypto_type = android::base::GetProperty("ro.crypto.type", "");
+    if (crypto_state == "encrypted" && crypto_type == "block") {
+        static size_t num_calls = 0;
+        if (++num_calls == 1) return {};
+    }
+
+    SendLoadPersistentPropertiesMessage();
+
+    start_waiting_for_property("ro.persistent_properties.ready", "true");
     return {};
 }
 
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 1a5ed28..dffd6af 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -602,19 +602,11 @@
         return;
     }
 
-    // Find the name of the super partition for the GSI. It will either be
-    // "userdata", or a block device such as an sdcard. There are no by-name
-    // partitions other than userdata that we support installing GSIs to.
+    // Find the super name. PartitionOpener will ensure this translates to the
+    // correct block device path.
     auto super = GetMetadataSuperBlockDevice(*metadata.get());
-    std::string super_name = android::fs_mgr::GetBlockDevicePartitionName(*super);
-    std::string super_path;
-    if (super_name == "userdata") {
-        super_path = "/dev/block/by-name/" + super_name;
-    } else {
-        super_path = "/dev/block/" + super_name;
-    }
-
-    if (!android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_path)) {
+    auto super_name = android::fs_mgr::GetBlockDevicePartitionName(*super);
+    if (!android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_name)) {
         LOG(ERROR) << "GSI partition layout could not be instantiated";
         return;
     }
diff --git a/init/init.cpp b/init/init.cpp
index 18fb0c3..e8eb571 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -61,6 +61,7 @@
 #include "mount_handler.h"
 #include "mount_namespace.h"
 #include "property_service.h"
+#include "proto_utils.h"
 #include "reboot.h"
 #include "reboot_utils.h"
 #include "security.h"
@@ -69,6 +70,7 @@
 #include "service.h"
 #include "service_parser.h"
 #include "sigchld_handler.h"
+#include "system/core/init/property_service.pb.h"
 #include "util.h"
 
 using namespace std::chrono_literals;
@@ -90,6 +92,7 @@
 static char qemu[32];
 
 static int signal_fd = -1;
+static int property_fd = -1;
 
 static std::unique_ptr<Timer> waiting_for_prop(nullptr);
 static std::string wait_prop_name;
@@ -613,6 +616,54 @@
                                 selinux_start_time_ns));
 }
 
+void SendLoadPersistentPropertiesMessage() {
+    auto init_message = InitMessage{};
+    init_message.set_load_persistent_properties(true);
+    if (auto result = SendMessage(property_fd, init_message); !result) {
+        LOG(ERROR) << "Failed to send load persistent properties message: " << result.error();
+    }
+}
+
+static void HandlePropertyFd() {
+    auto message = ReadMessage(property_fd);
+    if (!message) {
+        LOG(ERROR) << "Could not read message from property service: " << message.error();
+        return;
+    }
+
+    auto property_message = PropertyMessage{};
+    if (!property_message.ParseFromString(*message)) {
+        LOG(ERROR) << "Could not parse message from property service";
+        return;
+    }
+
+    switch (property_message.msg_case()) {
+        case PropertyMessage::kControlMessage: {
+            auto& control_message = property_message.control_message();
+            bool response = HandleControlMessage(control_message.msg(), control_message.name(),
+                                                 control_message.pid());
+
+            auto init_message = InitMessage{};
+            auto* control_response = init_message.mutable_control_response();
+
+            control_response->set_result(response);
+
+            if (auto result = SendMessage(property_fd, init_message); !result) {
+                LOG(ERROR) << "Failed to send control response: " << result.error();
+            }
+            break;
+        }
+        case PropertyMessage::kChangedMessage: {
+            auto& changed_message = property_message.changed_message();
+            property_changed(changed_message.name(), changed_message.value());
+            break;
+        }
+        default:
+            LOG(ERROR) << "Unknown message type from property service: "
+                       << property_message.msg_case();
+    }
+}
+
 int SecondStageMain(int argc, char** argv) {
     if (REBOOT_BOOTLOADER_ON_PANIC) {
         InstallRebootSignalHandlers();
@@ -684,7 +735,12 @@
     UmountDebugRamdisk();
     fs_mgr_vendor_overlay_mount_all();
     export_oem_lock_status();
-    StartPropertyService(&epoll);
+
+    StartPropertyService(&property_fd);
+    if (auto result = epoll.RegisterHandler(property_fd, HandlePropertyFd); !result) {
+        LOG(FATAL) << "Could not register epoll handler for property fd: " << result.error();
+    }
+
     MountHandler mount_handler(&epoll);
     set_usb_controller();
 
diff --git a/init/init.h b/init/init.h
index cfc28f1..832ce3f 100644
--- a/init/init.h
+++ b/init/init.h
@@ -31,16 +31,14 @@
 Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
 Parser CreateServiceOnlyParser(ServiceList& service_list);
 
-bool HandleControlMessage(const std::string& msg, const std::string& arg, pid_t pid);
-
-void property_changed(const std::string& name, const std::string& value);
-
 bool start_waiting_for_property(const char *name, const char *value);
 
 void DumpState();
 
 void ResetWaitForProp();
 
+void SendLoadPersistentPropertiesMessage();
+
 int SecondStageMain(int argc, char** argv);
 
 }  // namespace init
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 17622a3..b7d5743 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -39,10 +39,10 @@
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
-#include <atomic>
 #include <map>
 #include <memory>
 #include <mutex>
+#include <optional>
 #include <queue>
 #include <thread>
 #include <vector>
@@ -53,7 +53,6 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
 #include <property_info_parser/property_info_parser.h>
 #include <property_info_serializer/property_info_serializer.h>
 #include <selinux/android.h>
@@ -61,11 +60,14 @@
 #include <selinux/selinux.h>
 
 #include "debug_ramdisk.h"
+#include "epoll.h"
 #include "init.h"
 #include "persistent_properties.h"
 #include "property_type.h"
+#include "proto_utils.h"
 #include "selinux.h"
 #include "subcontext.h"
+#include "system/core/init/property_service.pb.h"
 #include "util.h"
 
 using namespace std::literals;
@@ -87,14 +89,15 @@
 namespace android {
 namespace init {
 
-static constexpr const char kRestoreconProperty[] = "selinux.restorecon_recursive";
-
 static bool persistent_properties_loaded = false;
 
 static int property_set_fd = -1;
+static int init_socket = -1;
 
 static PropertyInfoAreaFile property_info_area;
 
+uint32_t HandlePropertySet(const std::string& name, const std::string& value,
+                           const std::string& source_context, const ucred& cr, std::string* error);
 uint32_t InitPropertySet(const std::string& name, const std::string& value);
 
 uint32_t (*property_set)(const std::string& name, const std::string& value) = InitPropertySet;
@@ -166,6 +169,17 @@
     return has_access;
 }
 
+static void SendPropertyChanged(const std::string& name, const std::string& value) {
+    auto property_msg = PropertyMessage{};
+    auto* changed_message = property_msg.mutable_changed_message();
+    changed_message->set_name(name);
+    changed_message->set_value(value);
+
+    if (auto result = SendMessage(init_socket, property_msg); !result) {
+        LOG(ERROR) << "Failed to send property changed message: " << result.error();
+    }
+}
+
 static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
     size_t valuelen = value.size();
 
@@ -201,7 +215,11 @@
     if (persistent_properties_loaded && StartsWith(name, "persist.")) {
         WritePersistentProperty(name, value);
     }
-    property_changed(name, value);
+    // If init hasn't started its main loop, then it won't be handling property changed messages
+    // anyway, so there's no need to try to send them.
+    if (init_socket != -1) {
+        SendPropertyChanged(name, value);
+    }
     return PROP_SUCCESS;
 }
 
@@ -242,17 +260,6 @@
 };
 
 uint32_t InitPropertySet(const std::string& name, const std::string& value) {
-    if (StartsWith(name, "ctl.")) {
-        LOG(ERROR) << "InitPropertySet: Do not set ctl. properties from init; call the Service "
-                      "functions directly";
-        return PROP_ERROR_INVALID_NAME;
-    }
-    if (name == kRestoreconProperty) {
-        LOG(ERROR) << "InitPropertySet: Do not set '" << kRestoreconProperty
-                   << "' from init; use the restorecon builtin directly";
-        return PROP_ERROR_INVALID_NAME;
-    }
-
     uint32_t result = 0;
     ucred cr = {.pid = 1, .uid = 0, .gid = 0};
     std::string error;
@@ -268,8 +275,6 @@
   public:
     SocketConnection(int socket, const ucred& cred) : socket_(socket), cred_(cred) {}
 
-    ~SocketConnection() { close(socket_); }
-
     bool RecvUint32(uint32_t* value, uint32_t* timeout_ms) {
         return RecvFully(value, sizeof(*value), timeout_ms);
     }
@@ -391,12 +396,62 @@
         return bytes_left == 0;
     }
 
-    int socket_;
+    unique_fd socket_;
     ucred cred_;
-
-    DISALLOW_IMPLICIT_CONSTRUCTORS(SocketConnection);
 };
 
+// Init responds with whether or not the control message was successful.  However, init may set
+// properties in the process of handling the control message, particularly when starting services.
+// Therefore we cannot block in SendControlMessage() to wait for init's response.  Instead, we store
+// the SocketConnection for the socket that sent the control message.  We then return to the main
+// poll loop and handle messages until we get the response from init.
+//
+// Note that this is a queue, since it is possible for more control messages to come while init is
+// handling the first.  Both init and property service will handle these in order.
+//
+// Also note that the 1st version of the property service does not expect a result to be sent, so
+// we have a nullopt as a placeholder in the queue to keep track of which control messages have been
+// responded to.
+static std::queue<std::optional<SocketConnection>> pending_control_message_results;
+
+static uint32_t SendControlMessage(const std::string& msg, const std::string& name, pid_t pid,
+                                   std::string* error) {
+    auto property_msg = PropertyMessage{};
+    auto* control_message = property_msg.mutable_control_message();
+    control_message->set_msg(msg);
+    control_message->set_name(name);
+    control_message->set_pid(pid);
+
+    if (auto result = SendMessage(init_socket, property_msg); !result) {
+        *error = "Failed to send control message: " + result.error().message();
+        return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
+    }
+
+    return PROP_SUCCESS;
+}
+
+void HandleControlResponse(const InitMessage& init_message) {
+    if (pending_control_message_results.empty()) {
+        LOG(ERROR) << "Got a control response without pending control messages";
+        return;
+    }
+
+    if (!pending_control_message_results.front().has_value()) {
+        pending_control_message_results.pop();
+        return;
+    }
+
+    if (!pending_control_message_results.front().has_value()) {
+        return;
+    }
+
+    auto& control_response = init_message.control_response();
+    uint32_t response =
+            control_response.result() ? PROP_SUCCESS : PROP_ERROR_HANDLE_CONTROL_MESSAGE;
+    pending_control_message_results.front()->SendUint32(response);
+    pending_control_message_results.pop();
+}
+
 bool CheckControlPropertyPerms(const std::string& name, const std::string& value,
                                const std::string& source_context, const ucred& cr) {
     // We check the legacy method first but these properties are dontaudit, so we only log an audit
@@ -470,9 +525,7 @@
     }
 
     if (StartsWith(name, "ctl.")) {
-        return HandleControlMessage(name.c_str() + 4, value, cr.pid)
-                       ? PROP_SUCCESS
-                       : PROP_ERROR_HANDLE_CONTROL_MESSAGE;
+        return SendControlMessage(name.c_str() + 4, value, cr.pid, error);
     }
 
     // sys.powerctl is a special property that is used to make the device reboot.  We want to log
@@ -557,6 +610,10 @@
                        << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
         }
 
+        if (result == PROP_SUCCESS && StartsWith(prop_name, "ctl.")) {
+            pending_control_message_results.emplace(std::nullopt);
+        }
+
         break;
       }
 
@@ -584,7 +641,12 @@
             LOG(ERROR) << "Unable to set property '" << name << "' from uid:" << cr.uid
                        << " gid:" << cr.gid << " pid:" << cr.pid << ": " << error;
         }
-        socket.SendUint32(result);
+
+        if (result == PROP_SUCCESS && StartsWith(name, "ctl.")) {
+            pending_control_message_results.emplace(std::move(socket));
+        } else {
+            socket.SendUint32(result);
+        }
         break;
       }
 
@@ -743,33 +805,6 @@
     }
 }
 
-/* When booting an encrypted system, /data is not mounted when the
- * property service is started, so any properties stored there are
- * not loaded.  Vold triggers init to load these properties once it
- * has mounted /data.
- */
-void load_persist_props(void) {
-    // Devices with FDE have load_persist_props called twice; the first time when the temporary
-    // /data partition is mounted and then again once /data is truly mounted.  We do not want to
-    // read persistent properties from the temporary /data partition or mark persistent properties
-    // as having been loaded during the first call, so we return in that case.
-    std::string crypto_state = android::base::GetProperty("ro.crypto.state", "");
-    std::string crypto_type = android::base::GetProperty("ro.crypto.type", "");
-    if (crypto_state == "encrypted" && crypto_type == "block") {
-        static size_t num_calls = 0;
-        if (++num_calls == 1) return;
-    }
-
-    load_override_properties();
-    /* Read persistent properties after all default values have been loaded. */
-    auto persistent_properties = LoadPersistentProperties();
-    for (const auto& persistent_property_record : persistent_properties.properties()) {
-        property_set(persistent_property_record.name(), persistent_property_record.value());
-    }
-    persistent_properties_loaded = true;
-    property_set("ro.persistent_properties.ready", "true");
-}
-
 // If the ro.product.[brand|device|manufacturer|model|name] properties have not been explicitly
 // set, derive them from ro.product.${partition}.* properties
 static void property_initialize_ro_product_props() {
@@ -987,58 +1022,87 @@
     selinux_android_restorecon(kPropertyInfosPath, 0);
 }
 
-void StartPropertyService(Epoll* epoll) {
+static void HandleInitSocket() {
+    auto message = ReadMessage(init_socket);
+    if (!message) {
+        LOG(ERROR) << "Could not read message from init_dedicated_recv_socket: " << message.error();
+        return;
+    }
+
+    auto init_message = InitMessage{};
+    if (!init_message.ParseFromString(*message)) {
+        LOG(ERROR) << "Could not parse message from init";
+        return;
+    }
+
+    switch (init_message.msg_case()) {
+        case InitMessage::kControlResponse: {
+            HandleControlResponse(init_message);
+            break;
+        }
+        case InitMessage::kLoadPersistentProperties: {
+            load_override_properties();
+            // Read persistent properties after all default values have been loaded.
+            auto persistent_properties = LoadPersistentProperties();
+            for (const auto& persistent_property_record : persistent_properties.properties()) {
+                InitPropertySet(persistent_property_record.name(),
+                                persistent_property_record.value());
+            }
+            InitPropertySet("ro.persistent_properties.ready", "true");
+            persistent_properties_loaded = true;
+            break;
+        }
+        default:
+            LOG(ERROR) << "Unknown message type from init: " << init_message.msg_case();
+    }
+}
+
+static void PropertyServiceThread() {
+    Epoll epoll;
+    if (auto result = epoll.Open(); !result) {
+        LOG(FATAL) << result.error();
+    }
+
+    if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
+        LOG(FATAL) << result.error();
+    }
+
+    if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket); !result) {
+        LOG(FATAL) << result.error();
+    }
+
+    while (true) {
+        if (auto result = epoll.Wait(std::nullopt); !result) {
+            LOG(ERROR) << result.error();
+        }
+    }
+}
+
+void StartPropertyService(int* epoll_socket) {
     property_set("ro.property_service.version", "2");
 
-    if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
-                                   false, 0666, 0, 0, {})) {
+    int sockets[2];
+    if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
+        PLOG(FATAL) << "Failed to socketpair() between property_service and init";
+    }
+    *epoll_socket = sockets[0];
+    init_socket = sockets[1];
+
+    if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC, false, 0666, 0, 0,
+                                   {})) {
         property_set_fd = *result;
     } else {
-        PLOG(FATAL) << "start_property_service socket creation failed: " << result.error();
+        LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
     }
 
     listen(property_set_fd, 8);
 
-    if (auto result = epoll->RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
-        PLOG(FATAL) << result.error();
-    }
-}
+    std::thread{PropertyServiceThread}.detach();
 
-Result<int> CallFunctionAndHandlePropertiesImpl(const std::function<int()>& f) {
-    unique_fd reader;
-    unique_fd writer;
-    if (!Socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &reader, &writer)) {
-        return ErrnoError() << "Could not create socket pair";
-    }
-
-    int result = 0;
-    std::atomic<bool> end = false;
-    auto thread = std::thread{[&f, &result, &end, &writer] {
-        result = f();
-        end = true;
-        send(writer, "1", 1, 0);
-    }};
-
-    Epoll epoll;
-    if (auto result = epoll.Open(); !result) {
-        return Error() << "Could not create epoll: " << result.error();
-    }
-    if (auto result = epoll.RegisterHandler(property_set_fd, handle_property_set_fd); !result) {
-        return Error() << "Could not register epoll handler for property fd: " << result.error();
-    }
-
-    // No-op function, just used to break from loop.
-    if (auto result = epoll.RegisterHandler(reader, [] {}); !result) {
-        return Error() << "Could not register epoll handler for ending thread:" << result.error();
-    }
-
-    while (!end) {
-        epoll.Wait({});
-    }
-
-    thread.join();
-
-    return result;
+    property_set = [](const std::string& key, const std::string& value) -> uint32_t {
+        android::base::SetProperty(key, value);
+        return 0;
+    };
 }
 
 }  // namespace init
diff --git a/init/property_service.h b/init/property_service.h
index dc47b4d..8f7d8d9 100644
--- a/init/property_service.h
+++ b/init/property_service.h
@@ -18,34 +18,22 @@
 
 #include <sys/socket.h>
 
-#include <functional>
 #include <string>
 
 #include "epoll.h"
-#include "result.h"
 
 namespace android {
 namespace init {
 
+static constexpr const char kRestoreconProperty[] = "selinux.restorecon_recursive";
+
 bool CanReadProperty(const std::string& source_context, const std::string& name);
 
 extern uint32_t (*property_set)(const std::string& name, const std::string& value);
 
-uint32_t HandlePropertySet(const std::string& name, const std::string& value,
-                           const std::string& source_context, const ucred& cr, std::string* error);
-
 void property_init();
 void property_load_boot_defaults(bool load_debug_prop);
-void load_persist_props();
-void StartPropertyService(Epoll* epoll);
-
-template <typename F, typename... Args>
-Result<int> CallFunctionAndHandleProperties(F&& f, Args&&... args) {
-    Result<int> CallFunctionAndHandlePropertiesImpl(const std::function<int()>& f);
-
-    auto func = [&] { return f(args...); };
-    return CallFunctionAndHandlePropertiesImpl(func);
-}
+void StartPropertyService(int* epoll_socket);
 
 }  // namespace init
 }  // namespace android
diff --git a/init/property_service.proto b/init/property_service.proto
new file mode 100644
index 0000000..894fa13
--- /dev/null
+++ b/init/property_service.proto
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+message PropertyMessage {
+    message ControlMessage {
+        optional string msg = 1;
+        optional string name = 2;
+        optional int32 pid = 3;
+    }
+
+    message ChangedMessage {
+        optional string name = 1;
+        optional string value = 2;
+    }
+
+    oneof msg {
+        ControlMessage control_message = 1;
+        ChangedMessage changed_message = 2;
+    };
+}
+
+message InitMessage {
+    message ControlResponse { optional bool result = 1; }
+
+    oneof msg {
+        ControlResponse control_response = 1;
+        bool load_persistent_properties = 2;
+    };
+}
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index c038aff..0f4cd0d 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -56,6 +56,11 @@
 }
 
 TEST(property_service, non_utf8_value) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Skipping test, must be run as root.";
+        return;
+    }
+
     ASSERT_TRUE(SetProperty("property_service_utf8_test", "base_success"));
     EXPECT_FALSE(SetProperty("property_service_utf8_test", "\x80"));
     EXPECT_FALSE(SetProperty("property_service_utf8_test", "\xC2\x01"));
diff --git a/init/proto_utils.h b/init/proto_utils.h
new file mode 100644
index 0000000..93a7d57
--- /dev/null
+++ b/init/proto_utils.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 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
+
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "result.h"
+
+namespace android {
+namespace init {
+
+constexpr size_t kBufferSize = 4096;
+
+inline Result<std::string> ReadMessage(int socket) {
+    char buffer[kBufferSize] = {};
+    auto result = TEMP_FAILURE_RETRY(recv(socket, buffer, sizeof(buffer), 0));
+    if (result == 0) {
+        return Error();
+    } else if (result < 0) {
+        return ErrnoError();
+    }
+    return std::string(buffer, result);
+}
+
+template <typename T>
+Result<void> SendMessage(int socket, const T& message) {
+    std::string message_string;
+    if (!message.SerializeToString(&message_string)) {
+        return Error() << "Unable to serialize message";
+    }
+
+    if (message_string.size() > kBufferSize) {
+        return Error() << "Serialized message too long to send";
+    }
+
+    if (auto result =
+                TEMP_FAILURE_RETRY(send(socket, message_string.c_str(), message_string.size(), 0));
+        result != static_cast<long>(message_string.size())) {
+        return ErrnoError() << "send() failed to send message contents";
+    }
+    return {};
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index 00f91d8..ec93b58 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -18,16 +18,17 @@
 
 #include <fcntl.h>
 #include <poll.h>
-#include <sys/socket.h>
 #include <unistd.h>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <selinux/android.h>
 
 #include "action.h"
 #include "builtins.h"
+#include "proto_utils.h"
 #include "util.h"
 
 #if defined(__ANDROID__)
@@ -59,45 +60,6 @@
 
 namespace {
 
-constexpr size_t kBufferSize = 4096;
-
-Result<std::string> ReadMessage(int socket) {
-    char buffer[kBufferSize] = {};
-    auto result = TEMP_FAILURE_RETRY(recv(socket, buffer, sizeof(buffer), 0));
-    if (result == 0) {
-        return Error();
-    } else if (result < 0) {
-        return ErrnoError();
-    }
-    return std::string(buffer, result);
-}
-
-template <typename T>
-Result<void> SendMessage(int socket, const T& message) {
-    std::string message_string;
-    if (!message.SerializeToString(&message_string)) {
-        return Error() << "Unable to serialize message";
-    }
-
-    if (message_string.size() > kBufferSize) {
-        return Error() << "Serialized message too long to send";
-    }
-
-    if (auto result =
-            TEMP_FAILURE_RETRY(send(socket, message_string.c_str(), message_string.size(), 0));
-        result != static_cast<long>(message_string.size())) {
-        return ErrnoError() << "send() failed to send message contents";
-    }
-    return {};
-}
-
-std::vector<std::pair<std::string, std::string>> properties_to_set;
-
-uint32_t SubcontextPropertySet(const std::string& name, const std::string& value) {
-    properties_to_set.emplace_back(name, value);
-    return 0;
-}
-
 class SubcontextProcess {
   public:
     SubcontextProcess(const BuiltinFunctionMap* function_map, std::string context, int init_fd)
@@ -131,14 +93,6 @@
         result = RunBuiltinFunction(map_result->function, args, context_);
     }
 
-    for (const auto& [name, value] : properties_to_set) {
-        auto property = reply->add_properties_to_set();
-        property->set_name(name);
-        property->set_value(value);
-    }
-
-    properties_to_set.clear();
-
     if (result) {
         reply->set_success(true);
     } else {
@@ -224,7 +178,10 @@
 
     SelabelInitialize();
 
-    property_set = SubcontextPropertySet;
+    property_set = [](const std::string& key, const std::string& value) -> uint32_t {
+        android::base::SetProperty(key, value);
+        return 0;
+    };
 
     auto subcontext_process = SubcontextProcess(function_map, context, init_fd);
     subcontext_process.MainLoop();
@@ -311,15 +268,6 @@
         return subcontext_reply.error();
     }
 
-    for (const auto& property : subcontext_reply->properties_to_set()) {
-        ucred cr = {.pid = pid_, .uid = 0, .gid = 0};
-        std::string error;
-        if (HandlePropertySet(property.name(), property.value(), context_, cr, &error) != 0) {
-            LOG(ERROR) << "Subcontext init could not set '" << property.name() << "' to '"
-                       << property.value() << "': " << error;
-        }
-    }
-
     if (subcontext_reply->reply_case() == SubcontextReply::kFailure) {
         auto& failure = subcontext_reply->failure();
         return ResultError(failure.error_string(), failure.error_errno());
diff --git a/init/subcontext.proto b/init/subcontext.proto
index c31f4fb..e68115e 100644
--- a/init/subcontext.proto
+++ b/init/subcontext.proto
@@ -38,10 +38,4 @@
         Failure failure = 2;
         ExpandArgsReply expand_args_reply = 3;
     }
-
-    message PropertyToSet {
-        optional string name = 1;
-        optional string value = 2;
-    }
-    repeated PropertyToSet properties_to_set = 4;
 }
\ No newline at end of file
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index ae89c38..dcbff82 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -43,7 +43,7 @@
 template <typename F>
 void RunTest(F&& test_function) {
     if (getuid() != 0) {
-        GTEST_LOG_(INFO) << "Skipping test, must be run as root.";
+        GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
 
diff --git a/init/ueventd_test.cpp b/init/ueventd_test.cpp
index bfdc28e..2d7d2f8 100644
--- a/init/ueventd_test.cpp
+++ b/init/ueventd_test.cpp
@@ -68,7 +68,7 @@
 
 TEST(ueventd, setegid_IsPerThread) {
     if (getuid() != 0) {
-        GTEST_LOG_(INFO) << "Skipping test, must be run as root.";
+        GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
 
@@ -92,11 +92,11 @@
 
 TEST(ueventd, setfscreatecon_IsPerThread) {
     if (getuid() != 0) {
-        GTEST_LOG_(INFO) << "Skipping test, must be run as root.";
+        GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
     if (!is_selinux_enabled() || security_getenforce() == 1) {
-        GTEST_LOG_(INFO) << "Skipping test, SELinux must be enabled and in permissive mode.";
+        GTEST_SKIP() << "Skipping test, SELinux must be enabled and in permissive mode.";
         return;
     }
 
@@ -127,7 +127,7 @@
 
 TEST(ueventd, selabel_lookup_MultiThreaded) {
     if (getuid() != 0) {
-        GTEST_LOG_(INFO) << "Skipping test, must be run as root.";
+        GTEST_SKIP() << "Skipping test, must be run as root.";
         return;
     }
 
diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp
index 7f5768c..9a33b55 100644
--- a/libnativeloader/library_namespaces.cpp
+++ b/libnativeloader/library_namespaces.cpp
@@ -124,7 +124,7 @@
   // we might as well end up loading them from /system/lib or /product/lib
   // For now we rely on CTS test to catch things like this but
   // it should probably be addressed in the future.
-  for (const auto& soname : android::base::Split(default_public_libraries(), ":")) {
+  for (const auto& soname : android::base::Split(preloadable_public_libraries(), ":")) {
     LOG_ALWAYS_FATAL_IF(dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE) == nullptr,
                         "Error preloading public library %s: %s", soname.c_str(), dlerror());
   }
diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp
index a641109..75255b6 100644
--- a/libnativeloader/native_loader_test.cpp
+++ b/libnativeloader/native_loader_test.cpp
@@ -29,6 +29,7 @@
 #include "public_libraries.h"
 
 using namespace ::testing;
+using namespace ::android::nativeloader::internal;
 
 namespace android {
 namespace nativeloader {
@@ -289,7 +290,7 @@
 
   void SetExpectations() {
     std::vector<std::string> default_public_libs =
-        android::base::Split(default_public_libraries(), ":");
+        android::base::Split(preloadable_public_libraries(), ":");
     for (auto l : default_public_libs) {
       EXPECT_CALL(*mock, dlopen(StrEq(l.c_str()), RTLD_NOW | RTLD_NODELETE))
           .WillOnce(Return(any_nonnull));
@@ -576,5 +577,87 @@
 
 INSTANTIATE_TEST_SUITE_P(NativeLoaderTests_Create, NativeLoaderTest_Create, testing::Bool());
 
+const std::function<Result<bool>(const struct ConfigEntry&)> always_true =
+    [](const struct ConfigEntry&) -> Result<bool> { return true; };
+
+TEST(NativeLoaderConfigParser, NamesAndComments) {
+  const char file_content[] = R"(
+######
+
+libA.so
+#libB.so
+
+
+      libC.so
+libD.so
+    #### libE.so
+)";
+  const std::vector<std::string> expected_result = {"libA.so", "libC.so", "libD.so"};
+  Result<std::vector<std::string>> result = ParseConfig(file_content, always_true);
+  ASSERT_TRUE(result) << result.error().message();
+  ASSERT_EQ(expected_result, *result);
+}
+
+TEST(NativeLoaderConfigParser, WithBitness) {
+  const char file_content[] = R"(
+libA.so 32
+libB.so 64
+libC.so
+)";
+#if defined(__LP64__)
+  const std::vector<std::string> expected_result = {"libB.so", "libC.so"};
+#else
+  const std::vector<std::string> expected_result = {"libA.so", "libC.so"};
+#endif
+  Result<std::vector<std::string>> result = ParseConfig(file_content, always_true);
+  ASSERT_TRUE(result) << result.error().message();
+  ASSERT_EQ(expected_result, *result);
+}
+
+TEST(NativeLoaderConfigParser, WithNoPreload) {
+  const char file_content[] = R"(
+libA.so nopreload
+libB.so nopreload
+libC.so
+)";
+
+  const std::vector<std::string> expected_result = {"libC.so"};
+  Result<std::vector<std::string>> result =
+      ParseConfig(file_content,
+                  [](const struct ConfigEntry& entry) -> Result<bool> { return !entry.nopreload; });
+  ASSERT_TRUE(result) << result.error().message();
+  ASSERT_EQ(expected_result, *result);
+}
+
+TEST(NativeLoaderConfigParser, WithNoPreloadAndBitness) {
+  const char file_content[] = R"(
+libA.so nopreload 32
+libB.so 64 nopreload
+libC.so 32
+libD.so 64
+libE.so nopreload
+)";
+
+#if defined(__LP64__)
+  const std::vector<std::string> expected_result = {"libD.so"};
+#else
+  const std::vector<std::string> expected_result = {"libC.so"};
+#endif
+  Result<std::vector<std::string>> result =
+      ParseConfig(file_content,
+                  [](const struct ConfigEntry& entry) -> Result<bool> { return !entry.nopreload; });
+  ASSERT_TRUE(result) << result.error().message();
+  ASSERT_EQ(expected_result, *result);
+}
+
+TEST(NativeLoaderConfigParser, RejectMalformed) {
+  ASSERT_FALSE(ParseConfig("libA.so 32 64", always_true));
+  ASSERT_FALSE(ParseConfig("libA.so 32 32", always_true));
+  ASSERT_FALSE(ParseConfig("libA.so 32 nopreload 64", always_true));
+  ASSERT_FALSE(ParseConfig("32 libA.so nopreload", always_true));
+  ASSERT_FALSE(ParseConfig("nopreload libA.so 32", always_true));
+  ASSERT_FALSE(ParseConfig("libA.so nopreload # comment", always_true));
+}
+
 }  // namespace nativeloader
 }  // namespace android
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index 6cee668..3694360 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -34,7 +34,8 @@
 
 namespace android::nativeloader {
 
-using namespace std::string_literals;
+using namespace internal;
+using namespace ::std::string_literals;
 using android::base::ErrnoError;
 using android::base::Errorf;
 using android::base::Result;
@@ -95,53 +96,21 @@
   file_name->insert(insert_pos, vndk_version_str());
 }
 
-const std::function<Result<void>(const std::string&)> always_true =
-    [](const std::string&) -> Result<void> { return {}; };
+const std::function<Result<bool>(const struct ConfigEntry&)> always_true =
+    [](const struct ConfigEntry&) -> Result<bool> { return true; };
 
 Result<std::vector<std::string>> ReadConfig(
     const std::string& configFile,
-    const std::function<Result<void>(const std::string& /* soname */)>& check_soname) {
-  // Read list of public native libraries from the config file.
+    const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn) {
   std::string file_content;
   if (!base::ReadFileToString(configFile, &file_content)) {
     return ErrnoError();
   }
-
-  std::vector<std::string> lines = base::Split(file_content, "\n");
-
-  std::vector<std::string> sonames;
-  for (auto& line : lines) {
-    auto trimmed_line = base::Trim(line);
-    if (trimmed_line[0] == '#' || trimmed_line.empty()) {
-      continue;
-    }
-    size_t space_pos = trimmed_line.rfind(' ');
-    if (space_pos != std::string::npos) {
-      std::string type = trimmed_line.substr(space_pos + 1);
-      if (type != "32" && type != "64") {
-        return Errorf("Malformed line: {}", line);
-      }
-#if defined(__LP64__)
-      // Skip 32 bit public library.
-      if (type == "32") {
-        continue;
-      }
-#else
-      // Skip 64 bit public library.
-      if (type == "64") {
-        continue;
-      }
-#endif
-      trimmed_line.resize(space_pos);
-    }
-
-    auto ret = check_soname(trimmed_line);
-    if (!ret) {
-      return ret.error();
-    }
-    sonames.push_back(trimmed_line);
+  Result<std::vector<std::string>> result = ParseConfig(file_content, filter_fn);
+  if (!result) {
+    return Errorf("Cannot parse {}: {}", configFile, result.error().message());
   }
-  return sonames;
+  return result;
 }
 
 void ReadExtensionLibraries(const char* dirname, std::vector<std::string>* sonames) {
@@ -165,13 +134,13 @@
             config_file_path.c_str());
 
         auto ret = ReadConfig(
-            config_file_path, [&company_name](const std::string& soname) -> Result<void> {
-              if (android::base::StartsWith(soname, "lib") &&
-                  android::base::EndsWith(soname, "." + company_name + ".so")) {
-                return {};
+            config_file_path, [&company_name](const struct ConfigEntry& entry) -> Result<bool> {
+              if (android::base::StartsWith(entry.soname, "lib") &&
+                  android::base::EndsWith(entry.soname, "." + company_name + ".so")) {
+                return true;
               } else {
-                return Errorf("Library name \"{}\" does not end with the company name {}.", soname,
-                              company_name);
+                return Errorf("Library name \"{}\" does not end with the company name {}.",
+                              entry.soname, company_name);
               }
             });
         if (ret) {
@@ -185,9 +154,16 @@
   }
 }
 
-static std::string InitDefaultPublicLibraries() {
+static std::string InitDefaultPublicLibraries(bool for_preload) {
   std::string config_file = root_dir() + kDefaultPublicLibrariesFile;
-  auto sonames = ReadConfig(config_file, always_true);
+  auto sonames =
+      ReadConfig(config_file, [&for_preload](const struct ConfigEntry& entry) -> Result<bool> {
+        if (for_preload) {
+          return !entry.nopreload;
+        } else {
+          return true;
+        }
+      });
   if (!sonames) {
     LOG_ALWAYS_FATAL("Error reading public native library list from \"%s\": %s",
                      config_file.c_str(), sonames.error().message().c_str());
@@ -290,8 +266,13 @@
 
 }  // namespace
 
+const std::string& preloadable_public_libraries() {
+  static std::string list = InitDefaultPublicLibraries(/*for_preload*/ true);
+  return list;
+}
+
 const std::string& default_public_libraries() {
-  static std::string list = InitDefaultPublicLibraries();
+  static std::string list = InitDefaultPublicLibraries(/*for_preload*/ false);
   return list;
 }
 
@@ -325,4 +306,61 @@
   return list;
 }
 
+namespace internal {
+// Exported for testing
+Result<std::vector<std::string>> ParseConfig(
+    const std::string& file_content,
+    const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn) {
+  std::vector<std::string> lines = base::Split(file_content, "\n");
+
+  std::vector<std::string> sonames;
+  for (auto& line : lines) {
+    auto trimmed_line = base::Trim(line);
+    if (trimmed_line[0] == '#' || trimmed_line.empty()) {
+      continue;
+    }
+
+    std::vector<std::string> tokens = android::base::Split(trimmed_line, " ");
+    if (tokens.size() < 1 || tokens.size() > 3) {
+      return Errorf("Malformed line \"{}\"", line);
+    }
+    struct ConfigEntry entry = {.soname = "", .nopreload = false, .bitness = ALL};
+    size_t i = tokens.size();
+    while (i-- > 0) {
+      if (tokens[i] == "nopreload") {
+        entry.nopreload = true;
+      } else if (tokens[i] == "32" || tokens[i] == "64") {
+        if (entry.bitness != ALL) {
+          return Errorf("Malformed line \"{}\": bitness can be specified only once", line);
+        }
+        entry.bitness = tokens[i] == "32" ? ONLY_32 : ONLY_64;
+      } else {
+        if (i != 0) {
+          return Errorf("Malformed line \"{}\"", line);
+        }
+        entry.soname = tokens[i];
+      }
+    }
+
+    // skip 32-bit lib on 64-bit process and vice versa
+#if defined(__LP64__)
+    if (entry.bitness == ONLY_32) continue;
+#else
+    if (entry.bitness == ONLY_64) continue;
+#endif
+
+    Result<bool> ret = filter_fn(entry);
+    if (!ret) {
+      return ret.error();
+    }
+    if (*ret) {
+      // filter_fn has returned true.
+      sonames.push_back(entry.soname);
+    }
+  }
+  return sonames;
+}
+
+}  // namespace internal
+
 }  // namespace android::nativeloader
diff --git a/libnativeloader/public_libraries.h b/libnativeloader/public_libraries.h
index 9bb3366..2de4172 100644
--- a/libnativeloader/public_libraries.h
+++ b/libnativeloader/public_libraries.h
@@ -15,13 +15,19 @@
  */
 #pragma once
 
+#include <algorithm>
 #include <string>
 
+#include <android-base/result.h>
+
 namespace android::nativeloader {
 
+using android::base::Result;
+
 // These provide the list of libraries that are available to the namespace for apps.
 // Not all of the libraries are available to apps. Depending on the context,
 // e.g., if it is a vendor app or not, different set of libraries are made available.
+const std::string& preloadable_public_libraries();
 const std::string& default_public_libraries();
 const std::string& runtime_public_libraries();
 const std::string& vendor_public_libraries();
@@ -30,4 +36,21 @@
 const std::string& llndk_libraries();
 const std::string& vndksp_libraries();
 
-};  // namespace android::nativeloader
+// These are exported for testing
+namespace internal {
+
+enum Bitness { ALL = 0, ONLY_32, ONLY_64 };
+
+struct ConfigEntry {
+  std::string soname;
+  bool nopreload;
+  Bitness bitness;
+};
+
+Result<std::vector<std::string>> ParseConfig(
+    const std::string& file_content,
+    const std::function<Result<bool>(const ConfigEntry& /* entry */)>& filter_fn);
+
+}  // namespace internal
+
+}  // namespace android::nativeloader
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 4d34b67..9b0075d 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -127,6 +127,10 @@
     mkdir /mnt/expand 0771 system system
     mkdir /mnt/appfuse 0711 root root
 
+    # tmpfs place for BORINGSSL_self_test() to remember whether it has run
+    mkdir /dev/boringssl 0755 root root
+    mkdir /dev/boringssl/selftest 0755 root root
+
     # Storage views to support runtime permissions
     mkdir /mnt/runtime 0700 root root
     mkdir /mnt/runtime/default 0755 root root