Add `vm console` command to connect to serial console
`vm console` automatically connects to the first available VM.
`vm console CID` connects to the specified VM.
* Must also pass the `-t` flag to adb-shell to ensure adbd allocates a
tty.
Bug: 335362012
Test: Launch FC and connect to serial console
adb shell -t /apex/com.android.virt/bin/vm console
Change-Id: If5f1537d8994593ab7fa026bf98986c6a8c83cb5
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index 43f3db0..b6f811e 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -1214,6 +1214,9 @@
service.createVm(vmConfigParcel, consoleOutFd, consoleInFd, mLogWriter);
mVirtualMachine.registerCallback(new CallbackTranslator(service));
mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
+ if (mConnectVmConsole) {
+ mVirtualMachine.setHostConsoleName(getHostConsoleName());
+ }
mVirtualMachine.start();
} catch (IOException e) {
throw new VirtualMachineException("failed to persist files", e);
@@ -1335,7 +1338,7 @@
* @hide
*/
@NonNull
- public String getHostConsoleName() throws VirtualMachineException {
+ private String getHostConsoleName() throws VirtualMachineException {
if (!mConnectVmConsole) {
throw new VirtualMachineException("Host console is not enabled");
}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 8e1b6bb..3edefcb 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -1229,6 +1229,10 @@
.or_service_specific_exception(-1)?;
Ok(vsock_stream_to_pfd(stream))
}
+
+ fn setHostConsoleName(&self, ptsname: &str) -> binder::Result<()> {
+ self.instance.vm_context.global_context.setHostConsoleName(ptsname)
+ }
}
impl Drop for VirtualMachine {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 7769f61..637fe31 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -283,7 +283,7 @@
#[derive(Debug)]
pub struct VmContext {
#[allow(dead_code)] // Keeps the global context alive
- global_context: Strong<dyn IGlobalVmContext>,
+ pub(crate) global_context: Strong<dyn IGlobalVmContext>,
#[allow(dead_code)] // Keeps the server alive
vm_server: RpcServer,
}
@@ -302,7 +302,7 @@
pub vm_state: Mutex<VmState>,
/// Global resources allocated for this VM.
#[allow(dead_code)] // Keeps the context alive
- vm_context: VmContext,
+ pub(crate) vm_context: VmContext,
/// The CID assigned to the VM for vsock communication.
pub cid: Cid,
/// Path to crosvm control socket
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
index d76b586..d4001c8 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
@@ -47,4 +47,7 @@
/** Open a vsock connection to the CID of the VM on the given port. */
ParcelFileDescriptor connectVsock(int port);
+
+ /** Set the name of the peer end (ptsname) of the host console. */
+ void setHostConsoleName(in @utf8InCpp String pathname);
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
index 870a342..9f033b1 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
@@ -33,4 +33,7 @@
* the PID may have been reused for a different process, so this should not be trusted.
*/
int requesterPid;
+
+ /** The peer end (ptsname) of the host console. */
+ @nullable @utf8InCpp String hostConsoleName;
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
index a4d5d19..ea52591 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IGlobalVmContext.aidl
@@ -21,4 +21,7 @@
/** Get the path to the temporary folder of the VM. */
String getTemporaryDirectory();
+
+ /** Set the name of the peer end (ptsname) of the host console. */
+ void setHostConsoleName(@utf8InCpp String pathname);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index f69cad4..0b11416 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -287,11 +287,15 @@
.held_contexts
.iter()
.filter_map(|(_, inst)| Weak::upgrade(inst))
- .map(|vm| VirtualMachineDebugInfo {
- cid: vm.cid as i32,
- temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
- requesterUid: vm.requester_uid as i32,
- requesterPid: vm.requester_debug_pid,
+ .map(|vm| {
+ let vm = vm.lock().unwrap();
+ VirtualMachineDebugInfo {
+ cid: vm.cid as i32,
+ temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
+ requesterUid: vm.requester_uid as i32,
+ requesterPid: vm.requester_debug_pid,
+ hostConsoleName: vm.host_console_name.clone(),
+ }
})
.collect();
Ok(cids)
@@ -660,6 +664,8 @@
requester_uid: uid_t,
/// PID of the client who requested this VM instance.
requester_debug_pid: pid_t,
+ /// Name of the host console.
+ host_console_name: Option<String>,
}
impl GlobalVmInstance {
@@ -674,7 +680,7 @@
struct GlobalState {
/// VM contexts currently allocated to running VMs. A CID is never recycled as long
/// as there is a strong reference held by a GlobalVmContext.
- held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
+ held_contexts: HashMap<Cid, Weak<Mutex<GlobalVmInstance>>>,
/// Cached read-only FD of VM DTBO file. Also serves as a lock for creating the file.
dtbo_file: Mutex<Option<File>>,
@@ -754,8 +760,13 @@
self.held_contexts.retain(|_, instance| instance.strong_count() > 0);
let cid = self.get_next_available_cid()?;
- let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
- create_temporary_directory(&instance.get_temp_dir(), Some(requester_uid))?;
+ let instance = Arc::new(Mutex::new(GlobalVmInstance {
+ cid,
+ requester_uid,
+ requester_debug_pid,
+ ..Default::default()
+ }));
+ create_temporary_directory(&instance.lock().unwrap().get_temp_dir(), Some(requester_uid))?;
self.held_contexts.insert(cid, Arc::downgrade(&instance));
let binder = GlobalVmContext { instance, ..Default::default() };
@@ -835,7 +846,7 @@
#[derive(Debug, Default)]
struct GlobalVmContext {
/// Strong reference to the context's instance data structure.
- instance: Arc<GlobalVmInstance>,
+ instance: Arc<Mutex<GlobalVmInstance>>,
/// Keeps our service process running as long as this VM context exists.
#[allow(dead_code)]
lazy_service_guard: LazyServiceGuard,
@@ -845,11 +856,16 @@
impl IGlobalVmContext for GlobalVmContext {
fn getCid(&self) -> binder::Result<i32> {
- Ok(self.instance.cid as i32)
+ Ok(self.instance.lock().unwrap().cid as i32)
}
fn getTemporaryDirectory(&self) -> binder::Result<String> {
- Ok(self.instance.get_temp_dir().to_string_lossy().to_string())
+ Ok(self.instance.lock().unwrap().get_temp_dir().to_string_lossy().to_string())
+ }
+
+ fn setHostConsoleName(&self, pathname: &str) -> binder::Result<()> {
+ self.instance.lock().unwrap().host_console_name = Some(pathname.to_string());
+ Ok(())
}
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index a250c35..3c0887c 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -24,15 +24,18 @@
};
#[cfg(not(llpvm_changes))]
use anyhow::anyhow;
-use anyhow::{Context, Error};
+use anyhow::{bail, Context, Error};
use binder::{ProcessState, Strong};
use clap::{Args, Parser};
use create_idsig::command_create_idsig;
use create_partition::command_create_partition;
use run::{command_run, command_run_app, command_run_microdroid};
use serde::Serialize;
+use std::io::{self, IsTerminal};
use std::num::NonZeroU16;
+use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
+use std::process::Command;
#[derive(Args, Default)]
/// Collection of flags that are at VM level and therefore applicable to all subcommands
@@ -324,6 +327,11 @@
/// Path to idsig of the APK
path: PathBuf,
},
+ /// Connect to the serial console of a VM
+ Console {
+ /// CID of the VM
+ cid: Option<i32>,
+ },
}
fn parse_debug_level(s: &str) -> Result<DebugLevel, String> {
@@ -386,6 +394,7 @@
Opt::CreateIdsig { apk, path } => {
command_create_idsig(get_service()?.as_ref(), &apk, &path)
}
+ Opt::Console { cid } => command_console(cid),
}
}
@@ -450,6 +459,21 @@
Ok(())
}
+fn command_console(cid: Option<i32>) -> Result<(), Error> {
+ if !io::stdin().is_terminal() {
+ bail!("Stdin must be a terminal (tty). Use 'adb shell -t' to force allocate tty.");
+ }
+ let mut vms = get_service()?.debugListVms().context("Failed to get list of VMs")?;
+ if let Some(cid) = cid {
+ vms.retain(|vm_info| vm_info.cid == cid);
+ }
+ let host_console_name = vms
+ .into_iter()
+ .find_map(|vm_info| vm_info.hostConsoleName)
+ .context("Failed to get VM with console")?;
+ Err(Command::new("microcom").arg(host_console_name).exec().into())
+}
+
#[cfg(test)]
mod tests {
use super::*;