Write microdroid failure reason to serial device

/dev/ttyS1 is a serial device (in a VM) to pass VM failure reason. This
change makes microdroid_manager write its own failure reason to the
serial device. virtualizationservice reads the failure reason, and then
triggers a callback to users.

Bug: 220071963
Test: manual
Change-Id: I78c66cb75a9c77ba7e6151fc2bdd40021dee7e4f
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 929a96b..998b94b 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -41,6 +41,7 @@
 use rustutils::system_properties::PropertyWatcher;
 use std::convert::TryInto;
 use std::fs::{self, create_dir, File, OpenOptions};
+use std::io::Write;
 use std::os::unix::io::{FromRawFd, IntoRawFd};
 use std::path::Path;
 use std::process::{Child, Command, Stdio};
@@ -71,8 +72,13 @@
 const LOGD_ENABLED_PROP: &str = "ro.boot.logd.enabled";
 const APP_DEBUGGABLE_PROP: &str = "ro.boot.microdroid.app_debuggable";
 
+// SYNC WITH virtualizationservice/src/crosvm.rs
+const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
+
 #[derive(thiserror::Error, Debug)]
 enum MicrodroidError {
+    #[error("Cannot connect to virtualization service: {0}")]
+    FailedToConnectToVirtualizationService(String),
     #[error("Payload has changed: {0}")]
     PayloadChanged(String),
     #[error("Payload verification has failed: {0}")]
@@ -89,12 +95,50 @@
                 (ERROR_PAYLOAD_VERIFICATION_FAILED, msg.to_string())
             }
             MicrodroidError::InvalidConfig(msg) => (ERROR_PAYLOAD_INVALID_CONFIG, msg.to_string()),
+
+            // Connection failure won't be reported to VS; return the default value
+            MicrodroidError::FailedToConnectToVirtualizationService(msg) => {
+                (ERROR_UNKNOWN, msg.to_string())
+            }
         }
     } else {
         (ERROR_UNKNOWN, err.to_string())
     }
 }
 
+fn write_death_reason_to_serial(err: &Error) -> Result<()> {
+    let death_reason = if let Some(e) = err.downcast_ref::<MicrodroidError>() {
+        match e {
+            MicrodroidError::FailedToConnectToVirtualizationService(_) => {
+                "MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE"
+            }
+            MicrodroidError::PayloadChanged(_) => "MICRODROID_PAYLOAD_HAS_CHANGED",
+            MicrodroidError::PayloadVerificationFailed(_) => {
+                "MICRODROID_PAYLOAD_VERIFICATION_FAILED"
+            }
+            MicrodroidError::InvalidConfig(_) => "MICRODROID_INVALID_PAYLOAD_CONFIG",
+        }
+    } else {
+        "MICRODROID_UNKNOWN_RUNTIME_ERROR"
+    };
+
+    let death_reason_bytes = death_reason.as_bytes();
+    let mut sent_total = 0;
+    while sent_total < death_reason_bytes.len() {
+        // TODO(b/220071963): Sometimes, sending more than 16 bytes at once makes MM hang.
+        let begin = sent_total;
+        let end = std::cmp::min(begin.saturating_add(16), death_reason_bytes.len());
+        OpenOptions::new()
+            .read(false)
+            .write(true)
+            .open(FAILURE_SERIAL_DEVICE)?
+            .write_all(&death_reason_bytes[begin..end])?;
+        sent_total = end;
+    }
+
+    Ok(())
+}
+
 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.
@@ -114,6 +158,9 @@
 fn main() {
     if let Err(e) = try_main() {
         error!("Failed with {:?}. Shutting down...", e);
+        if let Err(e) = write_death_reason_to_serial(&e) {
+            error!("Failed to write death reason {:?}", e);
+        }
         if let Err(e) = system_properties::write("sys.powerctl", "shutdown") {
             error!("failed to shutdown {:?}", e);
         }
@@ -125,7 +172,9 @@
     let _ = kernlog::init();
     info!("started.");
 
-    let service = get_vms_rpc_binder().context("cannot connect to VirtualMachineService")?;
+    let service = get_vms_rpc_binder()
+        .context("cannot connect to VirtualMachineService")
+        .map_err(|e| MicrodroidError::FailedToConnectToVirtualizationService(e.to_string()))?;
     match try_run_payload(&service) {
         Ok(code) => {
             info!("notifying payload finished");
@@ -225,8 +274,9 @@
     }
 
     // Verify the payload before using it.
-    let verified_data =
-        verify_payload(&metadata, saved_data.as_ref()).context("Payload verification failed")?;
+    let verified_data = verify_payload(&metadata, saved_data.as_ref())
+        .context("Payload verification failed")
+        .map_err(|e| MicrodroidError::PayloadVerificationFailed(e.to_string()))?;
     if let Some(saved_data) = saved_data {
         ensure!(
             saved_data == verified_data,