A stopped VM with no files is deleted

I came across this accidentally while writing a demo app. It's very
much an edge case, but without this fix quite difficult to get out of.

(Writing the test to reproduce was most of the work here.)

Bug: 243512240
Test: atest MicrodroidTests
Change-Id: Ifd51b463302418d36c83a25c1d5dedb701179363
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 7c7f4b5..ffb2e14 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -621,15 +621,26 @@
             }
             virtualMachine = mVirtualMachine;
         }
+
+        int status;
         if (virtualMachine == null) {
-            return mVmRootPath.exists() ? STATUS_STOPPED : STATUS_DELETED;
+            status = STATUS_STOPPED;
         } else {
             try {
-                return stateToStatus(virtualMachine.getState());
+                status = stateToStatus(virtualMachine.getState());
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             }
         }
+        if (status == STATUS_STOPPED && !mVmRootPath.exists()) {
+            // A VM can quite happily keep running if its backing files have been deleted.
+            // But once it stops, it's gone forever.
+            synchronized (mLock) {
+                dropVm();
+            }
+            return STATUS_DELETED;
+        }
+        return status;
     }
 
     private int stateToStatus(@VirtualMachineState int state) {
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index e3c9961..5f9b915 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -24,6 +24,7 @@
         "MicrodroidTestNativeLib",
         "MicrodroidIdleNativeLib",
         "MicrodroidEmptyNativeLib",
+        "MicrodroidExitNativeLib",
         "MicrodroidPrivateLinkingNativeLib",
     ],
     jni_uses_platform_apis: true,
@@ -73,6 +74,14 @@
     stl: "none",
 }
 
+// A payload that exits immediately on start
+cc_library_shared {
+    name: "MicrodroidExitNativeLib",
+    srcs: ["src/native/exitbinary.cpp"],
+    header_libs: ["vm_payload_headers"],
+    stl: "libc++_static",
+}
+
 // A payload which tries to link against libselinux, one of private libraries
 cc_library_shared {
     name: "MicrodroidPrivateLinkingNativeLib",
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 7bd5f08..5cd0cb1 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -664,6 +664,40 @@
     }
 
     @Test
+    @CddTest(
+            requirements = {
+                "9.17/C-1-1",
+            })
+    public void deleteVmFiles() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidExitNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .build();
+
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_delete", config);
+        vm.run();
+        // If we explicitly stop a VM, that triggers some tidy up; so for this test we start a VM
+        // that immediately stops itself.
+        while (vm.getStatus() == STATUS_RUNNING) {
+            Thread.sleep(100);
+        }
+
+        // Delete the files without telling VMM. This isn't a good idea, but we can't stop an
+        // app doing it, and we should recover from it.
+        for (File f : vm.getRootDir().listFiles()) {
+            Files.delete(f.toPath());
+        }
+        vm.getRootDir().delete();
+
+        VirtualMachineManager vmm = getVirtualMachineManager();
+        assertThat(vmm.get("test_vm_delete")).isNull();
+        assertThat(vm.getStatus()).isEqualTo(STATUS_DELETED);
+    }
+
+    @Test
     @CddTest(requirements = {
             "9.17/C-1-1",
     })
diff --git a/tests/testapk/src/native/exitbinary.cpp b/tests/testapk/src/native/exitbinary.cpp
new file mode 100644
index 0000000..4b70727
--- /dev/null
+++ b/tests/testapk/src/native/exitbinary.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#include <vm_main.h>
+
+extern "C" int AVmPayload_main() {
+    return 0;
+}