Handle adb sync with Bionic under /bionic

Background:
We now have two sets of Bionic: the bootstrap Bionic which is at
/system/{lib|bin}/bootstrap for early processes and the default Bionic
which is from the runtime APEX for all the others. In order to give the
same path for Bionic to both categories of processes, the init prepares
two mount namespaces and bind-mount appropriate Bionic files onto the
common mount points under /bionic. For example,
/system/bin/bootstrap/linker is bind-mounted to /bionic/bin/linker for
the early processes. Likewise, /apex/com.android.runtime/bin/linker is
bind-mounted to the same path for rest of the processes.

In addition, in order not to propagate mount events in one mount
namespace to the other namespace, /bionic itself is created as a mount
namespace (via self bind-mount) and its propagation type is set to
private.

Changes required:
This however requires some adjustments to adb sync and remount
mechanism.

For remounting, /bionic path should also be re-mounted for RW, because
it is a RO mount in the beginning. This remounting is done only for the
system-as-root devices where entire / can be re-mounted as RW.

For synching, the sync thread creates a temporary mount namespace where
there is no bind-mount. This ensures that a path that the thread handles
is pointing to the correct file that is expected from the client side.

In addition, push operation to /bionic path is done without unlinking.
This is required because the mount points under /bionic are gone in the
current mount namespace but are still active in other mount namespaces.
If unlinked, the existing mounts on the path are all silently removed.
In order to prevent the unwanted situation, the moint points are not
unlinked but truncated to 0. This however is not a significant problem
because the files that serve as mount points do not carry any
useful information (i.e. the content is meaningless).

Bug: 879416
Test: adb sync
adb push <random_file> /bionic/bin/linker64
adb push <random_file> /system/bin/bootstrap/bin/linker64
system/core/fs_mgr/tests/adb-remount-test.sh
Change-Id: Id87dc9ee7ec5c43d06b54969b55e2cb394329317
diff --git a/adb/daemon/file_sync_service.cpp b/adb/daemon/file_sync_service.cpp
index 56b5cd8..9e1760d 100644
--- a/adb/daemon/file_sync_service.cpp
+++ b/adb/daemon/file_sync_service.cpp
@@ -25,6 +25,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -209,6 +210,22 @@
     return WriteFdExactly(s, &msg.dent, sizeof(msg.dent));
 }
 
+static bool is_mountpoint(const std::string& path, pid_t tid) {
+    const std::string mountinfo_path = "/proc/" + std::to_string(tid) + "/mountinfo";
+    std::string mountinfo;
+    if (!android::base::ReadFileToString(mountinfo_path, &mountinfo)) {
+        PLOG(ERROR) << "Failed to open " << mountinfo_path;
+        return false;
+    }
+    std::vector<std::string> lines = android::base::Split(mountinfo, "\n");
+    return std::find_if(lines.begin(), lines.end(), [&path](const auto& line) {
+               auto tokens = android::base::Split(line, " ");
+               // line format is ...
+               // mountid parentmountid major:minor sourcepath targetpath option ...
+               return tokens.size() >= 4 && tokens[4] == path;
+           }) != lines.end();
+}
+
 // Make sure that SendFail from adb_io.cpp isn't accidentally used in this file.
 #pragma GCC poison SendFail
 
@@ -415,6 +432,18 @@
     struct stat st;
     bool do_unlink = (lstat(path.c_str(), &st) == -1) || S_ISREG(st.st_mode) ||
                      (S_ISLNK(st.st_mode) && !S_ISLNK(mode));
+
+    // If the path is a file that is a mount point, don't unlink it, but instead
+    // truncate to zero. If unlinked, existing mounts on the path is all
+    // unmounted
+    if (S_ISREG(st.st_mode) && is_mountpoint(path, getpid())) {
+        do_unlink = false;
+        if (truncate(path.c_str(), 0) == -1) {
+            SendSyncFail(s, "truncate to zero failed");
+            return false;
+        }
+    }
+
     if (do_unlink) {
         adb_unlink(path.c_str());
     }
@@ -546,7 +575,64 @@
     return true;
 }
 
+#if defined(__ANDROID__)
+class FileSyncPreparer {
+  public:
+    FileSyncPreparer() : saved_ns_fd_(-1), rooted_(getuid() == 0) {
+        const std::string namespace_path = "/proc/" + std::to_string(gettid()) + "/ns/mnt";
+        const int ns_fd = adb_open(namespace_path.c_str(), O_RDONLY | O_CLOEXEC);
+        if (ns_fd == -1) {
+            if (rooted_) PLOG(ERROR) << "Failed to save mount namespace";
+            return;
+        }
+        saved_ns_fd_.reset(ns_fd);
+
+        // Note: this is for the current thread only
+        if (unshare(CLONE_NEWNS) != 0) {
+            if (rooted_) PLOG(ERROR) << "Failed to clone mount namespace";
+            return;
+        }
+
+        // Set the propagation type of / to private so that unmount below is
+        // not propagated to other mount namespaces.
+        if (mount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr) == -1) {
+            if (rooted_) PLOG(ERROR) << "Could not change propagation type of / to MS_PRIVATE";
+            return;
+        }
+
+        // unmount /bionic which is bind-mount to itself by init. Under /bionic,
+        // there are other bind mounts for the bionic files. By unmounting this,
+        // we unmount them all thus revealing the raw file system that is the
+        // same as the local file system seen by the adb client.
+        if (umount2("/bionic", MNT_DETACH) == -1 && errno != ENOENT) {
+            if (rooted_) PLOG(ERROR) << "Could not unmount /bionic to reveal raw filesystem";
+            return;
+        }
+    }
+
+    ~FileSyncPreparer() {
+        if (saved_ns_fd_.get() != -1) {
+            // In fact, this is not strictly required because this thread for file
+            // sync service will be destroyed after the current transfer is all
+            // done. However, let's restore the ns in case the same thread is
+            // reused by multiple transfers in the future refactoring.
+            if (setns(saved_ns_fd_, CLONE_NEWNS) == -1) {
+                PLOG(ERROR) << "Failed to restore saved mount namespace";
+            }
+        }
+    }
+
+  private:
+    unique_fd saved_ns_fd_;
+    bool rooted_;
+};
+#endif
+
 void file_sync_service(unique_fd fd) {
+#if defined(__ANDROID__)
+    FileSyncPreparer preparer;
+#endif
+
     std::vector<char> buffer(SYNC_DATA_MAX);
 
     while (handle_sync_command(fd.get(), buffer)) {
diff --git a/adb/daemon/remount_service.cpp b/adb/daemon/remount_service.cpp
index b26c691..c36f1b6 100644
--- a/adb/daemon/remount_service.cpp
+++ b/adb/daemon/remount_service.cpp
@@ -230,6 +230,23 @@
     android::base::SetProperty(ANDROID_RB_PROPERTY, reboot_cmd.c_str());
 }
 
+static void try_unmount_bionic(int fd) {
+    static constexpr const char* kBionic = "/bionic";
+    struct statfs buf;
+    if (statfs(kBionic, &buf) == -1) {
+        WriteFdFmt(fd, "statfs of the %s mount failed: %s.\n", kBionic, strerror(errno));
+        return;
+    }
+    if (buf.f_flags & ST_RDONLY) {
+        // /bionic is on a read-only partition; can happen for
+        // non-system-as-root-devices. Don' try to unmount.
+        return;
+    }
+    // Success/Fail of the actual remount will be reported by the function.
+    remount_partition(fd, kBionic);
+    return;
+}
+
 void remount_service(unique_fd fd, const std::string& cmd) {
     bool user_requested_reboot = cmd == "-R";
 
@@ -323,6 +340,8 @@
         return;
     }
 
+    try_unmount_bionic(fd.get());
+
     if (!success) {
         WriteFdExactly(fd.get(), "remount failed\n");
     } else {
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index ede0122..4d9bc61 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -569,6 +569,18 @@
   die "vendor hello"
 check_eq "${A}" "${B}" /vendor before reboot
 
+# download libc.so, append some gargage, push back, and check if the file is updated.
+tempdir="`mktemp -d`"
+cleanup() {
+  rm -rf ${tempdir}
+}
+adb pull /system/lib/bootstrap/libc.so ${tempdir} || die "pull libc.so from device"
+garbage="`hexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/random`"
+echo ${garbage} >> ${tempdir}/libc.so
+adb push ${tempdir}/libc.so /system/lib/bootstrap/libc.so || die "push libc.so to device"
+adb pull /system/lib/bootstrap/libc.so ${tempdir}/libc.so.fromdevice || die "pull libc.so from device"
+diff ${tempdir}/libc.so ${tempdir}/libc.so.fromdevice > /dev/null || die "libc.so differ"
+
 echo "${GREEN}[ RUN      ]${NORMAL} reboot to confirm content persistent" >&2
 
 adb_reboot &&
@@ -607,6 +619,14 @@
 check_eq "${A}" "${B}" vendor after reboot
 echo "${GREEN}[       OK ]${NORMAL} /vendor content remains after reboot" >&2
 
+# check if the updated libc.so is persistent after reboot
+adb_root &&
+  adb pull /system/lib/bootstrap/libc.so ${tempdir}/libc.so.fromdevice ||
+  die "pull libc.so from device"
+diff ${tempdir}/libc.so ${tempdir}/libc.so.fromdevice > /dev/null || die "libc.so differ"
+rm -r ${tempdir}
+echo "${GREEN}[       OK ]${NORMAL} /system/lib/bootstrap/libc.so content remains after reboot" >&2
+
 echo "${GREEN}[ RUN      ]${NORMAL} flash vendor, confirm its content disappears" >&2
 
 H=`adb_sh echo '${HOSTNAME}' </dev/null 2>/dev/null`