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/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/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/vm/Android.bp b/vm/Android.bp
index 0de6cae..5089e39 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -12,6 +12,7 @@
"libanyhow",
"libbinder_rs",
"libenv_logger",
+ "liblibc",
"liblog_rust",
],
apex_available: [
diff --git a/vm/src/main.rs b/vm/src/main.rs
index df375e4..96ec649 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -17,12 +17,17 @@
mod sync;
use android_system_virtmanager::aidl::android::system::virtmanager::IVirtManager::IVirtManager;
-use android_system_virtmanager::binder::{get_interface, ProcessState, Strong};
+use android_system_virtmanager::binder::{
+ get_interface, ParcelFileDescriptor, ProcessState, Strong,
+};
use anyhow::{bail, 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::fs::File;
+use std::io;
+use std::os::unix::io::{AsRawFd, FromRawFd};
use std::process::exit;
use sync::AtomicFlag;
@@ -54,7 +59,9 @@
/// 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")?;
+ 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 +92,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) })
+ }
+}