Add notifyError/onError notification for VM errors

For now, microdroid_manager shuts down a VM instance when it fails to
verify the VM payload. This change adds a new "error" notification to
deliver the error to the VM app.

Fow now this doesn't change the workflow. In a follow-up change this
will be changed so that VM app can decide to shutdown or not "onError".

Bug: 205778374
Test: MicrodroidHostTestCases
Change-Id: If7fdac3996b69601be0eda17da3e4cf218b4d1d8
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index f666294..2e6fa36 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -19,7 +19,7 @@
 mod payload;
 
 use crate::instance::{ApkData, InstanceDisk, MicrodroidData, RootHash};
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{anyhow, bail, ensure, Context, Error, Result};
 use apkverify::{get_public_key_der, verify};
 use binder::unstable_api::{new_spibinder, AIBinder};
 use binder::{FromIBinder, Strong};
@@ -39,7 +39,7 @@
 use vsock::VsockStream;
 
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
-    VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
+    ERROR_PAYLOAD_CHANGED, ERROR_PAYLOAD_VERIFICATION_FAILED, ERROR_UNKNOWN, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
 };
 
 const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
@@ -51,6 +51,27 @@
 const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
 const LOGD_ENABLED_PROP: &str = "ro.boot.logd.enabled";
 
+#[derive(thiserror::Error, Debug)]
+enum MicrodroidError {
+    #[error("Payload has changed: {0}")]
+    PayloadChanged(String),
+    #[error("Payload verification has failed: {0}")]
+    PayloadVerificationFailed(String),
+}
+
+fn translate_error(err: &Error) -> (i32, String) {
+    if let Some(e) = err.downcast_ref::<MicrodroidError>() {
+        match e {
+            MicrodroidError::PayloadChanged(msg) => (ERROR_PAYLOAD_CHANGED, msg.to_string()),
+            MicrodroidError::PayloadVerificationFailed(msg) => {
+                (ERROR_PAYLOAD_VERIFICATION_FAILED, msg.to_string())
+            }
+        }
+    } else {
+        (ERROR_UNKNOWN, err.to_string())
+    }
+}
+
 fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
     // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
     // safely taken by new_spibinder.
@@ -81,6 +102,17 @@
     kernlog::init()?;
     info!("started.");
 
+    let service = get_vms_rpc_binder().context("cannot connect to VirtualMachineService")?;
+    if let Err(err) = try_start_payload(&service) {
+        let (error_code, message) = translate_error(&err);
+        service.notifyError(error_code, &message)?;
+        Err(err)
+    } else {
+        Ok(())
+    }
+}
+
+fn try_start_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<()> {
     let metadata = load_metadata().context("Failed to load payload metadata")?;
 
     let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
@@ -90,11 +122,13 @@
     let verified_data =
         verify_payload(&metadata, saved_data.as_ref()).context("Payload verification failed")?;
     if let Some(saved_data) = saved_data {
-        if saved_data == verified_data {
-            info!("Saved data is verified.");
-        } else {
-            bail!("Detected an update of the payload which isn't supported yet.");
-        }
+        ensure!(
+            saved_data == verified_data,
+            MicrodroidError::PayloadChanged(String::from(
+                "Detected an update of the payload which isn't supported yet."
+            ))
+        );
+        info!("Saved data is verified.");
     } else {
         info!("Saving verified data.");
         instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
@@ -103,7 +137,6 @@
     // Before reading a file from the APK, start zipfuse
     system_properties::write("ctl.start", "zipfuse")?;
 
-    let service = get_vms_rpc_binder().expect("cannot connect to VirtualMachineService");
     if !metadata.payload_config_path.is_empty() {
         let config = load_config(Path::new(&metadata.payload_config_path))?;
 
@@ -117,7 +150,7 @@
         wait_for_apex_config_done()?;
 
         if let Some(main_task) = &config.task {
-            exec_task(main_task, &service).map_err(|e| {
+            exec_task(main_task, service).map_err(|e| {
                 error!("failed to execute task: {}", e);
                 e
             })?;
@@ -156,7 +189,10 @@
     let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
     if let Some(saved_data) = saved_data.map(|d| &d.apex_data) {
         // We don't support APEX updates. (assuming that update will change root digest)
-        ensure!(saved_data == &apex_data_from_payload, "APEX payloads has changed.");
+        ensure!(
+            saved_data == &apex_data_from_payload,
+            MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
+        );
         let apex_metadata = to_metadata(&apex_data_from_payload);
         // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
         // metadata instead of the default one (/dev/block/by-name/payload-metadata)
@@ -178,7 +214,10 @@
     // of the VM or APK was updated in the host.
     // TODO(jooyung): consider multithreading to make this faster
     let apk_pubkey = if !root_hash_trustful {
-        verify(DM_MOUNTED_APK_PATH).context(format!("failed to verify {}", DM_MOUNTED_APK_PATH))?
+        verify(DM_MOUNTED_APK_PATH).context(MicrodroidError::PayloadVerificationFailed(format!(
+            "failed to verify {}",
+            DM_MOUNTED_APK_PATH
+        )))?
     } else {
         get_public_key_der(DM_MOUNTED_APK_PATH)?
     };