Merge "Various minor fixes"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index b805d03..69f5518 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -10,6 +10,11 @@
       "name": "VirtualizationTestCases"
     }
   ],
+  "postsubmit": [
+    {
+      "name": "MicrodroidTestApp"
+    }
+  ],
   "imports": [
     {
       "path": "packages/modules/Virtualization/apkdmverity"
diff --git a/apex/Android.bp b/apex/Android.bp
index 983253e..af65e79 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -13,6 +13,7 @@
 
     key: "com.android.virt.key",
     certificate: ":com.android.virt.certificate",
+    custom_sign_tool: "sign_virt_apex",
 
     // crosvm and virtualizationservice are only enabled for 64-bit targets on device
     arch: {
@@ -100,4 +101,10 @@
             embedded_launcher: true,
         },
     },
+    required: [
+        "img2simg",
+        "lpmake",
+        "lpunpack",
+        "simg2img",
+    ],
 }
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index fd45e13..88c1ba6 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -29,10 +29,10 @@
     rustlibs: [
         "libandroid_logger",
         "libanyhow",
+        "liblibc",
         "libclap",
         "libcommand_fds",
         "liblog_rust",
-        "libnix",
     ],
     test_suites: ["general-tests"],
     test_harness: false,
diff --git a/authfs/tests/open_then_run.rs b/authfs/tests/open_then_run.rs
index a540f9d..3e6ae71 100644
--- a/authfs/tests/open_then_run.rs
+++ b/authfs/tests/open_then_run.rs
@@ -22,9 +22,8 @@
 use clap::{App, Arg, Values};
 use command_fds::{CommandFdExt, FdMapping};
 use log::{debug, error};
-use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode};
 use std::fs::{File, OpenOptions};
-use std::os::unix::io::{AsRawFd, RawFd};
+use std::os::unix::{fs::OpenOptionsExt, io::AsRawFd, io::RawFd};
 use std::process::Command;
 
 // `PseudoRawFd` is just an integer and not necessarily backed by a real FD. It is used to denote
@@ -32,31 +31,30 @@
 // with this alias is to improve readability by distinguishing from actual RawFd.
 type PseudoRawFd = RawFd;
 
-struct FileMapping<T: AsRawFd> {
-    file: T,
+struct FileMapping {
+    file: File,
     target_fd: PseudoRawFd,
 }
 
-impl<T: AsRawFd> FileMapping<T> {
+impl FileMapping {
     fn as_fd_mapping(&self) -> FdMapping {
         FdMapping { parent_fd: self.file.as_raw_fd(), child_fd: self.target_fd }
     }
 }
 
 struct Args {
-    ro_files: Vec<FileMapping<File>>,
-    rw_files: Vec<FileMapping<File>>,
-    dir_files: Vec<FileMapping<Dir>>,
+    ro_files: Vec<FileMapping>,
+    rw_files: Vec<FileMapping>,
+    dir_files: Vec<FileMapping>,
     cmdline_args: Vec<String>,
 }
 
-fn parse_and_create_file_mapping<F, T>(
+fn parse_and_create_file_mapping<F>(
     values: Option<Values<'_>>,
     opener: F,
-) -> Result<Vec<FileMapping<T>>>
+) -> Result<Vec<FileMapping>>
 where
-    F: Fn(&str) -> Result<T>,
-    T: AsRawFd,
+    F: Fn(&str) -> Result<File>,
 {
     if let Some(options) = values {
         options
@@ -118,7 +116,13 @@
     })?;
 
     let dir_files = parse_and_create_file_mapping(matches.values_of("open-dir"), |path| {
-        Dir::open(path, OFlag::O_DIRECTORY | OFlag::O_RDONLY, Mode::S_IRWXU)
+        // The returned FD represents a path (that's supposed to be a directory), and is not really
+        // a file. It's better to use std::os::unix::io::OwnedFd but it's currently experimental.
+        // Ideally, all FDs opened by this program should be `OwnedFd` since we are only opening
+        // them for the provided program, and are not supposed to do anything else.
+        OpenOptions::new()
+            .custom_flags(libc::O_PATH | libc::O_DIRECTORY)
+            .open(path)
             .with_context(|| format!("Open {} directory", path))
     })?;
 
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index a2fab07..f666294 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -69,7 +69,10 @@
 
 fn main() {
     if let Err(e) = try_main() {
-        error!("failed with {:?}", e);
+        error!("Failed with {:?}. Shutting down...", e);
+        if let Err(e) = system_properties::write("sys.powerctl", "shutdown") {
+            error!("failed to shutdown {:?}", e);
+        }
         std::process::exit(1);
     }
 }
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 493fc93..32c47dd 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -6,7 +6,10 @@
     name: "MicrodroidTestApp",
     test_suites: ["device-tests"],
     srcs: ["src/java/**/*.java"],
-    static_libs: ["androidx.test.runner"],
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+    ],
     libs: ["android.system.virtualmachine"],
     jni_libs: ["MicrodroidTestNativeLib"],
     platform_apis: true,
diff --git a/tests/testapk/AndroidManifest.xml b/tests/testapk/AndroidManifest.xml
index 21abeb5..bc955d2 100644
--- a/tests/testapk/AndroidManifest.xml
+++ b/tests/testapk/AndroidManifest.xml
@@ -15,8 +15,9 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.android.microdroid.test">
+    <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
     <application>
-        <uses-library android:name="android.system.virtualmachine" android:required="true" />
+        <uses-library android:name="android.system.virtualmachine" android:required="false" />
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.microdroid.test"
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index 25b1001..c7097db 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -17,8 +17,15 @@
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="MicrodroidTestApp.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+      <option
+        name="run-command"
+        value="pm grant com.android.microdroid.test android.permission.MANAGE_VIRTUAL_MACHINE" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.microdroid.test" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="shell-timeout" value="300000" />
+        <option name="test-timeout" value="300000" />
     </test>
 </configuration>
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 5e465d5..8ff2127 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -16,15 +16,164 @@
 package com.android.microdroid.test;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
 
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineCallback;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineException;
+import android.system.virtualmachine.VirtualMachineManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.Timeout;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
 public class MicrodroidTests {
+    @Rule public Timeout globalTimeout = Timeout.seconds(300);
+
+    private static class Inner {
+        public Context mContext;
+        public VirtualMachineManager mVmm;
+        public VirtualMachine mVm;
+    }
+
+    private boolean mPkvmSupported = false;
+    private Inner mInner;
+
+    @Before
+    public void setup() {
+        // In case when the virt APEX doesn't exist on the device, classes in the
+        // android.system.virtualmachine package can't be loaded. Therefore, before using the
+        // classes, check the existence of a class in the package and skip this test if not exist.
+        try {
+            Class.forName("android.system.virtualmachine.VirtualMachineManager");
+            mPkvmSupported = true;
+        } catch (ClassNotFoundException e) {
+            assumeNoException(e);
+            return;
+        }
+        mInner = new Inner();
+        mInner.mContext = ApplicationProvider.getApplicationContext();
+        mInner.mVmm = VirtualMachineManager.getInstance(mInner.mContext);
+    }
+
+    @After
+    public void cleanup() throws VirtualMachineException {
+        if (!mPkvmSupported) {
+            return;
+        }
+        if (mInner.mVm == null) {
+            return;
+        }
+        mInner.mVm.stop();
+        mInner.mVm.delete();
+    }
+
+    private abstract static class VmEventListener implements VirtualMachineCallback {
+        private final Handler mHandler;
+
+        VmEventListener() {
+            Looper.prepare();
+            mHandler = new Handler(Looper.myLooper());
+        }
+
+        void runToFinish(VirtualMachine vm) throws VirtualMachineException {
+            vm.setCallback(mCallback);
+            vm.run();
+            Looper.loop();
+        }
+
+        void forceStop(VirtualMachine vm) {
+            try {
+                vm.stop();
+            } catch (VirtualMachineException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        // This is the actual listener that is registered. Since the listener is executed in another
+        // thread, post a runnable to the current thread to call the corresponding mHandler method
+        // in the current thread.
+        private final VirtualMachineCallback mCallback =
+                new VirtualMachineCallback() {
+                    @Override
+                    public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+                        mHandler.post(() -> VmEventListener.this.onPayloadStarted(vm, stream));
+                    }
+
+                    @Override
+                    public void onPayloadReady(VirtualMachine vm) {
+                        mHandler.post(() -> VmEventListener.this.onPayloadReady(vm));
+                    }
+
+                    @Override
+                    public void onPayloadFinished(VirtualMachine vm, int exitCode) {
+                        mHandler.post(() -> VmEventListener.this.onPayloadFinished(vm, exitCode));
+                    }
+
+                    @Override
+                    public void onDied(VirtualMachine vm) {
+                        mHandler.post(
+                                () -> {
+                                    VmEventListener.this.onDied(vm);
+                                    Looper.myLooper().quitSafely();
+                                });
+                    }
+                };
+
+        @Override
+        public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {}
+
+        @Override
+        public void onPayloadReady(VirtualMachine vm) {}
+
+        @Override
+        public void onPayloadFinished(VirtualMachine vm, int exitCode) {}
+
+        @Override
+        public void onDied(VirtualMachine vm) {}
+    }
+
     @Test
-    public void testNothing() {
-        assertTrue(true);
+    public void startAndStop() throws VirtualMachineException, InterruptedException {
+        VirtualMachineConfig.Builder builder =
+                new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
+        VirtualMachineConfig config = builder.build();
+
+        mInner.mVm = mInner.mVmm.getOrCreate("test_vm", config);
+        VmEventListener listener =
+                new VmEventListener() {
+                    private boolean mPayloadReadyCalled = false;
+                    private boolean mPayloadStartedCalled = false;
+
+                    @Override
+                    public void onPayloadReady(VirtualMachine vm) {
+                        mPayloadReadyCalled = true;
+                    }
+
+                    @Override
+                    public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+                        mPayloadStartedCalled = true;
+                        forceStop(vm);
+                    }
+
+                    @Override
+                    public void onDied(VirtualMachine vm) {
+                        assertTrue(mPayloadReadyCalled);
+                        assertTrue(mPayloadStartedCalled);
+                    }
+                };
+        listener.runToFinish(mInner.mVm);
     }
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 74a9365..5d64684 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -256,7 +256,13 @@
             )
         })?;
         let image = clone_file(image_fd)?;
-
+        // initialize the file. Any data in the file will be erased.
+        image.set_len(0).map_err(|e| {
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("Failed to reset a file: {}", e),
+            )
+        })?;
         let mut part = QcowFile::new(image, size).map_err(|e| {
             new_binder_exception(
                 ExceptionCode::SERVICE_SPECIFIC,
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 49cab59..08be052 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -181,8 +181,8 @@
     /// `self.vm_state` to avoid holding the lock on `vm_state` while it is running.
     fn monitor(&self, child: Arc<SharedChild>) {
         match child.wait() {
-            Err(e) => error!("Error waiting for crosvm instance to die: {}", e),
-            Ok(status) => info!("crosvm exited with status {}", status),
+            Err(e) => error!("Error waiting for crosvm({}) instance to die: {}", child.id(), e),
+            Ok(status) => info!("crosvm({}) exited with status {}", child.id(), status),
         }
 
         let mut vm_state = self.vm_state.lock().unwrap();
@@ -220,9 +220,11 @@
     pub fn kill(&self) {
         let vm_state = &*self.vm_state.lock().unwrap();
         if let VmState::Running { child } = vm_state {
+            let id = child.id();
+            debug!("Killing crosvm({})", id);
             // TODO: Talk to crosvm to shutdown cleanly.
             if let Err(e) = child.kill() {
-                error!("Error killing crosvm instance: {}", e);
+                error!("Error killing crosvm({}) instance: {}", id, e);
             }
         }
     }
@@ -301,6 +303,7 @@
 
     info!("Running {:?}", command);
     let result = SharedChild::spawn(&mut command)?;
+    debug!("Spawned crosvm({}).", result.id());
     Ok(result)
 }
 
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index a59afd5..bc184ec 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -132,7 +132,11 @@
                     let staged_apex_info = pm.getStagedApexInfo(&apex_info.name)?;
                     if let Some(staged_apex_info) = staged_apex_info {
                         apex_info.path = PathBuf::from(staged_apex_info.diskImagePath);
-                        // TODO(b/201788989) copy bootclasspath/systemserverclasspath
+                        apex_info.boot_classpath = staged_apex_info.hasBootClassPathJars;
+                        apex_info.systemserver_classpath =
+                            staged_apex_info.hasSystemServerClassPathJars;
+                        apex_info.dex2oatboot_classpath =
+                            staged_apex_info.hasDex2OatBootClassPathJars;
                     }
                 }
             }