Andrew Walbran | 3a5a921 | 2021-05-04 17:09:08 +0000 | [diff] [blame] | 1 | // Copyright 2021, The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | //! Struct for VM configuration. |
| 16 | |
| 17 | use android_system_virtmanager::{ |
| 18 | aidl::android::system::virtmanager::DiskImage::DiskImage as AidlDiskImage, |
| 19 | aidl::android::system::virtmanager::VirtualMachineConfig::VirtualMachineConfig, |
| 20 | binder::ParcelFileDescriptor, |
| 21 | }; |
| 22 | use anyhow::{bail, Context, Error}; |
| 23 | use serde::{Deserialize, Serialize}; |
| 24 | use std::fs::{File, OpenOptions}; |
| 25 | use std::io::BufReader; |
| 26 | use std::path::{Path, PathBuf}; |
| 27 | |
| 28 | /// Configuration for a particular VM to be started. |
| 29 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| 30 | pub struct VmConfig { |
| 31 | /// The filename of the kernel image, if any. |
| 32 | pub kernel: Option<PathBuf>, |
| 33 | /// The filename of the initial ramdisk for the kernel, if any. |
| 34 | pub initrd: Option<PathBuf>, |
| 35 | /// Parameters to pass to the kernel. As far as the VMM and boot protocol are concerned this is |
| 36 | /// just a string, but typically it will contain multiple parameters separated by spaces. |
| 37 | pub params: Option<String>, |
| 38 | /// The bootloader to use. If this is supplied then the kernel and initrd must not be supplied; |
| 39 | /// the bootloader is instead responsibly for loading the kernel from one of the disks. |
| 40 | pub bootloader: Option<PathBuf>, |
| 41 | /// Disk images to be made available to the VM. |
| 42 | #[serde(default)] |
| 43 | pub disks: Vec<DiskImage>, |
| 44 | } |
| 45 | |
| 46 | impl VmConfig { |
| 47 | /// Ensure that the configuration has a valid combination of fields set, or return an error if |
| 48 | /// not. |
| 49 | pub fn validate(&self) -> Result<(), Error> { |
| 50 | if self.bootloader.is_none() && self.kernel.is_none() { |
| 51 | bail!("VM must have either a bootloader or a kernel image."); |
| 52 | } |
| 53 | if self.bootloader.is_some() && (self.kernel.is_some() || self.initrd.is_some()) { |
| 54 | bail!("Can't have both bootloader and kernel/initrd image."); |
| 55 | } |
| 56 | Ok(()) |
| 57 | } |
| 58 | |
| 59 | /// Load the configuration for a VM from the given JSON file, and check that it is valid. |
| 60 | pub fn load(file: &File) -> Result<VmConfig, Error> { |
| 61 | let buffered = BufReader::new(file); |
| 62 | let config: VmConfig = serde_json::from_reader(buffered)?; |
| 63 | config.validate()?; |
| 64 | Ok(config) |
| 65 | } |
| 66 | |
| 67 | /// Convert the `VmConfig` to a [`VirtualMachineConfig`] which can be passed to the Virt |
| 68 | /// Manager. |
| 69 | pub fn to_parcelable(&self) -> Result<VirtualMachineConfig, Error> { |
| 70 | Ok(VirtualMachineConfig { |
| 71 | kernel: maybe_open_parcel_file(&self.kernel)?, |
| 72 | initrd: maybe_open_parcel_file(&self.initrd)?, |
| 73 | params: self.params.clone(), |
| 74 | bootloader: maybe_open_parcel_file(&self.bootloader)?, |
| 75 | disks: self |
| 76 | .disks |
| 77 | .iter() |
| 78 | .map(|disk| { |
| 79 | Ok(AidlDiskImage { |
| 80 | writable: disk.writable, |
| 81 | image: Some(open_parcel_file(&disk.image, disk.writable)?), |
| 82 | }) |
| 83 | }) |
| 84 | .collect::<Result<_, Error>>()?, |
| 85 | }) |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | /// A disk image to be made available to the VM. |
| 90 | #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] |
| 91 | pub struct DiskImage { |
| 92 | /// The filename of the disk image. |
| 93 | pub image: PathBuf, |
| 94 | /// Whether this disk should be writable by the VM. |
| 95 | pub writable: bool, |
| 96 | } |
| 97 | |
| 98 | /// Try to open the given file and wrap it in a [`ParcelFileDescriptor`]. |
| 99 | fn open_parcel_file(filename: &Path, writable: bool) -> Result<ParcelFileDescriptor, Error> { |
| 100 | Ok(ParcelFileDescriptor::new( |
| 101 | OpenOptions::new() |
| 102 | .read(true) |
| 103 | .write(writable) |
| 104 | .open(filename) |
| 105 | .with_context(|| format!("Failed to open {:?}", filename))?, |
| 106 | )) |
| 107 | } |
| 108 | |
| 109 | /// If the given filename is `Some`, try to open it and wrap it in a [`ParcelFileDescriptor`]. |
| 110 | fn maybe_open_parcel_file( |
| 111 | filename: &Option<PathBuf>, |
| 112 | ) -> Result<Option<ParcelFileDescriptor>, Error> { |
| 113 | filename.as_deref().map(|filename| open_parcel_file(filename, false)).transpose() |
| 114 | } |