blob: a6f48cebea3e6fbabe86673e72007ceac00a75ce [file] [log] [blame]
Andrew Walbran3a5a9212021-05-04 17:09:08 +00001// 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
17use android_system_virtmanager::{
18 aidl::android::system::virtmanager::DiskImage::DiskImage as AidlDiskImage,
19 aidl::android::system::virtmanager::VirtualMachineConfig::VirtualMachineConfig,
20 binder::ParcelFileDescriptor,
21};
22use anyhow::{bail, Context, Error};
23use serde::{Deserialize, Serialize};
24use std::fs::{File, OpenOptions};
25use std::io::BufReader;
26use std::path::{Path, PathBuf};
27
28/// Configuration for a particular VM to be started.
29#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
30pub 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
46impl 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)]
91pub 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`].
99fn 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`].
110fn 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}