Wait for extra_apk mounts before starting payload

This extends my previous fix for the primary APK to the extra
APKs. Each mount gets its own "done" property, and we wait for all of
them to be set before proceeding.

Bug: 252811466
Test: atest MicrodroidTests
Test: composd_cmd test-compile
Change-Id: I0880d2c240ab02384a193f7196222cf905850589
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 3c490f4..6a37b88 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -77,7 +77,6 @@
 
 const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
 const DEBUGGABLE_PROP: &str = "ro.boot.microdroid.debuggable";
-const APK_MOUNT_DONE_PROP: &str = "microdroid_manager.apk.mounted";
 
 // SYNC WITH virtualizationservice/src/crosvm.rs
 const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
@@ -383,15 +382,16 @@
         None
     };
 
+    let mut zipfuse = Zipfuse::default();
+
     // Before reading a file from the APK, start zipfuse
-    run_zipfuse(
+    zipfuse.mount(
         MountForExec::Allowed,
         "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
         Path::new("/dev/block/mapper/microdroid-apk"),
         Path::new(VM_APK_CONTENTS_PATH),
-        Some(APK_MOUNT_DONE_PROP),
-    )
-    .context("Failed to run zipfuse")?;
+        "microdroid_manager.apk.mounted".to_owned(),
+    )?;
 
     // Restricted APIs are only allowed to be used by platform or test components. Infer this from
     // the use of a VM config file since those can only be used by platform and test components.
@@ -414,7 +414,7 @@
             verified_data.extra_apks_data.len()
         ));
     }
-    mount_extra_apks(&config)?;
+    mount_extra_apks(&config, &mut zipfuse)?;
 
     // Wait until apex config is done. (e.g. linker configuration for apexes)
     wait_for_apex_config_done()?;
@@ -428,8 +428,8 @@
         control_service("stop", "tombstoned")?;
     }
 
-    // Wait until zipfuse has mounted the APK so we can access the payload
-    wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
+    // Wait until zipfuse has mounted the APKs so we can access the payload
+    zipfuse.wait_until_done()?;
 
     register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
 
@@ -480,21 +480,40 @@
     Disallowed,
 }
 
-fn run_zipfuse(
-    noexec: MountForExec,
-    option: &str,
-    zip_path: &Path,
-    mount_dir: &Path,
-    ready_prop: Option<&str>,
-) -> Result<Child> {
-    let mut cmd = Command::new(ZIPFUSE_BIN);
-    if let MountForExec::Disallowed = noexec {
-        cmd.arg("--noexec");
+#[derive(Default)]
+struct Zipfuse {
+    ready_properties: Vec<String>,
+}
+
+impl Zipfuse {
+    fn mount(
+        &mut self,
+        noexec: MountForExec,
+        option: &str,
+        zip_path: &Path,
+        mount_dir: &Path,
+        ready_prop: String,
+    ) -> Result<Child> {
+        let mut cmd = Command::new(ZIPFUSE_BIN);
+        if let MountForExec::Disallowed = noexec {
+            cmd.arg("--noexec");
+        }
+        cmd.args(["-p", &ready_prop, "-o", option]);
+        cmd.arg(zip_path).arg(mount_dir);
+        self.ready_properties.push(ready_prop);
+        cmd.spawn().with_context(|| format!("Failed to run zipfuse for {mount_dir:?}"))
     }
-    if let Some(property_name) = ready_prop {
-        cmd.args(["-p", property_name]);
+
+    fn wait_until_done(self) -> Result<()> {
+        // We check the last-started check first in the hope that by the time it is done
+        // all or most of the others will also be done, minimising the number of times we
+        // block on a property.
+        for property in self.ready_properties.into_iter().rev() {
+            wait_for_property_true(&property)
+                .with_context(|| format!("Failed waiting for {property}"))?;
+        }
+        Ok(())
     }
-    cmd.arg("-o").arg(option).arg(zip_path).arg(mount_dir).spawn().context("Spawn zipfuse")
 }
 
 fn write_apex_payload_data(
@@ -664,21 +683,20 @@
     })
 }
 
-fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
+fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
     // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
     for i in 0..config.extra_apks.len() {
-        let mount_dir = format!("/mnt/extra-apk/{}", i);
+        let mount_dir = format!("/mnt/extra-apk/{i}");
         create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
 
         // don't wait, just detach
-        run_zipfuse(
+        zipfuse.mount(
             MountForExec::Disallowed,
             "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
-            Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
+            Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
             Path::new(&mount_dir),
-            None,
-        )
-        .context("Failed to zipfuse extra apks")?;
+            format!("microdroid_manager.extra_apk.mounted.{i}"),
+        )?;
     }
 
     Ok(())