Build composite image in VirtualizationService.
Bug: 184131523
Test: atest VirtualizationTestCases
Test: ran microdroid manually
Change-Id: I24eb776bb3049a4cdc5f000607447a73bd0adeec
diff --git a/microdroid/README.md b/microdroid/README.md
index 372c21f..489791a 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -46,14 +46,60 @@
```json
{
- "bootloader": "/data/local/tmp/microdroid/bootloader",
+ "bootloader": "/apex/com.android.virt/etc/microdroid_bootloader",
"disks": [
{
- "image": "/data/local/tmp/microdroid/os_composite.img",
+ "partitions": [
+ {
+ "label": "misc",
+ "path": "/data/local/tmp/microdroid/misc.img"
+ },
+ {
+ "label": "boot_a",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
+ },
+ {
+ "label": "boot_b",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
+ },
+ {
+ "label": "vendor_boot_a",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
+ },
+ {
+ "label": "vendor_boot_b",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
+ },
+ {
+ "label": "vbmeta_a",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
+ },
+ {
+ "label": "vbmeta_b",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
+ },
+ {
+ "label": "vbmeta_system_a",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
+ },
+ {
+ "label": "vbmeta_system_b",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
+ },
+ {
+ "label": "super",
+ "path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
+ }
+ ],
"writable": false
},
{
- "image": "/data/local/tmp/microdroid/env_composite.img",
+ "partitions": [
+ {
+ "label": "uboot_env",
+ "path": "/apex/com.android.virt/etc/uboot_env.img"
+ }
+ ],
"writable": false
},
{
@@ -61,7 +107,13 @@
"writable": false
},
{
- "image": "/data/local/tmp/microdroid/userdata_composite.img",
+ "partitions": [
+ {
+ "label": "userdata",
+ "path": "/data/local/tmp/microdroid/userdata.img",
+ "writable": true
+ }
+ ],
"writable": true
}
]
@@ -76,16 +128,10 @@
```sh
$ adb root
$ adb shell 'mkdir /data/local/tmp/microdroid'
-$ adb shell 'cp /apex/com.android.virt/etc/microdroid_bootloader /data/local/tmp/microdroid/bootloader'
-$ adb shell 'cp /apex/com.android.virt/etc/fs/*.img /data/local/tmp/microdroid'
-$ adb shell 'cp /apex/com.android.virt/etc/uboot_env.img /data/local/tmp/microdroid'
$ adb shell 'dd if=/dev/zero of=/data/local/tmp/microdroid/misc.img bs=4k count=256'
$ adb shell 'dd if=/dev/zero of=/data/local/tmp/microdroid/userdata.img bs=1 count=0 seek=4G'
-$ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_cdisk /apex/com.android.virt/etc/microdroid_cdisk.json os_composite.img'
-$ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_cdisk /apex/com.android.virt/etc/microdroid_cdisk_env.json env_composite.img'
-$ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_cdisk /apex/com.android.virt/etc/microdroid_cdisk_userdata.json userdata_composite.img'
$ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_payload /apex/com.android.virt/etc/microdroid_payload.json payload.img'
-$ adb shell 'chmod go+r /data/local/tmp/microdroid/*-header.img /data/local/tmp/microdroid/*-footer.img /data/local/tmp/microdroid/payload.img.*'
+$ adb shell 'chmod go+r /data/local/tmp/microdroid/payload*'
$ adb push microdroid.json /data/local/tmp/microdroid/microdroid.json
```
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 2c44200..2d78018 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -14,6 +14,8 @@
"libanyhow",
"libcommand_fds",
"liblog_rust",
+ "libserde_json",
+ "libserde",
"libshared_child",
],
apex_available: ["com.android.virt"],
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl
index 6bc747e..ab4c37d 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl
@@ -15,11 +15,18 @@
*/
package android.system.virtualizationservice;
+import android.system.virtualizationservice.Partition;
+
/** A disk image to be made available to the VM. */
parcelable DiskImage {
- /** The disk image. */
- ParcelFileDescriptor image;
+ /**
+ * The disk image, if it already exists. Exactly one of this and `partitions` must be specified.
+ */
+ @nullable ParcelFileDescriptor image;
/** Whether this disk should be writable by the VM. */
boolean writable;
+
+ /** Partition images to be assembled into a composite image. */
+ Partition[] partitions;
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/Partition.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/Partition.aidl
new file mode 100644
index 0000000..782c239
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/Partition.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+package android.system.virtualizationservice;
+
+/** A partition to be assembled into a composite image. */
+parcelable Partition {
+ /** A label for the partition. */
+ String label;
+
+ /** The backing file descriptor of the partition image. */
+ ParcelFileDescriptor image;
+
+ /** Whether the partition should be writable by the VM. */
+ boolean writable;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
index d20d91d..18b01ce 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
@@ -20,6 +20,9 @@
/** The CID assigned to the VM. */
int cid;
+ /** Directory of temporary files used by the VM while it is running. */
+ String temporaryDirectory;
+
/** The UID of the process which requested the VM. */
int requesterUid;
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)
+}
diff --git a/virtualizationservice/src/composite.rs b/virtualizationservice/src/composite.rs
new file mode 100644
index 0000000..eb738a7
--- /dev/null
+++ b/virtualizationservice/src/composite.rs
@@ -0,0 +1,112 @@
+// 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.
+
+//! Functions for running `mk_cdisk`.
+
+mod config;
+
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::Partition::Partition as AidlPartition;
+use anyhow::{bail, Context, Error};
+use command_fds::{CommandFdExt, FdMapping};
+use config::{Config, Partition};
+use log::info;
+use std::fs::File;
+use std::os::unix::io::AsRawFd;
+use std::panic;
+use std::path::Path;
+use std::process::{Command, Stdio};
+use std::str;
+use std::thread;
+
+const MK_CDISK_PATH: &str = "/apex/com.android.virt/bin/mk_cdisk";
+
+/// Calls `mk_cdisk` to construct a composite disk image for the given list of partitions, and opens
+/// it ready to use. Returns the composite disk image file, and a list of FD mappings which must be
+/// applied to any process which wants to use it. This is necessary because the composite image
+/// contains paths of the form `/proc/self/fd/N` for the partition images.
+pub fn make_composite_image(
+ partitions: &[AidlPartition],
+ output_filename: &Path,
+) -> Result<(File, Vec<File>), Error> {
+ let (config_json, files) = make_config_json(partitions)?;
+ let fd_mappings: Vec<_> = files
+ .iter()
+ .map(|file| FdMapping { parent_fd: file.as_raw_fd(), child_fd: file.as_raw_fd() })
+ .collect();
+
+ let mut command = Command::new(MK_CDISK_PATH);
+ command
+ .arg("-") // Read config JSON from stdin.
+ .arg(&output_filename)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
+ command.fd_mappings(fd_mappings)?;
+ let mut child = command.spawn().context("Failed to spawn mk_cdisk")?;
+ let stdin = child.stdin.take().unwrap();
+
+ // Write config to stdin of mk_cdisk on a separate thread to avoid deadlock, as it may not read
+ // all of stdin before it blocks on writing to stdout.
+ let writer_thread = thread::spawn(move || config_json.write_json(&stdin));
+ info!("Running {:?}", command);
+ let output = child.wait_with_output()?;
+ match writer_thread.join() {
+ Ok(result) => result?,
+ Err(panic_payload) => panic::resume_unwind(panic_payload),
+ }
+
+ if !output.status.success() {
+ info!("mk_cdisk stdout: {}", str::from_utf8(&output.stdout)?);
+ info!("mk_cdisk stderr: {}", str::from_utf8(&output.stderr)?);
+ bail!("mk_cdisk exited with error {}", output.status);
+ }
+
+ let composite_image = File::open(&output_filename)
+ .with_context(|| format!("Failed to open composite image {:?}", output_filename))?;
+
+ Ok((composite_image, files))
+}
+
+/// Given the AIDL config containing a list of partitions, with a [`ParcelFileDescriptor`] for each
+/// partition, return the list of file descriptors which must be passed to the mk_cdisk child
+/// process and the JSON configuration for it.
+fn make_config_json(partitions: &[AidlPartition]) -> Result<(Config, Vec<File>), Error> {
+ // File descriptors to pass to child process.
+ let mut files = vec![];
+
+ let partitions = partitions
+ .iter()
+ .map(|partition| {
+ // TODO(b/187187765): This shouldn't be an Option.
+ let file = partition
+ .image
+ .as_ref()
+ .context("Invalid partition image file descriptor")?
+ .as_ref()
+ .try_clone()
+ .context("Failed to clone partition image file descriptor")?;
+ let fd = file.as_raw_fd();
+ files.push(file);
+
+ Ok(Partition {
+ writable: partition.writable,
+ label: partition.label.to_owned(),
+ path: format!("/proc/self/fd/{}", fd).into(),
+ })
+ })
+ .collect::<Result<_, Error>>()?;
+ let config_json = Config { partitions };
+
+ Ok((config_json, files))
+}
diff --git a/virtualizationservice/src/composite/config.rs b/virtualizationservice/src/composite/config.rs
new file mode 100644
index 0000000..1a915ba
--- /dev/null
+++ b/virtualizationservice/src/composite/config.rs
@@ -0,0 +1,44 @@
+// 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.
+
+//! JSON configuration for running `mk_cdisk`.
+
+use anyhow::{Context, Error};
+use serde::{Deserialize, Serialize};
+use std::io::Write;
+use std::path::PathBuf;
+
+#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Config {
+ /// The set of partitions to be assembled into a composite image.
+ pub partitions: Vec<Partition>,
+}
+
+/// A partition to be assembled into a composite image.
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Partition {
+ /// A label for the partition.
+ pub label: String,
+ /// The filename of the partition image.
+ pub path: PathBuf,
+ /// Whether the partition should be writable.
+ #[serde(default)]
+ pub writable: bool,
+}
+
+impl Config {
+ pub fn write_json(&self, writer: impl Write) -> Result<(), Error> {
+ serde_json::to_writer(writer, self).context("Failed to write config JSON for mk_cdisk")
+ }
+}
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 552941d..138236c 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -16,13 +16,13 @@
use crate::aidl::VirtualMachineCallbacks;
use crate::Cid;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineConfig::VirtualMachineConfig;
-use anyhow::{bail, Context, Error};
+use anyhow::{bail, Error};
use command_fds::{CommandFdExt, FdMapping};
use log::{debug, error, info};
use shared_child::SharedChild;
-use std::fs::File;
+use std::fs::{remove_dir_all, File};
use std::os::unix::io::AsRawFd;
+use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@@ -30,6 +30,24 @@
const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
+/// Configuration for a VM to run with crosvm.
+#[derive(Debug)]
+pub struct CrosvmConfig<'a> {
+ pub cid: Cid,
+ pub bootloader: Option<&'a File>,
+ pub kernel: Option<&'a File>,
+ pub initrd: Option<&'a File>,
+ pub disks: Vec<DiskFile>,
+ pub params: Option<String>,
+}
+
+/// A disk image to pass to crosvm for a VM.
+#[derive(Debug)]
+pub struct DiskFile {
+ pub image: File,
+ pub writable: bool,
+}
+
/// Information about a particular instance of a VM which is running.
#[derive(Debug)]
pub struct VmInstance {
@@ -37,6 +55,8 @@
child: SharedChild,
/// The CID assigned to the VM for vsock communication.
pub cid: Cid,
+ /// Directory of temporary files used by the VM while it is running.
+ pub temporary_directory: PathBuf,
/// The UID of the process which requested the VM.
pub requester_uid: u32,
/// The SID of the process which requested the VM.
@@ -55,6 +75,7 @@
fn new(
child: SharedChild,
cid: Cid,
+ temporary_directory: PathBuf,
requester_uid: u32,
requester_sid: String,
requester_debug_pid: i32,
@@ -62,6 +83,7 @@
VmInstance {
child,
cid,
+ temporary_directory,
requester_uid,
requester_sid,
requester_debug_pid,
@@ -73,17 +95,19 @@
/// Start an instance of `crosvm` to manage a new VM. The `crosvm` instance will be killed when
/// the `VmInstance` is dropped.
pub fn start(
- config: &VirtualMachineConfig,
- cid: Cid,
+ config: &CrosvmConfig,
log_fd: Option<File>,
+ composite_disk_mappings: &[FdMapping],
+ temporary_directory: PathBuf,
requester_uid: u32,
requester_sid: String,
requester_debug_pid: i32,
) -> Result<Arc<VmInstance>, Error> {
- let child = run_vm(config, cid, log_fd)?;
+ let child = run_vm(config, log_fd, composite_disk_mappings)?;
let instance = Arc::new(VmInstance::new(
child,
- cid,
+ config.cid,
+ temporary_directory,
requester_uid,
requester_sid,
requester_debug_pid,
@@ -106,6 +130,11 @@
}
self.running.store(false, Ordering::Release);
self.callbacks.callback_on_died(self.cid);
+
+ // Delete temporary files.
+ if let Err(e) = remove_dir_all(&self.temporary_directory) {
+ error!("Error removing temporary directory {:?}: {:?}", self.temporary_directory, e);
+ }
}
/// Return whether `crosvm` is still running the VM.
@@ -124,15 +153,15 @@
/// Start an instance of `crosvm` to manage a new VM.
fn run_vm(
- config: &VirtualMachineConfig,
- cid: Cid,
+ config: &CrosvmConfig,
log_fd: Option<File>,
+ composite_disk_mappings: &[FdMapping],
) -> Result<SharedChild, Error> {
validate_config(config)?;
let mut command = Command::new(CROSVM_PATH);
// TODO(qwandor): Remove --disable-sandbox.
- command.arg("run").arg("--disable-sandbox").arg("--cid").arg(cid.to_string());
+ command.arg("run").arg("--disable-sandbox").arg("--cid").arg(config.cid.to_string());
if let Some(log_fd) = log_fd {
command.stdout(log_fd);
@@ -142,14 +171,14 @@
}
// Keep track of what file descriptors should be mapped to the crosvm process.
- let mut fd_mappings = vec![];
+ let mut fd_mappings = composite_disk_mappings.to_vec();
if let Some(bootloader) = &config.bootloader {
- command.arg("--bios").arg(add_fd_mapping(&mut fd_mappings, bootloader.as_ref()));
+ command.arg("--bios").arg(add_fd_mapping(&mut fd_mappings, bootloader));
}
if let Some(initrd) = &config.initrd {
- command.arg("--initrd").arg(add_fd_mapping(&mut fd_mappings, initrd.as_ref()));
+ command.arg("--initrd").arg(add_fd_mapping(&mut fd_mappings, initrd));
}
if let Some(params) = &config.params {
@@ -157,15 +186,13 @@
}
for disk in &config.disks {
- command.arg(if disk.writable { "--rwdisk" } else { "--disk" }).arg(add_fd_mapping(
- &mut fd_mappings,
- // TODO(b/187187765): This shouldn't be an Option.
- disk.image.as_ref().context("Invalid disk image file descriptor")?.as_ref(),
- ));
+ command
+ .arg(if disk.writable { "--rwdisk" } else { "--disk" })
+ .arg(add_fd_mapping(&mut fd_mappings, &disk.image));
}
if let Some(kernel) = &config.kernel {
- command.arg(add_fd_mapping(&mut fd_mappings, kernel.as_ref()));
+ command.arg(add_fd_mapping(&mut fd_mappings, kernel));
}
debug!("Setting mappings {:?}", fd_mappings);
@@ -177,7 +204,7 @@
}
/// Ensure that the configuration has a valid combination of fields set, or return an error if not.
-fn validate_config(config: &VirtualMachineConfig) -> Result<(), Error> {
+fn validate_config(config: &CrosvmConfig) -> Result<(), Error> {
if config.bootloader.is_none() && config.kernel.is_none() {
bail!("VM must have either a bootloader or a kernel image.");
}
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 5453146..cf0be38 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -15,6 +15,7 @@
//! Android VirtualizationService
mod aidl;
+mod composite;
mod crosvm;
use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER};
diff --git a/vm/src/config.rs b/vm/src/config.rs
index cbdd0bf..3bd023f 100644
--- a/vm/src/config.rs
+++ b/vm/src/config.rs
@@ -16,6 +16,7 @@
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::DiskImage::DiskImage as AidlDiskImage,
+ aidl::android::system::virtualizationservice::Partition::Partition as AidlPartition,
aidl::android::system::virtualizationservice::VirtualMachineConfig::VirtualMachineConfig,
binder::ParcelFileDescriptor,
};
@@ -53,6 +54,11 @@
if self.bootloader.is_some() && (self.kernel.is_some() || self.initrd.is_some()) {
bail!("Can't have both bootloader and kernel/initrd image.");
}
+ for disk in &self.disks {
+ if disk.image.is_none() == disk.partitions.is_empty() {
+ bail!("Exactly one of image and partitions must be specified. (Was {:?}.)", disk);
+ }
+ }
Ok(())
}
@@ -68,20 +74,11 @@
/// Manager.
pub fn to_parcelable(&self) -> Result<VirtualMachineConfig, Error> {
Ok(VirtualMachineConfig {
- kernel: maybe_open_parcel_file(&self.kernel)?,
- initrd: maybe_open_parcel_file(&self.initrd)?,
+ kernel: maybe_open_parcel_file(&self.kernel, false)?,
+ initrd: maybe_open_parcel_file(&self.initrd, false)?,
params: self.params.clone(),
- bootloader: maybe_open_parcel_file(&self.bootloader)?,
- disks: self
- .disks
- .iter()
- .map(|disk| {
- Ok(AidlDiskImage {
- writable: disk.writable,
- image: Some(open_parcel_file(&disk.image, disk.writable)?),
- })
- })
- .collect::<Result<_, Error>>()?,
+ bootloader: maybe_open_parcel_file(&self.bootloader, false)?,
+ disks: self.disks.iter().map(DiskImage::to_parcelable).collect::<Result<_, Error>>()?,
})
}
}
@@ -89,12 +86,52 @@
/// A disk image to be made available to the VM.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DiskImage {
- /// The filename of the disk image.
- pub image: PathBuf,
+ /// The filename of the disk image, if it already exists. Exactly one of this and `partitions`
+ /// must be specified.
+ #[serde(default)]
+ pub image: Option<PathBuf>,
+ /// A set of partitions to be assembled into a composite image.
+ #[serde(default)]
+ pub partitions: Vec<Partition>,
/// Whether this disk should be writable by the VM.
pub writable: bool,
}
+impl DiskImage {
+ fn to_parcelable(&self) -> Result<AidlDiskImage, Error> {
+ let partitions =
+ self.partitions.iter().map(Partition::to_parcelable).collect::<Result<_, Error>>()?;
+ Ok(AidlDiskImage {
+ image: maybe_open_parcel_file(&self.image, self.writable)?,
+ writable: self.writable,
+ partitions,
+ })
+ }
+}
+
+// TODO: Share this type with virtualizationservice::composite::config.
+/// A partition for a disk image to be made available to the VM.
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+pub struct Partition {
+ /// A label for the partition.
+ pub label: String,
+ /// The filename of the partition image.
+ pub path: PathBuf,
+ /// Whether the partition should be writable.
+ #[serde(default)]
+ pub writable: bool,
+}
+
+impl Partition {
+ fn to_parcelable(&self) -> Result<AidlPartition, Error> {
+ Ok(AidlPartition {
+ image: Some(open_parcel_file(&self.path, self.writable)?),
+ writable: self.writable,
+ label: self.label.to_owned(),
+ })
+ }
+}
+
/// Try to open the given file and wrap it in a [`ParcelFileDescriptor`].
fn open_parcel_file(filename: &Path, writable: bool) -> Result<ParcelFileDescriptor, Error> {
Ok(ParcelFileDescriptor::new(
@@ -109,6 +146,7 @@
/// If the given filename is `Some`, try to open it and wrap it in a [`ParcelFileDescriptor`].
fn maybe_open_parcel_file(
filename: &Option<PathBuf>,
+ writable: bool,
) -> Result<Option<ParcelFileDescriptor>, Error> {
- filename.as_deref().map(|filename| open_parcel_file(filename, false)).transpose()
+ filename.as_deref().map(|filename| open_parcel_file(filename, writable)).transpose()
}