Build composite image in VirtualizationService.
Bug: 184131523
Test: atest VirtualizationTestCases
Test: ran microdroid manually
Change-Id: I24eb776bb3049a4cdc5f000607447a73bd0adeec
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index ef973d1..c295388 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -14,9 +14,11 @@
//! Implementation of the AIDL interface of the VirtualizationService.
-use crate::crosvm::VmInstance;
+use crate::composite::make_composite_image;
+use crate::crosvm::{CrosvmConfig, DiskFile, VmInstance};
use crate::{Cid, FIRST_GUEST_CID};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DiskImage::DiskImage;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachine::{
BnVirtualMachine, IVirtualMachine,
};
@@ -26,11 +28,18 @@
use android_system_virtualizationservice::binder::{
self, BinderFeatures, Interface, ParcelFileDescriptor, StatusCode, Strong, ThreadState,
};
-use log::{debug, error};
+use command_fds::FdMapping;
+use log::{debug, error, warn};
+use std::fs::{File, create_dir};
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
+/// Directory in which to write disk image files used while running VMs.
+const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
+
// TODO(qwandor): Use PermissionController once it is available to Rust.
/// Only processes running with one of these UIDs are allowed to call debug methods.
const DEBUG_ALLOWED_UIDS: [u32; 2] = [0, 2000];
@@ -53,30 +62,63 @@
log_fd: Option<&ParcelFileDescriptor>,
) -> binder::Result<Strong<dyn IVirtualMachine>> {
let state = &mut *self.state.lock().unwrap();
- let log_fd = log_fd
- .map(|fd| fd.as_ref().try_clone().map_err(|_| StatusCode::UNKNOWN_ERROR))
- .transpose()?;
+ let log_fd = log_fd.map(clone_file).transpose()?;
let requester_uid = ThreadState::get_calling_uid();
- let requester_sid = ThreadState::with_calling_sid(|sid| {
- if let Some(sid) = sid {
- match sid.to_str() {
- Ok(sid) => Ok(sid.to_owned()),
- Err(e) => {
- error!("SID was not valid UTF-8: {:?}", e);
- Err(StatusCode::BAD_VALUE)
- }
- }
- } else {
- error!("Missing SID on startVm");
- Err(StatusCode::UNKNOWN_ERROR)
- }
- })?;
+ let requester_sid = get_calling_sid()?;
let requester_debug_pid = ThreadState::get_calling_pid();
let cid = state.allocate_cid()?;
- let instance = VmInstance::start(
- config,
+
+ // Counter to generate unique IDs for temporary image files.
+ let mut next_temporary_image_id = 0;
+ // Files which are referred to from composite images. These must be mapped to the crosvm
+ // child process, and not closed before it is started.
+ let mut indirect_files = vec![];
+
+ // Make directory for temporary files.
+ let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
+ create_dir(&temporary_directory).map_err(|e| {
+ error!(
+ "Failed to create temporary directory {:?} for VM files: {:?}",
+ temporary_directory, e
+ );
+ StatusCode::UNKNOWN_ERROR
+ })?;
+
+ // Assemble disk images if needed.
+ let disks = config
+ .disks
+ .iter()
+ .map(|disk| {
+ assemble_disk_image(
+ disk,
+ &temporary_directory,
+ &mut next_temporary_image_id,
+ &mut indirect_files,
+ )
+ })
+ .collect::<Result<Vec<DiskFile>, _>>()?;
+
+ // Actually start the VM.
+ let crosvm_config = CrosvmConfig {
cid,
+ bootloader: as_asref(&config.bootloader),
+ kernel: as_asref(&config.kernel),
+ initrd: as_asref(&config.initrd),
+ disks,
+ params: config.params.to_owned(),
+ };
+ let composite_disk_mappings: Vec<_> = indirect_files
+ .iter()
+ .map(|file| {
+ let fd = file.as_raw_fd();
+ FdMapping { parent_fd: fd, child_fd: fd }
+ })
+ .collect();
+ let instance = VmInstance::start(
+ &crosvm_config,
log_fd,
+ &composite_disk_mappings,
+ temporary_directory,
requester_uid,
requester_sid,
requester_debug_pid,
@@ -102,6 +144,7 @@
.into_iter()
.map(|vm| VirtualMachineDebugInfo {
cid: vm.cid as i32,
+ temporaryDirectory: vm.temporary_directory.to_string_lossy().to_string(),
requesterUid: vm.requester_uid as i32,
requesterSid: vm.requester_sid.clone(),
requesterPid: vm.requester_debug_pid,
@@ -136,6 +179,72 @@
}
}
+/// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
+///
+/// This may involve assembling a composite disk from a set of partition images.
+fn assemble_disk_image(
+ disk: &DiskImage,
+ temporary_directory: &Path,
+ next_temporary_image_id: &mut u64,
+ indirect_files: &mut Vec<File>,
+) -> Result<DiskFile, StatusCode> {
+ let image = if !disk.partitions.is_empty() {
+ if disk.image.is_some() {
+ warn!("DiskImage {:?} contains both image and partitions.", disk);
+ return Err(StatusCode::BAD_VALUE);
+ }
+
+ let composite_image_filename =
+ make_composite_image_filename(temporary_directory, next_temporary_image_id);
+ let (image, partition_files) =
+ make_composite_image(&disk.partitions, &composite_image_filename).map_err(|e| {
+ error!("Failed to make composite image with config {:?}: {:?}", disk, e);
+ StatusCode::UNKNOWN_ERROR
+ })?;
+
+ // Pass the file descriptors for the various partition files to crosvm when it
+ // is run.
+ indirect_files.extend(partition_files);
+
+ image
+ } else if let Some(image) = &disk.image {
+ clone_file(image)?
+ } else {
+ warn!("DiskImage {:?} didn't contain image or partitions.", disk);
+ return Err(StatusCode::BAD_VALUE);
+ };
+
+ Ok(DiskFile { image, writable: disk.writable })
+}
+
+/// Generates a unique filename to use for a composite disk image.
+fn make_composite_image_filename(
+ temporary_directory: &Path,
+ next_temporary_image_id: &mut u64,
+) -> PathBuf {
+ let id = *next_temporary_image_id;
+ *next_temporary_image_id += 1;
+ temporary_directory.join(format!("composite-{}.img", id))
+}
+
+/// Gets the calling SID of the current Binder thread.
+fn get_calling_sid() -> Result<String, StatusCode> {
+ ThreadState::with_calling_sid(|sid| {
+ if let Some(sid) = sid {
+ match sid.to_str() {
+ Ok(sid) => Ok(sid.to_owned()),
+ Err(e) => {
+ error!("SID was not valid UTF-8: {:?}", e);
+ Err(StatusCode::BAD_VALUE)
+ }
+ }
+ } else {
+ error!("Missing SID on startVm");
+ Err(StatusCode::UNKNOWN_ERROR)
+ }
+ })
+}
+
/// Check whether the caller of the current Binder method is allowed to call debug methods.
fn debug_access_allowed() -> bool {
let uid = ThreadState::get_calling_uid();
@@ -265,3 +374,13 @@
State { next_cid: FIRST_GUEST_CID, vms: vec![], debug_held_vms: vec![] }
}
}
+
+/// Converts an `&Option<T>` to an `Option<U>` where `T` implements `AsRef<U>`.
+fn as_asref<T: AsRef<U>, U>(option: &Option<T>) -> Option<&U> {
+ option.as_ref().map(|t| t.as_ref())
+}
+
+/// Converts a `&ParcelFileDescriptor` to a `File` by cloning the file.
+fn clone_file(file: &ParcelFileDescriptor) -> Result<File, StatusCode> {
+ file.as_ref().try_clone().map_err(|_| StatusCode::UNKNOWN_ERROR)
+}