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

Add bios and uefi cargo features (closes #287) #304

Merged
merged 9 commits into from
Jan 9, 2023
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,17 @@ bootloader_api = { version = "0.11.0", path = "api" }
bootloader-x86_64-common = { version = "0.11.0", path = "common" }
bootloader-x86_64-bios-common = { version = "0.11.0", path = "bios/common" }

[features]
default = ["bios", "uefi"]
bios = ["mbrman"]
uefi = ["gpt"]

[dependencies]
anyhow = "1.0.32"
fatfs = "0.3.4"
gpt = "3.0.0"
mbrman = "0.5.1"
tempfile = "3.3.0"
mbrman = { version = "0.5.1", optional = true }
gpt = { version = "3.0.0", optional = true }

[dev-dependencies]
bootloader_test_runner = { path = "tests/runner" }
Expand Down
83 changes: 43 additions & 40 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,49 @@ use std::{
process::Command,
};

const BOOTLOADER_X86_64_UEFI_VERSION: &str = env!("CARGO_PKG_VERSION");

const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = env!("CARGO_PKG_VERSION");
const BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION: &str = env!("CARGO_PKG_VERSION");
const BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION: &str = env!("CARGO_PKG_VERSION");
const BOOTLOADER_X86_64_BIOS_STAGE_4_VERSION: &str = env!("CARGO_PKG_VERSION");
const BOOTLOADER_VERSION: &str = env!("CARGO_PKG_VERSION");

fn main() {
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());

let uefi_path = build_uefi_bootloader(&out_dir);
println!(
"cargo:rustc-env=UEFI_BOOTLOADER_PATH={}",
uefi_path.display()
);
#[cfg(feature = "uefi")]
{
let uefi_path = build_uefi_bootloader(&out_dir);
println!(
"cargo:rustc-env=UEFI_BOOTLOADER_PATH={}",
uefi_path.display()
);
}

let bios_boot_sector_path = build_bios_boot_sector(&out_dir);
println!(
"cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}",
bios_boot_sector_path.display()
);
let bios_stage_2_path = build_bios_stage_2(&out_dir);
println!(
"cargo:rustc-env=BIOS_STAGE_2_PATH={}",
bios_stage_2_path.display()
);
#[cfg(feature = "bios")]
{
let bios_boot_sector_path = build_bios_boot_sector(&out_dir);
println!(
"cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}",
bios_boot_sector_path.display()
);
let bios_stage_2_path = build_bios_stage_2(&out_dir);
println!(
"cargo:rustc-env=BIOS_STAGE_2_PATH={}",
bios_stage_2_path.display()
);

let bios_stage_3_path = build_bios_stage_3(&out_dir);
println!(
"cargo:rustc-env=BIOS_STAGE_3_PATH={}",
bios_stage_3_path.display()
);
let bios_stage_3_path = build_bios_stage_3(&out_dir);
println!(
"cargo:rustc-env=BIOS_STAGE_3_PATH={}",
bios_stage_3_path.display()
);

let bios_stage_4_path = build_bios_stage_4(&out_dir);
println!(
"cargo:rustc-env=BIOS_STAGE_4_PATH={}",
bios_stage_4_path.display()
);
let bios_stage_4_path = build_bios_stage_4(&out_dir);
println!(
"cargo:rustc-env=BIOS_STAGE_4_PATH={}",
bios_stage_4_path.display()
);
}
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "uefi")]
fn build_uefi_bootloader(out_dir: &Path) -> PathBuf {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let mut cmd = Command::new(cargo);
Expand All @@ -53,7 +55,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf {
cmd.arg("--path").arg("uefi");
println!("cargo:rerun-if-changed=uefi");
} else {
cmd.arg("--version").arg(BOOTLOADER_X86_64_UEFI_VERSION);
cmd.arg("--version").arg(BOOTLOADER_VERSION);
}
cmd.arg("--locked");
cmd.arg("--target").arg("x86_64-unknown-uefi");
Expand All @@ -78,6 +80,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf {
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
fn build_bios_boot_sector(out_dir: &Path) -> PathBuf {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let mut cmd = Command::new(cargo);
Expand All @@ -90,8 +93,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf {
cmd.arg("--path").arg(&local_path);
println!("cargo:rerun-if-changed={}", local_path.display());
} else {
cmd.arg("--version")
.arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION);
cmd.arg("--version").arg(BOOTLOADER_VERSION);
}
cmd.arg("--locked");
cmd.arg("--target").arg("i386-code16-boot-sector.json");
Expand Down Expand Up @@ -121,6 +123,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf {
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
fn build_bios_stage_2(out_dir: &Path) -> PathBuf {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let mut cmd = Command::new(cargo);
Expand All @@ -133,8 +136,7 @@ fn build_bios_stage_2(out_dir: &Path) -> PathBuf {
cmd.arg("--path").arg(&local_path);
println!("cargo:rerun-if-changed={}", local_path.display());
} else {
cmd.arg("--version")
.arg(BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION);
cmd.arg("--version").arg(BOOTLOADER_VERSION);
}
cmd.arg("--locked");
cmd.arg("--target").arg("i386-code16-stage-2.json");
Expand Down Expand Up @@ -162,6 +164,7 @@ fn build_bios_stage_2(out_dir: &Path) -> PathBuf {
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
fn build_bios_stage_3(out_dir: &Path) -> PathBuf {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let mut cmd = Command::new(cargo);
Expand All @@ -174,8 +177,7 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf {
cmd.arg("--path").arg(&local_path);
println!("cargo:rerun-if-changed={}", local_path.display());
} else {
cmd.arg("--version")
.arg(BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION);
cmd.arg("--version").arg(BOOTLOADER_VERSION);
}
cmd.arg("--locked");
cmd.arg("--target").arg("i686-stage-3.json");
Expand Down Expand Up @@ -203,6 +205,7 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf {
}

#[cfg(not(docsrs_dummy_build))]
#[cfg(feature = "bios")]
fn build_bios_stage_4(out_dir: &Path) -> PathBuf {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let mut cmd = Command::new(cargo);
Expand All @@ -215,8 +218,7 @@ fn build_bios_stage_4(out_dir: &Path) -> PathBuf {
cmd.arg("--path").arg(&local_path);
println!("cargo:rerun-if-changed={}", local_path.display());
} else {
cmd.arg("--version")
.arg(BOOTLOADER_X86_64_BIOS_STAGE_4_VERSION);
cmd.arg("--version").arg(BOOTLOADER_VERSION);
}
cmd.arg("--locked");
cmd.arg("--target").arg("x86_64-stage-4.json");
Expand Down Expand Up @@ -244,6 +246,7 @@ fn build_bios_stage_4(out_dir: &Path) -> PathBuf {
convert_elf_to_bin(elf_path)
}

#[cfg(feature = "bios")]
fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf {
let flat_binary_path = elf_path.with_extension("bin");

Expand Down
File renamed without changes.
67 changes: 67 additions & 0 deletions src/bios/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::fat;
use anyhow::Context;
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};
use tempfile::NamedTempFile;

mod mbr;

const BIOS_STAGE_3: &str = "boot-stage-3";
const BIOS_STAGE_4: &str = "boot-stage-4";

/// Create disk images for booting on legacy BIOS systems.
pub struct BiosBoot {
kernel: PathBuf,
}

impl BiosBoot {
/// Start creating a disk image for the given bootloader ELF executable.
pub fn new(kernel_path: &Path) -> Self {
Self {
kernel: kernel_path.to_owned(),
}
}

/// Create a bootable UEFI disk image at the given path.
pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> {
let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH"));
let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH"));

let fat_partition = self
.create_fat_partition()
.context("failed to create FAT partition")?;

mbr::create_mbr_disk(
bootsector_path,
stage_2_path,
fat_partition.path(),
out_path,
)
.context("failed to create BIOS MBR disk image")?;

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

Ok(())
}

/// Creates an BIOS-bootable FAT partition with the kernel.
fn create_fat_partition(&self) -> anyhow::Result<NamedTempFile> {
let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH"));
let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH"));

let mut files = BTreeMap::new();
files.insert(crate::KERNEL_FILE_NAME, self.kernel.as_path());
files.insert(BIOS_STAGE_3, stage_3_path);
files.insert(BIOS_STAGE_4, stage_4_path);

let out_file = NamedTempFile::new().context("failed to create temp file")?;
fat::create_fat_filesystem(files, out_file.path())
.context("failed to create BIOS FAT filesystem")?;

Ok(out_file)
}
}
134 changes: 9 additions & 125 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,132 +4,16 @@ An experimental x86_64 bootloader that works on both BIOS and UEFI systems.

#![warn(missing_docs)]

use anyhow::Context;
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};
use tempfile::NamedTempFile;

#[cfg(feature = "bios")]
mod bios;
mod fat;
mod gpt;
mod mbr;
mod pxe;

const KERNEL_FILE_NAME: &str = "kernel-x86_64";
const BIOS_STAGE_3: &str = "boot-stage-3";
const BIOS_STAGE_4: &str = "boot-stage-4";

/// Create disk images for booting on legacy BIOS systems.
pub struct BiosBoot {
kernel: PathBuf,
}

impl BiosBoot {
/// Start creating a disk image for the given bootloader ELF executable.
pub fn new(kernel_path: &Path) -> Self {
Self {
kernel: kernel_path.to_owned(),
}
}

/// Create a bootable UEFI disk image at the given path.
pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> {
let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH"));
let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH"));

let fat_partition = self
.create_fat_partition()
.context("failed to create FAT partition")?;

mbr::create_mbr_disk(
bootsector_path,
stage_2_path,
fat_partition.path(),
out_path,
)
.context("failed to create BIOS MBR disk image")?;

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

Ok(())
}

/// Creates an BIOS-bootable FAT partition with the kernel.
fn create_fat_partition(&self) -> anyhow::Result<NamedTempFile> {
let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH"));
let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH"));

let mut files = BTreeMap::new();
files.insert(KERNEL_FILE_NAME, self.kernel.as_path());
files.insert(BIOS_STAGE_3, stage_3_path);
files.insert(BIOS_STAGE_4, stage_4_path);

let out_file = NamedTempFile::new().context("failed to create temp file")?;
fat::create_fat_filesystem(files, out_file.path())
.context("failed to create BIOS FAT filesystem")?;
#[cfg(feature = "uefi")]
mod uefi;

Ok(out_file)
}
}
#[cfg(feature = "bios")]
pub use bios::BiosBoot;

/// Create disk images for booting on UEFI systems.
pub struct UefiBoot {
kernel: PathBuf,
}
#[cfg(feature = "uefi")]
pub use uefi::UefiBoot;

impl UefiBoot {
/// Start creating a disk image for the given bootloader ELF executable.
pub fn new(kernel_path: &Path) -> Self {
Self {
kernel: kernel_path.to_owned(),
}
}

/// Create a bootable BIOS disk image at the given path.
pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> {
let fat_partition = self
.create_fat_partition()
.context("failed to create FAT partition")?;

gpt::create_gpt_disk(fat_partition.path(), out_path)
.context("failed to create UEFI GPT disk image")?;

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

Ok(())
}

/// Prepare a folder for use with booting over UEFI_PXE.
///
/// This places the bootloader executable under the path "bootloader". The
/// DHCP server should set the filename option to that path, otherwise the
/// bootloader won't be found.
pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> {
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));

pxe::create_uefi_tftp_folder(bootloader_path, self.kernel.as_path(), out_path)
.context("failed to create UEFI PXE tftp folder")?;

Ok(())
}

/// Creates an UEFI-bootable FAT partition with the kernel.
fn create_fat_partition(&self) -> anyhow::Result<NamedTempFile> {
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));

let mut files = BTreeMap::new();
files.insert("efi/boot/bootx64.efi", bootloader_path);
files.insert(KERNEL_FILE_NAME, self.kernel.as_path());

let out_file = NamedTempFile::new().context("failed to create temp file")?;
fat::create_fat_filesystem(files, out_file.path())
.context("failed to create UEFI FAT filesystem")?;

Ok(out_file)
}
}
const KERNEL_FILE_NAME: &str = "kernel-x86_64";
File renamed without changes.
Loading