Add VM ref holding to Virt Manager
So as to support running VMs from `adb shell` without a persistent
session (closing the shell), add two new methods to Virt Manager AIDL:
debugVmHoldRef and debugVmDropRef. The former will accept a strong VM
reference from the user and store it inside Virt Manager. The latter
can be used to retrieve the reference by CID and will make Virt Manager
drop it. Both are limited to the shell user.
On the `vm` tool side, a new '-d/--daemonize' flag is added to the
'vm run' command which calls debugVmHoldRef after a VM is created and
exits. 'vm stop <cid>' command is added to allow killing a previously
daemonized VM.
Bug: 182254712
Test: vm run -d <json>; vm list; vm drop <cid>
Change-Id: Ic7198b3fc7a19688fca762ec04dd2430a7626222
diff --git a/virtmanager/Android.bp b/virtmanager/Android.bp
index 9fc4f42..1b2aec1 100644
--- a/virtmanager/Android.bp
+++ b/virtmanager/Android.bp
@@ -10,6 +10,7 @@
rustlibs: [
"android.system.virtmanager-rust",
"libandroid_logger",
+ "libbinder_rs", // TODO(dbrazdil): remove once b/182890877 is fixed
"liblog_rust",
"libserde_json",
"libserde",
diff --git a/virtmanager/aidl/android/system/virtmanager/IVirtManager.aidl b/virtmanager/aidl/android/system/virtmanager/IVirtManager.aidl
index 79010da..ab03c18 100644
--- a/virtmanager/aidl/android/system/virtmanager/IVirtManager.aidl
+++ b/virtmanager/aidl/android/system/virtmanager/IVirtManager.aidl
@@ -30,4 +30,17 @@
* and as such is only permitted from the shell user.
*/
VirtualMachineDebugInfo[] debugListVms();
+
+ /**
+ * Hold a strong reference to a VM in Virt Manager. This method is only intended for debug
+ * purposes, and as such is only permitted from the shell user.
+ */
+ void debugHoldVmRef(IVirtualMachine vm);
+
+ /**
+ * Drop reference to a VM that is being held by Virt Manager. Returns the reference if VM was
+ * found and null otherwise. This method is only intended for debug purposes, and as such is
+ * only permitted from the shell user.
+ */
+ @nullable IVirtualMachine debugDropVmRef(int cid);
}
diff --git a/virtmanager/src/aidl.rs b/virtmanager/src/aidl.rs
index 8105051..8c963d2 100644
--- a/virtmanager/src/aidl.rs
+++ b/virtmanager/src/aidl.rs
@@ -17,6 +17,7 @@
use crate::config::VmConfig;
use crate::crosvm::VmInstance;
use crate::{Cid, FIRST_GUEST_CID};
+use ::binder::FromIBinder; // TODO(dbrazdil): remove once b/182890877 is fixed
use android_system_virtmanager::aidl::android::system::virtmanager::IVirtManager::IVirtManager;
use android_system_virtmanager::aidl::android::system::virtmanager::IVirtualMachine::{
BnVirtualMachine, IVirtualMachine,
@@ -82,6 +83,33 @@
.collect();
Ok(cids)
}
+
+ /// Hold a strong reference to a VM in Virt Manager. This method is only intended for debug
+ /// purposes, and as such is only permitted from the shell user.
+ fn debugHoldVmRef(&self, vmref: &dyn IVirtualMachine) -> binder::Result<()> {
+ if !debug_access_allowed() {
+ return Err(StatusCode::PERMISSION_DENIED.into());
+ }
+
+ // Workaround for b/182890877.
+ let vm: Strong<dyn IVirtualMachine> = FromIBinder::try_from(vmref.as_binder()).unwrap();
+
+ let state = &mut *self.state.lock().unwrap();
+ state.debug_hold_vm(vm);
+ Ok(())
+ }
+
+ /// Drop reference to a VM that is being held by Virt Manager. Returns the reference if VM was
+ /// found and None otherwise. This method is only intended for debug purposes, and as such is
+ /// only permitted from the shell user.
+ fn debugDropVmRef(&self, cid: i32) -> binder::Result<Option<Strong<dyn IVirtualMachine>>> {
+ if !debug_access_allowed() {
+ return Err(StatusCode::PERMISSION_DENIED.into());
+ }
+
+ let state = &mut *self.state.lock().unwrap();
+ Ok(state.debug_drop_vm(cid))
+ }
}
/// Check whether the caller of the current Binder method is allowed to call debug methods.
@@ -123,6 +151,10 @@
/// Binder client are dropped the weak reference here will become invalid, and will be removed
/// from the list opportunistically the next time `add_vm` is called.
vms: Vec<Weak<VmInstance>>,
+
+ /// Vector of strong VM references held on behalf of users that cannot hold them themselves.
+ /// This is only used for debugging purposes.
+ debug_held_vms: Vec<Strong<dyn IVirtualMachine>>,
}
impl State {
@@ -140,11 +172,22 @@
// Actually add the new VM.
self.vms.push(vm);
}
+
+ /// Store a strong VM reference.
+ fn debug_hold_vm(&mut self, vm: Strong<dyn IVirtualMachine>) {
+ self.debug_held_vms.push(vm);
+ }
+
+ /// Retrieve and remove a strong VM reference.
+ fn debug_drop_vm(&mut self, cid: i32) -> Option<Strong<dyn IVirtualMachine>> {
+ let pos = self.debug_held_vms.iter().position(|vm| vm.getCid() == Ok(cid))?;
+ Some(self.debug_held_vms.swap_remove(pos))
+ }
}
impl Default for State {
fn default() -> Self {
- State { next_cid: FIRST_GUEST_CID, vms: vec![] }
+ State { next_cid: FIRST_GUEST_CID, vms: vec![], debug_held_vms: vec![] }
}
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 34031f7..329c859 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -42,6 +42,15 @@
/// Path to VM config JSON
#[structopt(parse(from_os_str))]
config: PathBuf,
+
+ /// Detach VM from the terminal and run in the background
+ #[structopt(short, long)]
+ daemonize: bool,
+ },
+ /// Stop a virtual machine running in the background
+ Stop {
+ /// CID of the virtual machine
+ cid: u32,
},
/// List running virtual machines
List,
@@ -58,24 +67,44 @@
.context("Failed to find Virt Manager service")?;
match opt {
- Opt::Run { config } => command_run(virt_manager, &config),
+ Opt::Run { config, daemonize } => command_run(virt_manager, &config, daemonize),
+ Opt::Stop { cid } => command_stop(virt_manager, cid),
Opt::List => command_list(virt_manager),
}
}
/// Run a VM from the given configuration file.
-fn command_run(virt_manager: Strong<dyn IVirtManager>, config_path: &PathBuf) -> Result<(), Error> {
+fn command_run(
+ virt_manager: Strong<dyn IVirtManager>,
+ config_path: &PathBuf,
+ daemonize: bool,
+) -> 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 stdout = if daemonize { None } else { Some(&stdout_file) };
+ let vm = virt_manager.startVm(config_filename, stdout).context("Failed to start VM")?;
+
let cid = vm.getCid().context("Failed to get CID")?;
println!("Started VM from {} with CID {}.", config_filename, cid);
- // Wait until the VM dies. If we just returned immediately then the IVirtualMachine Binder
- // object would be dropped and the VM would be killed.
- wait_for_death(&mut vm.as_binder())?;
- println!("VM died");
+ if daemonize {
+ // Pass the VM reference back to Virt Manager and have it hold it in the background.
+ virt_manager.debugHoldVmRef(&*vm).context("Failed to pass VM to Virt Manager")
+ } else {
+ // Wait until the VM dies. If we just returned immediately then the IVirtualMachine Binder
+ // object would be dropped and the VM would be killed.
+ wait_for_death(&mut vm.as_binder())?;
+ println!("VM died");
+ Ok(())
+ }
+}
+
+/// Retrieve reference to a previously daemonized VM and stop it.
+fn command_stop(virt_manager: Strong<dyn IVirtManager>, cid: u32) -> Result<(), Error> {
+ virt_manager
+ .debugDropVmRef(cid as i32)
+ .context("Failed to get VM from Virt Manager")?
+ .context("CID does not correspond to a running background VM")?;
Ok(())
}