174 lines
5.6 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
use std::{
fs,
path::{Path, PathBuf},
};
use super::bin::make_install_bzimage;
use crate::{
bundle::{
bin::AsterBin,
file::BundleFile,
vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType},
},
config::{
scheme::{ActionChoice, BootProtocol},
Config,
},
util::get_current_crate_info,
};
pub fn create_bootdev_image(
target_dir: impl AsRef<Path>,
aster_bin: &AsterBin,
initramfs_path: Option<impl AsRef<Path>>,
config: &Config,
action: ActionChoice,
) -> AsterVmImage {
let target_name = get_current_crate_info().name;
let iso_root = &target_dir.as_ref().join("iso_root");
let action = match &action {
ActionChoice::Run => &config.run,
ActionChoice::Test => &config.test,
};
let protocol = &action.grub.boot_protocol;
// Clear or make the iso dir.
if iso_root.exists() {
fs::remove_dir_all(iso_root).unwrap();
}
fs::create_dir_all(iso_root.join("boot").join("grub")).unwrap();
// Copy the initramfs to the boot directory.
if let Some(init_path) = &initramfs_path {
fs::copy(
init_path.as_ref().to_str().unwrap(),
iso_root.join("boot").join("initramfs.cpio.gz"),
)
.unwrap();
}
// Make the kernel image and place it in the boot directory.
match protocol {
BootProtocol::Linux => {
make_install_bzimage(
iso_root.join("boot"),
&target_dir,
aster_bin,
action.build.linux_x86_legacy_boot,
config.build.encoding.clone(),
);
}
_ => {
// Copy the kernel image to the boot directory.
let target_path = iso_root.join("boot").join(&target_name);
fs::copy(aster_bin.path(), target_path).unwrap();
}
};
// Write the grub.cfg file
let initramfs_in_image = if initramfs_path.is_some() {
Some("/boot/initramfs.cpio.gz".to_string())
} else {
None
};
let grub_cfg = generate_grub_cfg(
&action.boot.kcmdline.join(" "),
!action.grub.display_grub_menu,
initramfs_in_image,
protocol,
);
let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg");
fs::write(grub_cfg_path, grub_cfg).unwrap();
// Make the boot device CDROM image using `grub-mkrescue`.
let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso");
let mut grub_mkrescue_cmd = std::process::Command::new(action.grub.grub_mkrescue.as_os_str());
grub_mkrescue_cmd
.arg(iso_root.as_os_str())
.arg("-o")
.arg(iso_path);
if !grub_mkrescue_cmd.status().unwrap().success() {
panic!("Failed to run {:#?}.", grub_mkrescue_cmd);
}
AsterVmImage::new(
iso_path,
AsterVmImageType::GrubIso(AsterGrubIsoImageMeta {
grub_version: get_grub_mkrescue_version(&action.grub.grub_mkrescue),
}),
aster_bin.version().clone(),
)
}
fn generate_grub_cfg(
kcmdline: &str,
skip_grub_menu: bool,
initramfs_path: Option<String>,
protocol: &BootProtocol,
) -> String {
let target_name = get_current_crate_info().name;
let grub_cfg = include_str!("grub.cfg.template").to_string();
// Delete the first two lines that notes the file a template file.
let grub_cfg = grub_cfg.lines().skip(2).collect::<Vec<&str>>().join("\n");
// Set the timeout style and timeout.
let grub_cfg = grub_cfg
.replace(
"#GRUB_TIMEOUT_STYLE#",
if skip_grub_menu { "hidden" } else { "menu" },
)
.replace("#GRUB_TIMEOUT#", if skip_grub_menu { "0" } else { "5" });
// Replace all occurrences of "#KERNEL_COMMAND_LINE#" with the desired value.
let grub_cfg = grub_cfg.replace("#KERNEL_COMMAND_LINE#", kcmdline);
// Replace the grub commands according to the protocol selected.
let aster_bin_path_on_device = PathBuf::from("/boot")
.join(target_name)
.into_os_string()
.into_string()
.unwrap();
match protocol {
BootProtocol::Multiboot => grub_cfg
.replace("#GRUB_CMD_KERNEL#", "multiboot")
.replace("#KERNEL#", &aster_bin_path_on_device)
.replace(
"#GRUB_CMD_INITRAMFS#",
&if let Some(p) = &initramfs_path {
"module --nounzip ".to_owned() + p
} else {
"".to_owned()
},
),
BootProtocol::Multiboot2 => grub_cfg
.replace("#GRUB_CMD_KERNEL#", "multiboot2")
.replace("#KERNEL#", &aster_bin_path_on_device)
.replace(
"#GRUB_CMD_INITRAMFS#",
&if let Some(p) = &initramfs_path {
"module2 --nounzip ".to_owned() + p
} else {
"".to_owned()
},
),
BootProtocol::Linux => grub_cfg
.replace("#GRUB_CMD_KERNEL#", "linux")
.replace("#KERNEL#", &aster_bin_path_on_device)
.replace(
"#GRUB_CMD_INITRAMFS#",
&if let Some(p) = &initramfs_path {
"initrd ".to_owned() + p
} else {
"".to_owned()
},
),
}
}
fn get_grub_mkrescue_version(grub_mkrescue: &PathBuf) -> String {
let mut cmd = std::process::Command::new(grub_mkrescue);
cmd.arg("--version");
let output = cmd.output().unwrap();
String::from_utf8(output.stdout).unwrap()
}