Use /bootstrap-apex for bootstrap APEXes

This new directory is bind-mounted to /apex in the bootstrap mount
namespace so that apexd-bootstrap mounts bootstrap APEXes there via
/apex.

The directory is detached from /apex in the default mount namespace but
still visible in case bootstrap APEXes are needed.

However, there are (mostly, virtual) devices which don't need two mount
namespaces. Those devices don't need to make /bootstrap-apex directory
at all.

Bug: 290148078
Test: atest VendorApexHostTestCases
Test: atest MicrodroidTests
Change-Id: I541cec71d9970b14971d46e01e4808b23590dbed
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 2176233..e4f0bd0 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1262,6 +1262,51 @@
 
     return {};
 }
+
+static Result<void> MountApexRootForDefaultNamespace() {
+    auto mount_namespace_id = GetCurrentMountNamespace();
+    if (!mount_namespace_id.ok()) {
+        return mount_namespace_id.error();
+    }
+    // There's nothing to do if it's still in the bootstrap mount namespace.
+    // This happens when we don't need to update APEXes (e.g. Microdroid)
+    // where bootstrap mount namespace == default mount namespace.
+    if (mount_namespace_id.value() == NS_BOOTSTRAP) {
+        return {};
+    }
+
+    // Now, we're in the "default" mount namespace and need a fresh /apex for
+    // the default mount namespace.
+    //
+    // At this point, there are two mounts at the same mount point: /apex
+    // - to tmpfs (private)
+    // - to /bootstrap-apex (shared)
+    //
+    // We need unmount the second mount so that /apex in the default mount
+    // namespace becomes RW/empty and "private" (we don't want mount events to
+    // propagate to the bootstrap mount namespace).
+    //
+    // Likewise, we don't want the unmount event itself to propagate to the
+    // bootstrap mount namespace. Otherwise, /apex in the bootstrap mount
+    // namespace would become empty due to the unmount.
+    //
+    // Hence, before unmounting, we make /apex (the second one) "private" first.
+    // so that the unmouting below doesn't affect to the bootstrap mount namespace.
+    if (mount(nullptr, "/apex", nullptr, MS_PRIVATE | MS_REC, nullptr) == -1) {
+        return ErrnoError() << "Failed to remount /apex as private";
+    }
+
+    // Now we can unmount /apex (bind-mount to /bootstrap-apex). This only affects
+    // in the default mount namespace and /apex is now seen as tmpfs mount.
+    // Note that /apex in the bootstrap mount namespace is still a bind-mount to
+    // /bootstrap-apex and holds the APEX mounts.
+    if (umount2("/apex", MNT_DETACH) == -1) {
+        return ErrnoError() << "Failed to umount /apex";
+    }
+
+    return {};
+}
+
 static Result<void> do_update_linker_config(const BuiltinArguments&) {
     return GenerateLinkerConfiguration();
 }
@@ -1314,6 +1359,11 @@
     if (auto result = SwitchToMountNamespaceIfNeeded(NS_DEFAULT); !result.ok()) {
         return result.error();
     }
+
+    if (auto result = MountApexRootForDefaultNamespace(); !result.ok()) {
+        return result.error();
+    }
+
     if (auto result = MountLinkerConfigForDefaultNamespace(); !result.ok()) {
         return result.error();
     }
diff --git a/init/init.cpp b/init/init.cpp
index da63fdc..4bb8eec 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -832,6 +832,12 @@
     CHECKCALL(mount("tmpfs", "/apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                     "mode=0755,uid=0,gid=0"));
 
+    if (NeedsTwoMountNamespaces()) {
+        // /bootstrap-apex is used to mount "bootstrap" APEXes.
+        CHECKCALL(mount("tmpfs", "/bootstrap-apex", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
+                        "mode=0755,uid=0,gid=0"));
+    }
+
     // /linkerconfig is used to keep generated linker configuration
     CHECKCALL(mount("tmpfs", "/linkerconfig", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                     "mode=0755,uid=0,gid=0"));
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index 5b53d50..e069a5d 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -66,15 +66,6 @@
     return ret;
 }
 
-// In case we have two sets of APEXes (non-updatable, updatable), we need two separate mount
-// namespaces.
-static bool NeedsTwoMountNamespaces() {
-    if (IsRecoveryMode()) return false;
-    // In microdroid, there's only one set of APEXes in built-in directories include block devices.
-    if (IsMicrodroid()) return false;
-    return true;
-}
-
 static android::base::unique_fd bootstrap_ns_fd;
 static android::base::unique_fd default_ns_fd;
 
@@ -83,6 +74,15 @@
 
 }  // namespace
 
+// In case we have two sets of APEXes (non-updatable, updatable), we need two separate mount
+// namespaces.
+bool NeedsTwoMountNamespaces() {
+    if (IsRecoveryMode()) return false;
+    // In microdroid, there's only one set of APEXes in built-in directories include block devices.
+    if (IsMicrodroid()) return false;
+    return true;
+}
+
 bool SetupMountNamespaces() {
     // Set the propagation type of / as shared so that any mounting event (e.g.
     // /data) is by default visible to all processes. When private mounting is
@@ -96,6 +96,27 @@
     // the bootstrap namespace get APEXes from the read-only partition.
     if (!(ChangeMount("/apex", MS_PRIVATE))) return false;
 
+    // However, some components (e.g. servicemanager) need to access bootstrap
+    // APEXes from the default mount namespace. To achieve that, we bind-mount
+    // /apex with /bootstrap-apex (not private) in the bootstrap mount namespace.
+    // Bootstrap APEXes are mounted in /apex and also visible in /bootstrap-apex.
+    // In the default mount namespace, we detach /bootstrap-apex from /apex and
+    // bootstrap APEXes are still be visible in /bootstrap-apex.
+    //
+    // The end result will look like:
+    //   in the bootstrap mount namespace:
+    //     /apex  (== /bootstrap-apex)
+    //       {bootstrap APEXes from the read-only partition}
+    //
+    //   in the default mount namespace:
+    //     /bootstrap-apex
+    //       {bootstrap APEXes from the read-only partition}
+    //     /apex
+    //       {APEXes, can be from /data partition}
+    if (NeedsTwoMountNamespaces()) {
+        if (!(BindMount("/bootstrap-apex", "/apex"))) return false;
+    }
+
     // /linkerconfig is a private mountpoint to give a different linker configuration
     // based on the mount namespace. Subdirectory will be bind-mounted based on current mount
     // namespace
diff --git a/init/mount_namespace.h b/init/mount_namespace.h
index 5e3dab2..43c5476 100644
--- a/init/mount_namespace.h
+++ b/init/mount_namespace.h
@@ -24,9 +24,12 @@
 enum MountNamespace { NS_BOOTSTRAP, NS_DEFAULT };
 
 bool SetupMountNamespaces();
+
 base::Result<void> SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace);
 
 base::Result<MountNamespace> GetCurrentMountNamespace();
 
+bool NeedsTwoMountNamespaces();
+
 }  // namespace init
 }  // namespace android
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 51093d8..8532c44 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -766,7 +766,7 @@
     selinux_android_restorecon("/dev/device-mapper", 0);
 
     selinux_android_restorecon("/apex", 0);
-
+    selinux_android_restorecon("/bootstrap-apex", 0);
     selinux_android_restorecon("/linkerconfig", 0);
 
     // adb remount, snapshot-based updates, and DSUs all create files during
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 3362872..5218753 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -91,7 +91,7 @@
 #
 # create some directories (some are mount points) and symlinks
 LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
-    dev proc sys system data data_mirror odm oem acct config storage mnt apex debug_ramdisk \
+    dev proc sys system data data_mirror odm oem acct config storage mnt apex bootstrap-apex debug_ramdisk \
     linkerconfig second_stage_resources postinstall $(BOARD_ROOT_EXTRA_FOLDERS)); \
     ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
     ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \