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
113 changes: 100 additions & 13 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use async_process::Command;

Check warning on line 1 in build.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `async_process::Command`
use futures::executor::block_on;
use futures_concurrency::future::Join;
use std::path::{Path, PathBuf};
const BOOTLOADER_VERSION: &str = env!("CARGO_PKG_VERSION");

Check warning on line 5 in build.rs

View workflow job for this annotation

GitHub Actions / Check

constant `BOOTLOADER_VERSION` is never used

fn main() {
#[cfg(not(feature = "uefi"))]
Expand All @@ -21,7 +21,6 @@
// BIOS crates don't have enough dependencies to utilize all cores on modern
// CPUs. So by running the build commands in parallel, we increase the number
// of utilized cores.)
#[cfg(not(docsrs_dummy_build))]
let (bios_boot_sector_path, bios_stage_2_path, bios_stage_3_path, bios_stage_4_path) = (
build_bios_boot_sector(&out_dir),
build_bios_stage_2(&out_dir),
Expand All @@ -30,14 +29,6 @@
)
.join()
.await;
// dummy implementations because docsrs builds have no network access
#[cfg(docsrs_dummy_build)]
let (bios_boot_sector_path, bios_stage_2_path, bios_stage_3_path, bios_stage_4_path) = (
PathBuf::new(),
PathBuf::new(),
PathBuf::new(),
PathBuf::new(),
);
println!(
"cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}",
bios_boot_sector_path.display()
Expand All @@ -60,11 +51,7 @@
async fn uefi_main() {
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());

#[cfg(not(docsrs_dummy_build))]
let uefi_path = build_uefi_bootloader(&out_dir).await;
// dummy implementation because docsrs builds have no network access
#[cfg(docsrs_dummy_build)]
let uefi_path = PathBuf::new();

println!(
"cargo:rustc-env=UEFI_BOOTLOADER_PATH={}",
Expand Down Expand Up @@ -109,6 +96,26 @@
}
}

// dummy implementation because docsrs builds have no network access.
// This will put an empty file in out_dir and return its path.
#[cfg(docsrs_dummy_build)]
#[cfg(feature = "uefi")]
async fn build_uefi_bootloader(out_dir: &Path) -> PathBuf {
use std::fs::File;

let path = out_dir.join("bootloader-dummy-bootloader-uefi");

if File::create(&path).is_err() {
panic!("Failed to create dummy uefi bootloader");
}
assert!(
path.exists(),
"uefi bootloader dummy file does not exist after file creation"
);

path
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
async fn build_bios_boot_sector(out_dir: &Path) -> PathBuf {
Expand Down Expand Up @@ -153,6 +160,26 @@
convert_elf_to_bin(elf_path).await
}

// dummy implementation because docsrs builds have no network access.
// This will put an empty file in out_dir and return its path.
#[cfg(docsrs_dummy_build)]
#[cfg(feature = "bios")]
async fn build_bios_boot_sector(out_dir: &Path) -> PathBuf {
use std::fs::File;

let path = out_dir.join("bootloader-dummy-bios-boot-sector");

if File::create(&path).is_err() {
panic!("Failed to create dummy bios boot sector");
}
assert!(
path.exists(),
"bios boot sector dummy file does not exist after file creation"
);

path
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
async fn build_bios_stage_2(out_dir: &Path) -> PathBuf {
Expand Down Expand Up @@ -199,6 +226,26 @@
convert_elf_to_bin(elf_path).await
}

// dummy implementation because docsrs builds have no network access.
// This will put an empty file in out_dir and return its path.
#[cfg(docsrs_dummy_build)]
#[cfg(feature = "bios")]
async fn build_bios_stage_2(out_dir: &Path) -> PathBuf {
use std::fs::File;

let path = out_dir.join("bootloader-dummy-bios-stage-2");

if File::create(&path).is_err() {
panic!("Failed to create dummy bios second stage");
}
assert!(
path.exists(),
"bios second stage dummy file does not exist after file creation"
);

path
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
async fn build_bios_stage_3(out_dir: &Path) -> PathBuf {
Expand Down Expand Up @@ -241,6 +288,26 @@
convert_elf_to_bin(elf_path).await
}

// dummy implementation because docsrs builds have no network access.
// This will put an empty file in out_dir and return its path.
#[cfg(docsrs_dummy_build)]
#[cfg(feature = "bios")]
async fn build_bios_stage_3(out_dir: &Path) -> PathBuf {
use std::fs::File;

let path = out_dir.join("bootloader-dummy-bios-stage-3");

if File::create(&path).is_err() {
panic!("Failed to create dummy bios stage-3");
}
assert!(
path.exists(),
"bios stage-3 dummy file does not exist after file creation"
);

path
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
async fn build_bios_stage_4(out_dir: &Path) -> PathBuf {
Expand Down Expand Up @@ -284,6 +351,26 @@
convert_elf_to_bin(elf_path).await
}

// dummy implementation because docsrs builds have no network access.
// This will put an empty file in out_dir and return its path.
#[cfg(docsrs_dummy_build)]
#[cfg(feature = "bios")]
async fn build_bios_stage_4(out_dir: &Path) -> PathBuf {
use std::fs::File;

let path = out_dir.join("bootloader-dummy-bios-stage-4");

if File::create(&path).is_err() {
panic!("Failed to create dummy bios stage-4");
}
assert!(
path.exists(),
"bios stage-4 dummy file does not exist after file creation"
);

path
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
async fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf {
Expand Down
9 changes: 9 additions & 0 deletions src/file_data_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::{fs, io};
pub enum FileDataSource {
File(PathBuf),
Data(Vec<u8>),
Bytes(&'static [u8]),
}

impl Debug for FileDataSource {
Expand All @@ -22,6 +23,9 @@ impl Debug for FileDataSource {
FileDataSource::Data(d) => {
f.write_fmt(format_args!("data source: {} raw bytes ", d.len()))
}
FileDataSource::Bytes(b) => {
f.write_fmt(format_args!("data source: {} raw bytes ", b.len()))
}
}
}
}
Expand All @@ -34,6 +38,7 @@ impl FileDataSource {
.with_context(|| format!("failed to read metadata of file `{}`", path.display()))?
.len(),
FileDataSource::Data(v) => v.len() as u64,
FileDataSource::Bytes(s) => s.len() as u64,
})
}
/// Copy this data source to the specified target that implements io::Write
Expand All @@ -51,6 +56,10 @@ impl FileDataSource {
let mut cursor = Cursor::new(contents);
io::copy(&mut cursor, target)?;
}
FileDataSource::Bytes(contents) => {
let mut cursor = Cursor::new(contents);
io::copy(&mut cursor, target)?;
}
};

Ok(())
Expand Down
49 changes: 23 additions & 26 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ const KERNEL_FILE_NAME: &str = "kernel-x86_64";
const RAMDISK_FILE_NAME: &str = "ramdisk";
const CONFIG_FILE_NAME: &str = "boot.json";

#[cfg(feature = "uefi")]
const UEFI_BOOTLOADER: &[u8] = include_bytes!(env!("UEFI_BOOTLOADER_PATH"));
#[cfg(feature = "bios")]
const BIOS_BOOT_SECTOR: &[u8] = include_bytes!(env!("BIOS_BOOT_SECTOR_PATH"));
#[cfg(feature = "bios")]
const BIOS_STAGE_2: &[u8] = include_bytes!(env!("BIOS_STAGE_2_PATH"));
#[cfg(feature = "bios")]
const BIOS_STAGE_3: &[u8] = include_bytes!(env!("BIOS_STAGE_3_PATH"));
#[cfg(feature = "bios")]
const BIOS_STAGE_4: &[u8] = include_bytes!(env!("BIOS_STAGE_4_PATH"));

/// 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 @@ -98,28 +109,19 @@ impl DiskImageBuilder {
#[cfg(feature = "bios")]
/// 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";
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"));
const BIOS_STAGE_3_NAME: &str = "boot-stage-3";
const BIOS_STAGE_4_NAME: &str = "boot-stage-4";
let stage_3 = FileDataSource::Bytes(BIOS_STAGE_3);
let stage_4 = FileDataSource::Bytes(BIOS_STAGE_4);
let mut internal_files = BTreeMap::new();
internal_files.insert(
BIOS_STAGE_3,
FileDataSource::File(stage_3_path.to_path_buf()),
);
internal_files.insert(
BIOS_STAGE_4,
FileDataSource::File(stage_4_path.to_path_buf()),
);

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(
bootsector_path,
stage_2_path,
BIOS_BOOT_SECTOR,
BIOS_STAGE_2,
fat_partition.path(),
image_path,
)
Expand All @@ -135,12 +137,9 @@ impl DiskImageBuilder {
/// 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"));

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, FileDataSource::Bytes(UEFI_BOOTLOADER));
let fat_partition = self
.create_fat_filesystem_image(internal_files)
.context("failed to create FAT partition")?;
Expand All @@ -159,15 +158,13 @@ impl DiskImageBuilder {
use std::{fs, ops::Deref};

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(|| {
fs::write(&to, UEFI_BOOTLOADER).with_context(|| {
format!(
"failed to copy bootloader from {} to {}",
bootloader_path.display(),
"failed to copy bootloader from the embedded binary to {}",
to.display()
)
})?;
Expand Down
17 changes: 8 additions & 9 deletions src/mbr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ use std::{
io::{self, Seek, SeekFrom},
path::Path,
};

const SECTOR_SIZE: u32 = 512;

pub fn create_mbr_disk(
bootsector_path: &Path,
second_stage_path: &Path,
bootsector_binary: &[u8],
second_stage_binary: &[u8],
boot_partition_path: &Path,
out_mbr_path: &Path,
) -> anyhow::Result<()> {
let mut boot_sector = File::open(bootsector_path).context("failed to open boot sector")?;
use std::io::Cursor;
let mut boot_sector = Cursor::new(bootsector_binary);
let mut mbr =
mbrman::MBR::read_from(&mut boot_sector, SECTOR_SIZE).context("failed to read MBR")?;

Expand All @@ -23,12 +25,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 = Cursor::new(second_stage_binary);
let second_stage_size = second_stage_binary.len() as u64;

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