Run odrefresh in composd

This is far from finished, but it is at least started. This currently
fails when pvm_exec tries to create a vsock; instead it will need to
request one from composd.

Test: adb shell apex/com.android.compos/bin/composd_cmd
Bug: 186126194
Change-Id: Ic193ddd3835be3daf70b15e78c56c0ccb98e7a1f
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index ada0976..547fd44 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -41,6 +41,7 @@
         "compos_key_cmd",
         "compos_verify_key",
         "composd",
+        "composd_cmd",
         "compsvc",
         "pvm_exec",
     ],
diff --git a/compos/apex/composd.rc b/compos/apex/composd.rc
index 099a346..3e2efb1 100644
--- a/compos/apex/composd.rc
+++ b/compos/apex/composd.rc
@@ -14,7 +14,7 @@
 
 service composd /apex/com.android.compos/bin/composd
     class main
-    user system
+    user root
     group system
     interface aidl android.system.composd
     disabled
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 80cf05a..03cc331 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -47,7 +47,6 @@
 pub struct VmInstance {
     #[allow(dead_code)] // Keeps the vm alive even if we don`t touch it
     vm: Strong<dyn IVirtualMachine>,
-    #[allow(dead_code)] // Likely to be useful
     cid: i32,
 }
 
@@ -132,6 +131,11 @@
 
         FromIBinder::try_from(ibinder).context("Connecting to CompOS service")
     }
+
+    /// Return the CID of the VM.
+    pub fn cid(&self) -> i32 {
+        self.cid
+    }
 }
 
 struct VsockFactory<'a> {
diff --git a/compos/common/lib.rs b/compos/common/lib.rs
index 081f086..6bea62c 100644
--- a/compos/common/lib.rs
+++ b/compos/common/lib.rs
@@ -27,3 +27,20 @@
 
 /// The root of the  data directory available for private use by the CompOS APEX.
 pub const COMPOS_DATA_ROOT: &str = "/data/misc/apexdata/com.android.compos";
+
+/// The sub-directory where we store information relating to the pending instance
+/// of CompOS (based on staged APEXes).
+pub const PENDING_DIR: &str = "pending";
+
+/// The sub-directory where we store information relating to the current instance
+/// of CompOS (based on active APEXes).
+pub const CURRENT_DIR: &str = "current";
+
+/// The file that holds the encrypted private key for a CompOS instance.
+pub const PRIVATE_KEY_BLOB_FILE: &str = "key.blob";
+
+/// The file that holds the public key for a CompOS instance.
+pub const PUBLIC_KEY_FILE: &str = "key.pubkey";
+
+/// The file that holds the instance image for a CompOS instance.
+pub const INSTANCE_IMAGE_FILE: &str = "instance.img";
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 007eda9..5c968b8 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -6,6 +6,7 @@
     name: "composd",
     srcs: ["src/composd_main.rs"],
     edition: "2018",
+    prefer_rlib: true,
     rustlibs: [
         "android.system.composd-rust",
         "compos_aidl_interface-rust",
@@ -13,9 +14,10 @@
         "libanyhow",
         "libbinder_rs",
         "libcompos_common",
+        "libnum_traits",
         "liblog_rust",
     ],
-    prefer_rlib: true,
+    proc_macros: ["libnum_derive"],
     apex_available: [
         "com.android.compos",
     ],
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 0dd5b6f..9240bc6 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -16,6 +16,6 @@
 package android.system.composd;
 
 interface IIsolatedCompilationService {
-    // TODO: Add real methods
-    void doSomething();
+    /// Run "odrefresh --force-compile" in CompOS
+    void runForcedCompile();
 }
diff --git a/compos/composd/src/compos_instance.rs b/compos/composd/src/compos_instance.rs
new file mode 100644
index 0000000..e30a8b3
--- /dev/null
+++ b/compos/composd/src/compos_instance.rs
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Starts and manages instances of the CompOS VM.
+
+use anyhow::{Context, Result};
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use compos_aidl_interface::binder::Strong;
+use compos_common::compos_client::VmInstance;
+use compos_common::{COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE, PRIVATE_KEY_BLOB_FILE};
+use std::fs;
+use std::path::PathBuf;
+
+#[allow(dead_code)]
+pub struct CompOsInstance {
+    instance: VmInstance,
+    service: Strong<dyn ICompOsService>,
+}
+
+impl CompOsInstance {
+    pub fn start_current_instance() -> Result<CompOsInstance> {
+        let instance_image: PathBuf =
+            [COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE].iter().collect();
+
+        let instance = VmInstance::start(&instance_image).context("Starting VM")?;
+        let service = instance.get_service().context("Connecting to CompOS")?;
+
+        let key_blob: PathBuf =
+            [COMPOS_DATA_ROOT, CURRENT_DIR, PRIVATE_KEY_BLOB_FILE].iter().collect();
+        let key_blob = fs::read(key_blob).context("Reading private key")?;
+        service.initializeSigningKey(&key_blob).context("Loading key")?;
+
+        Ok(CompOsInstance { instance, service })
+    }
+
+    pub fn cid(&self) -> i32 {
+        self.instance.cid()
+    }
+}
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index f674448..33da889 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -18,6 +18,8 @@
 //! responsible for managing the lifecycle of the CompOS VM instances, providing key management for
 //! them, and orchestrating trusted compilation.
 
+mod compos_instance;
+mod odrefresh;
 mod service;
 
 use android_system_composd::binder::{register_lazy_service, ProcessState};
@@ -29,6 +31,8 @@
         android_logger::Config::default().with_tag("composd").with_min_level(log::Level::Info),
     );
 
+    ProcessState::start_thread_pool();
+
     let service = service::new_binder();
     register_lazy_service("android.system.composd", service.as_binder())
         .context("Registering service")?;
diff --git a/compos/composd/src/odrefresh.rs b/compos/composd/src/odrefresh.rs
new file mode 100644
index 0000000..c0042f0
--- /dev/null
+++ b/compos/composd/src/odrefresh.rs
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Handle the details of executing odrefresh to generate compiled artifacts.
+
+use anyhow::{bail, Context, Result};
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
+use std::process::Command;
+
+// TODO: What if this changes?
+const EX_MAX: i32 = 78;
+const ODREFRESH_BIN: &str = "/apex/com.android.art/bin/odrefresh";
+
+#[derive(Debug, PartialEq, Eq, FromPrimitive)]
+#[repr(i32)]
+pub enum ExitCode {
+    // Copied from art/odrefresh/include/odrefresh/odrefresh.h
+    Okay = 0i32,
+    CompilationRequired = EX_MAX + 1,
+    CompilationSuccess = EX_MAX + 2,
+    CompilationFailed = EX_MAX + 3,
+    CleanupFailed = EX_MAX + 4,
+}
+
+pub fn run_forced_compile(cid: i32) -> Result<ExitCode> {
+    // We don`t need to capture stdout/stderr - odrefresh writes to the log
+    let mut odrefresh = Command::new(ODREFRESH_BIN)
+        .arg(format!("--use-compilation-os={}", cid))
+        .arg("--force-compile")
+        .spawn()
+        .context("Running odrefresh")?;
+
+    // TODO: timeout?
+    let status = odrefresh.wait()?;
+
+    if let Some(exit_code) = status.code().and_then(FromPrimitive::from_i32) {
+        Ok(exit_code)
+    } else {
+        bail!("odrefresh exited with {}", status)
+    }
+}
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index 8fe28ec..7fc9ab0 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -17,10 +17,15 @@
 //! Implementation of IIsolatedCompilationService, called from system server when compilation is
 //! desired.
 
+use crate::compos_instance::CompOsInstance;
+use crate::odrefresh;
 use android_system_composd::aidl::android::system::composd::IIsolatedCompilationService::{
     BnIsolatedCompilationService, IIsolatedCompilationService,
 };
-use android_system_composd::binder::{self, BinderFeatures, Interface, Strong};
+use android_system_composd::binder::{self, BinderFeatures, Interface, Status, Strong};
+use anyhow::{bail, Context, Result};
+use log::{error, info};
+use std::ffi::CString;
 
 pub struct IsolatedCompilationService {}
 
@@ -29,12 +34,35 @@
     BnIsolatedCompilationService::new_binder(service, BinderFeatures::default())
 }
 
-impl IsolatedCompilationService {}
-
 impl Interface for IsolatedCompilationService {}
 
 impl IIsolatedCompilationService for IsolatedCompilationService {
-    fn doSomething(&self) -> binder::Result<()> {
+    fn runForcedCompile(&self) -> binder::Result<()> {
+        to_binder_result(self.do_run_forced_compile())
+    }
+}
+
+fn to_binder_result<T>(result: Result<T>) -> binder::Result<T> {
+    result.map_err(|e| {
+        error!("Returning binder error: {:#}", e);
+        Status::new_service_specific_error(-1, CString::new(format!("{:#}", e)).ok().as_deref())
+    })
+}
+
+impl IsolatedCompilationService {
+    fn do_run_forced_compile(&self) -> Result<()> {
+        info!("runForcedCompile");
+
+        // TODO: Create instance if need be, handle instance failure, prevent
+        // multiple instances running
+        let comp_os = CompOsInstance::start_current_instance().context("Starting CompOS")?;
+
+        let exit_code = odrefresh::run_forced_compile(comp_os.cid())?;
+
+        if exit_code != odrefresh::ExitCode::CompilationSuccess {
+            bail!("Unexpected odrefresh result: {:?}", exit_code);
+        }
+
         Ok(())
     }
 }
diff --git a/compos/composd_cmd/Android.bp b/compos/composd_cmd/Android.bp
new file mode 100644
index 0000000..5fef86f
--- /dev/null
+++ b/compos/composd_cmd/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+    name: "composd_cmd",
+    srcs: ["composd_cmd.rs"],
+    edition: "2018",
+    rustlibs: [
+        "android.system.composd-rust",
+        "libanyhow",
+        "libbinder_rs",
+    ],
+    prefer_rlib: true,
+    apex_available: [
+        "com.android.compos",
+    ],
+}
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
new file mode 100644
index 0000000..e4884e3
--- /dev/null
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Simple command-line tool to drive composd for testing and debugging.
+
+use android_system_composd::{
+    aidl::android::system::composd::IIsolatedCompilationService::IIsolatedCompilationService,
+    binder::{wait_for_interface, ProcessState},
+};
+use anyhow::{Context, Result};
+
+fn main() -> Result<()> {
+    ProcessState::start_thread_pool();
+
+    let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
+        .context("Failed to connect to composd service")?;
+
+    service.runForcedCompile().context("Compilation failed")?;
+
+    println!("All Ok!");
+
+    Ok(())
+}
diff --git a/compos/verify_key/verify_key.rs b/compos/verify_key/verify_key.rs
index 2e3d206..8439b97 100644
--- a/compos/verify_key/verify_key.rs
+++ b/compos/verify_key/verify_key.rs
@@ -20,17 +20,14 @@
 use anyhow::{bail, Context, Result};
 use compos_aidl_interface::binder::ProcessState;
 use compos_common::compos_client::VmInstance;
-use compos_common::COMPOS_DATA_ROOT;
+use compos_common::{
+    COMPOS_DATA_ROOT, CURRENT_DIR, INSTANCE_IMAGE_FILE, PENDING_DIR, PRIVATE_KEY_BLOB_FILE,
+    PUBLIC_KEY_FILE,
+};
 use std::fs::{self, File};
 use std::io::Read;
 use std::path::{Path, PathBuf};
 
-const CURRENT_DIR: &str = "current";
-const PENDING_DIR: &str = "pending";
-const PRIVATE_KEY_BLOB_FILE: &str = "key.blob";
-const PUBLIC_KEY_FILE: &str = "key.pubkey";
-const INSTANCE_IMAGE_FILE: &str = "instance.img";
-
 const MAX_FILE_SIZE_BYTES: u64 = 8 * 1024;
 
 fn main() -> Result<()> {