Factor out Rust client library for VirtualizationService.

This reduces code duplication, and will also be useful for Rust tests.

Test: ComposHostTestCases compos_key_tests
Change-Id: I13c41d3b2bbe506495b723e7739f3181cb033f0f
diff --git a/compos/common/Android.bp b/compos/common/Android.bp
index 39e7c0a..0377474 100644
--- a/compos/common/Android.bp
+++ b/compos/common/Android.bp
@@ -17,6 +17,7 @@
         "liblog_rust",
         "libnum_traits",
         "librustutils",
+        "libvmclient",
     ],
     proc_macros: ["libnum_derive"],
     shared_libs: [
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 839280c..15f74cd 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -27,8 +27,7 @@
     VirtualMachineConfig::VirtualMachineConfig,
 };
 use android_system_virtualizationservice::binder::{
-    wait_for_interface, BinderFeatures, DeathRecipient, IBinder, Interface, ParcelFileDescriptor,
-    Result as BinderResult, Strong,
+    BinderFeatures, Interface, ParcelFileDescriptor, Result as BinderResult, Strong,
 };
 use anyhow::{anyhow, bail, Context, Result};
 use binder::{
@@ -44,15 +43,11 @@
 use std::os::raw;
 use std::os::unix::io::IntoRawFd;
 use std::path::{Path, PathBuf};
-use std::sync::{Arc, Condvar, Mutex};
 use std::thread;
+use vmclient::VmInstance;
 
 /// This owns an instance of the CompOS VM.
-pub struct VmInstance {
-    #[allow(dead_code)] // Keeps the VM alive even if we don`t touch it
-    vm: Strong<dyn IVirtualMachine>,
-    cid: i32,
-}
+pub struct ComposClient(VmInstance);
 
 /// Parameters to be used when creating a virtual machine instance.
 #[derive(Default, Debug, Clone)]
@@ -74,14 +69,7 @@
     pub never_log: bool,
 }
 
-impl VmInstance {
-    /// Return a new connection to the Virtualization Service binder interface. This will start the
-    /// service if necessary.
-    pub fn connect_to_virtualization_service() -> Result<Strong<dyn IVirtualizationService>> {
-        wait_for_interface::<dyn IVirtualizationService>("android.system.virtualizationservice")
-            .context("Failed to find VirtualizationService")
-    }
-
+impl ComposClient {
     /// Start a new CompOS VM instance using the specified instance image file and parameters.
     pub fn start(
         service: &dyn IVirtualizationService,
@@ -89,7 +77,7 @@
         idsig: &Path,
         idsig_manifest_apk: &Path,
         parameters: &VmParameters,
-    ) -> Result<VmInstance> {
+    ) -> Result<Self> {
         let protected_vm = want_protected_vm()?;
 
         let instance_fd = ParcelFileDescriptor::new(instance_image);
@@ -121,8 +109,6 @@
                 .context("Failed to create console log file")?;
             let log_fd = File::create(data_dir.join("vm.log"))
                 .context("Failed to create system log file")?;
-            let console_fd = ParcelFileDescriptor::new(console_fd);
-            let log_fd = ParcelFileDescriptor::new(log_fd);
             info!("Running in debug level {:?}", debug_level);
             (Some(console_fd), Some(log_fd))
         };
@@ -142,31 +128,18 @@
             taskProfiles: parameters.task_profiles.clone(),
         });
 
-        let vm = service
-            .createVm(&config, console_fd.as_ref(), log_fd.as_ref())
+        let instance = VmInstance::create(service, &config, console_fd, log_fd)
             .context("Failed to create VM")?;
-        let vm_state = Arc::new(VmStateMonitor::default());
 
-        let vm_state_clone = Arc::clone(&vm_state);
-        let mut death_recipient = DeathRecipient::new(move || {
-            vm_state_clone.set_died();
-            log::error!("VirtualizationService died");
-        });
-        // Note that dropping death_recipient cancels this, so we can't use a temporary here.
-        vm.as_binder().link_to_death(&mut death_recipient)?;
+        let callback =
+            BnVirtualMachineCallback::new_binder(VmCallback(), BinderFeatures::default());
+        instance.vm.registerCallback(&callback)?;
 
-        let vm_state_clone = Arc::clone(&vm_state);
-        let callback = BnVirtualMachineCallback::new_binder(
-            VmCallback(vm_state_clone),
-            BinderFeatures::default(),
-        );
-        vm.registerCallback(&callback)?;
+        instance.start()?;
 
-        vm.start()?;
+        instance.wait_until_ready(timeouts()?.vm_max_time_to_ready)?;
 
-        let cid = vm_state.wait_until_ready()?;
-
-        Ok(VmInstance { vm, cid })
+        Ok(Self(instance))
     }
 
     fn locate_config_apk(apex_dir: &Path) -> Result<PathBuf> {
@@ -186,7 +159,7 @@
 
     /// Create and return an RPC Binder connection to the Comp OS service in the VM.
     pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
-        let mut vsock_factory = VsockFactory::new(&*self.vm);
+        let mut vsock_factory = VsockFactory::new(&*self.0.vm);
 
         let ibinder = vsock_factory
             .connect_rpc_client()
@@ -194,12 +167,6 @@
 
         FromIBinder::try_from(ibinder).context("Connecting to CompOS service")
     }
-
-    /// Return the CID of the VM.
-    pub fn cid(&self) -> i32 {
-        // TODO: Do we actually need/use this?
-        self.cid
-    }
 }
 
 fn prepare_idsig(
@@ -295,67 +262,12 @@
     }
 }
 
-#[derive(Debug, Default)]
-struct VmState {
-    has_died: bool,
-    cid: Option<i32>,
-}
-
-#[derive(Debug)]
-struct VmStateMonitor {
-    mutex: Mutex<VmState>,
-    state_ready: Condvar,
-}
-
-impl Default for VmStateMonitor {
-    fn default() -> Self {
-        Self { mutex: Mutex::new(Default::default()), state_ready: Condvar::new() }
-    }
-}
-
-impl VmStateMonitor {
-    fn set_died(&self) {
-        let mut state = self.mutex.lock().unwrap();
-        state.has_died = true;
-        state.cid = None;
-        drop(state); // Unlock the mutex prior to notifying
-        self.state_ready.notify_all();
-    }
-
-    fn set_ready(&self, cid: i32) {
-        let mut state = self.mutex.lock().unwrap();
-        if state.has_died {
-            return;
-        }
-        state.cid = Some(cid);
-        drop(state); // Unlock the mutex prior to notifying
-        self.state_ready.notify_all();
-    }
-
-    fn wait_until_ready(&self) -> Result<i32> {
-        let (state, result) = self
-            .state_ready
-            .wait_timeout_while(
-                self.mutex.lock().unwrap(),
-                timeouts()?.vm_max_time_to_ready,
-                |state| state.cid.is_none() && !state.has_died,
-            )
-            .unwrap();
-        if result.timed_out() {
-            bail!("Timed out waiting for VM")
-        }
-        state.cid.ok_or_else(|| anyhow!("VM died"))
-    }
-}
-
-#[derive(Debug)]
-struct VmCallback(Arc<VmStateMonitor>);
+struct VmCallback();
 
 impl Interface for VmCallback {}
 
 impl IVirtualMachineCallback for VmCallback {
     fn onDied(&self, cid: i32, reason: DeathReason) -> BinderResult<()> {
-        self.0.set_died();
         log::warn!("VM died, cid = {}, reason = {:?}", cid, reason);
         Ok(())
     }
@@ -375,21 +287,16 @@
     }
 
     fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
-        self.0.set_ready(cid);
         log::info!("VM payload ready, cid = {}", cid);
         Ok(())
     }
 
     fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
-        // This should probably never happen in our case, but if it does we means our VM is no
-        // longer running
-        self.0.set_died();
         log::warn!("VM payload finished, cid = {}, exit code = {}", cid, exit_code);
         Ok(())
     }
 
     fn onError(&self, cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
-        self.0.set_died();
         log::warn!("VM error, cid = {}, error code = {}, message = {}", cid, error_code, message,);
         Ok(())
     }
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 55a3107..3a6119f 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -24,6 +24,7 @@
         "liblog_rust",
         "librustutils",
         "libshared_child",
+        "libvmclient",
     ],
     apex_available: [
         "com.android.compos",
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index d1b711d..ebcd689 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -27,7 +27,6 @@
 use crate::instance_manager::InstanceManager;
 use android_system_composd::binder::{register_lazy_service, ProcessState};
 use anyhow::{Context, Result};
-use compos_common::compos_client::VmInstance;
 use log::{error, info};
 use std::panic;
 use std::sync::Arc;
@@ -46,7 +45,8 @@
 
     ProcessState::start_thread_pool();
 
-    let virtualization_service = VmInstance::connect_to_virtualization_service()?;
+    let virtualization_service =
+        vmclient::connect().context("Failed to find VirtualizationService")?;
     let instance_manager = Arc::new(InstanceManager::new(virtualization_service));
     let composd_service = service::new_binder(instance_manager);
     register_lazy_service("android.system.composd", composd_service.as_binder())
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index f899497..340e8b7 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -24,7 +24,7 @@
 use binder_common::lazy_service::LazyServiceGuard;
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
 use compos_aidl_interface::binder::{ParcelFileDescriptor, Strong};
-use compos_common::compos_client::{VmInstance, VmParameters};
+use compos_common::compos_client::{ComposClient, VmParameters};
 use compos_common::{COMPOS_DATA_ROOT, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE, INSTANCE_IMAGE_FILE};
 use log::info;
 use std::fs;
@@ -33,7 +33,7 @@
 pub struct CompOsInstance {
     service: Strong<dyn ICompOsService>,
     #[allow(dead_code)] // Keeps VirtualizationService & the VM alive
-    vm_instance: VmInstance,
+    vm_instance: ComposClient,
     #[allow(dead_code)] // Keeps composd process alive
     lazy_service_guard: LazyServiceGuard,
 }
@@ -105,7 +105,7 @@
             .write(true)
             .open(&self.instance_image)
             .context("Failed to open instance image")?;
-        let vm_instance = VmInstance::start(
+        let vm_instance = ComposClient::start(
             virtualization_service,
             instance_image,
             &self.idsig,
diff --git a/compos/verify/Android.bp b/compos/verify/Android.bp
index d6875d1..5c74e4f 100644
--- a/compos/verify/Android.bp
+++ b/compos/verify/Android.bp
@@ -15,6 +15,7 @@
         "libcompos_common",
         "libcompos_verify_native_rust",
         "liblog_rust",
+        "libvmclient",
     ],
     prefer_rlib: true,
     apex_available: [
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 14ce798..7a22cfd 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -20,7 +20,7 @@
 use android_logger::LogId;
 use anyhow::{bail, Context, Result};
 use compos_aidl_interface::binder::ProcessState;
-use compos_common::compos_client::{VmInstance, VmParameters};
+use compos_common::compos_client::{ComposClient, VmParameters};
 use compos_common::odrefresh::{
     CURRENT_ARTIFACTS_SUBDIR, ODREFRESH_OUTPUT_ROOT_DIR, PENDING_ARTIFACTS_SUBDIR,
     TEST_ARTIFACTS_SUBDIR,
@@ -98,8 +98,8 @@
     // We need to start the thread pool to be able to receive Binder callbacks
     ProcessState::start_thread_pool();
 
-    let virtualization_service = VmInstance::connect_to_virtualization_service()?;
-    let vm_instance = VmInstance::start(
+    let virtualization_service = vmclient::connect()?;
+    let vm_instance = ComposClient::start(
         &*virtualization_service,
         instance_image,
         &idsig,