Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embed bios and uefi binaries #395

Merged
merged 9 commits into from
Dec 28, 2023
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ bootloader-x86_64-bios-common = { version = "0.11.4", path = "bios/common" }
default = ["bios", "uefi"]
bios = ["dep:mbrman"]
uefi = ["dep:gpt"]
embedded_binaries = []

[dependencies]
anyhow = "1.0.32"
Expand Down
96 changes: 78 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ const KERNEL_FILE_NAME: &str = "kernel-x86_64";
const RAMDISK_FILE_NAME: &str = "ramdisk";
const CONFIG_FILE_NAME: &str = "boot.json";

#[cfg(all(feature = "embedded_binaries", feature = "uefi"))]
static UEFI_BOOTLOADER: &'static [u8] = include_bytes!(env!("UEFI_BOOTLOADER_PATH"));

#[cfg(all(feature = "embedded_binaries", feature = "bios"))]
static BIOS_BOOT_SECTOR: &'static [u8] = include_bytes!(env!("BIOS_BOOT_SECTOR_PATH"));
#[cfg(all(feature = "embedded_binaries", feature = "bios"))]
static BIOS_STAGE_2: &'static [u8] = include_bytes!(env!("BIOS_STAGE_2_PATH"));
#[cfg(all(feature = "embedded_binaries", feature = "bios"))]
static BIOS_STAGE_3: &'static [u8] = include_bytes!(env!("BIOS_STAGE_3_PATH"));
#[cfg(all(feature = "embedded_binaries", feature = "bios"))]
static BIOS_STAGE_4: &'static [u8] = include_bytes!(env!("BIOS_STAGE_4_PATH"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of statics, I think we can use consts here. E.g. const BIOS_BOOT_SECTOR: &'static [u8] = ....


/// Allows creating disk images for a specified set of files.
///
/// It can currently create `MBR` (BIOS), `GPT` (UEFI), and `TFTP` (UEFI) images.
Expand Down Expand Up @@ -95,22 +107,22 @@ impl DiskImageBuilder {
self.set_file_source(destination.into(), FileDataSource::File(file_path))
}

#[cfg(feature = "bios")]
#[cfg(all(feature = "bios", not(feature = "embedded_binaries")))]
/// Create an MBR disk image for booting on BIOS systems.
pub fn create_bios_image(&self, image_path: &Path) -> anyhow::Result<()> {
const BIOS_STAGE_3: &str = "boot-stage-3";
const BIOS_STAGE_4: &str = "boot-stage-4";
const BIOS_STAGE_3_NAME: &str = "boot-stage-3";
const BIOS_STAGE_4_NAME: &str = "boot-stage-4";
let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH"));
let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH"));
let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH"));
let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH"));
let mut internal_files = BTreeMap::new();
internal_files.insert(
BIOS_STAGE_3,
BIOS_STAGE_3_NAME,
FileDataSource::File(stage_3_path.to_path_buf()),
);
internal_files.insert(
BIOS_STAGE_4,
BIOS_STAGE_4_NAME,
FileDataSource::File(stage_4_path.to_path_buf()),
);

Expand All @@ -131,16 +143,50 @@ impl DiskImageBuilder {
Ok(())
}

#[cfg(all(feature = "bios", feature = "embedded_binaries"))]
/// Create an MBR disk image for booting on BIOS systems.
pub fn create_bios_image(&self, image_path: &Path) -> anyhow::Result<()> {
const BIOS_STAGE_3_NAME: &str = "boot-stage-3";
const BIOS_STAGE_4_NAME: &str = "boot-stage-4";
let stage_3 = FileDataSource::Data(BIOS_STAGE_3.to_vec());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The to_vec call copies the data to the heap whenever this function is invoked. I think we could avoid this copy by introducing a new FileDataSource::Bytes(&'static [u8]) variant, which could be used with statics and const data.

let stage_4 = FileDataSource::Data(BIOS_STAGE_4.to_vec());
let mut internal_files = BTreeMap::new();
internal_files.insert(BIOS_STAGE_3_NAME, stage_3);
internal_files.insert(BIOS_STAGE_4_NAME, stage_4);
let fat_partition = self
.create_fat_filesystem_image(internal_files)
.context("failed to create FAT partition")?;
mbr::create_mbr_disk(
BIOS_BOOT_SECTOR,
BIOS_STAGE_2,
fat_partition.path(),
image_path,
)
.context("failed to create BIOS MBR disk image")?;

fat_partition
.close()
.context("failed to delete FAT partition after disk image creation")?;
Ok(())
}

#[cfg(feature = "uefi")]
/// Create a GPT disk image for booting on UEFI systems.
pub fn create_uefi_image(&self, image_path: &Path) -> anyhow::Result<()> {
const UEFI_BOOT_FILENAME: &str = "efi/boot/bootx64.efi";
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));

#[cfg(feature = "embedded_binaries")]
fn get_uefi_bootloader() -> FileDataSource {
FileDataSource::Data(UEFI_BOOTLOADER.to_vec())
}
#[cfg(not(feature = "embedded_binaries"))]
fn get_uefi_bootloader() -> FileDataSource {
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));
FileDataSource::File(bootloader_path.to_path_buf())
}

let mut internal_files = BTreeMap::new();
internal_files.insert(
UEFI_BOOT_FILENAME,
FileDataSource::File(bootloader_path.to_path_buf()),
);
internal_files.insert(UEFI_BOOT_FILENAME, get_uefi_bootloader());
let fat_partition = self
.create_fat_filesystem_image(internal_files)
.context("failed to create FAT partition")?;
Expand All @@ -158,19 +204,33 @@ impl DiskImageBuilder {
pub fn create_uefi_tftp_folder(&self, tftp_path: &Path) -> anyhow::Result<()> {
use std::{fs, ops::Deref};

#[cfg(feature = "embedded_binaries")]
fn write_uefi_bootloader(to: &PathBuf) -> anyhow::Result<()> {
fs::write(to, UEFI_BOOTLOADER).with_context(|| {
format!(
"failed to copy bootloader from the embedded binary to {}",
to.display()
)
})
}
#[cfg(not(feature = "embedded_binaries"))]
fn write_uefi_bootloader(to: &PathBuf) -> anyhow::Result<()> {
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));
fs::copy(bootloader_path, to).map(|_| ()).with_context(|| {
format!(
"failed to copy bootloader from {} to {}",
bootloader_path.display(),
to.display()
)
})
}

const UEFI_TFTP_BOOT_FILENAME: &str = "bootloader";
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));
fs::create_dir_all(tftp_path)
.with_context(|| format!("failed to create out dir at {}", tftp_path.display()))?;

let to = tftp_path.join(UEFI_TFTP_BOOT_FILENAME);
fs::copy(bootloader_path, &to).with_context(|| {
format!(
"failed to copy bootloader from {} to {}",
bootloader_path.display(),
to.display()
)
})?;
write_uefi_bootloader(&to)?;

for f in &self.files {
let to = tftp_path.join(f.0.deref());
Expand Down
62 changes: 54 additions & 8 deletions src/mbr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,67 @@ use anyhow::Context;
use mbrman::BOOT_ACTIVE;
use std::{
fs::{self, File},
io::{self, Seek, SeekFrom},
io::{self, Read, Seek, SeekFrom},
path::Path,
};

const SECTOR_SIZE: u32 = 512;

#[cfg(not(feature = "embedded_binaries"))]
pub fn create_mbr_disk(
bootsector_path: &Path,
second_stage_path: &Path,
boot_partition_path: &Path,
out_mbr_path: &Path,
) -> anyhow::Result<()> {
let mut boot_sector = File::open(bootsector_path).context("failed to open boot sector")?;
let second_stage =
File::open(second_stage_path).context("failed to open second stage binary")?;
create_mbr_disk_with_readers(
File::open(bootsector_path).context("failed to open boot sector")?,
SecondStageData {
size: second_stage
.metadata()
.context("failed to read file metadata of second stage")?
.len(),
reader: second_stage,
},
boot_partition_path,
out_mbr_path,
)
}

#[cfg(feature = "embedded_binaries")]
pub fn create_mbr_disk(
bootsector_binary: &[u8],
second_stage_binary: &[u8],
boot_partition_path: &Path,
out_mbr_path: &Path,
) -> anyhow::Result<()> {
use std::io::Cursor;
create_mbr_disk_with_readers(
Cursor::new(bootsector_binary),
SecondStageData {
size: second_stage_binary.len() as u64,
reader: Cursor::new(second_stage_binary),
},
boot_partition_path,
out_mbr_path,
)
}

struct SecondStageData<R> {
size: u64,
reader: R,
}

fn create_mbr_disk_with_readers<R: Read + Seek>(
bootsector_reader: R,
second_stage_data: SecondStageData<R>,
boot_partition_path: &Path,
out_mbr_path: &Path,
) -> anyhow::Result<()> {
// let mut boot_sector = File::open(bootsector_path).context("failed to open boot sector")?;
let mut boot_sector = bootsector_reader;
let mut mbr =
mbrman::MBR::read_from(&mut boot_sector, SECTOR_SIZE).context("failed to read MBR")?;

Expand All @@ -23,12 +72,9 @@ pub fn create_mbr_disk(
}
}

let mut second_stage =
File::open(second_stage_path).context("failed to open second stage binary")?;
let second_stage_size = second_stage
.metadata()
.context("failed to read file metadata of second stage")?
.len();
let mut second_stage = second_stage_data.reader;
let second_stage_size = second_stage_data.size;

let second_stage_start_sector = 1;
let second_stage_sectors = ((second_stage_size - 1) / u64::from(SECTOR_SIZE) + 1)
.try_into()
Expand Down
Loading