use std::path::PathBuf;
use objc2::AnyThread;
use objc2_foundation::NSString;
use objc2_virtualization::{
VZEFIBootLoader, VZEFIVariableStore, VZEFIVariableStoreInitializationOptions,
VZLinuxBootLoader, VZVirtualMachineConfiguration,
};
use crate::util::path_to_nsurl;
use crate::KasouError;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum BootConfig {
Linux {
kernel: PathBuf,
initrd: PathBuf,
cmdline: String,
},
Efi {
variable_store: Option<PathBuf>,
},
}
impl BootConfig {
pub fn linux(kernel: PathBuf, initrd: PathBuf, cmdline: impl Into<String>) -> Self {
Self::Linux {
kernel,
initrd,
cmdline: cmdline.into(),
}
}
pub fn efi() -> Self {
Self::Efi {
variable_store: None,
}
}
pub fn efi_with_variable_store(path: PathBuf) -> Self {
Self::Efi {
variable_store: Some(path),
}
}
}
pub(crate) fn setup_boot_loader(
config: &BootConfig,
vz_config: &VZVirtualMachineConfiguration,
) -> Result<(), KasouError> {
match config {
BootConfig::Linux {
kernel,
initrd,
cmdline,
} => {
if !kernel.exists() {
return Err(KasouError::BootFilesNotFound(format!(
"kernel not found: {}",
kernel.display()
)));
}
if !initrd.exists() {
return Err(KasouError::BootFilesNotFound(format!(
"initrd not found: {}",
initrd.display()
)));
}
let kernel_url = path_to_nsurl(kernel)?;
let initrd_url = path_to_nsurl(initrd)?;
let cmdline_ns = NSString::from_str(cmdline);
let loader = unsafe {
VZLinuxBootLoader::initWithKernelURL(VZLinuxBootLoader::alloc(), &kernel_url)
};
unsafe {
loader.setInitialRamdiskURL(Some(&initrd_url));
loader.setCommandLine(&cmdline_ns);
}
unsafe { vz_config.setBootLoader(Some(&loader)) };
}
BootConfig::Efi { variable_store } => {
let loader = unsafe { VZEFIBootLoader::new() };
if let Some(store_path) = variable_store {
let store_url = path_to_nsurl(store_path)?;
let store = if store_path.exists() {
unsafe {
VZEFIVariableStore::initWithURL(VZEFIVariableStore::alloc(), &store_url)
}
} else {
unsafe {
VZEFIVariableStore::initCreatingVariableStoreAtURL_options_error(
VZEFIVariableStore::alloc(),
&store_url,
VZEFIVariableStoreInitializationOptions::empty(),
)
}
.map_err(|e| {
let chain = crate::util::ns_error_chain(&e);
KasouError::Framework(format!(
"failed to create EFI variable store at {}: {chain}",
store_path.display(),
))
})?
};
unsafe { loader.setVariableStore(Some(&store)) };
}
unsafe { vz_config.setBootLoader(Some(&loader)) };
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linux_constructor() {
let config = BootConfig::linux(
PathBuf::from("/k"),
PathBuf::from("/i"),
"console=hvc0",
);
match config {
BootConfig::Linux { kernel, initrd, cmdline } => {
assert_eq!(kernel, PathBuf::from("/k"));
assert_eq!(initrd, PathBuf::from("/i"));
assert_eq!(cmdline, "console=hvc0");
}
BootConfig::Efi { .. } => panic!("wrong variant"),
}
}
#[test]
fn efi_constructor_volatile() {
match BootConfig::efi() {
BootConfig::Efi { variable_store: None } => {}
_ => panic!("expected Efi with no variable store"),
}
}
#[test]
fn efi_constructor_with_store() {
match BootConfig::efi_with_variable_store(PathBuf::from("/tmp/efi.vars")) {
BootConfig::Efi { variable_store: Some(p) } => {
assert_eq!(p, PathBuf::from("/tmp/efi.vars"));
}
_ => panic!("expected Efi with variable store"),
}
}
}