Merge "compos: mount BuildManifestSystemExt.apk if exists"
diff --git a/compos/apk/assets/vm_config_system_ext.json b/compos/apk/assets/vm_config_system_ext.json
new file mode 100644
index 0000000..e60dee7
--- /dev/null
+++ b/compos/apk/assets/vm_config_system_ext.json
@@ -0,0 +1,33 @@
+{
+  "version": 1,
+  "os": {
+    "name": "microdroid"
+  },
+  "task": {
+    "type": "executable",
+    "command": "/apex/com.android.compos/bin/compsvc"
+  },
+  "extra_apks": [
+    {
+      "path": "/system/etc/security/fsverity/BuildManifest.apk"
+    },
+    {
+      "path": "/system_ext/etc/security/fsverity/BuildManifestSystemExt.apk"
+    }
+  ],
+  "apexes": [
+    {
+      "name": "com.android.art"
+    },
+    {
+      "name": "com.android.compos"
+    },
+    {
+      "name": "com.android.sdkext"
+    },
+    {
+      "name": "{CLASSPATH}"
+    }
+  ],
+  "export_tombstones": true
+}
diff --git a/compos/apk/assets/vm_config_system_ext_staged.json b/compos/apk/assets/vm_config_system_ext_staged.json
new file mode 100644
index 0000000..99a4160
--- /dev/null
+++ b/compos/apk/assets/vm_config_system_ext_staged.json
@@ -0,0 +1,34 @@
+{
+  "version": 1,
+  "os": {
+    "name": "microdroid"
+  },
+  "task": {
+    "type": "executable",
+    "command": "/apex/com.android.compos/bin/compsvc"
+  },
+  "prefer_staged": true,
+  "extra_apks": [
+    {
+      "path": "/system/etc/security/fsverity/BuildManifest.apk"
+    },
+    {
+      "path": "/system_ext/etc/security/fsverity/BuildManifestSystemExt.apk"
+    }
+  ],
+  "apexes": [
+    {
+      "name": "com.android.art"
+    },
+    {
+      "name": "com.android.compos"
+    },
+    {
+      "name": "com.android.sdkext"
+    },
+    {
+      "name": "{CLASSPATH}"
+    }
+  ],
+  "export_tombstones": true
+}
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 5ea5c06..49396d7 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -17,7 +17,10 @@
 //! Support for starting CompOS in a VM and connecting to the service
 
 use crate::timeouts::TIMEOUTS;
-use crate::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT, DEFAULT_VM_CONFIG_PATH};
+use crate::{
+    get_vm_config_path, BUILD_MANIFEST_APK_PATH, BUILD_MANIFEST_SYSTEM_EXT_APK_PATH,
+    COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT,
+};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
     IVirtualizationService::IVirtualizationService,
     VirtualMachineAppConfig::{DebugLevel::DebugLevel, VirtualMachineAppConfig},
@@ -47,10 +50,10 @@
     pub cpus: Option<NonZeroU32>,
     /// List of task profiles to apply to the VM
     pub task_profiles: Vec<String>,
-    /// If present, overrides the path to the VM config JSON file
-    pub config_path: Option<String>,
     /// If present, overrides the amount of RAM to give the VM
     pub memory_mib: Option<i32>,
+    /// Whether the VM prefers staged APEXes or activated ones (false; default)
+    pub prefer_staged: bool,
 }
 
 impl ComposClient {
@@ -60,6 +63,7 @@
         instance_image: File,
         idsig: &Path,
         idsig_manifest_apk: &Path,
+        idsig_manifest_ext_apk: &Path,
         parameters: &VmParameters,
     ) -> Result<Self> {
         let protected_vm = want_protected_vm()?;
@@ -74,11 +78,27 @@
         let apk_fd = ParcelFileDescriptor::new(apk_fd);
         let idsig_fd = prepare_idsig(service, &apk_fd, idsig)?;
 
-        let manifest_apk_fd = File::open("/system/etc/security/fsverity/BuildManifest.apk")
+        let manifest_apk_fd = File::open(BUILD_MANIFEST_APK_PATH)
             .context("Failed to open build manifest APK file")?;
         let manifest_apk_fd = ParcelFileDescriptor::new(manifest_apk_fd);
         let idsig_manifest_apk_fd = prepare_idsig(service, &manifest_apk_fd, idsig_manifest_apk)?;
 
+        // Prepare a few things based on whether /system_ext exists, including:
+        // 1. generate the additional idsig FD for the APK from /system_ext, then pass to VS
+        // 2. select the correct VM config json
+        let (extra_idsigs, has_system_ext) =
+            if let Ok(manifest_ext_apk_fd) = File::open(BUILD_MANIFEST_SYSTEM_EXT_APK_PATH) {
+                // Optional idsig in /system_ext is found, so prepare additionally.
+                let manifest_ext_apk_fd = ParcelFileDescriptor::new(manifest_ext_apk_fd);
+                let idsig_manifest_ext_apk_fd =
+                    prepare_idsig(service, &manifest_ext_apk_fd, idsig_manifest_ext_apk)?;
+
+                (vec![idsig_manifest_apk_fd, idsig_manifest_ext_apk_fd], true)
+            } else {
+                (vec![idsig_manifest_apk_fd], false)
+            };
+        let config_path = get_vm_config_path(has_system_ext, parameters.prefer_staged);
+
         let debug_level = match (protected_vm, parameters.debug_mode) {
             (_, true) => DebugLevel::FULL,
             (false, false) => DebugLevel::APP_ONLY,
@@ -97,15 +117,14 @@
             (Some(console_fd), Some(log_fd))
         };
 
-        let config_path = parameters.config_path.as_deref().unwrap_or(DEFAULT_VM_CONFIG_PATH);
         let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
             name: String::from("Compos"),
             apk: Some(apk_fd),
             idsig: Some(idsig_fd),
             instanceImage: Some(instance_fd),
-            configPath: config_path.to_owned(),
+            configPath: config_path,
             debugLevel: debug_level,
-            extraIdsigs: vec![idsig_manifest_apk_fd],
+            extraIdsigs: extra_idsigs,
             protectedVm: protected_vm,
             memoryMib: parameters.memory_mib.unwrap_or(0), // 0 means use the default
             numCpus: parameters.cpus.map_or(1, NonZeroU32::get) as i32,
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index a5b1ea8..c9555d5 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -48,16 +48,31 @@
 /// The file that holds the idsig for the CompOS Payload APK.
 pub const IDSIG_FILE: &str = "idsig";
 
-/// The file that holds the idsig for the build manifest APK (that makes enumerated files from
-/// /system available in CompOS).
+/// The file that holds the idsig for the build manifest APK that makes enumerated files from
+/// /system available in CompOS.
 pub const IDSIG_MANIFEST_APK_FILE: &str = "idsig_manifest_apk";
 
-/// The path within our config APK of our default VM configuration file, used at boot time.
-pub const DEFAULT_VM_CONFIG_PATH: &str = "assets/vm_config.json";
-
-/// The path within our config APK of the VM configuration file we use when compiling staged
-/// APEXes before reboot.
-pub const PREFER_STAGED_VM_CONFIG_PATH: &str = "assets/vm_config_staged.json";
+/// The file that holds the idsig for the build manifest APK that makes enumerated files from
+/// /system_ext available in CompOS.
+pub const IDSIG_MANIFEST_EXT_APK_FILE: &str = "idsig_manifest_ext_apk";
 
 /// Number of CPUs to run dex2oat (actually the entire compos VM) with
 pub const DEX2OAT_THREADS_PROP_NAME: &str = "dalvik.vm.boot-dex2oat-threads";
+
+/// The Android path of fs-verity build manifest APK for /system.
+pub const BUILD_MANIFEST_APK_PATH: &str = "/system/etc/security/fsverity/BuildManifest.apk";
+
+/// The Android path of fs-verity build manifest APK for /system_ext.
+pub const BUILD_MANIFEST_SYSTEM_EXT_APK_PATH: &str =
+    "/system_ext/etc/security/fsverity/BuildManifestSystemExt.apk";
+
+/// Returns the path of proper VM config for the current device.
+pub fn get_vm_config_path(has_system_ext: bool, prefer_staged: bool) -> String {
+    match (has_system_ext, prefer_staged) {
+        (false, false) => "assets/vm_config.json",
+        (false, true) => "assets/vm_config_staged.json",
+        (true, false) => "assets/vm_config_system_ext.json",
+        (true, true) => "assets/vm_config_system_ext_staged.json",
+    }
+    .to_owned()
+}
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index 451222e..c3d6592 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -22,10 +22,7 @@
 use anyhow::{bail, Result};
 use binder::Strong;
 use compos_common::compos_client::VmParameters;
-use compos_common::{
-    CURRENT_INSTANCE_DIR, DEX2OAT_THREADS_PROP_NAME, PREFER_STAGED_VM_CONFIG_PATH,
-    TEST_INSTANCE_DIR,
-};
+use compos_common::{CURRENT_INSTANCE_DIR, DEX2OAT_THREADS_PROP_NAME, TEST_INSTANCE_DIR};
 use rustutils::system_properties;
 use std::num::NonZeroU32;
 use std::str::FromStr;
@@ -47,16 +44,14 @@
 
     pub fn start_current_instance(&self) -> Result<CompOsInstance> {
         let mut vm_parameters = new_vm_parameters()?;
-        vm_parameters.config_path = Some(PREFER_STAGED_VM_CONFIG_PATH.to_owned());
+        vm_parameters.prefer_staged = true;
         self.start_instance(CURRENT_INSTANCE_DIR, vm_parameters)
     }
 
     pub fn start_test_instance(&self, prefer_staged: bool) -> Result<CompOsInstance> {
         let mut vm_parameters = new_vm_parameters()?;
         vm_parameters.debug_mode = true;
-        if prefer_staged {
-            vm_parameters.config_path = Some(PREFER_STAGED_VM_CONFIG_PATH.to_owned());
-        }
+        vm_parameters.prefer_staged = prefer_staged;
         self.start_instance(TEST_INSTANCE_DIR, vm_parameters)
     }
 
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index aaa4695..fc4c58b 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -24,7 +24,10 @@
 use binder::{LazyServiceGuard, ParcelFileDescriptor, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
 use compos_common::compos_client::{ComposClient, VmParameters};
-use compos_common::{COMPOS_DATA_ROOT, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE, INSTANCE_IMAGE_FILE};
+use compos_common::{
+    COMPOS_DATA_ROOT, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE, IDSIG_MANIFEST_EXT_APK_FILE,
+    INSTANCE_IMAGE_FILE,
+};
 use log::info;
 use std::fs;
 use std::path::{Path, PathBuf};
@@ -66,6 +69,7 @@
     instance_image: PathBuf,
     idsig: PathBuf,
     idsig_manifest_apk: PathBuf,
+    idsig_manifest_ext_apk: PathBuf,
     vm_parameters: VmParameters,
 }
 
@@ -76,12 +80,14 @@
         let instance_image = instance_root_path.join(INSTANCE_IMAGE_FILE);
         let idsig = instance_root_path.join(IDSIG_FILE);
         let idsig_manifest_apk = instance_root_path.join(IDSIG_MANIFEST_APK_FILE);
+        let idsig_manifest_ext_apk = instance_root_path.join(IDSIG_MANIFEST_EXT_APK_FILE);
         Self {
             instance_name: instance_name.to_owned(),
             instance_root,
             instance_image,
             idsig,
             idsig_manifest_apk,
+            idsig_manifest_ext_apk,
             vm_parameters,
         }
     }
@@ -102,6 +108,7 @@
         // Delete existing idsig files. Ignore error in case idsig doesn't exist.
         let _ = fs::remove_file(&self.idsig);
         let _ = fs::remove_file(&self.idsig_manifest_apk);
+        let _ = fs::remove_file(&self.idsig_manifest_ext_apk);
 
         let instance = self.start_vm(virtualization_service)?;
 
@@ -126,6 +133,7 @@
             instance_image,
             &self.idsig,
             &self.idsig_manifest_apk,
+            &self.idsig_manifest_ext_apk,
             &self.vm_parameters,
         )
         .context("Starting VM")?;
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index 5c926b1..a07a7f9 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -161,9 +161,18 @@
     let output_dir_raw_fd = output_dir_fd.as_raw_fd();
     let staging_dir_raw_fd = staging_dir_fd.as_raw_fd();
 
+    // Get the /system_ext FD differently because it may not exist.
+    // TODO(245761690): pass system_ext_dir_raw_fd to service.odrefresh(...)
+    let (_system_ext_dir_raw_fd, ro_dir_fds) =
+        if let Ok(system_ext_dir_fd) = open_dir(Path::new("/system_ext")) {
+            (system_ext_dir_fd.as_raw_fd(), vec![system_dir_fd, system_ext_dir_fd])
+        } else {
+            (-1, vec![system_dir_fd])
+        };
+
     // Spawn a fd_server to serve the FDs.
     let fd_server_config = FdServerConfig {
-        ro_dir_fds: vec![system_dir_fd],
+        ro_dir_fds,
         rw_dir_fds: vec![staging_dir_fd, output_dir_fd],
         ..Default::default()
     };
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 3abdc74..5b7a8ad 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -28,7 +28,7 @@
 };
 use compos_common::{
     COMPOS_DATA_ROOT, CURRENT_INSTANCE_DIR, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE,
-    INSTANCE_IMAGE_FILE, TEST_INSTANCE_DIR,
+    IDSIG_MANIFEST_EXT_APK_FILE, INSTANCE_IMAGE_FILE, TEST_INSTANCE_DIR,
 };
 use log::error;
 use std::fs::File;
@@ -93,6 +93,7 @@
     let instance_image = instance_dir.join(INSTANCE_IMAGE_FILE);
     let idsig = instance_dir.join(IDSIG_FILE);
     let idsig_manifest_apk = instance_dir.join(IDSIG_MANIFEST_APK_FILE);
+    let idsig_manifest_ext_apk = instance_dir.join(IDSIG_MANIFEST_EXT_APK_FILE);
 
     let instance_image = File::open(instance_image).context("Failed to open instance image")?;
 
@@ -111,6 +112,7 @@
         instance_image,
         &idsig,
         &idsig_manifest_apk,
+        &idsig_manifest_ext_apk,
         &VmParameters { debug_mode: args.debug, ..Default::default() },
     )?;