| // 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 Virt Manager |
| |
| use android_system_virtmanager::aidl::android::system::virtmanager::IVirtManager::{ |
| BnVirtManager, IVirtManager, |
| }; |
| use android_system_virtmanager::aidl::android::system::virtmanager::IVirtualMachine::{ |
| BnVirtualMachine, IVirtualMachine, |
| }; |
| use android_system_virtmanager::binder::{self, add_service, Interface, StatusCode, Strong}; |
| use anyhow::{Context, Error}; |
| use log::{debug, error, info}; |
| use serde::{Deserialize, Serialize}; |
| use std::fs::File; |
| use std::io::{self, BufReader}; |
| use std::process::{Child, Command}; |
| use std::sync::{Arc, Mutex}; |
| |
| /// The first CID to assign to a guest VM managed by the Virt Manager. CIDs lower than this are |
| /// reserved for the host or other usage. |
| const FIRST_GUEST_CID: Cid = 10; |
| |
| const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtmanager"; |
| const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm"; |
| |
| /// The unique ID of a VM used (together with a port number) for vsock communication. |
| type Cid = u32; |
| |
| /// Configuration for a particular VM to be started. |
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| struct VmConfig { |
| kernel: String, |
| initrd: Option<String>, |
| params: Option<String>, |
| } |
| |
| fn main() { |
| env_logger::init(); |
| let state = Arc::new(Mutex::new(State::new())); |
| let virt_manager = VirtManager::new(state); |
| let virt_manager = BnVirtManager::new_binder(virt_manager); |
| add_service(BINDER_SERVICE_IDENTIFIER, virt_manager.as_binder()).unwrap(); |
| info!("Registered Binder service, joining threadpool."); |
| binder::ProcessState::join_thread_pool(); |
| } |
| |
| #[derive(Debug)] |
| struct VirtManager { |
| state: Arc<Mutex<State>>, |
| } |
| |
| impl VirtManager { |
| fn new(state: Arc<Mutex<State>>) -> Self { |
| VirtManager { state } |
| } |
| } |
| |
| impl Interface for VirtManager {} |
| |
| impl IVirtManager for VirtManager { |
| /// 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>> { |
| let state = &mut *self.state.lock().unwrap(); |
| let cid = state.next_cid; |
| let child = start_vm(config_path, cid)?; |
| // 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)?; |
| Ok(VirtualMachine::create(Arc::new(VmInstance::new(child, cid)))) |
| } |
| } |
| |
| /// Implementation of the AIDL IVirtualMachine interface. Used as a handle to a VM. |
| #[derive(Debug)] |
| struct VirtualMachine { |
| instance: Arc<VmInstance>, |
| } |
| |
| impl VirtualMachine { |
| fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine> { |
| let binder = VirtualMachine { instance }; |
| BnVirtualMachine::new_binder(binder) |
| } |
| } |
| |
| impl Interface for VirtualMachine {} |
| |
| impl IVirtualMachine for VirtualMachine { |
| fn getCid(&self) -> binder::Result<i32> { |
| Ok(self.instance.cid as i32) |
| } |
| } |
| |
| /// Information about a particular instance of a VM which is running. |
| #[derive(Debug)] |
| struct VmInstance { |
| /// The crosvm child process. |
| child: Child, |
| /// The CID assigned to the VM for vsock communication. |
| cid: Cid, |
| } |
| |
| impl VmInstance { |
| /// Create a new `VmInstance` with a single reference for the given process. |
| fn new(child: Child, cid: Cid) -> VmInstance { |
| VmInstance { child, cid } |
| } |
| } |
| |
| impl Drop for VmInstance { |
| fn drop(&mut self) { |
| debug!("Dropping {:?}", self); |
| // TODO: Talk to crosvm to shutdown cleanly. |
| if let Err(e) = self.child.kill() { |
| error!("Error killing crosvm instance: {}", e); |
| } |
| // We need to wait on the process after killing it to avoid zombies. |
| match self.child.wait() { |
| Err(e) => error!("Error waiting for crosvm instance to die: {}", e), |
| Ok(status) => info!("Crosvm exited with status {}", status), |
| } |
| } |
| } |
| |
| /// The mutable state of the Virt Manager. There should only be one instance of this struct. |
| #[derive(Debug)] |
| struct State { |
| next_cid: Cid, |
| } |
| |
| impl State { |
| fn new() -> Self { |
| State { next_cid: FIRST_GUEST_CID } |
| } |
| } |
| |
| /// 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<Child> { |
| let config = load_vm_config(config_path).map_err(|e| { |
| error!("Failed to load VM config {}: {:?}", config_path, e); |
| StatusCode::BAD_VALUE |
| })?; |
| Ok(run_vm(&config, cid).map_err(|e| { |
| error!("Failed to start VM {}: {:?}", config_path, e); |
| StatusCode::UNKNOWN_ERROR |
| })?) |
| } |
| |
| /// Load the configuration for the VM with the given ID from a JSON file. |
| fn load_vm_config(path: &str) -> Result<VmConfig, Error> { |
| let file = File::open(path).with_context(|| format!("Failed to open {}", path))?; |
| let buffered = BufReader::new(file); |
| Ok(serde_json::from_reader(buffered)?) |
| } |
| |
| /// Start an instance of `crosvm` to manage a new VM. |
| fn run_vm(config: &VmConfig, cid: Cid) -> Result<Child, io::Error> { |
| let mut command = Command::new(CROSVM_PATH); |
| // TODO(qwandor): Remove --disable-sandbox. |
| command.arg("run").arg("--disable-sandbox").arg("--cid").arg(cid.to_string()); |
| if let Some(initrd) = &config.initrd { |
| command.arg("--initrd").arg(initrd); |
| } |
| if let Some(params) = &config.params { |
| command.arg("--params").arg(params); |
| } |
| command.arg(&config.kernel); |
| info!("Running {:?}", command); |
| // TODO: Monitor child process, and remove from VM map if it dies. |
| command.spawn() |
| } |