Merge "libfs_avb: support rollback protection for Live GSI"
diff --git a/adb/adb_io.cpp b/adb/adb_io.cpp
index 605d27d..91b0d1f 100644
--- a/adb/adb_io.cpp
+++ b/adb/adb_io.cpp
@@ -187,79 +187,3 @@
         return false;
     }
 }
-
-#if defined(__linux__)
-bool SendFileDescriptor(int socket_fd, int fd) {
-    struct msghdr msg;
-    struct iovec iov;
-    char dummy = '!';
-    union {
-        cmsghdr cm;
-        char buffer[CMSG_SPACE(sizeof(int))];
-    } cm_un;
-
-    iov.iov_base = &dummy;
-    iov.iov_len = 1;
-    msg.msg_name = nullptr;
-    msg.msg_namelen = 0;
-    msg.msg_iov = &iov;
-    msg.msg_iovlen = 1;
-    msg.msg_flags = 0;
-    msg.msg_control = cm_un.buffer;
-    msg.msg_controllen = sizeof(cm_un.buffer);
-
-    cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
-    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-    cmsg->cmsg_level = SOL_SOCKET;
-    cmsg->cmsg_type = SCM_RIGHTS;
-    ((int*)CMSG_DATA(cmsg))[0] = fd;
-
-    int ret = TEMP_FAILURE_RETRY(sendmsg(socket_fd, &msg, 0));
-    if (ret < 0) {
-        D("sending file descriptor via socket %d failed: %s", socket_fd, strerror(errno));
-        return false;
-    }
-
-    D("sent file descriptor %d to via socket %d", fd, socket_fd);
-    return true;
-}
-
-bool ReceiveFileDescriptor(int socket_fd, unique_fd* fd, std::string* error) {
-    char dummy = '!';
-    union {
-        cmsghdr cm;
-        char buffer[CMSG_SPACE(sizeof(int))];
-    } cm_un;
-
-    iovec iov;
-    iov.iov_base = &dummy;
-    iov.iov_len = 1;
-
-    msghdr msg;
-    msg.msg_name = nullptr;
-    msg.msg_namelen = 0;
-    msg.msg_iov = &iov;
-    msg.msg_iovlen = 1;
-    msg.msg_flags = 0;
-    msg.msg_control = cm_un.buffer;
-    msg.msg_controllen = sizeof(cm_un.buffer);
-
-    cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
-    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-    cmsg->cmsg_level = SOL_SOCKET;
-    cmsg->cmsg_type = SCM_RIGHTS;
-    ((int*)(CMSG_DATA(cmsg)))[0] = -1;
-
-    int rc = TEMP_FAILURE_RETRY(recvmsg(socket_fd, &msg, 0));
-    if (rc <= 0) {
-        *error = perror_str("receiving file descriptor via socket failed");
-        D("receiving file descriptor via socket %d failed: %s", socket_fd, strerror(errno));
-        return false;
-    }
-
-    fd->reset(((int*)(CMSG_DATA(cmsg)))[0]);
-    D("received file descriptor %d to via socket %d", fd->get(), socket_fd);
-
-    return true;
-}
-#endif
diff --git a/adb/adb_io.h b/adb/adb_io.h
index 2ccaa32..e2df1b1 100644
--- a/adb/adb_io.h
+++ b/adb/adb_io.h
@@ -74,13 +74,4 @@
 
 // Same as above, but formats the string to send.
 bool WriteFdFmt(int fd, const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3)));
-
-#if !ADB_HOST
-// Sends an FD via Unix domain socket.
-bool SendFileDescriptor(int socket_fd, int fd);
-
-// Receives an FD via Unix domain socket.
-bool ReceiveFileDescriptor(int socket_fd, unique_fd* fd, std::string* error);
-#endif
-
 #endif /* ADB_IO_H */
diff --git a/adb/client/file_sync_client.cpp b/adb/client/file_sync_client.cpp
index b8827ef..7e470e1 100644
--- a/adb/client/file_sync_client.cpp
+++ b/adb/client/file_sync_client.cpp
@@ -681,9 +681,7 @@
     if (sync) {
         struct stat st;
         if (sync_lstat(sc, rpath, &st)) {
-            // For links, we cannot update the atime/mtime.
-            if ((S_ISREG(mode & st.st_mode) && st.st_mtime == static_cast<time_t>(mtime)) ||
-                (S_ISLNK(mode & st.st_mode) && st.st_mtime >= static_cast<time_t>(mtime))) {
+            if (st.st_mtime == static_cast<time_t>(mtime)) {
                 sc.RecordFilesSkipped(1);
                 return true;
             }
@@ -921,12 +919,8 @@
         for (copyinfo& ci : file_list) {
             struct stat st;
             if (sc.FinishStat(&st)) {
-                if (st.st_size == static_cast<off_t>(ci.size)) {
-                    // For links, we cannot update the atime/mtime.
-                    if ((S_ISREG(ci.mode & st.st_mode) && st.st_mtime == ci.time) ||
-                        (S_ISLNK(ci.mode & st.st_mode) && st.st_mtime >= ci.time)) {
-                        ci.skip = true;
-                    }
+                if (st.st_size == static_cast<off_t>(ci.size) && st.st_mtime == ci.time) {
+                    ci.skip = true;
                 }
             }
         }
diff --git a/adb/daemon/abb.cpp b/adb/daemon/abb.cpp
index f69babe..d949dd1 100644
--- a/adb/daemon/abb.cpp
+++ b/adb/daemon/abb.cpp
@@ -22,6 +22,8 @@
 
 #include <sys/wait.h>
 
+#include <android-base/cmsg.h>
+
 namespace {
 
 class AdbFdTextOutput : public android::TextOutput {
@@ -83,8 +85,8 @@
             break;
         }
 
-        auto result = StartCommandInProcess(std::move(data), &execCmd);
-        if (!SendFileDescriptor(fd, result)) {
+        unique_fd result = StartCommandInProcess(std::move(data), &execCmd);
+        if (android::base::SendFileDescriptors(fd, "", 1, result.get()) != 1) {
             PLOG(ERROR) << "Failed to send an inprocess fd for command: " << data;
             break;
         }
diff --git a/adb/daemon/abb_service.cpp b/adb/daemon/abb_service.cpp
index 817aea1..d32bf52 100644
--- a/adb/daemon/abb_service.cpp
+++ b/adb/daemon/abb_service.cpp
@@ -20,6 +20,8 @@
 #include "adb_utils.h"
 #include "shell_service.h"
 
+#include <android-base/cmsg.h>
+
 namespace {
 
 struct AbbProcess;
@@ -59,8 +61,9 @@
 
         unique_fd fd;
         std::string error;
-        if (!ReceiveFileDescriptor(socket_fd_, &fd, &error)) {
-            LOG(ERROR) << "failed to receive FD from abb: " << error;
+        char buf;
+        if (android::base::ReceiveFileDescriptors(socket_fd_, &buf, 1, &fd) != 1) {
+            PLOG(ERROR) << "failed to receive FD from abb";
             socket_fd_.reset();
             continue;
         }
diff --git a/adb/daemon/file_sync_service.cpp b/adb/daemon/file_sync_service.cpp
index 9e1760d..70deb31 100644
--- a/adb/daemon/file_sync_service.cpp
+++ b/adb/daemon/file_sync_service.cpp
@@ -27,6 +27,7 @@
 #include <string.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <utime.h>
@@ -242,10 +243,10 @@
     return SendSyncFail(fd, StringPrintf("%s: %s", reason.c_str(), strerror(errno)));
 }
 
-static bool handle_send_file(int s, const char* path, uid_t uid, gid_t gid, uint64_t capabilities,
-                             mode_t mode, std::vector<char>& buffer, bool do_unlink) {
+static bool handle_send_file(int s, const char* path, uint32_t* timestamp, uid_t uid, gid_t gid,
+                             uint64_t capabilities, mode_t mode, std::vector<char>& buffer,
+                             bool do_unlink) {
     syncmsg msg;
-    unsigned int timestamp = 0;
 
     __android_log_security_bswrite(SEC_TAG_ADB_SEND_FILE, path);
 
@@ -291,7 +292,7 @@
 
         if (msg.data.id != ID_DATA) {
             if (msg.data.id == ID_DONE) {
-                timestamp = msg.data.size;
+                *timestamp = msg.data.size;
                 break;
             }
             SendSyncFail(s, "invalid data message");
@@ -316,11 +317,6 @@
         goto fail;
     }
 
-    utimbuf u;
-    u.actime = timestamp;
-    u.modtime = timestamp;
-    utime(path, &u);
-
     msg.status.id = ID_OKAY;
     msg.status.msglen = 0;
     return WriteFdExactly(s, &msg.status, sizeof(msg.status));
@@ -360,9 +356,12 @@
 }
 
 #if defined(_WIN32)
-extern bool handle_send_link(int s, const std::string& path, std::vector<char>& buffer) __attribute__((error("no symlinks on Windows")));
+extern bool handle_send_link(int s, const std::string& path,
+                             uint32_t* timestamp, std::vector<char>& buffer)
+        __attribute__((error("no symlinks on Windows")));
 #else
-static bool handle_send_link(int s, const std::string& path, std::vector<char>& buffer) {
+static bool handle_send_link(int s, const std::string& path, uint32_t* timestamp,
+                             std::vector<char>& buffer) {
     syncmsg msg;
 
     if (!ReadFdExactly(s, &msg.data, sizeof(msg.data))) return false;
@@ -399,6 +398,7 @@
     if (!ReadFdExactly(s, &msg.data, sizeof(msg.data))) return false;
 
     if (msg.data.id == ID_DONE) {
+        *timestamp = msg.data.size;
         msg.status.id = ID_OKAY;
         msg.status.msglen = 0;
         if (!WriteFdExactly(s, &msg.status, sizeof(msg.status))) return false;
@@ -448,24 +448,40 @@
         adb_unlink(path.c_str());
     }
 
+    bool result;
+    uint32_t timestamp;
     if (S_ISLNK(mode)) {
-        return handle_send_link(s, path.c_str(), buffer);
+        result = handle_send_link(s, path.c_str(), &timestamp, buffer);
+    } else {
+        // Copy user permission bits to "group" and "other" permissions.
+        mode &= 0777;
+        mode |= ((mode >> 3) & 0070);
+        mode |= ((mode >> 3) & 0007);
+
+        uid_t uid = -1;
+        gid_t gid = -1;
+        uint64_t capabilities = 0;
+        if (should_use_fs_config(path)) {
+            unsigned int broken_api_hack = mode;
+            fs_config(path.c_str(), 0, nullptr, &uid, &gid, &broken_api_hack, &capabilities);
+            mode = broken_api_hack;
+        }
+
+        result = handle_send_file(s, path.c_str(), &timestamp, uid, gid, capabilities, mode, buffer,
+                                  do_unlink);
     }
 
-    // Copy user permission bits to "group" and "other" permissions.
-    mode &= 0777;
-    mode |= ((mode >> 3) & 0070);
-    mode |= ((mode >> 3) & 0007);
-
-    uid_t uid = -1;
-    gid_t gid = -1;
-    uint64_t capabilities = 0;
-    if (should_use_fs_config(path)) {
-        unsigned int broken_api_hack = mode;
-        fs_config(path.c_str(), 0, nullptr, &uid, &gid, &broken_api_hack, &capabilities);
-        mode = broken_api_hack;
+    if (!result) {
+      return false;
     }
-    return handle_send_file(s, path.c_str(), uid, gid, capabilities, mode, buffer, do_unlink);
+
+    struct timeval tv[2];
+    tv[0].tv_sec = timestamp;
+    tv[0].tv_usec = 0;
+    tv[1].tv_sec = timestamp;
+    tv[1].tv_usec = 0;
+    lutimes(path.c_str(), tv);
+    return true;
 }
 
 static bool do_recv(int s, const char* path, std::vector<char>& buffer) {
diff --git a/adb/daemon/jdwp_service.cpp b/adb/daemon/jdwp_service.cpp
index 032ee42..66bfc0d 100644
--- a/adb/daemon/jdwp_service.cpp
+++ b/adb/daemon/jdwp_service.cpp
@@ -32,6 +32,8 @@
 #include <memory>
 #include <vector>
 
+#include <android-base/cmsg.h>
+
 #include "adb.h"
 #include "adb_io.h"
 #include "adb_unique_fd.h"
@@ -237,7 +239,7 @@
         CHECK(!proc->out_fds.empty());
 
         int fd = proc->out_fds.back().get();
-        if (!SendFileDescriptor(socket, fd)) {
+        if (android::base::SendFileDescriptors(socket, "", 1, fd) != 1) {
             D("sending new file descriptor to JDWP %d failed: %s", proc->pid, strerror(errno));
             goto CloseProcess;
         }
diff --git a/adb/daemon/restart_service.cpp b/adb/daemon/restart_service.cpp
index 6803d93..16d2627 100644
--- a/adb/daemon/restart_service.cpp
+++ b/adb/daemon/restart_service.cpp
@@ -20,6 +20,7 @@
 
 #include <unistd.h>
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <log/log_properties.h>
@@ -37,6 +38,7 @@
         return;
     }
 
+    LOG(INFO) << "adbd restarting as root";
     android::base::SetProperty("service.adb.root", "1");
     WriteFdExactly(fd.get(), "restarting adbd as root\n");
 }
@@ -46,6 +48,8 @@
         WriteFdExactly(fd.get(), "adbd not running as root\n");
         return;
     }
+
+    LOG(INFO) << "adbd restarting as nonroot";
     android::base::SetProperty("service.adb.root", "0");
     WriteFdExactly(fd.get(), "restarting adbd as non root\n");
 }
@@ -56,11 +60,13 @@
         return;
     }
 
+    LOG(INFO) << "adbd restarting in TCP mode (port = " << port << ")";
     android::base::SetProperty("service.adb.tcp.port", android::base::StringPrintf("%d", port));
     WriteFdFmt(fd.get(), "restarting in TCP mode port: %d\n", port);
 }
 
 void restart_usb_service(unique_fd fd) {
+    LOG(INFO) << "adbd restarting in USB mode";
     android::base::SetProperty("service.adb.tcp.port", "0");
     WriteFdExactly(fd.get(), "restarting in USB mode\n");
 }
diff --git a/adb/test_adb.py b/adb/test_adb.py
index 14e5071..8272722 100755
--- a/adb/test_adb.py
+++ b/adb/test_adb.py
@@ -416,6 +416,7 @@
                     output.strip(),
                     "already connected to {}".format(serial).encode("utf8"))
 
+    @unittest.skip("Currently failing b/123247844")
     def test_reconnect(self):
         """Ensure that a disconnected device reconnects."""
 
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 90f94ee..0c7b0b6 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -71,6 +71,7 @@
 const char* const kFeatureApex = "apex";
 const char* const kFeatureFixedPushMkdir = "fixed_push_mkdir";
 const char* const kFeatureAbb = "abb";
+const char* const kFeatureFixedPushSymlinkTimestamp = "fixed_push_symlink_timestamp";
 
 namespace {
 
@@ -1005,8 +1006,13 @@
 const FeatureSet& supported_features() {
     // Local static allocation to avoid global non-POD variables.
     static const FeatureSet* features = new FeatureSet{
-            kFeatureShell2,         kFeatureCmd,  kFeatureStat2,
-            kFeatureFixedPushMkdir, kFeatureApex, kFeatureAbb,
+            kFeatureShell2,
+            kFeatureCmd,
+            kFeatureStat2,
+            kFeatureFixedPushMkdir,
+            kFeatureApex,
+            kFeatureAbb,
+            kFeatureFixedPushSymlinkTimestamp,
             // Increment ADB_SERVER_VERSION when adding a feature that adbd needs
             // to know about. Otherwise, the client can be stuck running an old
             // version of the server even after upgrading their copy of adb.
diff --git a/adb/transport.h b/adb/transport.h
index 065c81f..a0174b8 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -67,6 +67,8 @@
 extern const char* const kFeatureFixedPushMkdir;
 // adbd supports android binder bridge (abb).
 extern const char* const kFeatureAbb;
+// adbd properly updates symlink timestamps on push.
+extern const char* const kFeatureFixedPushSymlinkTimestamp;
 
 TransportId NextTransportId();
 
diff --git a/base/Android.bp b/base/Android.bp
index b0181aa..38f301a 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -46,6 +46,7 @@
     defaults: ["libbase_cflags_defaults"],
     srcs: [
         "chrono_utils.cpp",
+        "cmsg.cpp",
         "file.cpp",
         "logging.cpp",
         "mapped_file.cpp",
@@ -85,6 +86,9 @@
                 "errors_windows.cpp",
                 "utf8.cpp",
             ],
+            exclude_srcs: [
+                "cmsg.cpp",
+            ],
             enabled: true,
         },
     },
@@ -121,6 +125,7 @@
     defaults: ["libbase_cflags_defaults"],
     host_supported: true,
     srcs: [
+        "cmsg_test.cpp",
         "endian_test.cpp",
         "errors_test.cpp",
         "file_test.cpp",
diff --git a/base/cmsg.cpp b/base/cmsg.cpp
new file mode 100644
index 0000000..42866f8
--- /dev/null
+++ b/base/cmsg.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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/cmsg.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/user.h>
+
+#include <memory>
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace base {
+
+ssize_t SendFileDescriptorVector(int sockfd, const void* data, size_t len,
+                                 const std::vector<int>& fds) {
+  size_t cmsg_space = CMSG_SPACE(sizeof(int) * fds.size());
+  size_t cmsg_len = CMSG_LEN(sizeof(int) * fds.size());
+  if (cmsg_space >= PAGE_SIZE) {
+    errno = ENOMEM;
+    return -1;
+  }
+
+  alignas(struct cmsghdr) char cmsg_buf[cmsg_space];
+  iovec iov = {.iov_base = const_cast<void*>(data), .iov_len = len};
+  msghdr msg = {
+      .msg_name = nullptr,
+      .msg_namelen = 0,
+      .msg_iov = &iov,
+      .msg_iovlen = 1,
+      .msg_control = cmsg_buf,
+      // We can't cast to the actual type of the field, because it's different across platforms.
+      .msg_controllen = static_cast<unsigned int>(cmsg_space),
+      .msg_flags = 0,
+  };
+
+  struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = SCM_RIGHTS;
+  cmsg->cmsg_len = cmsg_len;
+
+  int* cmsg_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+  for (size_t i = 0; i < fds.size(); ++i) {
+    cmsg_fds[i] = fds[i];
+  }
+
+#if defined(__linux__)
+  int flags = MSG_NOSIGNAL;
+#else
+  int flags = 0;
+#endif
+
+  return TEMP_FAILURE_RETRY(sendmsg(sockfd, &msg, flags));
+}
+
+ssize_t ReceiveFileDescriptorVector(int sockfd, void* data, size_t len, size_t max_fds,
+                                    std::vector<unique_fd>* fds) {
+  fds->clear();
+
+  size_t cmsg_space = CMSG_SPACE(sizeof(int) * max_fds);
+  if (cmsg_space >= PAGE_SIZE) {
+    errno = ENOMEM;
+    return -1;
+  }
+
+  alignas(struct cmsghdr) char cmsg_buf[cmsg_space];
+  iovec iov = {.iov_base = const_cast<void*>(data), .iov_len = len};
+  msghdr msg = {
+      .msg_name = nullptr,
+      .msg_namelen = 0,
+      .msg_iov = &iov,
+      .msg_iovlen = 1,
+      .msg_control = cmsg_buf,
+      // We can't cast to the actual type of the field, because it's different across platforms.
+      .msg_controllen = static_cast<unsigned int>(cmsg_space),
+      .msg_flags = 0,
+  };
+
+  int flags = MSG_TRUNC | MSG_CTRUNC;
+#if defined(__linux__)
+  flags |= MSG_CMSG_CLOEXEC | MSG_NOSIGNAL;
+#endif
+
+  ssize_t rc = TEMP_FAILURE_RETRY(recvmsg(sockfd, &msg, flags));
+
+  if (rc == -1) {
+    return -1;
+  }
+
+  int error = 0;
+  if ((msg.msg_flags & MSG_TRUNC)) {
+    LOG(ERROR) << "message was truncated when receiving file descriptors";
+    error = EMSGSIZE;
+  } else if ((msg.msg_flags & MSG_CTRUNC)) {
+    LOG(ERROR) << "control message was truncated when receiving file descriptors";
+    error = EMSGSIZE;
+  }
+
+  std::vector<unique_fd> received_fds;
+  struct cmsghdr* cmsg;
+  for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+    if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
+      LOG(ERROR) << "received unexpected cmsg: [" << cmsg->cmsg_level << ", " << cmsg->cmsg_type
+                 << "]";
+      error = EBADMSG;
+      continue;
+    }
+
+    // There isn't a macro that does the inverse of CMSG_LEN, so hack around it ourselves, with
+    // some asserts to ensure that CMSG_LEN behaves as we expect.
+#if defined(__linux__)
+#define CMSG_ASSERT static_assert
+#else
+// CMSG_LEN is somehow not constexpr on darwin.
+#define CMSG_ASSERT CHECK
+#endif
+    CMSG_ASSERT(CMSG_LEN(0) + 1 * sizeof(int) == CMSG_LEN(1 * sizeof(int)));
+    CMSG_ASSERT(CMSG_LEN(0) + 2 * sizeof(int) == CMSG_LEN(2 * sizeof(int)));
+    CMSG_ASSERT(CMSG_LEN(0) + 3 * sizeof(int) == CMSG_LEN(3 * sizeof(int)));
+    CMSG_ASSERT(CMSG_LEN(0) + 4 * sizeof(int) == CMSG_LEN(4 * sizeof(int)));
+
+    if (cmsg->cmsg_len % sizeof(int) != 0) {
+      LOG(FATAL) << "cmsg_len(" << cmsg->cmsg_len << ") not aligned to sizeof(int)";
+    } else if (cmsg->cmsg_len <= CMSG_LEN(0)) {
+      LOG(FATAL) << "cmsg_len(" << cmsg->cmsg_len << ") not long enough to hold any data";
+    }
+
+    int* cmsg_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+    size_t cmsg_fdcount = static_cast<size_t>(cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+    for (size_t i = 0; i < cmsg_fdcount; ++i) {
+#if !defined(__linux__)
+      // Linux uses MSG_CMSG_CLOEXEC instead of doing this manually.
+      fcntl(cmsg_fds[i], F_SETFD, FD_CLOEXEC);
+#endif
+      received_fds.emplace_back(cmsg_fds[i]);
+    }
+  }
+
+  if (error != 0) {
+    errno = error;
+    return -1;
+  }
+
+  if (received_fds.size() > max_fds) {
+    LOG(ERROR) << "received too many file descriptors, expected " << fds->size() << ", received "
+               << received_fds.size();
+    errno = EMSGSIZE;
+    return -1;
+  }
+
+  *fds = std::move(received_fds);
+  return rc;
+}
+
+}  // namespace base
+}  // namespace android
diff --git a/base/cmsg_test.cpp b/base/cmsg_test.cpp
new file mode 100644
index 0000000..9ee5c82
--- /dev/null
+++ b/base/cmsg_test.cpp
@@ -0,0 +1,202 @@
+/*
+ * 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/cmsg.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#if !defined(_WIN32)
+
+using android::base::ReceiveFileDescriptors;
+using android::base::SendFileDescriptors;
+using android::base::unique_fd;
+
+static ino_t GetInode(int fd) {
+  struct stat st;
+  if (fstat(fd, &st) != 0) {
+    PLOG(FATAL) << "fstat failed";
+  }
+
+  return st.st_ino;
+}
+
+struct CmsgTest : ::testing::TestWithParam<bool> {
+  bool Seqpacket() { return GetParam(); }
+
+  void SetUp() override {
+    ASSERT_TRUE(
+        android::base::Socketpair(Seqpacket() ? SOCK_SEQPACKET : SOCK_STREAM, &send, &recv));
+    int dup1 = dup(tmp1.fd);
+    ASSERT_NE(-1, dup1);
+    int dup2 = dup(tmp2.fd);
+    ASSERT_NE(-1, dup2);
+
+    fd1.reset(dup1);
+    fd2.reset(dup2);
+
+    ino1 = GetInode(dup1);
+    ino2 = GetInode(dup2);
+  }
+
+  unique_fd send;
+  unique_fd recv;
+
+  TemporaryFile tmp1;
+  TemporaryFile tmp2;
+
+  unique_fd fd1;
+  unique_fd fd2;
+
+  ino_t ino1;
+  ino_t ino2;
+};
+
+TEST_P(CmsgTest, smoke) {
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "x", 1, fd1.get()));
+
+  char buf[2];
+  unique_fd received;
+  ASSERT_EQ(1, ReceiveFileDescriptors(recv.get(), buf, 2, &received));
+  ASSERT_EQ('x', buf[0]);
+  ASSERT_NE(-1, received.get());
+
+  ASSERT_EQ(ino1, GetInode(received.get()));
+}
+
+TEST_P(CmsgTest, msg_trunc) {
+  ASSERT_EQ(2, SendFileDescriptors(send.get(), "ab", 2, fd1.get(), fd2.get()));
+
+  char buf[2];
+  unique_fd received1, received2;
+
+  ssize_t rc = ReceiveFileDescriptors(recv.get(), buf, 1, &received1, &received2);
+  if (Seqpacket()) {
+    ASSERT_EQ(-1, rc);
+    ASSERT_EQ(EMSGSIZE, errno);
+    ASSERT_EQ(-1, received1.get());
+    ASSERT_EQ(-1, received2.get());
+  } else {
+    ASSERT_EQ(1, rc);
+    ASSERT_NE(-1, received1.get());
+    ASSERT_NE(-1, received2.get());
+    ASSERT_EQ(ino1, GetInode(received1.get()));
+    ASSERT_EQ(ino2, GetInode(received2.get()));
+    ASSERT_EQ(1, read(recv.get(), buf, 2));
+  }
+}
+
+TEST_P(CmsgTest, msg_ctrunc) {
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "a", 1, fd1.get(), fd2.get()));
+
+  char buf[2];
+  unique_fd received;
+  ASSERT_EQ(-1, ReceiveFileDescriptors(recv.get(), buf, 1, &received));
+  ASSERT_EQ(EMSGSIZE, errno);
+  ASSERT_EQ(-1, received.get());
+}
+
+TEST_P(CmsgTest, peek) {
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "a", 1, fd1.get()));
+
+  char buf[2];
+  ASSERT_EQ(1, ::recv(recv.get(), buf, sizeof(buf), MSG_PEEK));
+  ASSERT_EQ('a', buf[0]);
+
+  unique_fd received;
+  ASSERT_EQ(1, ReceiveFileDescriptors(recv.get(), buf, 1, &received));
+  ASSERT_EQ(ino1, GetInode(received.get()));
+}
+
+TEST_P(CmsgTest, stream_fd_association) {
+  if (Seqpacket()) {
+    return;
+  }
+
+  // fds are associated with the first byte of the write.
+  ASSERT_EQ(1, TEMP_FAILURE_RETRY(write(send.get(), "a", 1)));
+  ASSERT_EQ(2, SendFileDescriptors(send.get(), "bc", 2, fd1.get()));
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "d", 1, fd2.get()));
+  char buf[2];
+  ASSERT_EQ(2, TEMP_FAILURE_RETRY(read(recv.get(), buf, 2)));
+  ASSERT_EQ(0, memcmp(buf, "ab", 2));
+
+  std::vector<unique_fd> received1;
+  ssize_t rc = ReceiveFileDescriptorVector(recv.get(), buf, 1, 1, &received1);
+  ASSERT_EQ(1, rc);
+  ASSERT_EQ('c', buf[0]);
+  ASSERT_TRUE(received1.empty());
+
+  unique_fd received2;
+  rc = ReceiveFileDescriptors(recv.get(), buf, 1, &received2);
+  ASSERT_EQ(1, rc);
+  ASSERT_EQ('d', buf[0]);
+  ASSERT_EQ(ino2, GetInode(received2.get()));
+}
+
+TEST_P(CmsgTest, multiple_fd_ordering) {
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "a", 1, fd1.get(), fd2.get()));
+
+  char buf[2];
+  unique_fd received1, received2;
+  ASSERT_EQ(1, ReceiveFileDescriptors(recv.get(), buf, 1, &received1, &received2));
+
+  ASSERT_NE(-1, received1.get());
+  ASSERT_NE(-1, received2.get());
+
+  ASSERT_EQ(ino1, GetInode(received1.get()));
+  ASSERT_EQ(ino2, GetInode(received2.get()));
+}
+
+TEST_P(CmsgTest, separate_fd_ordering) {
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "a", 1, fd1.get()));
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "b", 1, fd2.get()));
+
+  char buf[2];
+  unique_fd received1, received2;
+  ASSERT_EQ(1, ReceiveFileDescriptors(recv.get(), buf, 1, &received1));
+  ASSERT_EQ(1, ReceiveFileDescriptors(recv.get(), buf, 1, &received2));
+
+  ASSERT_NE(-1, received1.get());
+  ASSERT_NE(-1, received2.get());
+
+  ASSERT_EQ(ino1, GetInode(received1.get()));
+  ASSERT_EQ(ino2, GetInode(received2.get()));
+}
+
+TEST_P(CmsgTest, separate_fds_no_coalescing) {
+  unique_fd sent1(dup(tmp1.fd));
+  unique_fd sent2(dup(tmp2.fd));
+
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "", 1, fd1.get()));
+  ASSERT_EQ(1, SendFileDescriptors(send.get(), "", 1, fd2.get()));
+
+  char buf[2];
+  std::vector<unique_fd> received;
+  ASSERT_EQ(1, ReceiveFileDescriptorVector(recv.get(), buf, 2, 2, &received));
+  ASSERT_EQ(1U, received.size());
+  ASSERT_EQ(ino1, GetInode(received[0].get()));
+
+  ASSERT_EQ(1, ReceiveFileDescriptorVector(recv.get(), buf, 2, 2, &received));
+  ASSERT_EQ(1U, received.size());
+  ASSERT_EQ(ino2, GetInode(received[0].get()));
+}
+
+INSTANTIATE_TEST_CASE_P(CmsgTest, CmsgTest, testing::Bool());
+
+#endif
diff --git a/base/include/android-base/cmsg.h b/base/include/android-base/cmsg.h
new file mode 100644
index 0000000..7f93ddc
--- /dev/null
+++ b/base/include/android-base/cmsg.h
@@ -0,0 +1,106 @@
+/*
+ * 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/stat.h>
+#include <sys/types.h>
+
+#include <type_traits>
+#include <vector>
+
+#include <android-base/collections.h>
+#include <android-base/macros.h>
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace base {
+
+#if !defined(_WIN32)
+
+// Helpers for sending and receiving file descriptors across Unix domain sockets.
+//
+// The cmsg(3) API is very hard to get right, with multiple landmines that can
+// lead to death. Almost all of the uses of cmsg in Android make at least one of
+// the following mistakes:
+//
+//   - not aligning the cmsg buffer
+//   - leaking fds if more fds are received than expected
+//   - blindly dereferencing CMSG_DATA without checking the header
+//   - using CMSG_SPACE instead of CMSG_LEN for .cmsg_len
+//   - using CMSG_LEN instead of CMSG_SPACE for .msg_controllen
+//   - using a length specified in number of fds instead of bytes
+//
+// These functions wrap the hard-to-use cmsg API with an easier to use abstraction.
+
+// Send file descriptors across a Unix domain socket.
+//
+// Note that the write can return short if the socket type is SOCK_STREAM. When
+// this happens, file descriptors are still sent to the other end, but with
+// truncated data. For this reason, using SOCK_SEQPACKET or SOCK_DGRAM is recommended.
+ssize_t SendFileDescriptorVector(int sock, const void* data, size_t len,
+                                 const std::vector<int>& fds);
+
+// Receive file descriptors from a Unix domain socket.
+//
+// If more FDs (or bytes, for datagram sockets) are received than expected,
+// -1 is returned with errno set to EMSGSIZE, and all received FDs are thrown away.
+ssize_t ReceiveFileDescriptorVector(int sock, void* data, size_t len, size_t max_fds,
+                                    std::vector<android::base::unique_fd>* fds);
+
+// Helper for SendFileDescriptorVector that constructs a std::vector for you, e.g.:
+//   SendFileDescriptors(sock, "foo", 3, std::move(fd1), std::move(fd2))
+template <typename... Args>
+ssize_t SendFileDescriptors(int sock, const void* data, size_t len, Args&&... sent_fds) {
+  // Do not allow implicit conversion to int: people might try to do something along the lines of:
+  //   SendFileDescriptors(..., std::move(a_unique_fd))
+  // and be surprised when the unique_fd isn't closed afterwards.
+  AssertType<int>(std::forward<Args>(sent_fds)...);
+  std::vector<int> fds;
+  Append(fds, std::forward<Args>(sent_fds)...);
+  return SendFileDescriptorVector(sock, data, len, fds);
+}
+
+// Helper for ReceiveFileDescriptorVector that receives an exact number of file descriptors.
+// If more file descriptors are received than requested, -1 is returned with errno set to EMSGSIZE.
+// If fewer file descriptors are received than requested, -1 is returned with errno set to ENOMSG.
+// In both cases, all arguments are cleared and any received FDs are thrown away.
+template <typename... Args>
+ssize_t ReceiveFileDescriptors(int sock, void* data, size_t len, Args&&... received_fds) {
+  std::vector<unique_fd*> fds;
+  Append(fds, std::forward<Args>(received_fds)...);
+
+  std::vector<unique_fd> result;
+  ssize_t rc = ReceiveFileDescriptorVector(sock, data, len, fds.size(), &result);
+  if (rc == -1 || result.size() != fds.size()) {
+    int err = rc == -1 ? errno : ENOMSG;
+    for (unique_fd* fd : fds) {
+      fd->reset();
+    }
+    errno = err;
+    return -1;
+  }
+
+  for (size_t i = 0; i < fds.size(); ++i) {
+    *fds[i] = std::move(result[i]);
+  }
+  return rc;
+}
+
+#endif
+
+}  // namespace base
+}  // namespace android
diff --git a/base/include/android-base/collections.h b/base/include/android-base/collections.h
new file mode 100644
index 0000000..be0683a
--- /dev/null
+++ b/base/include/android-base/collections.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <utility>
+
+namespace android {
+namespace base {
+
+// Helpers for converting a variadic template parameter pack to a homogeneous collection.
+// Parameters must be implictly convertible to the contained type (including via move/copy ctors).
+//
+// Use as follows:
+//
+//   template <typename... Args>
+//   std::vector<int> CreateVector(Args&&... args) {
+//     std::vector<int> result;
+//     Append(result, std::forward<Args>(args)...);
+//     return result;
+//   }
+template <typename CollectionType, typename T>
+void Append(CollectionType& collection, T&& arg) {
+  collection.push_back(std::forward<T>(arg));
+}
+
+template <typename CollectionType, typename T, typename... Args>
+void Append(CollectionType& collection, T&& arg, Args&&... args) {
+  collection.push_back(std::forward<T>(arg));
+  return Append(collection, std::forward<Args>(args)...);
+}
+
+// Assert that all of the arguments in a variadic template parameter pack are of a given type
+// after std::decay.
+template <typename T, typename Arg, typename... Args>
+void AssertType(Arg&&) {
+  static_assert(std::is_same<T, typename std::decay<Arg>::type>::value);
+}
+
+template <typename T, typename Arg, typename... Args>
+void AssertType(Arg&&, Args&&... args) {
+  static_assert(std::is_same<T, typename std::decay<Arg>::type>::value);
+  AssertType<T>(std::forward<Args>(args)...);
+}
+
+}  // namespace base
+}  // namespace android
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index 610b96b..60eb241 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -26,6 +26,7 @@
 
 #include <chrono>
 
+#include <android-base/cmsg.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
@@ -41,6 +42,7 @@
 
 using namespace std::chrono_literals;
 
+using android::base::SendFileDescriptors;
 using android::base::unique_fd;
 
 static bool send_signal(pid_t pid, const DebuggerdDumpType dump_type) {
@@ -146,15 +148,16 @@
     PLOG(ERROR) << "failed to set pipe buffer size";
   }
 
-  if (send_fd(set_timeout(sockfd), &req, sizeof(req), std::move(pipe_write)) != sizeof(req)) {
+  ssize_t rc = SendFileDescriptors(set_timeout(sockfd), &req, sizeof(req), pipe_write.get());
+  pipe_write.reset();
+  if (rc != sizeof(req)) {
     PLOG(ERROR) << "libdebuggerd_client: failed to send output fd to tombstoned";
     return false;
   }
 
   // Check to make sure we've successfully registered.
   InterceptResponse response;
-  ssize_t rc =
-      TEMP_FAILURE_RETRY(recv(set_timeout(sockfd.get()), &response, sizeof(response), MSG_TRUNC));
+  rc = TEMP_FAILURE_RETRY(recv(set_timeout(sockfd.get()), &response, sizeof(response), MSG_TRUNC));
   if (rc == 0) {
     LOG(ERROR) << "libdebuggerd_client: failed to read initial response from tombstoned: "
                << "timeout reached?";
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index bea8b43..64df53e 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -32,6 +32,7 @@
 #include <android/fdsan.h>
 #include <android/set_abort_message.h>
 
+#include <android-base/cmsg.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/macros.h>
@@ -53,6 +54,8 @@
 #include "util.h"
 
 using namespace std::chrono_literals;
+
+using android::base::SendFileDescriptors;
 using android::base::unique_fd;
 
 #if defined(__LP64__)
@@ -123,12 +126,14 @@
 
   ASSERT_GE(pipe_buffer_size, 1024 * 1024);
 
-  if (send_fd(intercept_fd->get(), &req, sizeof(req), std::move(output_pipe_write)) != sizeof(req)) {
+  ssize_t rc = SendFileDescriptors(intercept_fd->get(), &req, sizeof(req), output_pipe_write.get());
+  output_pipe_write.reset();
+  if (rc != sizeof(req)) {
     FAIL() << "failed to send output fd to tombstoned: " << strerror(errno);
   }
 
   InterceptResponse response;
-  ssize_t rc = TEMP_FAILURE_RETRY(read(intercept_fd->get(), &response, sizeof(response)));
+  rc = TEMP_FAILURE_RETRY(read(intercept_fd->get(), &response, sizeof(response)));
   if (rc == -1) {
     FAIL() << "failed to read response from tombstoned: " << strerror(errno);
   } else if (rc == 0) {
diff --git a/debuggerd/tombstoned/intercept_manager.cpp b/debuggerd/tombstoned/intercept_manager.cpp
index c446dbb..7d25c50 100644
--- a/debuggerd/tombstoned/intercept_manager.cpp
+++ b/debuggerd/tombstoned/intercept_manager.cpp
@@ -24,6 +24,7 @@
 #include <event2/event.h>
 #include <event2/listener.h>
 
+#include <android-base/cmsg.h>
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <cutils/sockets.h>
@@ -31,6 +32,7 @@
 #include "protocol.h"
 #include "util.h"
 
+using android::base::ReceiveFileDescriptors;
 using android::base::unique_fd;
 
 static void intercept_close_cb(evutil_socket_t sockfd, short event, void* arg) {
@@ -96,7 +98,8 @@
   {
     unique_fd rcv_fd;
     InterceptRequest intercept_request;
-    ssize_t result = recv_fd(sockfd, &intercept_request, sizeof(intercept_request), &rcv_fd);
+    ssize_t result =
+        ReceiveFileDescriptors(sockfd, &intercept_request, sizeof(intercept_request), &rcv_fd);
 
     if (result == -1) {
       PLOG(WARNING) << "failed to read from intercept socket";
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index ad92067..bbeb181 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -31,6 +31,7 @@
 #include <event2/listener.h>
 #include <event2/thread.h>
 
+#include <android-base/cmsg.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
@@ -45,6 +46,7 @@
 #include "intercept_manager.h"
 
 using android::base::GetIntProperty;
+using android::base::SendFileDescriptors;
 using android::base::StringPrintf;
 using android::base::unique_fd;
 
@@ -224,7 +226,10 @@
   TombstonedCrashPacket response = {
     .packet_type = CrashPacketType::kPerformDump
   };
-  ssize_t rc = send_fd(crash->crash_socket_fd, &response, sizeof(response), std::move(output_fd));
+  ssize_t rc =
+      SendFileDescriptors(crash->crash_socket_fd, &response, sizeof(response), output_fd.get());
+  output_fd.reset();
+
   if (rc == -1) {
     PLOG(WARNING) << "failed to send response to CrashRequest";
     goto fail;
diff --git a/debuggerd/tombstoned/tombstoned_client.cpp b/debuggerd/tombstoned/tombstoned_client.cpp
index bdb4c1a..2c23c98 100644
--- a/debuggerd/tombstoned/tombstoned_client.cpp
+++ b/debuggerd/tombstoned/tombstoned_client.cpp
@@ -21,6 +21,7 @@
 
 #include <utility>
 
+#include <android-base/cmsg.h>
 #include <android-base/unique_fd.h>
 #include <async_safe/log.h>
 #include <cutils/sockets.h>
@@ -28,6 +29,7 @@
 #include "protocol.h"
 #include "util.h"
 
+using android::base::ReceiveFileDescriptors;
 using android::base::unique_fd;
 
 bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* output_fd,
@@ -53,7 +55,7 @@
   }
 
   unique_fd tmp_output_fd;
-  ssize_t rc = recv_fd(sockfd, &packet, sizeof(packet), &tmp_output_fd);
+  ssize_t rc = ReceiveFileDescriptors(sockfd, &packet, sizeof(packet), &tmp_output_fd);
   if (rc == -1) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                           "failed to read response to DumpRequest packet: %s", strerror(errno));
diff --git a/debuggerd/util.cpp b/debuggerd/util.cpp
index 50c5efc..a37b3b9 100644
--- a/debuggerd/util.cpp
+++ b/debuggerd/util.cpp
@@ -24,73 +24,9 @@
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <android-base/unique_fd.h>
 #include <cutils/sockets.h>
 #include "protocol.h"
 
-using android::base::unique_fd;
-
-ssize_t send_fd(int sockfd, const void* data, size_t len, unique_fd fd) {
-  char cmsg_buf[CMSG_SPACE(sizeof(int))];
-
-  iovec iov = { .iov_base = const_cast<void*>(data), .iov_len = len };
-  msghdr msg = {
-    .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf, .msg_controllen = sizeof(cmsg_buf),
-  };
-  auto cmsg = CMSG_FIRSTHDR(&msg);
-  cmsg->cmsg_level = SOL_SOCKET;
-  cmsg->cmsg_type = SCM_RIGHTS;
-  cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-  *reinterpret_cast<int*>(CMSG_DATA(cmsg)) = fd.get();
-
-  return TEMP_FAILURE_RETRY(sendmsg(sockfd, &msg, 0));
-}
-
-ssize_t recv_fd(int sockfd, void* _Nonnull data, size_t len, unique_fd* _Nullable out_fd) {
-  char cmsg_buf[CMSG_SPACE(sizeof(int))];
-
-  iovec iov = { .iov_base = const_cast<void*>(data), .iov_len = len };
-  msghdr msg = {
-    .msg_iov = &iov,
-    .msg_iovlen = 1,
-    .msg_control = cmsg_buf,
-    .msg_controllen = sizeof(cmsg_buf),
-    .msg_flags = 0,
-  };
-  auto cmsg = CMSG_FIRSTHDR(&msg);
-  cmsg->cmsg_level = SOL_SOCKET;
-  cmsg->cmsg_type = SCM_RIGHTS;
-  cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-
-  ssize_t result = TEMP_FAILURE_RETRY(recvmsg(sockfd, &msg, 0));
-  if (result == -1) {
-    return -1;
-  }
-
-  unique_fd fd;
-  bool received_fd = msg.msg_controllen == sizeof(cmsg_buf);
-  if (received_fd) {
-    fd.reset(*reinterpret_cast<int*>(CMSG_DATA(cmsg)));
-  }
-
-  if ((msg.msg_flags & MSG_TRUNC) != 0) {
-    errno = EFBIG;
-    return -1;
-  } else if ((msg.msg_flags & MSG_CTRUNC) != 0) {
-    errno = ERANGE;
-    return -1;
-  }
-
-  if (out_fd) {
-    *out_fd = std::move(fd);
-  } else if (received_fd) {
-    errno = ERANGE;
-    return -1;
-  }
-
-  return result;
-}
-
 std::string get_process_name(pid_t pid) {
   std::string result = "<unknown>";
   android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &result);
diff --git a/debuggerd/util.h b/debuggerd/util.h
index 8260b44..e964423 100644
--- a/debuggerd/util.h
+++ b/debuggerd/util.h
@@ -21,29 +21,5 @@
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
-#include <android-base/unique_fd.h>
-
-// *** WARNING ***
-// tombstoned's sockets are SOCK_SEQPACKET sockets.
-// Short reads are treated as errors and short writes are assumed to not happen.
-
-// Sends a packet with an attached fd.
-ssize_t send_fd(int sockfd, const void* _Nonnull data, size_t len, android::base::unique_fd fd);
-
-// Receives a packet and optionally, its attached fd.
-// If out_fd is non-null, packets can optionally have an attached fd.
-// If out_fd is null, received packets must not have an attached fd.
-//
-// Errors:
-//   EOVERFLOW: sockfd is SOCK_DGRAM or SOCK_SEQPACKET and buffer is too small.
-//              The first len bytes of the packet are stored in data, but the
-//              rest of the packet is dropped.
-//   ERANGE:    too many file descriptors were attached to the packet.
-//   ENOMSG:    not enough file descriptors were attached to the packet.
-//
-//   plus any errors returned by the underlying recvmsg.
-ssize_t recv_fd(int sockfd, void* _Nonnull data, size_t len,
-                android::base::unique_fd* _Nullable out_fd);
-
 std::string get_process_name(pid_t pid);
 std::string get_thread_name(pid_t tid);
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index b69e773..0c904c4 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1098,9 +1098,7 @@
             }
         } else if ((current_entry.fs_mgr_flags.verify)) {
             int rc = fs_mgr_setup_verity(&current_entry, true);
-            if (__android_log_is_debuggable() &&
-                    (rc == FS_MGR_SETUP_VERITY_DISABLED ||
-                     rc == FS_MGR_SETUP_VERITY_SKIPPED)) {
+            if (rc == FS_MGR_SETUP_VERITY_DISABLED || rc == FS_MGR_SETUP_VERITY_SKIPPED) {
                 LINFO << "Verity disabled";
             } else if (rc != FS_MGR_SETUP_VERITY_SUCCESS) {
                 LERROR << "Could not set up verified partition, skipping!";
@@ -1331,9 +1329,7 @@
             }
         } else if (fstab_entry.fs_mgr_flags.verify) {
             int rc = fs_mgr_setup_verity(&fstab_entry, true);
-            if (__android_log_is_debuggable() &&
-                    (rc == FS_MGR_SETUP_VERITY_DISABLED ||
-                     rc == FS_MGR_SETUP_VERITY_SKIPPED)) {
+            if (rc == FS_MGR_SETUP_VERITY_DISABLED || rc == FS_MGR_SETUP_VERITY_SKIPPED) {
                 LINFO << "Verity disabled";
             } else if (rc != FS_MGR_SETUP_VERITY_SUCCESS) {
                 LERROR << "Could not set up verified partition, skipping!";
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 4659add..82d9144 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -607,10 +607,14 @@
     return userdata;
 }
 
-void EraseFstabEntry(Fstab* fstab, const std::string& mount_point) {
+bool EraseFstabEntry(Fstab* fstab, const std::string& mount_point) {
     auto iter = std::remove_if(fstab->begin(), fstab->end(),
                                [&](const auto& entry) { return entry.mount_point == mount_point; });
-    fstab->erase(iter, fstab->end());
+    if (iter != fstab->end()) {
+        fstab->erase(iter, fstab->end());
+        return true;
+    }
+    return false;
 }
 
 void TransformFstabForGsi(Fstab* fstab) {
@@ -628,11 +632,13 @@
         userdata = BuildGsiUserdataFstabEntry();
     }
 
-    EraseFstabEntry(fstab, "/system");
-    EraseFstabEntry(fstab, "/data");
+    if (EraseFstabEntry(fstab, "/system")) {
+        fstab->emplace_back(BuildGsiSystemFstabEntry());
+    }
 
-    fstab->emplace_back(BuildGsiSystemFstabEntry());
-    fstab->emplace_back(userdata);
+    if (EraseFstabEntry(fstab, "/data")) {
+        fstab->emplace_back(userdata);
+    }
 }
 
 }  // namespace
diff --git a/init/init.cpp b/init/init.cpp
index a8924f2..bbef1a9 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -42,6 +42,7 @@
 #include <fs_mgr_vendor_overlay.h>
 #include <keyutils.h>
 #include <libavb/libavb.h>
+#include <libgsi/libgsi.h>
 #include <processgroup/processgroup.h>
 #include <selinux/android.h>
 
@@ -695,6 +696,13 @@
     // Nexus 9 boot time, so it's disabled by default.
     if (false) DumpState();
 
+    // Make the GSI status available before scripts start running.
+    if (android::gsi::IsGsiRunning()) {
+        property_set("ro.gsid.image_running", "1");
+    } else {
+        property_set("ro.gsid.image_running", "0");
+    }
+
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
 
     am.QueueEventTrigger("early-init");
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 46e5e12..4bc857a 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -39,6 +39,7 @@
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
+#include <map>
 #include <memory>
 #include <queue>
 #include <vector>
@@ -442,8 +443,8 @@
 }
 
 // This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
-uint32_t HandlePropertySet(const std::string& name, const std::string& value,
-                           const std::string& source_context, const ucred& cr, std::string* error) {
+uint32_t CheckPermissions(const std::string& name, const std::string& value,
+                          const std::string& source_context, const ucred& cr, std::string* error) {
     if (!IsLegalPropertyName(name)) {
         *error = "Illegal property name";
         return PROP_ERROR_INVALID_NAME;
@@ -456,7 +457,6 @@
             return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
         }
 
-        HandleControlMessage(name.c_str() + 4, value, cr.pid);
         return PROP_SUCCESS;
     }
 
@@ -475,6 +475,21 @@
         return PROP_ERROR_INVALID_VALUE;
     }
 
+    return PROP_SUCCESS;
+}
+
+// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
+uint32_t HandlePropertySet(const std::string& name, const std::string& value,
+                           const std::string& source_context, const ucred& cr, std::string* error) {
+    if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) {
+        return ret;
+    }
+
+    if (StartsWith(name, "ctl.")) {
+        HandleControlMessage(name.c_str() + 4, value, cr.pid);
+        return PROP_SUCCESS;
+    }
+
     // sys.powerctl is a special property that is used to make the device reboot.  We want to log
     // any process that sets this property to be able to accurately blame the cause of a shutdown.
     if (name == "sys.powerctl") {
@@ -579,13 +594,15 @@
     }
 }
 
-static bool load_properties_from_file(const char *, const char *);
+static bool load_properties_from_file(const char*, const char*,
+                                      std::map<std::string, std::string>*);
 
 /*
  * Filter is used to decide which properties to load: NULL loads all keys,
  * "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
  */
-static void LoadProperties(char* data, const char* filter, const char* filename) {
+static void LoadProperties(char* data, const char* filter, const char* filename,
+                           std::map<std::string, std::string>* properties) {
     char *key, *value, *eol, *sol, *tmp, *fn;
     size_t flen = 0;
 
@@ -624,7 +641,7 @@
                 while (isspace(*key)) key++;
             }
 
-            load_properties_from_file(fn, key);
+            load_properties_from_file(fn, key, properties);
 
         } else {
             value = strchr(key, '=');
@@ -651,12 +668,19 @@
                 continue;
             }
 
-            uint32_t result = 0;
             ucred cr = {.pid = 1, .uid = 0, .gid = 0};
             std::string error;
-            result = HandlePropertySet(key, value, context, cr, &error);
-            if (result != PROP_SUCCESS) {
-                LOG(ERROR) << "Unable to set property '" << key << "' to '" << value
+            if (CheckPermissions(key, value, context, cr, &error) == PROP_SUCCESS) {
+                auto it = properties->find(key);
+                if (it == properties->end()) {
+                    (*properties)[key] = value;
+                } else if (it->second != value) {
+                    LOG(WARNING) << "Overriding previous 'ro.' property '" << key << "':'"
+                                 << it->second << "' with new value '" << value << "'";
+                    it->second = value;
+                }
+            } else {
+                LOG(ERROR) << "Do not have permissions to set '" << key << "' to '" << value
                            << "' in property file '" << filename << "': " << error;
             }
         }
@@ -665,7 +689,8 @@
 
 // Filter is used to decide which properties to load: NULL loads all keys,
 // "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
-static bool load_properties_from_file(const char* filename, const char* filter) {
+static bool load_properties_from_file(const char* filename, const char* filter,
+                                      std::map<std::string, std::string>* properties) {
     Timer t;
     auto file_contents = ReadFile(filename);
     if (!file_contents) {
@@ -675,7 +700,7 @@
     }
     file_contents->push_back('\n');
 
-    LoadProperties(file_contents->data(), filter, filename);
+    LoadProperties(file_contents->data(), filter, filename, properties);
     LOG(VERBOSE) << "(Loading properties from " << filename << " took " << t << ".)";
     return true;
 }
@@ -698,7 +723,15 @@
 
 static void load_override_properties() {
     if (ALLOW_LOCAL_PROP_OVERRIDE) {
-        load_properties_from_file("/data/local.prop", NULL);
+        std::map<std::string, std::string> properties;
+        load_properties_from_file("/data/local.prop", nullptr, &properties);
+        for (const auto& [name, value] : properties) {
+            std::string error;
+            if (PropertySet(name, value, &error) != PROP_SUCCESS) {
+                LOG(ERROR) << "Could not set '" << name << "' to '" << value
+                           << "' in /data/local.prop: " << error;
+            }
+        }
     }
 }
 
@@ -835,24 +868,33 @@
 
 void property_load_boot_defaults() {
     // TODO(b/117892318): merge prop.default and build.prop files into one
-    // TODO(b/122864654): read the prop files from all partitions and then
-    // resolve the duplication by their origin so that RO and non-RO properties
-    // have a consistent overriding order.
-    if (!load_properties_from_file("/system/etc/prop.default", NULL)) {
+    // We read the properties and their values into a map, in order to always allow properties
+    // loaded in the later property files to override the properties in loaded in the earlier
+    // property files, regardless of if they are "ro." properties or not.
+    std::map<std::string, std::string> properties;
+    if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) {
         // Try recovery path
-        if (!load_properties_from_file("/prop.default", NULL)) {
+        if (!load_properties_from_file("/prop.default", nullptr, &properties)) {
             // Try legacy path
-            load_properties_from_file("/default.prop", NULL);
+            load_properties_from_file("/default.prop", nullptr, &properties);
         }
     }
-    load_properties_from_file("/product/build.prop", NULL);
-    load_properties_from_file("/product_services/build.prop", NULL);
-    load_properties_from_file("/odm/default.prop", NULL);
-    load_properties_from_file("/vendor/default.prop", NULL);
-    load_properties_from_file("/system/build.prop", NULL);
-    load_properties_from_file("/odm/build.prop", NULL);
-    load_properties_from_file("/vendor/build.prop", NULL);
-    load_properties_from_file("/factory/factory.prop", "ro.*");
+    load_properties_from_file("/system/build.prop", nullptr, &properties);
+    load_properties_from_file("/vendor/default.prop", nullptr, &properties);
+    load_properties_from_file("/vendor/build.prop", nullptr, &properties);
+    load_properties_from_file("/odm/default.prop", nullptr, &properties);
+    load_properties_from_file("/odm/build.prop", nullptr, &properties);
+    load_properties_from_file("/product/build.prop", nullptr, &properties);
+    load_properties_from_file("/product_services/build.prop", nullptr, &properties);
+    load_properties_from_file("/factory/factory.prop", "ro.*", &properties);
+
+    for (const auto& [name, value] : properties) {
+        std::string error;
+        if (PropertySet(name, value, &error) != PROP_SUCCESS) {
+            LOG(ERROR) << "Could not set '" << name << "' to '" << value
+                       << "' while loading .prop files" << error;
+        }
+    }
 
     property_initialize_ro_product_props();
     property_derive_build_fingerprint();
@@ -908,6 +950,13 @@
             LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
                                      &property_infos);
         }
+        if (access("/product/etc/selinux/product_property_contexts", R_OK) != -1) {
+            LoadPropertyInfoFromFile("/product/etc/selinux/product_property_contexts",
+                                     &property_infos);
+        }
+        if (access("/odm/etc/selinux/odm_property_contexts", R_OK) != -1) {
+            LoadPropertyInfoFromFile("/odm/etc/selinux/odm_property_contexts", &property_infos);
+        }
     } else {
         if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
             return;
@@ -916,6 +965,8 @@
             // Fallback to nonplat_* if vendor_* doesn't exist.
             LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
         }
+        LoadPropertyInfoFromFile("/product_property_contexts", &property_infos);
+        LoadPropertyInfoFromFile("/odm_property_contexts", &property_infos);
     }
 
     auto serialized_contexts = std::string();
diff --git a/libcutils/trace-dev.inc b/libcutils/trace-dev.inc
index c9580af..e3da77b 100644
--- a/libcutils/trace-dev.inc
+++ b/libcutils/trace-dev.inc
@@ -80,7 +80,7 @@
 // Determine whether application-level tracing is enabled for this process.
 static bool atrace_is_app_tracing_enabled()
 {
-    bool sys_debuggable = __android_log_is_debuggable();
+    bool sys_debuggable = property_get_bool("ro.debuggable", 0);
     bool result = false;
 
     if (sys_debuggable || atrace_is_debuggable) {
diff --git a/liblog/Android.bp b/liblog/Android.bp
index ea9977b..9b41ebe 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -101,6 +101,11 @@
     header_libs: ["liblog_headers"],
     export_header_lib_headers: ["liblog_headers"],
 
+    stubs: {
+        symbol_file: "liblog.map.txt",
+        versions: ["10000"],
+    },
+
     cflags: [
         "-Werror",
         // This is what we want to do:
diff --git a/liblog/liblog.map.txt b/liblog/liblog.map.txt
index 191ef1b..ce4c53c 100644
--- a/liblog/liblog.map.txt
+++ b/liblog/liblog.map.txt
@@ -19,12 +19,12 @@
     android_logger_get_log_readable_size; # vndk
     android_logger_get_log_version; # vndk
     android_logger_get_log_size; # vndk
-    android_logger_list_alloc; # vndk
-    android_logger_list_alloc_time; # vndk
-    android_logger_list_free; # vndk
+    android_logger_list_alloc; # apex vndk
+    android_logger_list_alloc_time; # apex vndk
+    android_logger_list_free; # apex vndk
     android_logger_list_open; # vndk
-    android_logger_list_read; # vndk
-    android_logger_open; # vndk
+    android_logger_list_read; # apex vndk
+    android_logger_open; # apex vndk
     android_logger_set_log_size; # vndk
 };
 
@@ -33,18 +33,18 @@
     android_logger_get_prune_list; # vndk
     android_logger_set_prune_list; # vndk
     android_logger_get_statistics; # vndk
-    __android_log_error_write; # vndk
+    __android_log_error_write; # apex vndk
     __android_log_is_loggable;
-    create_android_logger; #vndk
-    android_log_destroy; #vndk
-    android_log_write_list_begin; #vndk
-    android_log_write_list_end; #vndk
-    android_log_write_int32; #vndk
-    android_log_write_int64; #vndk
-    android_log_write_string8; #vndk
-    android_log_write_string8_len; #vndk
-    android_log_write_float32; #vndk
-    android_log_write_list; #vndk
+    create_android_logger; # apex vndk
+    android_log_destroy; # apex vndk
+    android_log_write_list_begin; # apex vndk
+    android_log_write_list_end; # apex vndk
+    android_log_write_int32; # apex vndk
+    android_log_write_int64; # apex vndk
+    android_log_write_string8; # apex vndk
+    android_log_write_string8_len; # apex vndk
+    android_log_write_float32; # apex vndk
+    android_log_write_list; # apex vndk
 
 };
 
@@ -56,19 +56,19 @@
 
 LIBLOG_Q {
   global:
+    __android_log_bswrite; # apex
+    __android_log_btwrite; # apex
+    __android_log_bwrite; # apex
+    __android_log_close; # apex
+    __android_log_security; # apex
     android_log_reset; #vndk
     android_log_parser_reset; #vndk
 };
 
 LIBLOG_PRIVATE {
   global:
-    __android_log_bswrite;
-    __android_log_btwrite;
-    __android_log_bwrite;
-    __android_log_close;
     __android_log_pmsg_file_read;
     __android_log_pmsg_file_write;
-    __android_log_security;
     __android_log_security_bswrite;
     __android_logger_get_buffer_size;
     __android_logger_property_get_bool;
diff --git a/libnativeloader/Android.bp b/libnativeloader/Android.bp
index b806ae4..b9f0dbf 100644
--- a/libnativeloader/Android.bp
+++ b/libnativeloader/Android.bp
@@ -21,7 +21,6 @@
     shared_libs: [
         "libnativehelper",
         "liblog",
-        "libcutils",
         "libnativebridge",
         "libbase",
     ],
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index 5394d7e..f9c161c 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -21,7 +21,6 @@
 #ifdef __ANDROID__
 #define LOG_TAG "libnativeloader"
 #include "nativeloader/dlext_namespaces.h"
-#include "cutils/properties.h"
 #include "log/log.h"
 #endif
 #include <dirent.h>
@@ -137,10 +136,18 @@
 
 static constexpr const char* kApexPath = "/apex/";
 
+#if defined(__LP64__)
+static constexpr const char* kRuntimeApexLibPath = "/apex/com.android.runtime/lib64";
+#else
+static constexpr const char* kRuntimeApexLibPath = "/apex/com.android.runtime/lib";
+#endif
+
 static bool is_debuggable() {
-  char debuggable[PROP_VALUE_MAX];
-  property_get("ro.debuggable", debuggable, "0");
-  return std::string(debuggable) == "1";
+  bool debuggable = false;
+#ifdef __BIONIC__
+  debuggable = android::base::GetBoolProperty("ro.debuggable", false);
+#endif
+  return debuggable;
 }
 
 static std::string vndk_version_str() {
@@ -408,6 +415,14 @@
       }
     }
 
+    // Remove the public libs in the runtime namespace.
+    // These libs are listed in public.android.txt, but we don't want the rest of android
+    // in default namespace to dlopen the libs.
+    // For example, libicuuc.so is exposed to classloader namespace from runtime namespace.
+    // Unfortunately, it does not have stable C symbols, and default namespace should only use
+    // stable symbols in libandroidicu.so. http://b/120786417
+    removePublicLibsIfExistsInRuntimeApex(sonames);
+
     // android_init_namespaces() expects all the public libraries
     // to be loaded so that they can be found by soname alone.
     //
@@ -502,6 +517,27 @@
     }
   }
 
+  /**
+   * Remove the public libs in runtime namespace
+   */
+  void removePublicLibsIfExistsInRuntimeApex(std::vector<std::string>& sonames) {
+    for (const std::string& lib_name : kRuntimePublicLibraries) {
+      std::string path(kRuntimeApexLibPath);
+      path.append("/").append(lib_name);
+
+      struct stat s;
+      // Do nothing if the path in /apex does not exist.
+      // Runtime APEX must be mounted since libnativeloader is in the same APEX
+      if (stat(path.c_str(), &s) != 0) {
+        continue;
+      }
+
+      auto it = std::find(sonames.begin(), sonames.end(), lib_name);
+      if (it != sonames.end()) {
+        sonames.erase(it);
+      }
+    }
+  }
 
   bool ReadConfig(const std::string& configFile, std::vector<std::string>* sonames,
                   const std::function<bool(const std::string& /* soname */,
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index e9dec12..8505e61 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -200,7 +200,7 @@
         cgroups.push_back(path);
     }
     if (CgroupGetControllerPath("memory", &path)) {
-        cgroups.push_back(path);
+        cgroups.push_back(path + "/apps");
     }
 
     for (std::string cgroup_root_path : cgroups) {
@@ -317,6 +317,7 @@
 
     CgroupGetControllerPath("cpuacct", &cpuacct_path);
     CgroupGetControllerPath("memory", &memory_path);
+    memory_path += "/apps";
 
     const char* cgroup =
             (!access(ConvertUidPidToPath(cpuacct_path.c_str(), uid, initialPid).c_str(), F_OK))
@@ -380,6 +381,7 @@
     std::string cgroup;
     if (isMemoryCgroupSupported() && (memControl || UsePerAppMemcg())) {
         CgroupGetControllerPath("memory", &cgroup);
+        cgroup += "/apps";
     } else {
         CgroupGetControllerPath("cpuacct", &cgroup);
     }
diff --git a/libprocessgroup/profiles/Android.bp b/libprocessgroup/profiles/Android.bp
new file mode 100644
index 0000000..15d0172
--- /dev/null
+++ b/libprocessgroup/profiles/Android.bp
@@ -0,0 +1,80 @@
+// 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.
+
+prebuilt_etc {
+    name: "cgroups.json",
+    src: "cgroups.json",
+}
+
+prebuilt_etc {
+    name: "cgroups.recovery.json",
+    filename: "cgroups.json",
+    recovery: true,
+    src: "cgroups.recovery.json",
+}
+
+prebuilt_etc {
+    name: "task_profiles.json",
+    src: "task_profiles.json",
+}
+
+cc_library_static {
+    name: "libprocessgroup_proto",
+    host_supported: true,
+    srcs: [
+        "cgroups.proto",
+        "task_profiles.proto",
+    ],
+    proto: {
+        type: "full",
+        export_proto_headers: true,
+    },
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+}
+
+cc_test_host {
+    name: "libprocessgroup_proto_test",
+    srcs: [
+        "test.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libgmock",
+        "liblog",
+        "libjsoncpp",
+        "libjsonpbverify",
+        "libjsonpbparse",
+        "libprocessgroup_proto",
+    ],
+    shared_libs: [
+        "libprotobuf-cpp-full",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    data: [
+        "cgroups.json",
+        "cgroups.recovery.json",
+        "task_profiles.json",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+}
diff --git a/libprocessgroup/profiles/TEST_MAPPING b/libprocessgroup/profiles/TEST_MAPPING
new file mode 100644
index 0000000..5ff4112
--- /dev/null
+++ b/libprocessgroup/profiles/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "libprocessgroup_proto_test",
+      "host": true
+    }
+  ]
+}
diff --git a/rootdir/cgroups.json b/libprocessgroup/profiles/cgroups.json
similarity index 100%
rename from rootdir/cgroups.json
rename to libprocessgroup/profiles/cgroups.json
diff --git a/libprocessgroup/profiles/cgroups.proto b/libprocessgroup/profiles/cgroups.proto
new file mode 100644
index 0000000..f4070c5
--- /dev/null
+++ b/libprocessgroup/profiles/cgroups.proto
@@ -0,0 +1,42 @@
+/*
+ * 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 = "proto3";
+
+package android.profiles;
+
+// Next: 3
+message Cgroups {
+    repeated Cgroup cgroups = 1 [json_name = "Cgroups"];
+    Cgroups2 cgroups2 = 2 [json_name = "Cgroups2"];
+}
+
+// Next: 6
+message Cgroup {
+    string controller = 1 [json_name = "Controller"];
+    string path = 2 [json_name = "Path"];
+    string mode = 3 [json_name = "Mode"];
+    string uid = 4 [json_name = "UID"];
+    string gid = 5 [json_name = "GID"];
+}
+
+// Next: 5
+message Cgroups2 {
+    string path = 1 [json_name = "Path"];
+    string mode = 2 [json_name = "Mode"];
+    string uid = 3 [json_name = "UID"];
+    string gid = 4 [json_name = "GID"];
+}
diff --git a/rootdir/cgroups.recovery.json b/libprocessgroup/profiles/cgroups.recovery.json
similarity index 100%
rename from rootdir/cgroups.recovery.json
rename to libprocessgroup/profiles/cgroups.recovery.json
diff --git a/rootdir/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
similarity index 100%
rename from rootdir/task_profiles.json
rename to libprocessgroup/profiles/task_profiles.json
diff --git a/libprocessgroup/profiles/task_profiles.proto b/libprocessgroup/profiles/task_profiles.proto
new file mode 100644
index 0000000..578f0d3
--- /dev/null
+++ b/libprocessgroup/profiles/task_profiles.proto
@@ -0,0 +1,44 @@
+/*
+ * 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 = "proto3";
+
+package android.profiles;
+
+// Next: 3
+message TaskProfiles {
+    repeated Attribute attributes = 1 [json_name = "Attributes"];
+    repeated Profile profiles = 2 [json_name = "Profiles"];
+}
+
+// Next: 4
+message Attribute {
+    string name = 1 [json_name = "Name"];
+    string controller = 2 [json_name = "Controller"];
+    string file = 3 [json_name = "File"];
+}
+
+// Next: 3
+message Profile {
+    string name = 1 [json_name = "Name"];
+    repeated Action actions = 2 [json_name = "Actions"];
+}
+
+// Next: 3
+message Action {
+    string name = 1 [json_name = "Name"];
+    map<string, string> params = 2 [json_name = "Params"];
+}
diff --git a/libprocessgroup/profiles/test.cpp b/libprocessgroup/profiles/test.cpp
new file mode 100644
index 0000000..8ba14d6
--- /dev/null
+++ b/libprocessgroup/profiles/test.cpp
@@ -0,0 +1,135 @@
+/*
+ * 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 <string>
+
+#include <android-base/file.h>
+#include <gmock/gmock.h>
+#include <jsonpb/json_schema_test.h>
+
+#include "cgroups.pb.h"
+#include "task_profiles.pb.h"
+
+using namespace ::android::jsonpb;
+using ::android::base::GetExecutableDirectory;
+using ::testing::MatchesRegex;
+
+namespace android {
+namespace profiles {
+
+template <typename T>
+JsonSchemaTestConfigFactory MakeTestParam(const std::string& path) {
+    return jsonpb::MakeTestParam<T>(GetExecutableDirectory() + path);
+}
+
+TEST(LibProcessgroupProto, EmptyMode) {
+    EXPECT_EQ(0, strtoul("", nullptr, 8))
+            << "Empty mode string cannot be silently converted to 0; this should not happen";
+}
+
+class CgroupsTest : public JsonSchemaTest {
+  public:
+    void SetUp() override {
+        JsonSchemaTest::SetUp();
+        cgroups_ = static_cast<Cgroups*>(message());
+    }
+    Cgroups* cgroups_;
+};
+
+TEST_P(CgroupsTest, CgroupRequiredFields) {
+    for (int i = 0; i < cgroups_->cgroups_size(); ++i) {
+        auto&& cgroup = cgroups_->cgroups(i);
+        EXPECT_FALSE(cgroup.controller().empty())
+                << "No controller name for cgroup #" << i << " in " << file_path_;
+        EXPECT_FALSE(cgroup.path().empty()) << "No path for cgroup #" << i << " in " << file_path_;
+    }
+}
+
+TEST_P(CgroupsTest, Cgroup2RequiredFields) {
+    if (cgroups_->has_cgroups2()) {
+        EXPECT_FALSE(cgroups_->cgroups2().path().empty())
+                << "No path for cgroup2 in " << file_path_;
+    }
+}
+
+// "Mode" field must be in the format of "0xxx".
+static constexpr const char* REGEX_MODE = "(0[0-7]{3})?";
+TEST_P(CgroupsTest, CgroupMode) {
+    for (int i = 0; i < cgroups_->cgroups_size(); ++i) {
+        EXPECT_THAT(cgroups_->cgroups(i).mode(), MatchesRegex(REGEX_MODE))
+                << "For cgroup controller #" << i << " in " << file_path_;
+    }
+}
+
+TEST_P(CgroupsTest, Cgroup2Mode) {
+    EXPECT_THAT(cgroups_->cgroups2().mode(), MatchesRegex(REGEX_MODE))
+            << "For cgroups2 in " << file_path_;
+}
+
+class TaskProfilesTest : public JsonSchemaTest {
+  public:
+    void SetUp() override {
+        JsonSchemaTest::SetUp();
+        task_profiles_ = static_cast<TaskProfiles*>(message());
+    }
+    TaskProfiles* task_profiles_;
+};
+
+TEST_P(TaskProfilesTest, AttributeRequiredFields) {
+    for (int i = 0; i < task_profiles_->attributes_size(); ++i) {
+        auto&& attribute = task_profiles_->attributes(i);
+        EXPECT_FALSE(attribute.name().empty())
+                << "No name for attribute #" << i << " in " << file_path_;
+        EXPECT_FALSE(attribute.controller().empty())
+                << "No controller for attribute #" << i << " in " << file_path_;
+        EXPECT_FALSE(attribute.file().empty())
+                << "No file for attribute #" << i << " in " << file_path_;
+    }
+}
+
+TEST_P(TaskProfilesTest, ProfileRequiredFields) {
+    for (int profile_idx = 0; profile_idx < task_profiles_->profiles_size(); ++profile_idx) {
+        auto&& profile = task_profiles_->profiles(profile_idx);
+        EXPECT_FALSE(profile.name().empty())
+                << "No name for profile #" << profile_idx << " in " << file_path_;
+        for (int action_idx = 0; action_idx < profile.actions_size(); ++action_idx) {
+            auto&& action = profile.actions(action_idx);
+            EXPECT_FALSE(action.name().empty())
+                    << "No name for profiles[" << profile_idx << "].actions[" << action_idx
+                    << "] in " << file_path_;
+        }
+    }
+}
+
+// Test suite instantiations
+
+INSTANTIATE_TEST_SUITE_P(, JsonSchemaTest,
+                         ::testing::Values(MakeTestParam<Cgroups>("/cgroups.json"),
+                                           MakeTestParam<Cgroups>("/cgroups.recovery.json"),
+                                           MakeTestParam<TaskProfiles>("/task_profiles.json")));
+INSTANTIATE_TEST_SUITE_P(, CgroupsTest,
+                         ::testing::Values(MakeTestParam<Cgroups>("/cgroups.json"),
+                                           MakeTestParam<Cgroups>("/cgroups.recovery.json")));
+INSTANTIATE_TEST_SUITE_P(, TaskProfilesTest,
+                         ::testing::Values(MakeTestParam<TaskProfiles>("/task_profiles.json")));
+
+}  // namespace profiles
+}  // namespace android
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp
index cbca1ce..9538bba 100644
--- a/libziparchive/Android.bp
+++ b/libziparchive/Android.bp
@@ -126,7 +126,6 @@
         },
     },
     test_suites: ["device-tests"],
-    test_config: "ziparchive-tests.xml",  // TODO: Remove after b/117891984.
 }
 
 // Performance benchmarks.
diff --git a/libziparchive/ziparchive-tests.xml b/libziparchive/ziparchive-tests.xml
deleted file mode 100644
index 2be0a99..0000000
--- a/libziparchive/ziparchive-tests.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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.
--->
-<!-- Derived from auto-generated config. b/117891984 & b/124515549. -->
-<configuration description="Runs ziparchive-tests.">
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-suite-tag" value="apct-native" />
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="ziparchive-tests->/data/local/tmp/ziparchive-tests/ziparchive-tests" />
-        <option name="push" value="testdata->/data/local/tmp/ziparchive-tests/testdata" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp/ziparchive-tests" />
-        <option name="module-name" value="ziparchive-tests" />
-    </test>
-</configuration>
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index b5e5a71..944554a 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -64,38 +64,6 @@
 include $(BUILD_PREBUILT)
 
 #######################################
-# cgroups.json
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := cgroups.json
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-
-include $(BUILD_PREBUILT)
-
-#######################################
-# cgroups.json for recovery
-include $(CLEAR_VARS)
-LOCAL_MODULE := cgroups.recovery.json
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc
-LOCAL_MODULE_STEM := cgroups.json
-include $(BUILD_PREBUILT)
-
-#######################################
-# task_profiles.json
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := task_profiles.json
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-
-include $(BUILD_PREBUILT)
-
-#######################################
 # asan.options
 ifneq ($(filter address,$(SANITIZE_TARGET)),)
 
diff --git a/rootdir/etc/ld.config.legacy.txt b/rootdir/etc/ld.config.legacy.txt
index e897d81..fa0a01a 100644
--- a/rootdir/etc/ld.config.legacy.txt
+++ b/rootdir/etc/ld.config.legacy.txt
@@ -54,6 +54,10 @@
 namespace.default.link.runtime.shared_libs += libnativebridge.so
 namespace.default.link.runtime.shared_libs += libnativehelper.so
 namespace.default.link.runtime.shared_libs += libnativeloader.so
+namespace.default.link.runtime.shared_libs += libandroidicu.so
+
+# TODO(b/122876336): Remove libpac.so once it's migrated to Webview
+namespace.default.link.runtime.shared_libs += libpac.so
 
 # When libnetd_resolv.so can't be found in the default namespace, search for it
 # in the resolv namespace. Don't allow any other libraries from the resolv namespace
@@ -110,6 +114,7 @@
 namespace.conscrypt.link.default.shared_libs  = libc.so
 namespace.conscrypt.link.default.shared_libs += libm.so
 namespace.conscrypt.link.default.shared_libs += libdl.so
+namespace.conscrypt.link.default.shared_libs += liblog.so
 
 ###############################################################################
 # "resolv" APEX namespace
@@ -126,6 +131,7 @@
 namespace.resolv.link.default.shared_libs += libm.so
 namespace.resolv.link.default.shared_libs += libdl.so
 namespace.resolv.link.default.shared_libs += libbinder_ndk.so
+namespace.resolv.link.default.shared_libs += liblog.so
 namespace.resolv.link.default.shared_libs += libvndksupport.so
 
 ###############################################################################
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index df9abbd..cf834f0 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -132,6 +132,10 @@
 namespace.default.link.runtime.shared_libs += libnativebridge.so
 namespace.default.link.runtime.shared_libs += libnativehelper.so
 namespace.default.link.runtime.shared_libs += libnativeloader.so
+namespace.default.link.runtime.shared_libs += libandroidicu.so
+
+# TODO(b/122876336): Remove libpac.so once it's migrated to Webview
+namespace.default.link.runtime.shared_libs += libpac.so
 
 # When libnetd_resolv.so can't be found in the default namespace, search for it
 # in the resolv namespace. Don't allow any other libraries from the resolv namespace
@@ -188,6 +192,7 @@
 namespace.conscrypt.link.default.shared_libs  = libc.so
 namespace.conscrypt.link.default.shared_libs += libm.so
 namespace.conscrypt.link.default.shared_libs += libdl.so
+namespace.conscrypt.link.default.shared_libs += liblog.so
 
 ###############################################################################
 # "resolv" APEX namespace
@@ -204,6 +209,7 @@
 namespace.resolv.link.default.shared_libs += libm.so
 namespace.resolv.link.default.shared_libs += libdl.so
 namespace.resolv.link.default.shared_libs += libbinder_ndk.so
+namespace.resolv.link.default.shared_libs += liblog.so
 namespace.resolv.link.default.shared_libs += libvndksupport.so
 
 ###############################################################################
@@ -481,7 +487,8 @@
 namespace.system.link.runtime.shared_libs += libnativebridge.so
 namespace.system.link.runtime.shared_libs += libnativehelper.so
 namespace.system.link.runtime.shared_libs += libnativeloader.so
-
+# Workaround for b/124772622
+namespace.system.link.runtime.shared_libs += libandroidicu.so
 
 ###############################################################################
 # Namespace config for native tests that need access to both system and vendor
@@ -569,6 +576,7 @@
 namespace.conscrypt.link.default.shared_libs  = libc.so
 namespace.conscrypt.link.default.shared_libs += libm.so
 namespace.conscrypt.link.default.shared_libs += libdl.so
+namespace.conscrypt.link.default.shared_libs += liblog.so
 
 ###############################################################################
 # "resolv" APEX namespace
@@ -585,6 +593,7 @@
 namespace.resolv.link.default.shared_libs += libm.so
 namespace.resolv.link.default.shared_libs += libdl.so
 namespace.resolv.link.default.shared_libs += libbinder_ndk.so
+namespace.resolv.link.default.shared_libs += liblog.so
 
 
 ###############################################################################
diff --git a/rootdir/etc/ld.config.vndk_lite.txt b/rootdir/etc/ld.config.vndk_lite.txt
index 3c97a49..2c6e01f 100644
--- a/rootdir/etc/ld.config.vndk_lite.txt
+++ b/rootdir/etc/ld.config.vndk_lite.txt
@@ -20,6 +20,13 @@
 dir.vendor = /data/benchmarktest/vendor
 dir.vendor = /data/benchmarktest64/vendor
 
+dir.unrestricted = /data/nativetest/unrestricted
+dir.unrestricted = /data/nativetest64/unrestricted
+
+# TODO(b/123864775): Ensure tests are run from /data/nativetest{,64} or (if
+# necessary) the unrestricted subdirs above. Then clean this up.
+dir.unrestricted = /data/local/tmp
+
 dir.system = /data/nativetest
 dir.system = /data/nativetest64
 dir.system = /data/benchmarktest
@@ -72,6 +79,10 @@
 namespace.default.link.runtime.shared_libs += libnativebridge.so
 namespace.default.link.runtime.shared_libs += libnativehelper.so
 namespace.default.link.runtime.shared_libs += libnativeloader.so
+namespace.default.link.runtime.shared_libs += libandroidicu.so
+
+# TODO(b/122876336): Remove libpac.so once it's migrated to Webview
+namespace.default.link.runtime.shared_libs += libpac.so
 
 # When libnetd_resolv.so can't be found in the default namespace, search for it
 # in the resolv namespace. Don't allow any other libraries from the resolv namespace
@@ -129,6 +140,7 @@
 namespace.conscrypt.link.default.shared_libs  = libc.so
 namespace.conscrypt.link.default.shared_libs += libm.so
 namespace.conscrypt.link.default.shared_libs += libdl.so
+namespace.conscrypt.link.default.shared_libs += liblog.so
 
 ###############################################################################
 # "resolv" APEX namespace
@@ -145,6 +157,7 @@
 namespace.resolv.link.default.shared_libs += libm.so
 namespace.resolv.link.default.shared_libs += libdl.so
 namespace.resolv.link.default.shared_libs += libbinder_ndk.so
+namespace.resolv.link.default.shared_libs += liblog.so
 namespace.resolv.link.default.shared_libs += libvndksupport.so
 
 ###############################################################################
@@ -340,6 +353,8 @@
 namespace.default.link.runtime.shared_libs += libnativebridge.so
 namespace.default.link.runtime.shared_libs += libnativehelper.so
 namespace.default.link.runtime.shared_libs += libnativeloader.so
+# Workaround for b/124772622
+namespace.default.link.runtime.shared_libs += libandroidicu.so
 
 ###############################################################################
 # "runtime" APEX namespace
@@ -357,6 +372,109 @@
 namespace.runtime.link.default.allow_all_shared_libs = true
 
 ###############################################################################
+# Namespace config for native tests that need access to both system and vendor
+# libraries. This replicates the default linker config (done by
+# init_default_namespace_no_config in bionic/linker/linker.cpp), except that it
+# includes the requisite namespace setup for APEXes.
+###############################################################################
+[unrestricted]
+additional.namespaces = runtime,media,conscrypt,resolv
+
+namespace.default.search.paths  = /system/${LIB}
+namespace.default.search.paths += /odm/${LIB}
+namespace.default.search.paths += /vendor/${LIB}
+
+namespace.default.asan.search.paths  = /data/asan/system/${LIB}
+namespace.default.asan.search.paths +=           /system/${LIB}
+namespace.default.asan.search.paths += /data/asan/odm/${LIB}
+namespace.default.asan.search.paths +=           /odm/${LIB}
+namespace.default.asan.search.paths += /data/asan/vendor/${LIB}
+namespace.default.asan.search.paths +=           /vendor/${LIB}
+
+# Keep in sync with ld.config.txt in the com.android.runtime APEX.
+namespace.default.links = runtime,resolv
+namespace.default.visible = true
+
+namespace.default.link.runtime.shared_libs  = libart.so:libartd.so
+namespace.default.link.runtime.shared_libs += libdexfile_external.so
+namespace.default.link.runtime.shared_libs += libnativebridge.so
+namespace.default.link.runtime.shared_libs += libnativehelper.so
+namespace.default.link.runtime.shared_libs += libnativeloader.so
+namespace.default.link.runtime.shared_libs += libandroidicu.so
+
+# TODO(b/122876336): Remove libpac.so once it's migrated to Webview
+namespace.default.link.runtime.shared_libs += libpac.so
+
+namespace.default.link.resolv.shared_libs = libnetd_resolv.so
+
+###############################################################################
+# "runtime" APEX namespace
+#
+# This namespace exposes externally accessible libraries from the Runtime APEX.
+###############################################################################
+namespace.runtime.isolated = true
+namespace.runtime.visible = true
+
+# Keep in sync with ld.config.txt in the com.android.runtime APEX.
+namespace.runtime.search.paths = /apex/com.android.runtime/${LIB}
+namespace.runtime.asan.search.paths = /apex/com.android.runtime/${LIB}
+namespace.runtime.links = default
+# TODO(b/119867084): Restrict to Bionic dlopen dependencies and PALette library
+# when it exists.
+namespace.runtime.link.default.allow_all_shared_libs = true
+
+###############################################################################
+# "media" APEX namespace
+#
+# This namespace is for libraries within the media APEX.
+###############################################################################
+namespace.media.isolated = true
+namespace.media.visible = true
+
+namespace.media.search.paths = /apex/com.android.media/${LIB}
+namespace.media.asan.search.paths = /apex/com.android.media/${LIB}
+
+namespace.media.links = default
+namespace.media.link.default.shared_libs  = %LLNDK_LIBRARIES%
+namespace.media.link.default.shared_libs += libandroid.so
+namespace.media.link.default.shared_libs += libbinder_ndk.so
+namespace.media.link.default.shared_libs += libmediametrics.so
+namespace.media.link.default.shared_libs += %SANITIZER_RUNTIME_LIBRARIES%
+
+###############################################################################
+# "conscrypt" APEX namespace
+#
+# This namespace is for libraries within the conscrypt APEX.
+###############################################################################
+namespace.conscrypt.isolated = true
+namespace.conscrypt.visible = true
+
+# Keep in sync with ld.config.txt in the com.android.runtime APEX.
+namespace.conscrypt.search.paths = /apex/com.android.conscrypt/${LIB}
+namespace.conscrypt.asan.search.paths = /apex/com.android.conscrypt/${LIB}
+namespace.conscrypt.links = runtime,default
+namespace.conscrypt.link.runtime.shared_libs  = libjavacore.so
+namespace.conscrypt.link.default.shared_libs  = libc.so
+namespace.conscrypt.link.default.shared_libs += libm.so
+namespace.conscrypt.link.default.shared_libs += libdl.so
+
+###############################################################################
+# "resolv" APEX namespace
+#
+# This namespace is for libraries within the resolv APEX.
+###############################################################################
+namespace.resolv.isolated = true
+namespace.resolv.visible = true
+
+namespace.resolv.search.paths = /apex/com.android.resolv/${LIB}
+namespace.resolv.asan.search.paths = /apex/com.android.resolv/${LIB}
+namespace.resolv.links = default
+namespace.resolv.link.default.shared_libs  = libc.so
+namespace.resolv.link.default.shared_libs += libm.so
+namespace.resolv.link.default.shared_libs += libdl.so
+namespace.resolv.link.default.shared_libs += libbinder_ndk.so
+
+###############################################################################
 # Namespace config for binaries under /postinstall.
 # Only default namespace is defined and default has no directories
 # other than /system/lib in the search paths. This is because linker calls
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 57032bc..8bd7c4c 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -538,7 +538,7 @@
     mkdir /data/apex/active 0750 root system
     mkdir /data/apex/backup 0700 root system
     mkdir /data/apex/sessions 0700 root system
-    mkdir /data/staging 0750 system system
+    mkdir /data/pkg_staging 0750 system system
 
     # NFC: create data/nfc for nv storage
     mkdir /data/nfc 0770 nfc nfc