Skip to content

Commit e997c74

Browse files
committed
Refactor image creation API
1 parent 5f51766 commit e997c74

File tree

4 files changed

+177
-130
lines changed

4 files changed

+177
-130
lines changed

Cargo.lock

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ anyhow = "1.0.32"
3333
fatfs = "0.3.4"
3434
gpt = "3.0.0"
3535
mbrman = "0.4.2"
36+
tempfile = "3.3.0"
3637

3738
[dev-dependencies]
3839
bootloader_test_runner = { path = "tests/runner" }

src/lib.rs

Lines changed: 111 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,15 @@
11
/*!
22
An experimental x86_64 bootloader that works on both BIOS and UEFI systems.
3-
4-
To use this crate, specify it as a dependency in the `Cargo.toml` of your operating system
5-
kernel. Then you can use the [`entry_point`] macro to mark your entry point function. This
6-
gives you access to the [`BootInfo`] struct, which is passed by the bootloader.
7-
8-
## Disk Image Creation
9-
10-
Including the `bootloader` crate as a dependency makes the kernel binary suitable for booting,
11-
but does not create any bootable disk images. To create them, two additional steps are needed:
12-
13-
1. **Locate the source code of the `bootloader` dependency** on your local system. By using the
14-
dependency source code directly, we ensure that the kernel and bootloader use the same version
15-
of the [`BootInfo`] struct.
16-
- When creating a builder binary written in Rust, the
17-
[`bootloader_locator`](https://docs.rs/bootloader-locator/0.0.4/bootloader_locator/) crate can
18-
be used to automate this step.
19-
- Otherwise, the
20-
[`cargo metadata`](https://doc.rust-lang.org/cargo/commands/cargo-metadata.html) subcommand
21-
can be used to locate the dependency. The command outputs a JSON object with various metadata
22-
for the current package. To find the `bootloader` source path in it, first look for the
23-
"bootloader" dependency under `resolve.nodes.deps` to find out its ID (in the `pkg` field).
24-
Then use that ID to find the bootloader in `packages`. Its `manifest_path` field contains the
25-
local path to the `Cargo.toml` of the bootloader.
26-
2. **Run the following command** in the source code directory of the `bootloader` dependency to create
27-
the bootable disk images:
28-
29-
```notrust
30-
cargo builder --kernel-manifest path/to/kernel/Cargo.toml --kernel-binary path/to/kernel_bin
31-
```
32-
33-
The `--kernel-manifest` argument should point to the `Cargo.toml` of your kernel. It is used
34-
for applying configuration settings. The `--kernel-binary` argument should point to the kernel
35-
executable that should be used for the bootable disk images.
36-
37-
In addition to the `--kernel-manifest` and `--kernel-binary` arguments, it is recommended to also
38-
set the `--target-dir` and `--out-dir` arguments. The former specifies the directory that should
39-
used for cargo build artifacts and the latter specfies the directory where the resulting disk
40-
images should be placed. It is recommended to set `--target-dir` to the `target` folder of your
41-
kernel and `--out-dir` to the the parent folder of `--kernel-binary`.
42-
43-
This will result in the following files, which are placed in the specified `--out-dir`:
44-
45-
- A disk image suitable for BIOS booting, named `boot-bios-<kernel>.img`, where `<kernel>` is the
46-
name of your kernel executable. This image can be started in QEMU or booted on a real machine
47-
after burning it to an USB stick..
48-
- A disk image suitable for UEFI booting, named `boot-uefi-<kernel>.img`. Like the BIOS disk image,
49-
this can be started in QEMU (requires OVMF) and burned to an USB stick to run it on a real
50-
machine.
51-
- Intermediate UEFI files
52-
- A FAT partition image named `boot-uefi-<kernel>.fat`, which can be directly started in QEMU
53-
or written as an EFI system partition to a GPT-formatted disk.
54-
- An EFI file named `boot-uefi-<kernel>.efi`. This executable is the combination of the
55-
bootloader and kernel executables. It can be started in QEMU or used to construct a bootable
56-
disk image: Create an EFI system partition formatted with the FAT filesystem and place the
57-
EFI file under `efi\boot\bootx64.efi` on that filesystem.
58-
59-
**You can find some examples that implement the above steps [in our GitHub repo](https://github.com/rust-osdev/bootloader/tree/main/examples).**
60-
61-
## Configuration
62-
63-
The bootloader can be configured through a `[package.metadata.bootloader]` table in the
64-
`Cargo.toml` of the kernel (the one passed as `--kernel-manifest`). See the [`Config`] struct
65-
for all possible configuration options.
663
*/
674

685
#![warn(missing_docs)]
696

707
use anyhow::Context;
71-
use std::{collections::BTreeMap, path::Path};
8+
use std::{
9+
collections::BTreeMap,
10+
path::{Path, PathBuf},
11+
};
12+
use tempfile::NamedTempFile;
7213

7314
mod fat;
7415
mod gpt;
@@ -79,61 +20,116 @@ const KERNEL_FILE_NAME: &str = "kernel-x86_64";
7920
const BIOS_STAGE_3: &str = "boot-stage-3";
8021
const BIOS_STAGE_4: &str = "boot-stage-4";
8122

82-
/// Creates a bootable FAT partition at the given path.
83-
pub fn create_boot_partition(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> {
84-
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));
85-
let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH"));
86-
let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH"));
87-
88-
let mut files = BTreeMap::new();
89-
files.insert("efi/boot/bootx64.efi", bootloader_path);
90-
files.insert(KERNEL_FILE_NAME, kernel_binary);
91-
files.insert(BIOS_STAGE_3, stage_3_path);
92-
files.insert(BIOS_STAGE_4, stage_4_path);
93-
94-
fat::create_fat_filesystem(files, &out_path).context("failed to create UEFI FAT filesystem")?;
95-
96-
Ok(())
23+
/// Create disk images for booting on legacy BIOS systems.
24+
pub struct BiosBoot {
25+
kernel: PathBuf,
9726
}
9827

99-
pub fn create_uefi_disk_image(
100-
boot_partition_path: &Path,
101-
out_gpt_path: &Path,
102-
) -> anyhow::Result<()> {
103-
gpt::create_gpt_disk(boot_partition_path, out_gpt_path)
104-
.context("failed to create UEFI GPT disk image")?;
105-
106-
Ok(())
28+
impl BiosBoot {
29+
/// Start creating a disk image for the given bootloader ELF executable.
30+
pub fn new(kernel_path: &Path) -> Self {
31+
Self {
32+
kernel: kernel_path.to_owned(),
33+
}
34+
}
35+
36+
/// Create a bootable UEFI disk image at the given path.
37+
pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> {
38+
let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH"));
39+
let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH"));
40+
41+
let fat_partition = self
42+
.create_fat_partition()
43+
.context("failed to create FAT partition")?;
44+
45+
mbr::create_mbr_disk(
46+
bootsector_path,
47+
stage_2_path,
48+
fat_partition.path(),
49+
out_path,
50+
)
51+
.context("failed to create BIOS MBR disk image")?;
52+
53+
fat_partition
54+
.close()
55+
.context("failed to delete FAT partition after disk image creation")?;
56+
57+
Ok(())
58+
}
59+
60+
/// Creates an BIOS-bootable FAT partition with the kernel.
61+
fn create_fat_partition(&self) -> anyhow::Result<NamedTempFile> {
62+
let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH"));
63+
let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH"));
64+
65+
let mut files = BTreeMap::new();
66+
files.insert(KERNEL_FILE_NAME, self.kernel.as_path());
67+
files.insert(BIOS_STAGE_3, stage_3_path);
68+
files.insert(BIOS_STAGE_4, stage_4_path);
69+
70+
let out_file = NamedTempFile::new().context("failed to create temp file")?;
71+
fat::create_fat_filesystem(files, out_file.path())
72+
.context("failed to create BIOS FAT filesystem")?;
73+
74+
Ok(out_file)
75+
}
10776
}
10877

109-
pub fn create_bios_disk_image(
110-
boot_partition_path: &Path,
111-
out_mbr_path: &Path,
112-
) -> anyhow::Result<()> {
113-
let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH"));
114-
let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH"));
115-
116-
mbr::create_mbr_disk(
117-
bootsector_path,
118-
stage_2_path,
119-
boot_partition_path,
120-
out_mbr_path,
121-
)
122-
.context("failed to create BIOS MBR disk image")?;
123-
124-
Ok(())
78+
/// Create disk images for booting on UEFI systems.
79+
pub struct UefiBoot {
80+
kernel: PathBuf,
12581
}
12682

127-
/// Prepare a folder for use with booting over UEFI_PXE.
128-
///
129-
/// This places the bootloader executable under the path "bootloader". The
130-
/// DHCP server should set the filename option to that path, otherwise the
131-
/// bootloader won't be found.
132-
pub fn create_uefi_pxe_tftp_folder(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> {
133-
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));
134-
135-
pxe::create_uefi_tftp_folder(bootloader_path, kernel_binary, out_path)
136-
.context("failed to create UEFI PXE tftp folder")?;
137-
138-
Ok(())
83+
impl UefiBoot {
84+
/// Start creating a disk image for the given bootloader ELF executable.
85+
pub fn new(kernel_path: &Path) -> Self {
86+
Self {
87+
kernel: kernel_path.to_owned(),
88+
}
89+
}
90+
91+
/// Create a bootable BIOS disk image at the given path.
92+
pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> {
93+
let fat_partition = self
94+
.create_fat_partition()
95+
.context("failed to create FAT partition")?;
96+
97+
gpt::create_gpt_disk(fat_partition.path(), out_path)
98+
.context("failed to create UEFI GPT disk image")?;
99+
100+
fat_partition
101+
.close()
102+
.context("failed to delete FAT partition after disk image creation")?;
103+
104+
Ok(())
105+
}
106+
107+
/// Prepare a folder for use with booting over UEFI_PXE.
108+
///
109+
/// This places the bootloader executable under the path "bootloader". The
110+
/// DHCP server should set the filename option to that path, otherwise the
111+
/// bootloader won't be found.
112+
pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> {
113+
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));
114+
115+
pxe::create_uefi_tftp_folder(bootloader_path, self.kernel.as_path(), out_path)
116+
.context("failed to create UEFI PXE tftp folder")?;
117+
118+
Ok(())
119+
}
120+
121+
/// Creates an UEFI-bootable FAT partition with the kernel.
122+
fn create_fat_partition(&self) -> anyhow::Result<NamedTempFile> {
123+
let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH"));
124+
125+
let mut files = BTreeMap::new();
126+
files.insert("efi/boot/bootx64.efi", bootloader_path);
127+
files.insert(KERNEL_FILE_NAME, self.kernel.as_path());
128+
129+
let out_file = NamedTempFile::new().context("failed to create temp file")?;
130+
fat::create_fat_filesystem(files, &out_file.path())
131+
.context("failed to create UEFI FAT filesystem")?;
132+
133+
Ok(out_file)
134+
}
139135
}

tests/runner/src/lib.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,25 @@ const QEMU_ARGS: &[&str] = &[
1313
pub fn run_test_kernel(kernel_binary_path: &str) {
1414
let kernel_path = Path::new(kernel_binary_path);
1515

16-
// create FAT filesystem with kernel executable and UEFI bootloader
17-
let out_fat_path = kernel_path.with_extension("fat");
18-
bootloader::create_boot_partition(kernel_path, &out_fat_path).unwrap();
19-
20-
// wrap the created filesystem in an GPT disk image for UEFI booting
21-
let out_gpt_path = kernel_path.with_extension("gpt");
22-
bootloader::create_uefi_disk_image(&out_fat_path, &out_gpt_path).unwrap();
16+
// create an MBR disk image for legacy BIOS booting
17+
let mbr_path = kernel_path.with_extension("mbr");
18+
bootloader::BiosBoot::new(kernel_path)
19+
.create_disk_image(&mbr_path)
20+
.unwrap();
2321

24-
// wrap the created filesystem in an MBR disk image for legacy BIOS booting
25-
let out_mbr_path = kernel_path.with_extension("mbr");
26-
bootloader::create_bios_disk_image(&out_fat_path, &out_mbr_path).unwrap();
22+
// create a GPT disk image for UEFI booting
23+
let gpt_path = kernel_path.with_extension("gpt");
24+
let uefi_builder = bootloader::UefiBoot::new(kernel_path);
25+
uefi_builder.create_disk_image(&gpt_path).unwrap();
2726

2827
// create a TFTP folder with the kernel executable and UEFI bootloader for
2928
// UEFI PXE booting
30-
let out_tftp_path = kernel_path.with_extension(".tftp");
31-
bootloader::create_uefi_pxe_tftp_folder(kernel_path, &out_tftp_path).unwrap();
29+
let tftp_path = kernel_path.with_extension(".tftp");
30+
uefi_builder.create_pxe_tftp_folder(&tftp_path).unwrap();
3231

33-
run_test_kernel_on_uefi(&out_gpt_path);
34-
run_test_kernel_on_bios(&out_mbr_path);
35-
run_test_kernel_on_uefi_pxe(&out_tftp_path);
32+
run_test_kernel_on_uefi(&gpt_path);
33+
run_test_kernel_on_bios(&mbr_path);
34+
run_test_kernel_on_uefi_pxe(&tftp_path);
3635
}
3736

3837
pub fn run_test_kernel_on_uefi(out_gpt_path: &Path) {

0 commit comments

Comments
 (0)