Async interface for running odrefresh

This creates a variant of startTestOdrefresh that uses the
ICompilationTask async mechanism of startTestCompile.

OdrefreshTask implements ICompilationTask; instead of owning a spawned
odrefresh process it owns the VM and runs the blocking call to compsvc
inside a thread.

Add a new command to composd_cmd to exercise this.

Test: composd_cmd async-odrefresh
Bug: 209572296
Change-Id: I3780d40876dbca17d4e2a3d8f7c2b809f5561aee
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
new file mode 100644
index 0000000..f017f67
--- /dev/null
+++ b/compos/composd/src/odrefresh_task.rs
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::fd_server_helper::FdServerConfig;
+use crate::instance_starter::CompOsInstance;
+use crate::odrefresh;
+use crate::service::open_dir;
+use android_system_composd::aidl::android::system::composd::{
+    ICompilationTask::ICompilationTask, ICompilationTaskCallback::ICompilationTaskCallback,
+};
+use android_system_composd::binder::{Interface, Result as BinderResult, Strong};
+use anyhow::{bail, Result};
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use log::{error, warn};
+use num_traits::FromPrimitive;
+use rustutils::system_properties;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::sync::{Arc, Mutex};
+use std::thread;
+
+#[derive(Clone)]
+pub struct OdrefreshTask {
+    running_task: Arc<Mutex<Option<RunningTask>>>,
+}
+
+impl Interface for OdrefreshTask {}
+
+impl ICompilationTask for OdrefreshTask {
+    fn cancel(&self) -> BinderResult<()> {
+        let task = self.take();
+        // Drop the VM, which should end compilation - and cause our thread to exit
+        drop(task);
+        Ok(())
+    }
+}
+
+impl OdrefreshTask {
+    /// Return the current running task, if any, removing it from this CompilationTask.
+    /// Once removed, meaning the task has ended or been canceled, further calls will always return
+    /// None.
+    fn take(&self) -> Option<RunningTask> {
+        self.running_task.lock().unwrap().take()
+    }
+
+    pub fn start(
+        comp_os: Arc<CompOsInstance>,
+        output_dir_path: PathBuf,
+        callback: &Strong<dyn ICompilationTaskCallback>,
+    ) -> Result<OdrefreshTask> {
+        let service = comp_os.get_service();
+        let task = RunningTask { comp_os, callback: callback.clone() };
+        let task = OdrefreshTask { running_task: Arc::new(Mutex::new(Some(task))) };
+
+        task.clone().start_thread(service, output_dir_path);
+
+        Ok(task)
+    }
+
+    fn start_thread(self, service: Strong<dyn ICompOsService>, output_dir_path: PathBuf) {
+        thread::spawn(move || {
+            let exit_code = try_odrefresh(service, &output_dir_path);
+
+            let task = self.take();
+            // We don't do the callback if cancel has already happened.
+            if let Some(task) = task {
+                let result = match exit_code {
+                    Ok(odrefresh::ExitCode::CompilationSuccess) => task.callback.onSuccess(),
+                    Ok(exit_code) => {
+                        error!("Unexpected odrefresh result: {:?}", exit_code);
+                        task.callback.onFailure()
+                    }
+                    Err(e) => {
+                        error!("Running odrefresh failed: {:?}", e);
+                        task.callback.onFailure()
+                    }
+                };
+                if let Err(e) = result {
+                    warn!("Failed to deliver callback: {:?}", e);
+                }
+            }
+        });
+    }
+}
+
+fn try_odrefresh(
+    service: Strong<dyn ICompOsService>,
+    output_dir_path: &Path,
+) -> Result<odrefresh::ExitCode> {
+    let output_dir = open_dir(output_dir_path)?;
+    let system_dir = open_dir(Path::new("/system"))?;
+
+    // Spawn a fd_server to serve the FDs.
+    let fd_server_config = FdServerConfig {
+        ro_dir_fds: vec![system_dir.as_raw_fd()],
+        rw_dir_fds: vec![output_dir.as_raw_fd()],
+        ..Default::default()
+    };
+    let fd_server_raii = fd_server_config.into_fd_server()?;
+
+    let zygote_arch = system_properties::read("ro.zygote")?;
+    let exit_code =
+        service.odrefresh(system_dir.as_raw_fd(), output_dir.as_raw_fd(), &zygote_arch)?.exitCode;
+
+    drop(fd_server_raii);
+    if let Some(exit_code) = FromPrimitive::from_i8(exit_code) {
+        Ok(exit_code)
+    } else {
+        bail!("odrefresh exited with {}", exit_code)
+    }
+}
+
+struct RunningTask {
+    callback: Strong<dyn ICompilationTaskCallback>,
+    #[allow(dead_code)] // Keeps the CompOS VM alive
+    comp_os: Arc<CompOsInstance>,
+}