Merge "Add command to start an empty Microdroid instance"
diff --git a/apex/Android.bp b/apex/Android.bp
index 4e64e50..596493a 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -104,6 +104,9 @@
     host_required: [
         "vm_shell",
     ],
+    apps: [
+        "EmptyPayloadApp",
+    ],
 }
 
 apex_defaults {
diff --git a/apex/empty-payload-apk/Android.bp b/apex/empty-payload-apk/Android.bp
new file mode 100644
index 0000000..70e6754
--- /dev/null
+++ b/apex/empty-payload-apk/Android.bp
@@ -0,0 +1,26 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "EmptyPayloadApp",
+    installable: true,
+    jni_libs: ["MicrodroidEmptyPayloadJniLib"],
+    apex_available: ["com.android.virt"],
+    sdk_version: "system_current",
+    jni_uses_platform_apis: true,
+    min_sdk_version: "UpsideDownCake",
+    target_sdk_version: "UpsideDownCake",
+    compile_multilib: "first",
+    stl: "none",
+}
+
+cc_library {
+    name: "MicrodroidEmptyPayloadJniLib",
+    srcs: ["empty_binary.cpp"],
+    shared_libs: ["libvm_payload#current"],
+    installable: true,
+    apex_available: ["com.android.virt"],
+    compile_multilib: "first",
+    stl: "none",
+}
diff --git a/apex/empty-payload-apk/AndroidManifest.xml b/apex/empty-payload-apk/AndroidManifest.xml
new file mode 100644
index 0000000..e649744
--- /dev/null
+++ b/apex/empty-payload-apk/AndroidManifest.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.microdroid.empty_payload">
+
+    <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+    <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
+    <application android:testOnly="true" android:hasCode="false" />
+
+</manifest>
diff --git a/apex/empty-payload-apk/empty_binary.cpp b/apex/empty-payload-apk/empty_binary.cpp
new file mode 100644
index 0000000..4308954
--- /dev/null
+++ b/apex/empty-payload-apk/empty_binary.cpp
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <vm_main.h>
+#include <vm_payload.h>
+
+extern "C" int AVmPayload_main() {
+    // disable buffering to communicate seamlessly
+    setvbuf(stdin, nullptr, _IONBF, 0);
+    setvbuf(stdout, nullptr, _IONBF, 0);
+    setvbuf(stderr, nullptr, _IONBF, 0);
+
+    printf("Hello Microdroid\n");
+
+    AVmPayload_notifyPayloadReady();
+
+    // Wait forever to allow developer to interact with Microdroid shell
+    for (;;) {
+        pause();
+    }
+
+    return 0;
+}
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 5f552f9..a15c4c7 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -123,7 +123,16 @@
 ## Spawning your own VMs with Microdroid
 
 [Microdroid](../../microdroid/README.md) is a lightweight version of Android that is intended to run
-on pVM. You can manually run the demo app on top of Microdroid as follows:
+on pVM. You can run a Microdroid with empty payload using the following command:
+
+```shell
+adb shell /apex/com.android.virt/bin/vm run-microdroid --debug full
+```
+
+The `instance.img` and `apk.idsig` files will be stored in a subdirectory under
+`/data/local/tmp/microdroid`, that `vm` will create.
+
+Atlernatively, you can manually run the demo app on top of Microdroid as follows:
 
 ```shell
 UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true TARGET_BUILD_APPS=MicrodroidDemoApp m apps_only dist
diff --git a/vm/Android.bp b/vm/Android.bp
index 7b016d4..95ef082 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -17,6 +17,7 @@
         "liblibc",
         "liblog_rust",
         "libmicrodroid_payload_config",
+        "librand",
         "librustutils",
         "libserde_json",
         "libserde",
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 89d56d4..b5046fb 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -27,7 +27,7 @@
 use clap::Parser;
 use create_idsig::command_create_idsig;
 use create_partition::command_create_partition;
-use run::{command_run, command_run_app};
+use run::{command_run, command_run_app, command_run_microdroid};
 use rustutils::system_properties;
 use std::path::{Path, PathBuf};
 
@@ -110,6 +110,65 @@
         #[clap(long = "extra-idsig")]
         extra_idsigs: Vec<PathBuf>,
     },
+    /// Run a virtual machine with Microdroid inside
+    RunMicrodroid {
+        /// Path to the directory where VM-related files (e.g. instance.img, apk.idsig, etc.) will
+        /// be stored. If not specified a random directory under /data/local/tmp/microdroid will be
+        /// created and used.
+        #[clap(long)]
+        work_dir: Option<PathBuf>,
+
+        /// Name of VM
+        #[clap(long)]
+        name: Option<String>,
+
+        /// Detach VM from the terminal and run in the background
+        #[clap(short, long)]
+        daemonize: bool,
+
+        /// Path to the file backing the storage.
+        /// Created if the option is used but the path does not exist in the device.
+        #[clap(long)]
+        storage: Option<PathBuf>,
+
+        /// Size of the storage. Used only if --storage is supplied but path does not exist
+        /// Default size is 10*1024*1024
+        #[clap(long)]
+        storage_size: Option<u64>,
+
+        /// Path to file for VM console output.
+        #[clap(long)]
+        console: Option<PathBuf>,
+
+        /// Path to file for VM log output.
+        #[clap(long)]
+        log: Option<PathBuf>,
+
+        /// Path to file where ramdump is recorded on kernel panic
+        #[clap(long)]
+        ramdump: Option<PathBuf>,
+
+        /// Debug level of the VM. Supported values: "none" (default), "app_only", and "full".
+        #[clap(long, default_value = "none", value_parser = parse_debug_level)]
+        debug: DebugLevel,
+
+        /// Run VM in protected mode.
+        #[clap(short, long)]
+        protected: bool,
+
+        /// Memory size (in MiB) of the VM. If unspecified, defaults to the value of `memory_mib`
+        /// in the VM config file.
+        #[clap(short, long)]
+        mem: Option<u32>,
+
+        /// Number of vCPUs in the VM. If unspecified, defaults to 1.
+        #[clap(long)]
+        cpus: Option<u32>,
+
+        /// Comma separated list of task profile names to apply to the VM
+        #[clap(long)]
+        task_profiles: Vec<String>,
+    },
     /// Run a virtual machine
     Run {
         /// Path to VM config JSON
@@ -238,6 +297,36 @@
             task_profiles,
             &extra_idsigs,
         ),
+        Opt::RunMicrodroid {
+            name,
+            work_dir,
+            storage,
+            storage_size,
+            daemonize,
+            console,
+            log,
+            ramdump,
+            debug,
+            protected,
+            mem,
+            cpus,
+            task_profiles,
+        } => command_run_microdroid(
+            name,
+            service.as_ref(),
+            work_dir,
+            storage.as_deref(),
+            storage_size,
+            daemonize,
+            console.as_deref(),
+            log.as_deref(),
+            ramdump.as_deref(),
+            debug,
+            protected,
+            mem,
+            cpus,
+            task_profiles,
+        ),
         Opt::Run { name, config, daemonize, cpus, task_profiles, console, log } => {
             command_run(
                 name,
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 01b916b..3f25bba 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -23,13 +23,16 @@
     VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
     VirtualMachineState::VirtualMachineState,
 };
-use anyhow::{bail, Context, Error};
+use anyhow::{anyhow, bail, Context, Error};
 use binder::ParcelFileDescriptor;
 use microdroid_payload_config::VmPayloadConfig;
+use rand::{distributions::Alphanumeric, Rng};
+use std::fs;
 use std::fs::File;
 use std::io;
 use std::os::unix::io::{AsRawFd, FromRawFd};
 use std::path::{Path, PathBuf};
+use std::process::Command;
 use vmclient::{ErrorCode, VmInstance};
 use vmconfig::{open_parcel_file, VmConfig};
 use zip::ZipArchive;
@@ -144,6 +147,83 @@
     run(service, &config, &payload_config_str, daemonize, console_path, log_path, ramdump_path)
 }
 
+const EMPTY_PAYLOAD_APK: &str = "com.android.microdroid.empty_payload";
+
+fn find_empty_payload_apk_path() -> Result<PathBuf, Error> {
+    let output = Command::new("/system/bin/pm")
+        .arg("path")
+        .arg(EMPTY_PAYLOAD_APK)
+        .output()
+        .context("failed to execute pm path")?;
+    let output_str = String::from_utf8(output.stdout).context("failed to parse output")?;
+    match output_str.strip_prefix("package:") {
+        None => Err(anyhow!("Unexpected output {}", output_str)),
+        Some(apk_path) => Ok(PathBuf::from(apk_path.trim())),
+    }
+}
+
+fn create_work_dir() -> Result<PathBuf, Error> {
+    let s: String =
+        rand::thread_rng().sample_iter(&Alphanumeric).take(17).map(char::from).collect();
+    let work_dir = PathBuf::from("/data/local/tmp/microdroid").join(s);
+    println!("creating work dir {}", work_dir.display());
+    fs::create_dir_all(&work_dir).context("failed to mkdir")?;
+    Ok(work_dir)
+}
+
+/// Run a VM with Microdroid
+#[allow(clippy::too_many_arguments)]
+pub fn command_run_microdroid(
+    name: Option<String>,
+    service: &dyn IVirtualizationService,
+    work_dir: Option<PathBuf>,
+    storage: Option<&Path>,
+    storage_size: Option<u64>,
+    daemonize: bool,
+    console_path: Option<&Path>,
+    log_path: Option<&Path>,
+    ramdump_path: Option<&Path>,
+    debug_level: DebugLevel,
+    protected: bool,
+    mem: Option<u32>,
+    cpus: Option<u32>,
+    task_profiles: Vec<String>,
+) -> Result<(), Error> {
+    let apk = find_empty_payload_apk_path()
+        .context(anyhow!("failed to find path for {} apk", EMPTY_PAYLOAD_APK))?;
+    println!("found path for {} apk: {}", EMPTY_PAYLOAD_APK, apk.display());
+
+    let work_dir = work_dir.unwrap_or(create_work_dir()?);
+    let idsig = work_dir.join("apk.idsig");
+    println!("apk.idsig path: {}", idsig.display());
+    let instance_img = work_dir.join("instance.img");
+    println!("instance.img path: {}", instance_img.display());
+
+    let payload_path = "MicrodroidEmptyPayloadJniLib.so";
+    let extra_sig = [];
+    command_run_app(
+        name,
+        service,
+        &apk,
+        &idsig,
+        &instance_img,
+        storage,
+        storage_size,
+        /* config_path= */ None,
+        Some(payload_path.to_owned()),
+        daemonize,
+        console_path,
+        log_path,
+        ramdump_path,
+        debug_level,
+        protected,
+        mem,
+        cpus,
+        task_profiles,
+        &extra_sig,
+    )
+}
+
 /// Run a VM from the given configuration file.
 #[allow(clippy::too_many_arguments)]
 pub fn command_run(