Merge changes I36abf21b,Iede88046

* changes:
  Move APEX handling to a library
  Migrate payload/apex.rs to thiserror
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 31f8458..749f3c1 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -16,6 +16,7 @@
 
 package com.android.virt.fs;
 
+import static android.virt.test.CommandResultSubject.assertThat;
 import static android.virt.test.LogArchiver.archiveLogThenDelete;
 
 import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
@@ -24,9 +25,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
@@ -46,7 +45,6 @@
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
 import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
 
 import org.junit.After;
 import org.junit.Before;
@@ -255,7 +253,7 @@
         runAuthFsOnMicrodroid("--remote-ro-file 3:" + DIGEST_4M + " --cid " + VMADDR_CID_HOST);
 
         // Verify
-        assertFalse(copyFile(sMicrodroid, MOUNT_DIR + "/3", "/dev/null"));
+        assertThat(copyFile(sMicrodroid, MOUNT_DIR + "/3", "/dev/null")).isFailed();
     }
 
     @Test
@@ -268,7 +266,7 @@
         String srcPath = "/system/bin/linker64";
         String destPath = MOUNT_DIR + "/3";
         String backendPath = TEST_OUTPUT_DIR + "/out.file";
-        assertTrue(copyFile(sMicrodroid, srcPath, destPath));
+        assertThat(copyFile(sMicrodroid, srcPath, destPath)).isSuccess();
 
         // Verify
         String expectedHash = computeFileHash(sMicrodroid, srcPath);
@@ -284,32 +282,36 @@
         String srcPath = "/system/bin/linker64";
         String destPath = MOUNT_DIR + "/3";
         String backendPath = TEST_OUTPUT_DIR + "/out.file";
-        assertTrue(copyFile(sMicrodroid, srcPath, destPath));
+        assertThat(copyFile(sMicrodroid, srcPath, destPath)).isSuccess();
 
         // Action
         // Tampering with the first 2 4K-blocks of the backing file.
-        assertTrue(
+        assertThat(
                 writeZerosAtFileOffset(sAndroid, backendPath,
-                        /* offset */ 0, /* number */ 8192, /* writeThrough */ false));
+                        /* offset */ 0, /* number */ 8192, /* writeThrough */ false))
+                .isSuccess();
 
         // Verify
         // Write to a block partially requires a read back to calculate the new hash. It should fail
         // when the content is inconsistent to the known hash. Use direct I/O to avoid simply
         // writing to the filesystem cache.
-        assertFalse(
+        assertThat(
                 writeZerosAtFileOffset(sMicrodroid, destPath,
-                        /* offset */ 0, /* number */ 1024, /* writeThrough */ true));
+                        /* offset */ 0, /* number */ 1024, /* writeThrough */ true))
+                .isFailed();
 
         // A full 4K write does not require to read back, so write can succeed even if the backing
         // block has already been tampered.
-        assertTrue(
+        assertThat(
                 writeZerosAtFileOffset(sMicrodroid, destPath,
-                        /* offset */ 4096, /* number */ 4096, /* writeThrough */ false));
+                        /* offset */ 4096, /* number */ 4096, /* writeThrough */ false))
+                .isSuccess();
 
         // Otherwise, a partial write with correct backing file should still succeed.
-        assertTrue(
+        assertThat(
                 writeZerosAtFileOffset(sMicrodroid, destPath,
-                        /* offset */ 8192, /* number */ 1024, /* writeThrough */ false));
+                        /* offset */ 8192, /* number */ 1024, /* writeThrough */ false))
+                .isSuccess();
     }
 
     @Test
@@ -321,20 +323,23 @@
         String srcPath = "/system/bin/linker64";
         String destPath = MOUNT_DIR + "/3";
         String backendPath = TEST_OUTPUT_DIR + "/out.file";
-        assertTrue(copyFile(sMicrodroid, srcPath, destPath));
+        assertThat(copyFile(sMicrodroid, srcPath, destPath)).isSuccess();
 
         // Action
         // Tampering with the first 4K-block of the backing file.
-        assertTrue(
+        assertThat(
                 writeZerosAtFileOffset(sAndroid, backendPath,
-                        /* offset */ 0, /* number */ 4096, /* writeThrough */ false));
+                        /* offset */ 0, /* number */ 4096, /* writeThrough */ false))
+                .isSuccess();
 
         // Verify
         // Force dropping the page cache, so that the next read can be validated.
         sMicrodroid.run("echo 1 > /proc/sys/vm/drop_caches");
         // A read will fail if the backing data has been tampered.
-        assertFalse(checkReadAt(sMicrodroid, destPath, /* offset */ 0, /* number */ 4096));
-        assertTrue(checkReadAt(sMicrodroid, destPath, /* offset */ 4096, /* number */ 4096));
+        assertThat(checkReadAt(sMicrodroid, destPath, /* offset */ 0, /* number */ 4096))
+                .isFailed();
+        assertThat(checkReadAt(sMicrodroid, destPath, /* offset */ 4096, /* number */ 4096))
+                .isSuccess();
     }
 
     @Test
@@ -349,15 +354,16 @@
 
         // Action
         // Tampering with the last 4K-block of the backing file.
-        assertTrue(
+        assertThat(
                 writeZerosAtFileOffset(sAndroid, backendPath,
-                        /* offset */ 4096, /* number */ 1, /* writeThrough */ false));
+                        /* offset */ 4096, /* number */ 1, /* writeThrough */ false))
+                .isSuccess();
 
         // Verify
         // A resize (to a non-multiple of 4K) will fail if the last backing chunk has been
         // tampered. The original data is necessary (and has to be verified) to calculate the new
         // hash with shorter data.
-        assertFalse(resizeFile(sMicrodroid, outputPath, 8000));
+        assertThat(resizeFile(sMicrodroid, outputPath, 8000)).isFailed();
     }
 
     @Test
@@ -376,14 +382,14 @@
                 backendPath,
                 "684ad25fdc2bbb80cbc910dd1bde6d5499ccf860ca6ee44704b77ec445271353");
 
-        assertTrue(resizeFile(sMicrodroid, outputPath, 15000));
+        assertThat(resizeFile(sMicrodroid, outputPath, 15000)).isSuccess();
         assertEquals(getFileSizeInBytes(sMicrodroid, outputPath), 15000);
         expectBackingFileConsistency(
                 outputPath,
                 backendPath,
                 "567c89f62586e0d33369157afdfe99a2fa36cdffb01e91dcdc0b7355262d610d");
 
-        assertTrue(resizeFile(sMicrodroid, outputPath, 5000));
+        assertThat(resizeFile(sMicrodroid, outputPath, 5000)).isSuccess();
         assertEquals(getFileSizeInBytes(sMicrodroid, outputPath), 5000);
         expectBackingFileConsistency(
                 outputPath,
@@ -412,7 +418,7 @@
                 "684ad25fdc2bbb80cbc910dd1bde6d5499ccf860ca6ee44704b77ec445271353");
 
         // Regular file operations work, e.g. resize.
-        assertTrue(resizeFile(sMicrodroid, authfsPath, 15000));
+        assertThat(resizeFile(sMicrodroid, authfsPath, 15000)).isSuccess();
         assertEquals(getFileSizeInBytes(sMicrodroid, authfsPath), 15000);
         expectBackingFileConsistency(
                 authfsPath,
@@ -512,7 +518,7 @@
         sMicrodroid.run("test ! -d " + authfsOutputDir + "/dir/dir2");
         sAndroid.run("test ! -d " + androidOutputDir + "/dir/dir2");
         // Can only delete a directory if empty
-        assertFailed(sMicrodroid, "rmdir " + authfsOutputDir + "/dir");
+        assertThat(sMicrodroid.runForResult("rmdir " + authfsOutputDir + "/dir")).isFailed();
         sMicrodroid.run("test -d " + authfsOutputDir + "/dir");  // still there
         sMicrodroid.run("rm " + authfsOutputDir + "/dir/file");
         sMicrodroid.run("rmdir " + authfsOutputDir + "/dir");
@@ -536,10 +542,12 @@
 
         // Action & Verify
         // Cannot create directory if an entry with the same name already exists.
-        assertFailed(sMicrodroid, "mkdir " + authfsOutputDir + "/some_file");
-        assertFailed(sMicrodroid, "mkdir " + authfsOutputDir + "/some_dir");
-        assertFailed(sMicrodroid, "mkdir " + authfsOutputDir + "/some_dir/file");
-        assertFailed(sMicrodroid, "mkdir " + authfsOutputDir + "/some_dir/dir");
+        assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_file")).isFailed();
+        assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir")).isFailed();
+        assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir/file"))
+            .isFailed();
+        assertThat(sMicrodroid.runForResult("mkdir " + authfsOutputDir + "/some_dir/dir"))
+            .isFailed();
     }
 
     @Test
@@ -603,7 +611,8 @@
 
         // Verify
         sMicrodroid.run("test -f " + authfsInputDir + "/system/framework/services.jar");
-        assertFailed(sMicrodroid, "test -f " + authfsInputDir + "/system/bin/sh");
+        assertThat(sMicrodroid.runForResult("test -f " + authfsInputDir + "/system/bin/sh"))
+                .isFailed();
     }
 
     @Test
@@ -658,8 +667,8 @@
         sMicrodroid.run("chmod 321 " + MOUNT_DIR + "/3");
         expectFileMode("--wx-w---x", MOUNT_DIR + "/3", TEST_OUTPUT_DIR + "/file");
         // Can't set the disallowed bits
-        assertFailed(sMicrodroid, "chmod +s " + MOUNT_DIR + "/3");
-        assertFailed(sMicrodroid, "chmod +t " + MOUNT_DIR + "/3");
+        assertThat(sMicrodroid.runForResult("chmod +s " + MOUNT_DIR + "/3")).isFailed();
+        assertThat(sMicrodroid.runForResult("chmod +t " + MOUNT_DIR + "/3")).isFailed();
     }
 
     @Test
@@ -681,8 +690,9 @@
         sMicrodroid.run("chmod 321 " + authfsOutputDir + "/dir");
         expectFileMode("d-wx-w---x", authfsOutputDir + "/dir", TEST_OUTPUT_DIR + "/dir");
         // Can't set the disallowed bits
-        assertFailed(sMicrodroid, "chmod +s " + authfsOutputDir + "/dir/dir2");
-        assertFailed(sMicrodroid, "chmod +t " + authfsOutputDir + "/dir");
+        assertThat(sMicrodroid.runForResult("chmod +s " + authfsOutputDir + "/dir/dir2"))
+                .isFailed();
+        assertThat(sMicrodroid.runForResult("chmod +t " + authfsOutputDir + "/dir")).isFailed();
     }
 
     @Test
@@ -704,8 +714,8 @@
         sMicrodroid.run("chmod 321 " + authfsOutputDir + "/file2");
         expectFileMode("--wx-w---x", authfsOutputDir + "/file2", TEST_OUTPUT_DIR + "/file2");
         // Can't set the disallowed bits
-        assertFailed(sMicrodroid, "chmod +s " + authfsOutputDir + "/file");
-        assertFailed(sMicrodroid, "chmod +t " + authfsOutputDir + "/file2");
+        assertThat(sMicrodroid.runForResult("chmod +s " + authfsOutputDir + "/file")).isFailed();
+        assertThat(sMicrodroid.runForResult("chmod +t " + authfsOutputDir + "/file2")).isFailed();
     }
 
     @Test
@@ -752,13 +762,12 @@
         }
     }
 
-    private static boolean copyFile(CommandRunner runner, String src, String dest)
+    private static CommandResult copyFile(CommandRunner runner, String src, String dest)
             throws DeviceNotAvailableException {
         // toybox's cp(1) implementation ignores most read(2) errors, and it's unclear what the
         // canonical behavior should be (not mentioned in manpage). For this test, use cat(1) in
         // order to fail on I/O error.
-        CommandResult result = runner.runForResult("cat " + src + " > " + dest);
-        return result.getStatus() == CommandStatus.SUCCESS;
+        return runner.runForResult("cat " + src + " > " + dest);
     }
 
     private void expectFileMode(String expected, String microdroidPath, String androidPath)
@@ -770,10 +779,9 @@
         assertEquals("Inconsistent mode for " + androidPath + " (android)", expected, actual);
     }
 
-    private static boolean resizeFile(CommandRunner runner, String path, long size)
+    private static CommandResult resizeFile(CommandRunner runner, String path, long size)
             throws DeviceNotAvailableException {
-        CommandResult result = runner.runForResult("truncate -c -s " + size + " " + path);
-        return result.getStatus() == CommandStatus.SUCCESS;
+        return runner.runForResult("truncate -c -s " + size + " " + path);
     }
 
     private static long getFileSizeInBytes(CommandRunner runner, String path)
@@ -787,19 +795,17 @@
                 "yes $'\\x01' | tr -d '\\n' | dd bs=1 count=" + numberOfOnes + " of=" + filePath);
     }
 
-    private static boolean checkReadAt(CommandRunner runner, String filePath, long offset,
+    private static CommandResult checkReadAt(CommandRunner runner, String filePath, long offset,
             long size) throws DeviceNotAvailableException {
         String cmd = "dd if=" + filePath + " of=/dev/null bs=1 count=" + size;
         if (offset > 0) {
             cmd += " skip=" + offset;
         }
-        CommandResult result = runner.runForResult(cmd);
-        return result.getStatus() == CommandStatus.SUCCESS;
+        return runner.runForResult(cmd);
     }
 
-    private static boolean writeZerosAtFileOffset(CommandRunner runner, String filePath,
-            long offset, long numberOfZeros, boolean writeThrough)
-            throws DeviceNotAvailableException {
+    private CommandResult writeZerosAtFileOffset(CommandRunner runner, String filePath, long offset,
+            long numberOfZeros, boolean writeThrough) throws DeviceNotAvailableException {
         String cmd = "dd if=/dev/zero of=" + filePath + " bs=1 count=" + numberOfZeros
                 + " conv=notrunc";
         if (offset > 0) {
@@ -808,14 +814,7 @@
         if (writeThrough) {
             cmd += " direct";
         }
-        CommandResult result = runner.runForResult(cmd);
-        return result.getStatus() == CommandStatus.SUCCESS;
-    }
-
-    private static void assertFailed(CommandRunner runner, String... cmd)
-            throws DeviceNotAvailableException {
-        CommandResult result = runner.runForResult(cmd);
-        assertThat(result.getStatus()).isEqualTo(CommandStatus.FAILED);
+        return runner.runForResult(cmd);
     }
 
     private void runAuthFsOnMicrodroid(String flags) {
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index a2ae144..8702568 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -157,16 +157,34 @@
 
 genrule {
     name: "microdroid_build_prop_gen_x86_64",
-    srcs: ["build.prop"],
+    srcs: [
+        "build.prop",
+        ":buildinfo.prop",
+    ],
     out: ["build.prop.out"],
-    cmd: "cp $(in) $(out); echo ro.product.cpu.abilist=x86_64 >> $(out)",
+    cmd: "(echo '# build properties from buildinfo.prop module' && " +
+        "grep ro\\.build\\.version\\.codename= $(location :buildinfo.prop) && " +
+        "grep ro\\.build\\.version\\.release= $(location :buildinfo.prop) && " +
+        "grep ro\\.build\\.version\\.sdk= $(location :buildinfo.prop) && " +
+        "grep ro\\.build\\.version\\.security_patch= $(location :buildinfo.prop) && " +
+        "cat $(location build.prop) && " +
+        "echo ro.product.cpu.abilist=x86_64) > $(out)",
 }
 
 genrule {
     name: "microdroid_build_prop_gen_arm64",
-    srcs: ["build.prop"],
+    srcs: [
+        "build.prop",
+        ":buildinfo.prop",
+    ],
     out: ["build.prop.out"],
-    cmd: "cp $(in) $(out); echo ro.product.cpu.abilist=arm64-v8a >> $(out)",
+    cmd: "(echo '# build properties from buildinfo.prop module' && " +
+        "grep ro\\.build\\.version\\.codename= $(location :buildinfo.prop) && " +
+        "grep ro\\.build\\.version\\.release= $(location :buildinfo.prop) && " +
+        "grep ro\\.build\\.version\\.sdk= $(location :buildinfo.prop) && " +
+        "grep ro\\.build\\.version\\.security_patch= $(location :buildinfo.prop) && " +
+        "cat $(location build.prop) && " +
+        "echo ro.product.cpu.abilist=arm64-v8a) > $(out)",
 }
 
 android_filesystem {
diff --git a/microdroid/build.prop b/microdroid/build.prop
index 0908a4c..a9824c0 100644
--- a/microdroid/build.prop
+++ b/microdroid/build.prop
@@ -3,11 +3,5 @@
 ro.adb.secure=0
 service.adb.listen_addrs=vsock:5555
 
-# TODO(b/189164487): support build related properties
-ro.build.version.codename=UpsideDownCake
-ro.build.version.release=13
-ro.build.version.sdk=33
-ro.build.version.security_patch=2022-06-05
-
 # Payload metadata partition
 apexd.payload_metadata.path=/dev/block/by-name/payload-metadata
diff --git a/microdroid/payload/mk_payload.cc b/microdroid/payload/mk_payload.cc
index 6e3f526..4dbcabf 100644
--- a/microdroid/payload/mk_payload.cc
+++ b/microdroid/payload/mk_payload.cc
@@ -166,11 +166,10 @@
     Metadata metadata;
     metadata.set_version(1);
 
-    int apex_index = 0;
     for (const auto& apex_config : config.apexes) {
         auto* apex = metadata.add_apexes();
         apex->set_name(apex_config.name);
-        apex->set_partition_name("microdroid-apex-" + std::to_string(apex_index++));
+        apex->set_partition_name(apex_config.name);
         apex->set_is_factory(true);
     }
 
@@ -303,4 +302,4 @@
     }
 
     return 0;
-}
\ No newline at end of file
+}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 5dbd4ec..7385288 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -13,7 +13,7 @@
         "libcore.rust_sysroot",
     ],
     rustlibs: [
-        "libspin_nostd",
+        "libvmbase",
     ],
     enabled: false,
     target: {
diff --git a/pvmfw/src/exceptions.rs b/pvmfw/src/exceptions.rs
index 2bdcf9c..61f7846 100644
--- a/pvmfw/src/exceptions.rs
+++ b/pvmfw/src/exceptions.rs
@@ -14,61 +14,59 @@
 
 //! Exception handlers.
 
-use crate::console::emergency_write_str;
-use crate::eprintln;
-use crate::psci::system_reset;
 use core::arch::asm;
+use vmbase::{console::emergency_write_str, eprintln, power::reboot};
 
 #[no_mangle]
 extern "C" fn sync_exception_current() {
     emergency_write_str("sync_exception_current\n");
     print_esr();
-    system_reset();
+    reboot();
 }
 
 #[no_mangle]
 extern "C" fn irq_current() {
     emergency_write_str("irq_current\n");
-    system_reset();
+    reboot();
 }
 
 #[no_mangle]
 extern "C" fn fiq_current() {
     emergency_write_str("fiq_current\n");
-    system_reset();
+    reboot();
 }
 
 #[no_mangle]
 extern "C" fn serr_current() {
     emergency_write_str("serr_current\n");
     print_esr();
-    system_reset();
+    reboot();
 }
 
 #[no_mangle]
 extern "C" fn sync_lower() {
     emergency_write_str("sync_lower\n");
     print_esr();
-    system_reset();
+    reboot();
 }
 
 #[no_mangle]
 extern "C" fn irq_lower() {
     emergency_write_str("irq_lower\n");
-    system_reset();
+    reboot();
 }
 
 #[no_mangle]
 extern "C" fn fiq_lower() {
     emergency_write_str("fiq_lower\n");
-    system_reset();
+    reboot();
 }
 
 #[no_mangle]
 extern "C" fn serr_lower() {
     emergency_write_str("serr_lower\n");
     print_esr();
-    system_reset();
+    reboot();
 }
 
 #[inline]
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index d38b1e3..3fe3435 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -17,13 +17,9 @@
 #![no_main]
 #![no_std]
 
-mod console;
 mod exceptions;
-mod psci;
-mod uart;
 
-use core::panic::PanicInfo;
-use psci::{system_off, system_reset};
+use vmbase::{console, power::shutdown, println};
 
 /// Entry point for pVM firmware.
 #[no_mangle]
@@ -31,14 +27,5 @@
     console::init();
     println!("Hello world");
 
-    system_off();
-    #[allow(clippy::empty_loop)]
-    loop {}
-}
-
-#[panic_handler]
-fn panic(info: &PanicInfo) -> ! {
-    eprintln!("{}", info);
-    system_reset();
-    loop {}
+    shutdown();
 }
diff --git a/pvmfw/src/psci.rs b/pvmfw/src/psci.rs
deleted file mode 100644
index 8dcbcaa..0000000
--- a/pvmfw/src/psci.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-// 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.
-
-//! PSCI calls.
-
-const PSCI_SYSTEM_OFF: u32 = 0x84000008;
-const PSCI_SYSTEM_RESET: u32 = 0x84000009;
-const PSCI_SYSTEM_RESET2: u32 = 0x84000012;
-
-pub fn system_off() -> u32 {
-    hvc32(PSCI_SYSTEM_OFF, 0, 0, 0, 0, 0, 0, 0)[0]
-}
-
-pub fn system_reset() -> u32 {
-    hvc32(PSCI_SYSTEM_RESET, 0, 0, 0, 0, 0, 0, 0)[0]
-}
-
-#[allow(unused)]
-pub fn system_reset2(reset_type: u32, cookie: u32) -> u32 {
-    hvc32(PSCI_SYSTEM_RESET2, reset_type, cookie, 0, 0, 0, 0, 0)[0]
-}
-
-/// Make an HVC32 call to the hypervisor, following the SMC Calling Convention version 1.3.
-#[inline(always)]
-#[allow(clippy::too_many_arguments)]
-fn hvc32(
-    function: u32,
-    arg1: u32,
-    arg2: u32,
-    arg3: u32,
-    arg4: u32,
-    arg5: u32,
-    arg6: u32,
-    arg7: u32,
-) -> [u32; 8] {
-    let mut ret = [0; 8];
-
-    #[cfg(target_arch = "aarch64")]
-    unsafe {
-        core::arch::asm!(
-            "hvc #0",
-            inout("w0") function => ret[0],
-            inout("w1") arg1 => ret[1],
-            inout("w2") arg2 => ret[2],
-            inout("w3") arg3 => ret[3],
-            inout("w4") arg4 => ret[4],
-            inout("w5") arg5 => ret[5],
-            inout("w6") arg6 => ret[6],
-            inout("w7") arg7 => ret[7],
-            options(nomem, nostack)
-        )
-    }
-
-    #[cfg(not(target_arch = "aarch64"))]
-    unimplemented!();
-
-    ret
-}
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index 4ca0bf0..6ab02f8 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -6,7 +6,8 @@
     name: "VirtualizationTestHelper",
     srcs: ["java/**/*.java"],
     libs: [
-        "tradefed",
         "compatibility-tradefed",
+        "tradefed",
+        "truth-prebuilt",
     ],
 }
diff --git a/tests/hostside/helper/java/android/virt/test/CommandResultSubject.java b/tests/hostside/helper/java/android/virt/test/CommandResultSubject.java
new file mode 100644
index 0000000..5312f5a
--- /dev/null
+++ b/tests/hostside/helper/java/android/virt/test/CommandResultSubject.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 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.virt.test;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+
+/**
+ * A <a href="https://github.com/google/truth">Truth</a> subject for {@link CommandResult}.
+ */
+public class CommandResultSubject extends Subject {
+    private final CommandResult mActual;
+
+    public static Factory<CommandResultSubject, CommandResult> command_results() {
+        return CommandResultSubject::new;
+    }
+
+    public static CommandResultSubject assertThat(CommandResult actual) {
+        return assertAbout(command_results()).that(actual);
+    }
+
+    private CommandResultSubject(FailureMetadata metadata, CommandResult actual) {
+        super(metadata, actual);
+        this.mActual = actual;
+    }
+
+    public void isSuccess() {
+        check("isSuccess()").that(mActual.getStatus()).isEqualTo(CommandStatus.SUCCESS);
+    }
+
+    public void isFailed() {
+        check("isFailed()").that(mActual.getStatus()).isEqualTo(CommandStatus.FAILED);
+    }
+
+    public void isTimedOut() {
+        check("isTimedOut()").that(mActual.getStatus()).isEqualTo(CommandStatus.TIMED_OUT);
+    }
+
+    public void isException() {
+        check("isException()").that(mActual.getStatus()).isEqualTo(CommandStatus.EXCEPTION);
+    }
+
+    public IntegerSubject exitCode() {
+        return check("exitCode()").that(mActual.getExitCode());
+    }
+
+    public StringSubject stdoutTrimmed() {
+        return check("stdout()").that(mActual.getStdout().trim());
+    }
+
+    public StringSubject stderrTrimmed() {
+        return check("stderr()").that(mActual.getStderr().trim());
+    }
+}
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index dc0284f..a55ebe1 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -16,10 +16,13 @@
 
 package android.virt.test;
 
+import static android.virt.test.CommandResultSubject.assertThat;
+import static android.virt.test.CommandResultSubject.command_results;
+
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
@@ -29,10 +32,8 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.TestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.RunUtil;
 
 import java.io.File;
@@ -113,16 +114,17 @@
     private static String runOnHostWithTimeout(long timeoutMillis, String... cmd) {
         assertTrue(timeoutMillis >= 0);
         CommandResult result = RunUtil.getDefault().runTimedCmd(timeoutMillis, cmd);
-        assertThat(result.getStatus(), is(CommandStatus.SUCCESS));
+        assertThat(result).isSuccess();
         return result.getStdout().trim();
     }
 
     // Run a shell command on Microdroid
     public static String runOnMicrodroid(String... cmd) {
         CommandResult result = runOnMicrodroidForResult(cmd);
-        if (result.getStatus() != CommandStatus.SUCCESS) {
-            fail(join(cmd) + " has failed: " + result);
-        }
+        assertWithMessage("microdroid shell cmd `" + join(cmd) + "`")
+                .about(command_results())
+                .that(result)
+                .isSuccess();
         return result.getStdout().trim();
     }
 
@@ -133,23 +135,13 @@
         CommandResult result = RunUtil.getDefault()
                 .runTimedCmdRetry(timeoutMs, 500, attempts,
                         "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
-        if (result.getStatus() != CommandStatus.SUCCESS) {
-            fail(join(cmd) + " has failed: " + result);
-        }
+        assertWithMessage("Command `" + cmd + "` has failed")
+                .about(command_results())
+                .that(result)
+                .isSuccess();
         return result.getStdout().trim();
     }
 
-    // Same as runOnMicrodroid, but returns null on error.
-    public static String tryRunOnMicrodroid(String... cmd) {
-        CommandResult result = runOnMicrodroidForResult(cmd);
-        if (result.getStatus() == CommandStatus.SUCCESS) {
-            return result.getStdout().trim();
-        } else {
-            CLog.d(join(cmd) + " has failed (but ok): " + result);
-            return null;
-        }
-    }
-
     public static CommandResult runOnMicrodroidForResult(String... cmd) {
         final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
         return RunUtil.getDefault()
@@ -168,15 +160,15 @@
                                 "pull",
                                 path,
                                 target.getPath());
-        if (result.getStatus() != CommandStatus.SUCCESS) {
-            fail("pulling " + path + " has failed: " + result);
-        }
+        assertWithMessage("pulling " + path + " from microdroid")
+                .about(command_results())
+                .that(result)
+                .isSuccess();
     }
 
     // Asserts the command will fail on Microdroid.
     public static void assertFailedOnMicrodroid(String... cmd) {
-        CommandResult result = runOnMicrodroidForResult(cmd);
-        assertThat(result.getStatus(), is(CommandStatus.FAILED));
+        assertThat(runOnMicrodroidForResult(cmd)).isFailed();
     }
 
     private static String join(String... strs) {
@@ -390,6 +382,8 @@
         }
 
         // Check if it actually booted by reading a sysprop.
-        assertThat(runOnMicrodroid("getprop", "ro.hardware"), is("microdroid"));
+        assertThat(runOnMicrodroidForResult("getprop", "ro.hardware"))
+                .stdoutTrimmed()
+                .isEqualTo("microdroid");
     }
 }
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 32bdf3b..6a5689d 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -16,11 +16,16 @@
 
 package android.virt.test;
 
+import static android.virt.test.CommandResultSubject.assertThat;
+import static android.virt.test.CommandResultSubject.command_results;
+
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
@@ -34,7 +39,6 @@
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.RunUtil;
 
@@ -105,7 +109,7 @@
     // the microdroid boot procedure. Therefore, waiting for the service means that we wait for
     // the boot to complete. TODO: we need a better marker eventually.
     private void waitForLogdInit() {
-        tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\"");
+        runOnMicrodroidForResult("watch -e \"getprop init.svc.logd-reinit | grep '^$'\"");
     }
 
     @Test
@@ -122,7 +126,7 @@
         assertFalse(runDeviceTests(options));
 
         Map<TestDescription, TestResult> results = getLastDeviceRunResults().getTestResults();
-        assertThat(results.size(), is(1));
+        assertThat(results).hasSize(1);
         TestResult result = results.values().toArray(new TestResult[0])[0];
         assertTrue("The test should fail with a permission error",
                 result.getStackTrace()
@@ -161,9 +165,11 @@
                                     String.join(" ", command));
         String out = result.getStdout();
         String err = result.getStderr();
-        assertEquals(
-                "resigning the Virt APEX failed:\n\tout: " + out + "\n\terr: " + err + "\n",
-                CommandStatus.SUCCESS, result.getStatus());
+        assertWithMessage(
+                "resigning the Virt APEX failed:\n\tout: " + out + "\n\terr: " + err + "\n")
+                .about(command_results())
+                .that(result)
+                .isSuccess();
     }
 
     private static <T> void assertThatEventually(long timeoutMillis, Callable<T> callable,
@@ -263,8 +269,8 @@
         // - apk and idsig
         disks.put(new JSONObject().put("writable", false).put("partitions", new JSONArray()
                 .put(newPartition("payload-metadata", payloadMetadataPath))
-                .put(newPartition("microdroid-apex-0", statsdApexPath))
-                .put(newPartition("microdroid-apex-1", adbdApexPath))
+                .put(newPartition("com.android.os.statsd", statsdApexPath))
+                .put(newPartition("com.android.adbd", adbdApexPath))
                 .put(newPartition("microdroid-apk", apkPath))
                 .put(newPartition("microdroid-apk-idsig", idSigPath))));
 
@@ -458,10 +464,10 @@
                                     "-w",
                                     "-f",
                                     generalPolicyConfFile.getPath());
-            assertEquals(
-                    "neverallow check failed: " + result.getStderr().trim(),
-                    result.getStatus(),
-                    CommandStatus.SUCCESS);
+            assertWithMessage("neverallow check failed: " + result.getStderr().trim())
+                    .about(command_results())
+                    .that(result)
+                    .isSuccess();
         }
 
         shutdownMicrodroid(getDevice(), cid);
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
new file mode 100644
index 0000000..972cd1b
--- /dev/null
+++ b/vmbase/Android.bp
@@ -0,0 +1,18 @@
+rust_library_rlib {
+    name: "libvmbase",
+    host_supported: false,
+    crate_name: "vmbase",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libpsci",
+        "libspin_nostd",
+    ],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+    apex_available: ["com.android.virt"],
+}
diff --git a/pvmfw/src/console.rs b/vmbase/src/console.rs
similarity index 100%
rename from pvmfw/src/console.rs
rename to vmbase/src/console.rs
diff --git a/vmbase/src/lib.rs b/vmbase/src/lib.rs
new file mode 100644
index 0000000..0901e04
--- /dev/null
+++ b/vmbase/src/lib.rs
@@ -0,0 +1,30 @@
+// 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.
+
+//! Basic functionality for bare-metal binaries to run in a VM under crosvm.
+
+#![no_std]
+
+pub mod console;
+pub mod power;
+pub mod uart;
+
+use core::panic::PanicInfo;
+use power::reboot;
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+    eprintln!("{}", info);
+    reboot()
+}
diff --git a/vmbase/src/power.rs b/vmbase/src/power.rs
new file mode 100644
index 0000000..10a5e5d
--- /dev/null
+++ b/vmbase/src/power.rs
@@ -0,0 +1,35 @@
+// 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.
+
+//! Functions for shutting down the VM.
+
+use psci::{system_off, system_reset};
+
+/// Makes a `PSCI_SYSTEM_OFF` call to shutdown the VM.
+///
+/// Panics if it returns an error.
+pub fn shutdown() -> ! {
+    system_off().unwrap();
+    #[allow(clippy::empty_loop)]
+    loop {}
+}
+
+/// Makes a `PSCI_SYSTEM_RESET` call to shutdown the VM abnormally.
+///
+/// Panics if it returns an error.
+pub fn reboot() -> ! {
+    system_reset().unwrap();
+    #[allow(clippy::empty_loop)]
+    loop {}
+}
diff --git a/pvmfw/src/uart.rs b/vmbase/src/uart.rs
similarity index 100%
rename from pvmfw/src/uart.rs
rename to vmbase/src/uart.rs