Merge "authfs: Integration test"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index a6b1f95..00f34b9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,6 +4,7 @@
 bpfmt = true
 clang_format = true
 jsonlint = true
+google_java_format = true
 pylint3 = true
 rustfmt = true
 xmllint = true
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 3eee6f4..e818420 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -37,6 +37,7 @@
     use_avb: true,
     avb_private_key: "microdroid.pem",
     avb_algorithm: "SHA256_RSA4096",
+    partition_name: "system",
     deps: [
         "init_second_stage",
         "microdroid_init_rc",
@@ -162,6 +163,8 @@
     dtb_prebuilt: "dummy_dtb.img",
     header_version: "4",
     partition_name: "boot",
+    use_avb: true,
+    avb_private_key: "microdroid.pem",
 }
 
 android_filesystem {
@@ -189,6 +192,8 @@
     header_version: "4",
     vendor_boot: true,
     partition_name: "vendor_boot",
+    use_avb: true,
+    avb_private_key: "microdroid.pem",
 }
 
 android_filesystem {
@@ -287,3 +292,34 @@
     relative_install_path: "selinux",
     installable: false,
 }
+
+vbmeta {
+    name: "microdroid_vbmeta",
+    partition_name: "vbmeta",
+    private_key: "microdroid.pem",
+    partitions: [
+        "microdroid_vendor",
+        "microdroid_vendor_boot-5.10",
+    ],
+    chained_partitions: [
+        {
+            name: "vbmeta_system",
+            rollback_index_location: 1,
+            private_key: "microdroid.pem",
+        },
+        {
+            name: "boot",
+            rollback_index_location: 2,
+            private_key: "microdroid.pem",
+        },
+    ],
+}
+
+vbmeta {
+    name: "microdroid_vbmeta_system",
+    partition_name: "vbmeta_system",
+    private_key: "microdroid.pem",
+    partitions: [
+        "microdroid",
+    ],
+}
diff --git a/microdroid/README.md b/microdroid/README.md
index fe0843f..363d623 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -22,6 +22,8 @@
 $ m microdroid_boot-5.10
 $ m microdroid_vendor_boot-5.10
 $ m microdroid_uboot_env
+$ m microdroid_vbmeta
+$ m microdroid_vbmeta_system
 ```
 
 ## Installing
@@ -34,6 +36,8 @@
 $ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_super.img /data/local/tmp/super.img
 $ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_boot-5.10.img /data/local/tmp/boot.img
 $ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_vendor_boot-5.10.img /data/local/tmp/vendor_boot.img
+$ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_vbmeta.img /data/local/tmp/vbmeta.img
+$ adb push $ANDROID_PRODUCT_OUT/system/etc/microdroid_vbmeta_system.img /data/local/tmp/vbmeta_system.img
 $ adb shell mkdir /data/local/tmp/cuttlefish_runtime.1/
 $ adb push $ANDROID_PRODUCT_OUT/system/etc/uboot_env.img /data/local/tmp/cuttlefish_runtime.1/
 $ adb shell mkdir -p /data/local/tmp/etc/cvd_config
@@ -41,8 +45,6 @@
 $ dd if=/dev/zero of=empty.img bs=4k count=600
 $ mkfs.ext4 -F empty.img
 $ adb push empty.img /data/local/tmp/userdata.img
-$ adb push empty.img /data/local/tmp/vbmeta.img
-$ adb push empty.img /data/local/tmp/vbmeta_system.img
 $ adb push empty.img /data/local/tmp/cache.img
 ```
 
@@ -52,7 +54,7 @@
 future, this shall be done via [`virtmanager`](../virtmanager/).
 
 ```
-$ adb shell 'HOME=/data/local/tmp; PATH=$PATH:/apex/com.android.virt/bin; assemble_cvd < /dev/null'
+$ adb shell 'HOME=/data/local/tmp; PATH=$PATH:/apex/com.android.virt/bin; assemble_cvd -protected_vm < /dev/null'
 $ adb shell 'cd /data/local/tmp; /apex/com.android.virt/bin/crosvm run --cid=5 --disable-sandbox --bios=bootloader --serial=type=stdout --disk=cuttlefish_runtime/composite.img'
 ```
 
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index e07459a..c030e8d 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -14,6 +14,8 @@
         ":microdroid_uboot_env",
         ":cuttlefish_crosvm_bootloader",
         ":MicrodroidHostTestCase_EmptyImage",
+        ":microdroid_vbmeta",
+        ":microdroid_vbmeta_system",
     ],
 }
 
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 7a45a8c..32b3c11 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -75,8 +75,8 @@
         pushFile("microdroid_vendor_boot-5.10.img", "vendor_boot.img");
         pushFile("uboot_env.img", "cuttlefish_runtime.1/uboot_env.img");
         pushFile("empty.img", "userdata.img");
-        pushFile("empty.img", "vbmeta.img");
-        pushFile("empty.img", "vbmeta_system.img");
+        pushFile("microdroid_vbmeta.img", "vbmeta.img");
+        pushFile("microdroid_vbmeta_system.img", "vbmeta_system.img");
         pushFile("empty.img", "cache.img");
         getDevice().executeShellCommand("mkdir -p " + TEST_ROOT + "etc/cvd_config");
         getDevice().pushString("{}", TEST_ROOT + "etc/cvd_config/cvd_config_phone.json");
@@ -84,7 +84,7 @@
         // Run assemble_cvd to create composite.img
         getDevice().executeShellCommand("HOME=" + TEST_ROOT + "; "
                 + "PATH=$PATH:" + VIRT_APEX + "bin; "
-                + VIRT_APEX + "bin/assemble_cvd < /dev/null");
+                + VIRT_APEX + "bin/assemble_cvd -protected_vm < /dev/null");
 
         // Make sure that composite.img is created
         final String compositeImg = TEST_ROOT + "cuttlefish_runtime/composite.img";
diff --git a/tests/vsock_test.cc b/tests/vsock_test.cc
index 74e984f..57a03ca 100644
--- a/tests/vsock_test.cc
+++ b/tests/vsock_test.cc
@@ -21,6 +21,7 @@
 #include <linux/vm_sockets.h>
 
 #include <iostream>
+#include <optional>
 
 #include "android-base/file.h"
 #include "android-base/logging.h"
@@ -57,7 +58,7 @@
     ASSERT_EQ(ret, 0) << strerror(errno);
 
     sp<IVirtualMachine> vm;
-    status = mVirtManager->startVm(String16(kVmConfigPath), &vm);
+    status = mVirtManager->startVm(String16(kVmConfigPath), std::nullopt, &vm);
     ASSERT_TRUE(status.isOk()) << "Error starting VM: " << status;
 
     int32_t cid;
diff --git a/virtmanager/Android.bp b/virtmanager/Android.bp
index 5ff5db4..9fc4f42 100644
--- a/virtmanager/Android.bp
+++ b/virtmanager/Android.bp
@@ -9,7 +9,7 @@
     edition: "2018",
     rustlibs: [
         "android.system.virtmanager-rust",
-        "libenv_logger",
+        "libandroid_logger",
         "liblog_rust",
         "libserde_json",
         "libserde",
diff --git a/virtmanager/aidl/android/system/virtmanager/IVirtManager.aidl b/virtmanager/aidl/android/system/virtmanager/IVirtManager.aidl
index a401fe6..79010da 100644
--- a/virtmanager/aidl/android/system/virtmanager/IVirtManager.aidl
+++ b/virtmanager/aidl/android/system/virtmanager/IVirtManager.aidl
@@ -19,8 +19,11 @@
 import android.system.virtmanager.VirtualMachineDebugInfo;
 
 interface IVirtManager {
-    /** Start the VM with the given config file, and return a handle to it. */
-    IVirtualMachine startVm(String configPath);
+    /**
+     * Start the VM with the given config file, and return a handle to it. If `logFd` is provided
+     * then console logs from the VM will be sent to it.
+     */
+    IVirtualMachine startVm(String configPath, in @nullable ParcelFileDescriptor logFd);
 
     /**
      * Get a list of all currently running VMs. This method is only intended for debug purposes,
diff --git a/virtmanager/src/aidl.rs b/virtmanager/src/aidl.rs
index b7595a9..8105051 100644
--- a/virtmanager/src/aidl.rs
+++ b/virtmanager/src/aidl.rs
@@ -22,8 +22,11 @@
     BnVirtualMachine, IVirtualMachine,
 };
 use android_system_virtmanager::aidl::android::system::virtmanager::VirtualMachineDebugInfo::VirtualMachineDebugInfo;
-use android_system_virtmanager::binder::{self, Interface, StatusCode, Strong, ThreadState};
+use android_system_virtmanager::binder::{
+    self, Interface, ParcelFileDescriptor, StatusCode, Strong, ThreadState,
+};
 use log::error;
+use std::fs::File;
 use std::sync::{Arc, Mutex, Weak};
 
 pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtmanager";
@@ -44,10 +47,17 @@
     /// Create and start a new VM with the given configuration, assigning it the next available CID.
     ///
     /// Returns a binder `IVirtualMachine` object referring to it, as a handle for the client.
-    fn startVm(&self, config_path: &str) -> binder::Result<Strong<dyn IVirtualMachine>> {
+    fn startVm(
+        &self,
+        config_path: &str,
+        log_fd: Option<&ParcelFileDescriptor>,
+    ) -> binder::Result<Strong<dyn IVirtualMachine>> {
         let state = &mut *self.state.lock().unwrap();
         let cid = state.next_cid;
-        let instance = Arc::new(start_vm(config_path, cid)?);
+        let log_fd = log_fd
+            .map(|fd| fd.as_ref().try_clone().map_err(|_| StatusCode::UNKNOWN_ERROR))
+            .transpose()?;
+        let instance = Arc::new(start_vm(config_path, cid, log_fd)?);
         // TODO(qwandor): keep track of which CIDs are currently in use so that we can reuse them.
         state.next_cid = state.next_cid.checked_add(1).ok_or(StatusCode::UNKNOWN_ERROR)?;
         state.add_vm(Arc::downgrade(&instance));
@@ -140,12 +150,12 @@
 
 /// Start a new VM instance from the given VM config filename. This assumes the VM is not already
 /// running.
-fn start_vm(config_path: &str, cid: Cid) -> binder::Result<VmInstance> {
+fn start_vm(config_path: &str, cid: Cid, log_fd: Option<File>) -> binder::Result<VmInstance> {
     let config = VmConfig::load(config_path).map_err(|e| {
         error!("Failed to load VM config {}: {:?}", config_path, e);
         StatusCode::BAD_VALUE
     })?;
-    Ok(VmInstance::start(&config, cid, config_path).map_err(|e| {
+    Ok(VmInstance::start(&config, cid, config_path, log_fd).map_err(|e| {
         error!("Failed to start VM {}: {:?}", config_path, e);
         StatusCode::UNKNOWN_ERROR
     })?)
diff --git a/virtmanager/src/crosvm.rs b/virtmanager/src/crosvm.rs
index 4ae1fcd..814a1a7 100644
--- a/virtmanager/src/crosvm.rs
+++ b/virtmanager/src/crosvm.rs
@@ -18,6 +18,7 @@
 use crate::Cid;
 use anyhow::Error;
 use log::{debug, error, info};
+use std::fs::File;
 use std::process::{Child, Command};
 
 const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
@@ -42,8 +43,13 @@
 
     /// Start an instance of `crosvm` to manage a new VM. The `crosvm` instance will be killed when
     /// the `VmInstance` is dropped.
-    pub fn start(config: &VmConfig, cid: Cid, config_path: &str) -> Result<VmInstance, Error> {
-        let child = run_vm(config, cid)?;
+    pub fn start(
+        config: &VmConfig,
+        cid: Cid,
+        config_path: &str,
+        log_fd: Option<File>,
+    ) -> Result<VmInstance, Error> {
+        let child = run_vm(config, cid, log_fd)?;
         Ok(VmInstance::new(child, cid, config_path))
     }
 }
@@ -64,14 +70,18 @@
 }
 
 /// Start an instance of `crosvm` to manage a new VM.
-fn run_vm(config: &VmConfig, cid: Cid) -> Result<Child, Error> {
+fn run_vm(config: &VmConfig, cid: Cid, log_fd: Option<File>) -> Result<Child, Error> {
     config.validate()?;
 
     let mut command = Command::new(CROSVM_PATH);
     // TODO(qwandor): Remove --disable-sandbox.
     command.arg("run").arg("--disable-sandbox").arg("--cid").arg(cid.to_string());
-    // TODO(jiyong): Don't redirect console to the host syslog
-    command.arg("--serial=type=syslog");
+    if let Some(log_fd) = log_fd {
+        command.stdout(log_fd);
+    } else {
+        // Ignore console output.
+        command.arg("--serial=type=sink");
+    }
     if let Some(bootloader) = &config.bootloader {
         command.arg("--bios").arg(bootloader);
     }
diff --git a/virtmanager/src/main.rs b/virtmanager/src/main.rs
index 7cca4a9..486efeb 100644
--- a/virtmanager/src/main.rs
+++ b/virtmanager/src/main.rs
@@ -21,17 +21,22 @@
 use crate::aidl::{VirtManager, BINDER_SERVICE_IDENTIFIER};
 use android_system_virtmanager::aidl::android::system::virtmanager::IVirtManager::BnVirtManager;
 use android_system_virtmanager::binder::{add_service, ProcessState};
-use log::info;
+use log::{info, Level};
 
 /// The first CID to assign to a guest VM managed by the Virt Manager. CIDs lower than this are
 /// reserved for the host or other usage.
 const FIRST_GUEST_CID: Cid = 10;
 
+const LOG_TAG: &str = "VirtManager";
+
 /// The unique ID of a VM used (together with a port number) for vsock communication.
 type Cid = u32;
 
 fn main() {
-    env_logger::init();
+    android_logger::init_once(
+        android_logger::Config::default().with_tag(LOG_TAG).with_min_level(Level::Trace),
+    );
+
     let virt_manager = VirtManager::default();
     let virt_manager = BnVirtManager::new_binder(virt_manager);
     add_service(BINDER_SERVICE_IDENTIFIER, virt_manager.as_binder()).unwrap();
diff --git a/vm/Android.bp b/vm/Android.bp
index 0de6cae..248af4d 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -12,7 +12,9 @@
         "libanyhow",
         "libbinder_rs",
         "libenv_logger",
+        "liblibc",
         "liblog_rust",
+        "libstructopt",
     ],
     apex_available: [
         "com.android.virt",
diff --git a/vm/src/main.rs b/vm/src/main.rs
index df375e4..34031f7 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -17,27 +17,39 @@
 mod sync;
 
 use android_system_virtmanager::aidl::android::system::virtmanager::IVirtManager::IVirtManager;
-use android_system_virtmanager::binder::{get_interface, ProcessState, Strong};
-use anyhow::{bail, Context, Error};
+use android_system_virtmanager::binder::{
+    get_interface, ParcelFileDescriptor, ProcessState, Strong,
+};
+use anyhow::{Context, Error};
 // TODO: Import these via android_system_virtmanager::binder once https://r.android.com/1619403 is
 // submitted.
 use binder::{DeathRecipient, IBinder};
-use std::env;
-use std::process::exit;
+use std::fs::File;
+use std::io;
+use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::path::PathBuf;
+use structopt::clap::AppSettings;
+use structopt::StructOpt;
 use sync::AtomicFlag;
 
 const VIRT_MANAGER_BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtmanager";
 
+#[derive(StructOpt)]
+#[structopt(no_version, global_settings = &[AppSettings::DisableVersion])]
+enum Opt {
+    /// Run a virtual machine
+    Run {
+        /// Path to VM config JSON
+        #[structopt(parse(from_os_str))]
+        config: PathBuf,
+    },
+    /// List running virtual machines
+    List,
+}
+
 fn main() -> Result<(), Error> {
     env_logger::init();
-
-    let args: Vec<_> = env::args().collect();
-    if args.len() < 2 {
-        eprintln!("Usage:");
-        eprintln!("  {} run <vm_config.json>", args[0]);
-        eprintln!("  {} list", args[0]);
-        exit(1);
-    }
+    let opt = Opt::from_args();
 
     // We need to start the thread pool for Binder to work properly, especially link_to_death.
     ProcessState::start_thread_pool();
@@ -45,16 +57,18 @@
     let virt_manager = get_interface(VIRT_MANAGER_BINDER_SERVICE_IDENTIFIER)
         .context("Failed to find Virt Manager service")?;
 
-    match args[1].as_ref() {
-        "run" if args.len() == 3 => command_run(virt_manager, &args[2]),
-        "list" if args.len() == 2 => command_list(virt_manager),
-        command => bail!("Invalid command '{}' or wrong number of arguments", command),
+    match opt {
+        Opt::Run { config } => command_run(virt_manager, &config),
+        Opt::List => command_list(virt_manager),
     }
 }
 
 /// Run a VM from the given configuration file.
-fn command_run(virt_manager: Strong<dyn IVirtManager>, config_filename: &str) -> Result<(), Error> {
-    let vm = virt_manager.startVm(config_filename).context("Failed to start VM")?;
+fn command_run(virt_manager: Strong<dyn IVirtManager>, config_path: &PathBuf) -> Result<(), Error> {
+    let config_filename = config_path.to_str().context("Failed to parse VM config path")?;
+    let stdout_file = ParcelFileDescriptor::new(duplicate_stdout()?);
+    let vm =
+        virt_manager.startVm(config_filename, Some(&stdout_file)).context("Failed to start VM")?;
     let cid = vm.getCid().context("Failed to get CID")?;
     println!("Started VM from {} with CID {}.", config_filename, cid);
 
@@ -85,3 +99,18 @@
     dead.wait();
     Ok(())
 }
+
+/// Safely duplicate the standard output file descriptor.
+fn duplicate_stdout() -> io::Result<File> {
+    let stdout_fd = io::stdout().as_raw_fd();
+    // Safe because this just duplicates a file descriptor which we know to be valid, and we check
+    // for an error.
+    let dup_fd = unsafe { libc::dup(stdout_fd) };
+    if dup_fd < 0 {
+        Err(io::Error::last_os_error())
+    } else {
+        // Safe because we have just duplicated the file descriptor so we own it, and `from_raw_fd`
+        // takes ownership of it.
+        Ok(unsafe { File::from_raw_fd(dup_fd) })
+    }
+}