Microdroid_manager reports error/finished as terminal events

Bug: 205778374
Test: atest --test-mapping packages/modules/Virtualization
Change-Id: Ie0223b6ed58a43ae6e92ed1aa56991445444488c
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 76ff06f..27c7275 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -152,9 +152,13 @@
 
     ::ndk::ScopedAStatus onError(int32_t in_cid, int32_t in_error_code,
                                  const std::string& in_message) override {
-        // For now, just log the error as onDied() will follow.
         LOG(WARNING) << "VM error! cid = " << in_cid << ", error_code = " << in_error_code
                      << ", message = " << in_message;
+        {
+            std::unique_lock lock(mMutex);
+            mDied = true;
+        }
+        mCv.notify_all();
         return ScopedAStatus::ok();
     }
 
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index 9dbed64..2ddaf30 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -33,7 +33,12 @@
 public interface VirtualMachineCallback {
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ERROR_UNKNOWN, ERROR_PAYLOAD_VERIFICATION_FAILED, ERROR_PAYLOAD_CHANGED})
+    @IntDef({
+        ERROR_UNKNOWN,
+        ERROR_PAYLOAD_VERIFICATION_FAILED,
+        ERROR_PAYLOAD_CHANGED,
+        ERROR_PAYLOAD_INVALID_CONFIG
+    })
     @interface ErrorCode {}
 
     /** Error code for all other errors not listed below. */
@@ -48,6 +53,9 @@
     /** Error code indicating that the payload is verified, but has changed since the last boot. */
     int ERROR_PAYLOAD_CHANGED = 2;
 
+    /** Error code indicating that the payload config is invalid. */
+    int ERROR_PAYLOAD_INVALID_CONFIG = 3;
+
     /** Called when the payload starts in the VM. */
     void onPayloadStarted(@NonNull VirtualMachine vm, @Nullable ParcelFileDescriptor stream);
 
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 99ebc51..93a0759 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -39,7 +39,7 @@
 use vsock::VsockStream;
 
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
-    ERROR_PAYLOAD_CHANGED, ERROR_PAYLOAD_VERIFICATION_FAILED, ERROR_UNKNOWN, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
+    ERROR_PAYLOAD_CHANGED, ERROR_PAYLOAD_VERIFICATION_FAILED, ERROR_PAYLOAD_INVALID_CONFIG, ERROR_UNKNOWN, VM_BINDER_SERVICE_PORT, VM_STREAM_SERVICE_PORT, IVirtualMachineService,
 };
 
 const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
@@ -66,6 +66,8 @@
     PayloadChanged(String),
     #[error("Payload verification has failed: {0}")]
     PayloadVerificationFailed(String),
+    #[error("Payload config is invalid: {0}")]
+    InvalidConfig(String),
 }
 
 fn translate_error(err: &Error) -> (i32, String) {
@@ -75,6 +77,7 @@
             MicrodroidError::PayloadVerificationFailed(msg) => {
                 (ERROR_PAYLOAD_VERIFICATION_FAILED, msg.to_string())
             }
+            MicrodroidError::InvalidConfig(msg) => (ERROR_PAYLOAD_INVALID_CONFIG, msg.to_string()),
         }
     } else {
         (ERROR_UNKNOWN, err.to_string())
@@ -112,16 +115,27 @@
     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(())
+    match try_run_payload(&service) {
+        Ok(code) => {
+            info!("notifying payload finished");
+            service.notifyPayloadFinished(code)?;
+            if code == 0 {
+                info!("task successfully finished");
+            } else {
+                error!("task exited with exit code: {}", code);
+            }
+            Ok(())
+        }
+        Err(err) => {
+            error!("task terminated: {:?}", err);
+            let (error_code, message) = translate_error(&err);
+            service.notifyError(error_code, &message)?;
+            Err(err)
+        }
     }
 }
 
-fn try_start_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<()> {
+fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
     let metadata = load_metadata().context("Failed to load payload metadata")?;
 
     let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
@@ -151,27 +165,26 @@
     )
     .context("Failed to run zipfuse")?;
 
-    if !metadata.payload_config_path.is_empty() {
-        let config = load_config(Path::new(&metadata.payload_config_path))?;
+    ensure!(
+        !metadata.payload_config_path.is_empty(),
+        MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
+    );
+    let config = load_config(Path::new(&metadata.payload_config_path))?;
 
-        let fake_secret = "This is a placeholder for a value that is derived from the images that are loaded in the VM.";
-        if let Err(err) = rustutils::system_properties::write("ro.vmsecret.keymint", fake_secret) {
-            warn!("failed to set ro.vmsecret.keymint: {}", err);
-        }
-
-        // Wait until apex config is done. (e.g. linker configuration for apexes)
-        // TODO(jooyung): wait until sys.boot_completed?
-        wait_for_apex_config_done()?;
-
-        if let Some(main_task) = &config.task {
-            exec_task(main_task, service).map_err(|e| {
-                error!("failed to execute task: {}", e);
-                e
-            })?;
-        }
+    let fake_secret = "This is a placeholder for a value that is derived from the images that are loaded in the VM.";
+    if let Err(err) = rustutils::system_properties::write("ro.vmsecret.keymint", fake_secret) {
+        warn!("failed to set ro.vmsecret.keymint: {}", err);
     }
 
-    Ok(())
+    // Wait until apex config is done. (e.g. linker configuration for apexes)
+    // TODO(jooyung): wait until sys.boot_completed?
+    wait_for_apex_config_done()?;
+
+    ensure!(
+        config.task.is_some(),
+        MicrodroidError::InvalidConfig("No task in VM config".to_string())
+    );
+    exec_task(&config.task.unwrap(), service)
 }
 
 struct ApkDmverityArgument<'a> {
@@ -305,7 +318,7 @@
 
 /// Executes the given task. Stdout of the task is piped into the vsock stream to the
 /// virtualizationservice in the host side.
-fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<()> {
+fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
     info!("executing main task {:?}...", task);
     let mut command = build_command(task)?;
 
@@ -319,19 +332,7 @@
     }
 
     let exit_status = command.spawn()?.wait()?;
-    if let Some(code) = exit_status.code() {
-        info!("notifying payload finished");
-        service.notifyPayloadFinished(code)?;
-
-        if code == 0 {
-            info!("task successfully finished");
-        } else {
-            error!("task exited with exit code: {}", code);
-        }
-    } else {
-        error!("task terminated: {}", exit_status);
-    }
-    Ok(())
+    exit_status.code().ok_or_else(|| anyhow!("Failed to get exit_code from the paylaod."))
 }
 
 fn build_command(task: &Task) -> Result<Command> {
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index 97f6ca3..1a16f2a 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -64,4 +64,9 @@
      * Error code indicating that the payload is verified, but has changed since the last boot.
      */
     const int ERROR_PAYLOAD_CHANGED = 2;
+
+    /**
+     * Error code indicating that the payload config is invalid.
+     */
+    const int ERROR_PAYLOAD_INVALID_CONFIG = 3;
 }