| // Copyright 2021, The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! Android VM control tool. |
| |
| mod sync; |
| |
| use android_system_virtmanager::aidl::android::system::virtmanager::IVirtManager::IVirtManager; |
| use android_system_virtmanager::aidl::android::system::virtmanager::IVirtualMachine::IVirtualMachine; |
| use android_system_virtmanager::aidl::android::system::virtmanager::IVirtualMachineCallback::{ |
| BnVirtualMachineCallback, IVirtualMachineCallback, |
| }; |
| use android_system_virtmanager::binder::{ |
| get_interface, BinderFeatures, DeathRecipient, IBinder, ParcelFileDescriptor, ProcessState, |
| Strong, |
| }; |
| use android_system_virtmanager::binder::{Interface, Result as BinderResult}; |
| use anyhow::{Context, Error}; |
| use std::fs::File; |
| use std::io; |
| use std::os::unix::io::{AsRawFd, FromRawFd}; |
| use std::path::{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, |
| |
| /// 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, |
| } |
| |
| fn main() -> Result<(), Error> { |
| env_logger::init(); |
| 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(); |
| |
| let virt_manager = get_interface(VIRT_MANAGER_BINDER_SERVICE_IDENTIFIER) |
| .context("Failed to find Virt Manager service")?; |
| |
| match opt { |
| 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: &Path, |
| daemonize: bool, |
| ) -> Result<(), Error> { |
| let config_filename = config_path.to_str().context("Failed to parse VM config path")?; |
| let config_file = ParcelFileDescriptor::new( |
| File::open(config_filename).context("Failed to open config file")?, |
| ); |
| let stdout_file = ParcelFileDescriptor::new(duplicate_stdout()?); |
| let stdout = if daemonize { None } else { Some(&stdout_file) }; |
| let vm = virt_manager.startVm(&config_file, stdout).context("Failed to start VM")?; |
| |
| let cid = vm.getCid().context("Failed to get CID")?; |
| println!("Started VM from {} with CID {}.", config_filename, cid); |
| |
| 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 or VirtManager dies. If we just returned immediately then the |
| // IVirtualMachine Binder object would be dropped and the VM would be killed. |
| wait_for_vm(vm) |
| } |
| } |
| |
| /// Wait until the given VM or the VirtManager itself dies. |
| fn wait_for_vm(vm: Strong<dyn IVirtualMachine>) -> Result<(), Error> { |
| let dead = AtomicFlag::default(); |
| let callback = BnVirtualMachineCallback::new_binder( |
| VirtualMachineCallback { dead: dead.clone() }, |
| BinderFeatures::default(), |
| ); |
| vm.registerCallback(&callback)?; |
| let death_recipient = wait_for_death(&mut vm.as_binder(), dead.clone())?; |
| dead.wait(); |
| // Ensure that death_recipient isn't dropped before we wait on the flag, as it is removed |
| // from the Binder when it's dropped. |
| drop(death_recipient); |
| 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(()) |
| } |
| |
| /// List the VMs currently running. |
| fn command_list(virt_manager: Strong<dyn IVirtManager>) -> Result<(), Error> { |
| let vms = virt_manager.debugListVms().context("Failed to get list of VMs")?; |
| println!("Running VMs: {:#?}", vms); |
| Ok(()) |
| } |
| |
| /// Raise the given flag when the given Binder object dies. |
| /// |
| /// If the returned DeathRecipient is dropped then this will no longer do anything. |
| fn wait_for_death(binder: &mut impl IBinder, dead: AtomicFlag) -> Result<DeathRecipient, Error> { |
| let mut death_recipient = DeathRecipient::new(move || { |
| println!("VirtManager died"); |
| dead.raise(); |
| }); |
| binder.link_to_death(&mut death_recipient)?; |
| Ok(death_recipient) |
| } |
| |
| #[derive(Debug)] |
| struct VirtualMachineCallback { |
| dead: AtomicFlag, |
| } |
| |
| impl Interface for VirtualMachineCallback {} |
| |
| impl IVirtualMachineCallback for VirtualMachineCallback { |
| fn onDied(&self, _cid: i32) -> BinderResult<()> { |
| println!("VM died"); |
| self.dead.raise(); |
| 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) }) |
| } |
| } |