Start to add a function to compile staged APEXes

Add a new method to IIsolatedCompilationService which will eventually
run compilation over staged APEXes. Currently it doesn't, but it is
slightly more realistic than the test compile (which I'm leaving in
place since it's useful for testing) - we use the pending instance of
compos (so odsign will attempt to verify the keys) and we run
odrefresh --compile instead of --force-compile. For now the artifacts
are still written to the test-artifacts directory so odsign doesn't
see them.

Bug: 205296305
Test: composd_cmd staged-apex-compile
Change-Id: If4d449878e38a97aa0f1f1dbb71c9df9b86ca4d5
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 6f4476c..5e72cd2 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -20,6 +20,17 @@
 
 interface IIsolatedCompilationService {
     /**
+     * Compile BCP extensions and system server, using any staged APEXes that are present in
+     * preference to active APEXes, writing the results to the pending artifacts directory to be
+     * verified by odsing on next boot.
+     *
+     * Compilation continues in the background, and success/failure is reported via the supplied
+     * callback, unless the returned ICompilationTask is cancelled. The caller should maintain
+     * a reference to the ICompilationTask until compilation completes or is cancelled.
+     */
+    ICompilationTask startStagedApexCompile(ICompilationTaskCallback callback);
+
+    /**
      * Run "odrefresh --dalvik-cache=pending-test --force-compile" in a test instance of CompOS.
      *
      * This compiles BCP extensions and system server, even if the system artifacts are up to date,
diff --git a/compos/composd/src/compilation_task.rs b/compos/composd/src/compilation_task.rs
index c4eed52..18f5aac 100644
--- a/compos/composd/src/compilation_task.rs
+++ b/compos/composd/src/compilation_task.rs
@@ -52,6 +52,23 @@
         self.running_task.lock().unwrap().take()
     }
 
+    pub fn start_staged_apex_compile(
+        comp_os: Arc<CompOsInstance>,
+        callback: &Strong<dyn ICompilationTaskCallback>,
+    ) -> Result<CompilationTask> {
+        // TODO: Write to pending
+        // TODO: Delete any existing artifacts
+        let odrefresh = Odrefresh::spawn_compile("test-artifacts")?;
+        let odrefresh = Arc::new(odrefresh);
+        let task =
+            RunningTask { odrefresh: odrefresh.clone(), comp_os, callback: callback.clone() };
+        let task = CompilationTask { running_task: Arc::new(Mutex::new(Some(task))) };
+
+        task.clone().start_waiting_thread(odrefresh);
+
+        Ok(task)
+    }
+
     pub fn start_test_compile(
         comp_os: Arc<CompOsInstance>,
         callback: &Strong<dyn ICompilationTaskCallback>,
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index 6291d59..767e9f7 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -23,7 +23,7 @@
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
 use compos_aidl_interface::binder::Strong;
 use compos_common::compos_client::VmParameters;
-use compos_common::{CURRENT_INSTANCE_DIR, TEST_INSTANCE_DIR};
+use compos_common::{PENDING_INSTANCE_DIR, TEST_INSTANCE_DIR};
 use std::sync::{Arc, Mutex, Weak};
 use virtualizationservice::IVirtualizationService::IVirtualizationService;
 
@@ -43,9 +43,8 @@
         Ok(instance.get_service())
     }
 
-    #[allow(dead_code)] // TODO: Make use of this
-    pub fn start_current_instance(&self) -> Result<Arc<CompOsInstance>> {
-        self.start_instance(CURRENT_INSTANCE_DIR, VmParameters::default())
+    pub fn start_pending_instance(&self) -> Result<Arc<CompOsInstance>> {
+        self.start_instance(PENDING_INSTANCE_DIR, VmParameters::default())
     }
 
     pub fn start_test_instance(&self) -> Result<Arc<CompOsInstance>> {
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 3e18c3e..8189fe0 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -199,7 +199,7 @@
 
     fn check_files_exist(&self) -> Result<()> {
         if !self.instance_root.is_dir() {
-            bail!("Directory {} not found", self.instance_root.display())
+            bail!("Directory {:?} not found", self.instance_root)
         };
         Self::check_file_exists(&self.instance_image)?;
         Self::check_file_exists(&self.key_blob)?;
@@ -209,7 +209,7 @@
 
     fn check_file_exists(file: &Path) -> Result<()> {
         if !file.is_file() {
-            bail!("File {} not found", file.display())
+            bail!("File {:?} not found", file)
         };
         Ok(())
     }
diff --git a/compos/composd/src/odrefresh.rs b/compos/composd/src/odrefresh.rs
index 16dcb0f..9debf00 100644
--- a/compos/composd/src/odrefresh.rs
+++ b/compos/composd/src/odrefresh.rs
@@ -44,7 +44,15 @@
 }
 
 impl Odrefresh {
+    pub fn spawn_compile(target_dir: &str) -> Result<Self> {
+        Self::spawn_odrefresh(target_dir, "--compile")
+    }
+
     pub fn spawn_forced_compile(target_dir: &str) -> Result<Self> {
+        Self::spawn_odrefresh(target_dir, "--force-compile")
+    }
+
+    fn spawn_odrefresh(target_dir: &str, compile_arg: &str) -> Result<Self> {
         // We don`t need to capture stdout/stderr - odrefresh writes to the log
         let mut cmdline = Command::new(ODREFRESH_BIN);
         if need_extra_time()? {
@@ -61,7 +69,7 @@
         cmdline
             .arg(format!("--use-compilation-os={}", VMADDR_CID_ANY as i32))
             .arg(format!("--dalvik-cache={}", target_dir))
-            .arg("--force-compile");
+            .arg(compile_arg);
         let child = SharedChild::spawn(&mut cmdline).context("Running odrefresh")?;
         Ok(Odrefresh { child })
     }
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index 3738e18..aa96ddf 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -53,21 +53,41 @@
 impl Interface for IsolatedCompilationService {}
 
 impl IIsolatedCompilationService for IsolatedCompilationService {
+    fn startStagedApexCompile(
+        &self,
+        callback: &Strong<dyn ICompilationTaskCallback>,
+    ) -> binder::Result<Strong<dyn ICompilationTask>> {
+        check_permissions()?;
+        to_binder_result(self.do_start_staged_apex_compile(callback))
+    }
+
     fn startTestCompile(
         &self,
         callback: &Strong<dyn ICompilationTaskCallback>,
     ) -> binder::Result<Strong<dyn ICompilationTask>> {
-        check_test_permissions()?;
+        check_permissions()?;
         to_binder_result(self.do_start_test_compile(callback))
     }
 
     fn startTestOdrefresh(&self) -> binder::Result<i8> {
-        check_test_permissions()?;
+        check_permissions()?;
         to_binder_result(self.do_odrefresh_for_test())
     }
 }
 
 impl IsolatedCompilationService {
+    fn do_start_staged_apex_compile(
+        &self,
+        callback: &Strong<dyn ICompilationTaskCallback>,
+    ) -> Result<Strong<dyn ICompilationTask>> {
+        // TODO: Try to start the current instance with staged APEXes to see if it works?
+        let comp_os = self.instance_manager.start_pending_instance().context("Starting CompOS")?;
+
+        let task = CompilationTask::start_staged_apex_compile(comp_os, callback)?;
+
+        Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
+    }
+
     fn do_start_test_compile(
         &self,
         callback: &Strong<dyn ICompilationTaskCallback>,
@@ -114,7 +134,7 @@
     }
 }
 
-fn check_test_permissions() -> binder::Result<()> {
+fn check_permissions() -> binder::Result<()> {
     let calling_uid = ThreadState::get_calling_uid();
     // This should only be called by system server, or root while testing
     if calling_uid != AID_SYSTEM && calling_uid != AID_ROOT {
@@ -135,5 +155,5 @@
         // make the library happy. The value does not appear to matter elsewhere in the library.
         .read(true)
         .open(path)
-        .with_context(|| format!("Failed to open {} directory as path fd", path.display()))
+        .with_context(|| format!("Failed to open {:?} directory as path fd", path))
 }
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index e591794..f22dc13 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -18,12 +18,13 @@
 
 use android_system_composd::{
     aidl::android::system::composd::{
+        ICompilationTask::ICompilationTask,
         ICompilationTaskCallback::{BnCompilationTaskCallback, ICompilationTaskCallback},
         IIsolatedCompilationService::IIsolatedCompilationService,
     },
     binder::{
         wait_for_interface, BinderFeatures, DeathRecipient, IBinder, Interface, ProcessState,
-        Result as BinderResult,
+        Result as BinderResult, Strong,
     },
 };
 use anyhow::{bail, Context, Result};
@@ -37,7 +38,7 @@
             .index(1)
             .takes_value(true)
             .required(true)
-            .possible_values(&["forced-compile-test", "forced-odrefresh"]),
+            .possible_values(&["staged-apex-compile", "forced-compile-test", "forced-odrefresh"]),
     );
     let args = app.get_matches();
     let command = args.value_of("command").unwrap();
@@ -45,6 +46,7 @@
     ProcessState::start_thread_pool();
 
     match command {
+        "staged-apex-compile" => run_staged_apex_compile()?,
         "forced-compile-test" => run_forced_compile_for_test()?,
         "forced-odrefresh" => run_forced_odrefresh_for_test()?,
         _ => panic!("Unexpected command {}", command),
@@ -103,14 +105,28 @@
     }
 }
 
+fn run_staged_apex_compile() -> Result<()> {
+    run_async_compilation(|service, callback| service.startStagedApexCompile(callback))
+}
+
 fn run_forced_compile_for_test() -> Result<()> {
+    run_async_compilation(|service, callback| service.startTestCompile(callback))
+}
+
+fn run_async_compilation<F>(start_compile_fn: F) -> Result<()>
+where
+    F: FnOnce(
+        &dyn IIsolatedCompilationService,
+        &Strong<dyn ICompilationTaskCallback>,
+    ) -> BinderResult<Strong<dyn ICompilationTask>>,
+{
     let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
         .context("Failed to connect to composd service")?;
 
     let state = Arc::new(State::default());
     let callback = Callback(state.clone());
     let callback = BnCompilationTaskCallback::new_binder(callback, BinderFeatures::default());
-    let task = service.startTestCompile(&callback).context("Compilation failed")?;
+    let task = start_compile_fn(&*service, &callback).context("Compilation failed")?;
 
     // Make sure composd keeps going even if we don't hold a reference to its service.
     drop(service);
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
index 2aacc2d..60da96c 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
@@ -137,6 +137,7 @@
             }
 
             try {
+                // TODO(b/205296305) Call startStagedApexCompile instead
                 ICompilationTask composTask = composd.startTestCompile(this);
                 mTask.set(composTask);
                 composTask.asBinder().linkToDeath(this, 0);