Allow client to pass a file descriptor for VM logs.

Bug: 180893082
Test: Ran vm tool on VIM3L
Test: atest VirtualizationTestCases
Change-Id: I6c7729eb01d953559e1ddb0b5eb84655a84159a8
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);
     }