Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
37 changes: 17 additions & 20 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ 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());
Expand Down Expand Up @@ -44,6 +39,7 @@ fn main() {
}

#[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 +49,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 +74,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 +87,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 +117,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 +130,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 +158,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 +171,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 +199,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 +212,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 +240,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 Expand Up @@ -273,23 +270,23 @@ fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf {

// dummy implementations because docsrs builds have no network access

#[cfg(docsrs_dummy_build)]
#[cfg(any(docsrs_dummy_build, not(feature = "uefi")))]
fn build_uefi_bootloader(_out_dir: &Path) -> PathBuf {
PathBuf::new()
}
#[cfg(docsrs_dummy_build)]
#[cfg(any(docsrs_dummy_build, not(feature = "bios")))]
fn build_bios_boot_sector(_out_dir: &Path) -> PathBuf {
PathBuf::new()
}
#[cfg(docsrs_dummy_build)]
#[cfg(any(docsrs_dummy_build, not(feature = "bios")))]
fn build_bios_stage_2(_out_dir: &Path) -> PathBuf {
PathBuf::new()
}
#[cfg(docsrs_dummy_build)]
#[cfg(any(docsrs_dummy_build, not(feature = "bios")))]
fn build_bios_stage_3(_out_dir: &Path) -> PathBuf {
PathBuf::new()
}
#[cfg(docsrs_dummy_build)]
#[cfg(any(docsrs_dummy_build, not(feature = "bios")))]
fn build_bios_stage_4(_out_dir: &Path) -> PathBuf {
PathBuf::new()
}
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