Build composite image in VirtualizationService.

Bug: 184131523
Test: atest VirtualizationTestCases
Test: ran microdroid manually
Change-Id: I24eb776bb3049a4cdc5f000607447a73bd0adeec
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};