Enable --extended-status for crosvm and return reason why VM died.

This will allow us to detect guest VM kernel panics because they can be
configured to reboot on panic.

Bug: 211704107
Test: Ran some VMs manually with vm tool
Change-Id: I3845bb9d569ad0dc098013b527b69b31352e7e08
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 4216e1a..46f3020 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -19,6 +19,7 @@
 use crate::timeouts::timeouts;
 use crate::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT, DEFAULT_VM_CONFIG_PATH};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+    DeathReason::DeathReason,
     IVirtualMachine::IVirtualMachine,
     IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
     IVirtualizationService::IVirtualizationService,
@@ -289,9 +290,9 @@
 impl Interface for VmCallback {}
 
 impl IVirtualMachineCallback for VmCallback {
-    fn onDied(&self, cid: i32) -> BinderResult<()> {
+    fn onDied(&self, cid: i32, reason: DeathReason) -> BinderResult<()> {
         self.0.set_died();
-        log::warn!("VM died, cid = {}", cid);
+        log::warn!("VM died, cid = {}, reason = {:?}", cid, reason);
         Ok(())
     }
 
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 0305cd4..d19b713 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -50,6 +50,7 @@
 using namespace std::literals;
 
 using aidl::android::system::virtualizationservice::BnVirtualMachineCallback;
+using aidl::android::system::virtualizationservice::DeathReason;
 using aidl::android::system::virtualizationservice::IVirtualizationService;
 using aidl::android::system::virtualizationservice::IVirtualMachine;
 using aidl::android::system::virtualizationservice::IVirtualMachineCallback;
@@ -163,8 +164,8 @@
         return ScopedAStatus::ok();
     }
 
-    ::ndk::ScopedAStatus onDied(int32_t in_cid) override {
-        LOG(WARNING) << "VM died! cid = " << in_cid;
+    ::ndk::ScopedAStatus onDied(int32_t in_cid, DeathReason reason) override {
+        LOG(WARNING) << "VM died! cid = " << in_cid << " reason = " << static_cast<int>(reason);
         {
             std::unique_lock lock(mMutex);
             mDied = true;
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 1a0e14d..e53f95d 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -274,7 +274,7 @@
                         }
 
                         @Override
-                        public void onDied(VirtualMachine vm) {
+                        public void onDied(VirtualMachine vm, @DeathReason int reason) {
                             mService.shutdownNow();
                             mStatus.postValue(VirtualMachine.Status.STOPPED);
                         }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index c2a897b..ecd4491 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -411,12 +411,12 @@
                         }
 
                         @Override
-                        public void onDied(int cid) {
+                        public void onDied(int cid, int reason) {
                             final VirtualMachineCallback cb = mCallback;
                             if (cb == null) {
                                 return;
                             }
-                            mCallbackExecutor.execute(() -> cb.onDied(VirtualMachine.this));
+                            mCallbackExecutor.execute(() -> cb.onDied(VirtualMachine.this, reason));
                         }
                     });
             service.asBinder()
@@ -426,7 +426,8 @@
                                 public void binderDied() {
                                     final VirtualMachineCallback cb = mCallback;
                                     if (cb != null) {
-                                        cb.onDied(VirtualMachine.this);
+                                        cb.onDied(VirtualMachine.this, VirtualMachineCallback
+                                                .DEATH_REASON_VIRTUALIZATIONSERVICE_DIED);
                                     }
                                 }
                             },
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index 2ddaf30..df3ad37 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -56,6 +56,39 @@
     /** Error code indicating that the payload config is invalid. */
     int ERROR_PAYLOAD_INVALID_CONFIG = 3;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        DEATH_REASON_VIRTUALIZATIONSERVICE_DIED,
+        DEATH_REASON_SHUTDOWN,
+        DEATH_REASON_REBOOT,
+        DEATH_REASON_KILLED,
+        DEATH_REASON_UNKNOWN,
+        DEATH_REASON_INFRASTRUCTURE_ERROR
+    })
+    @interface DeathReason {}
+
+    /**
+     * virtualizationservice itself died, taking the VM down with it. This is a negative number to
+     * avoid conflicting with the other death reasons which match the ones in the AIDL interface.
+     */
+    int DEATH_REASON_VIRTUALIZATIONSERVICE_DIED = -1;
+
+    /** The VM requested to shut down. */
+    int DEATH_REASON_SHUTDOWN = 0;
+
+    /** The VM requested to reboot, possibly as the result of a kernel panic. */
+    int DEATH_REASON_REBOOT = 1;
+
+    /** The VM was killed. */
+    int DEATH_REASON_KILLED = 2;
+
+    /** The VM died for an unknown reason. */
+    int DEATH_REASON_UNKNOWN = 3;
+
+    /** There was an error waiting for the VM. */
+    int DEATH_REASON_INFRASTRUCTURE_ERROR = 4;
+
     /** Called when the payload starts in the VM. */
     void onPayloadStarted(@NonNull VirtualMachine vm, @Nullable ParcelFileDescriptor stream);
 
@@ -69,5 +102,5 @@
     void onError(@NonNull VirtualMachine vm, @ErrorCode int errorCode, @NonNull String message);
 
     /** Called when the VM died. */
-    void onDied(@NonNull VirtualMachine vm);
+    void onDied(@NonNull VirtualMachine vm, @DeathReason int reason);
 }
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 49575be..b03a915 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -105,7 +105,7 @@
         void forceStop(VirtualMachine vm) {
             try {
                 vm.stop();
-                this.onDied(vm);
+                this.onDied(vm, VirtualMachineCallback.DEATH_REASON_KILLED);
                 mExecutorService.shutdown();
             } catch (VirtualMachineException e) {
                 throw new RuntimeException(e);
@@ -125,7 +125,7 @@
         public void onError(VirtualMachine vm, int errorCode, String message) {}
 
         @Override
-        public void onDied(VirtualMachine vm) {}
+        public void onDied(VirtualMachine vm, @DeathReason int reason) {}
     }
 
     private static final int MIN_MEM_ARM64 = 135;
@@ -166,7 +166,7 @@
                     }
 
                     @Override
-                    public void onDied(VirtualMachine vm) {
+                    public void onDied(VirtualMachine vm, @DeathReason int reason) {
                         assertTrue(mPayloadReadyCalled);
                         assertTrue(mPayloadStartedCalled);
                     }
@@ -227,7 +227,7 @@
                     }
 
                     @Override
-                    public void onDied(VirtualMachine vm) {
+                    public void onDied(VirtualMachine vm, @DeathReason int reason) {
                         assertFalse(mPayloadStarted);
                         assertTrue(mErrorOccurred);
                     }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl
new file mode 100644
index 0000000..2f454a9
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/DeathReason.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2022 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.
+ */
+package android.system.virtualizationservice;
+
+/**
+ * The reason why a VM died.
+ */
+@Backing(type="int")
+enum DeathReason {
+    /** The VM requested to shut down. */
+    SHUTDOWN = 0,
+    /** The VM requested to reboot, possibly as the result of a kernel panic. */
+    REBOOT = 1,
+    /** The VM was killed. */
+    KILLED = 2,
+    /** The VM died for an unknown reason. */
+    UNKNOWN = 3,
+    /** There was an error waiting for the VM. */
+    INFRASTRUCTURE_ERROR = 4,
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index aa8105f..12a056c 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -15,6 +15,8 @@
  */
 package android.system.virtualizationservice;
 
+import android.system.virtualizationservice.DeathReason;
+
 /**
  * An object which a client may register with the VirtualizationService to get callbacks about the
  * state of a particular VM.
@@ -50,5 +52,5 @@
      * Note that this will not be called if the VirtualizationService itself dies, so you should
      * also use `link_to_death` to handle that.
      */
-    void onDied(int cid);
+    void onDied(int cid, in DeathReason reason);
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index c264270..5a7322b 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -22,6 +22,7 @@
 use ::binder::unstable_api::AsNative;
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+    DeathReason::DeathReason,
     DiskImage::DiskImage,
     IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
     IVirtualMachineCallback::IVirtualMachineCallback,
@@ -790,10 +791,10 @@
     }
 
     /// Call all registered callbacks to say that the VM has died.
-    pub fn callback_on_died(&self, cid: Cid) {
+    pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
         let callbacks = &*self.0.lock().unwrap();
         for callback in callbacks {
-            if let Err(e) = callback.onDied(cid as i32) {
+            if let Err(e) = callback.onDied(cid as i32, reason) {
                 error!("Error notifying exit of VM CID {}: {}", cid, e);
             }
         }
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 0b1429c..76f4f47 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -21,19 +21,24 @@
 use log::{debug, error, info};
 use shared_child::SharedChild;
 use std::fs::{remove_dir_all, File};
+use std::io;
 use std::mem;
 use std::num::NonZeroU32;
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::path::PathBuf;
-use std::process::Command;
+use std::process::{Command, ExitStatus};
 use std::sync::{Arc, Mutex};
 use std::thread;
 use vsock::VsockStream;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DeathReason::DeathReason;
 use android_system_virtualmachineservice::binder::Strong;
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
 
 const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
 
+/// The exit status which crosvm returns when a VM requests a reboot.
+const CROSVM_REBOOT_STATUS: i32 = 32;
+
 /// Configuration for a VM to run with crosvm.
 #[derive(Debug)]
 pub struct CrosvmConfig {
@@ -182,7 +187,8 @@
     /// This takes a separate reference to the `SharedChild` rather than using the one in
     /// `self.vm_state` to avoid holding the lock on `vm_state` while it is running.
     fn monitor(&self, child: Arc<SharedChild>) {
-        match child.wait() {
+        let result = child.wait();
+        match &result {
             Err(e) => error!("Error waiting for crosvm({}) instance to die: {}", child.id(), e),
             Ok(status) => info!("crosvm({}) exited with status {}", child.id(), status),
         }
@@ -192,7 +198,7 @@
         // Ensure that the mutex is released before calling the callbacks.
         drop(vm_state);
 
-        self.callbacks.callback_on_died(self.cid);
+        self.callbacks.callback_on_died(self.cid, death_reason(&result));
 
         // Delete temporary files.
         if let Err(e) = remove_dir_all(&self.temporary_directory) {
@@ -232,13 +238,31 @@
     }
 }
 
+fn death_reason(result: &Result<ExitStatus, io::Error>) -> DeathReason {
+    if let Ok(status) = result {
+        match status.code() {
+            None => DeathReason::KILLED,
+            Some(0) => DeathReason::SHUTDOWN,
+            Some(CROSVM_REBOOT_STATUS) => DeathReason::REBOOT,
+            Some(_) => DeathReason::UNKNOWN,
+        }
+    } else {
+        DeathReason::INFRASTRUCTURE_ERROR
+    }
+}
+
 /// Starts an instance of `crosvm` to manage a new VM.
 fn run_vm(config: CrosvmConfig) -> Result<SharedChild, Error> {
     validate_config(&config)?;
 
     let mut command = Command::new(CROSVM_PATH);
     // TODO(qwandor): Remove --disable-sandbox.
-    command.arg("run").arg("--disable-sandbox").arg("--cid").arg(config.cid.to_string());
+    command
+        .arg("--extended-status")
+        .arg("run")
+        .arg("--disable-sandbox")
+        .arg("--cid")
+        .arg(config.cid.to_string());
 
     if config.protected {
         command.arg("--protected-vm");
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 19982ea..8583fe2 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -17,7 +17,8 @@
 use crate::create_partition::command_create_partition;
 use crate::sync::AtomicFlag;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    IVirtualMachine::IVirtualMachine, IVirtualMachineCallback::BnVirtualMachineCallback,
+    DeathReason::DeathReason, IVirtualMachine::IVirtualMachine,
+    IVirtualMachineCallback::BnVirtualMachineCallback,
     IVirtualMachineCallback::IVirtualMachineCallback,
     IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
     VirtualMachineAppConfig::DebugLevel::DebugLevel,
@@ -286,13 +287,17 @@
         Ok(())
     }
 
-    fn onDied(&self, _cid: i32) -> BinderResult<()> {
-        // No need to explicitly report the event to the user (e.g. via println!) because this
-        // callback is registered only when the vm tool is invoked as interactive mode (e.g. not
-        // --daemonize) in which case the tool will exit to the shell prompt upon VM shutdown.
-        // Printing something will actually even confuse the user as the output from the app
-        // payload is printed.
+    fn onDied(&self, _cid: i32, reason: DeathReason) -> BinderResult<()> {
         self.dead.raise();
+
+        match reason {
+            DeathReason::SHUTDOWN => println!("VM shutdown cleanly."),
+            DeathReason::REBOOT => println!("VM tried to reboot, possibly due to a kernel panic."),
+            DeathReason::KILLED => println!("VM was killed."),
+            DeathReason::UNKNOWN => println!("VM died for an unknown reason."),
+            DeathReason::INFRASTRUCTURE_ERROR => println!("Error waiting for VM to finish."),
+            _ => println!("VM died for an unrecognised reason."),
+        }
         Ok(())
     }
 }