Skip to content

Commit c7828d3

Browse files
committed
Create a GPT disk image with an UEFI boot partition
1 parent f20ac53 commit c7828d3

File tree

3 files changed

+210
-50
lines changed

3 files changed

+210
-50
lines changed

Cargo.lock

+55-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ thiserror = { version = "1.0.20", optional = true }
4747
json = { version = "0.12.4", optional = true }
4848
rsdp = { version = "1.0.0", optional = true }
4949
fatfs = { version = "0.3.4", optional = true }
50+
gpt = { version = "2.0.0", optional = true }
5051

5152
[dependencies.font8x8]
5253
version = "0.2.5"
@@ -61,7 +62,7 @@ serde = { version = "1.0", features = ["derive"], optional = true}
6162

6263
[features]
6364
default = []
64-
builder = ["argh", "thiserror", "displaydoc", "anyhow", "llvm-tools", "json", "fatfs"]
65+
builder = ["argh", "thiserror", "displaydoc", "anyhow", "llvm-tools", "json", "fatfs", "gpt"]
6566
runner = ["anyhow"]
6667
bios_bin = ["binary", "vga_320x200", "rsdp"]
6768
uefi_bin = ["binary", "uefi", "font8x8"]

src/bin/builder.rs

+153-48
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
use anyhow::{anyhow, Context};
1+
use anyhow::{anyhow, bail, Context};
22
use argh::FromArgs;
33
use bootloader::disk_image::create_disk_image;
44
use std::{
5-
fs, io,
5+
convert::TryFrom,
6+
fs::{self, File},
7+
io::{self, Seek},
68
path::{Path, PathBuf},
79
process::Command,
810
str::FromStr,
@@ -129,55 +131,32 @@ fn main() -> anyhow::Result<()> {
129131

130132
assert_eq!(executables.len(), 1);
131133
let executable_path = executables.pop().unwrap();
132-
let executable_name = executable_path.file_stem().unwrap().to_str().unwrap();
133-
let kernel_name = args.kernel_binary.file_name().unwrap().to_str().unwrap();
134+
135+
let executable_name = executable_path
136+
.file_stem()
137+
.and_then(|stem| stem.to_str())
138+
.ok_or_else(|| {
139+
anyhow!(
140+
"executable path `{}` has invalid file stem",
141+
executable_path.display()
142+
)
143+
})?;
144+
let kernel_name = args
145+
.kernel_binary
146+
.file_name()
147+
.and_then(|name| name.to_str())
148+
.ok_or_else(|| {
149+
anyhow!(
150+
"kernel binary path `{}` has invalid file name",
151+
args.kernel_binary.display()
152+
)
153+
})?;
134154

135155
if let Some(out_dir) = &args.out_dir {
136156
let efi_file =
137157
out_dir.join(format!("bootimage-{}-{}.efi", executable_name, kernel_name));
138-
fs::copy(&executable_path, &efi_file).context("failed to copy efi file to out dir")?;
139-
140-
let efi_size = fs::metadata(&efi_file)
141-
.context("failed to read metadata of efi file")?
142-
.len();
143-
144-
// create fat partition
145-
{
146-
const MB: u64 = 1024 * 1024;
147-
148-
let fat_path = efi_file.with_extension("fat");
149-
dbg!(&fat_path);
150-
let fat_file = fs::OpenOptions::new()
151-
.read(true)
152-
.write(true)
153-
.create(true)
154-
.truncate(true)
155-
.open(&fat_path)
156-
.context("Failed to create UEFI FAT file")?;
157-
let efi_size_rounded = ((efi_size - 1) / MB + 1) * MB;
158-
fat_file
159-
.set_len(dbg!(efi_size_rounded))
160-
.context("failed to set UEFI FAT file length")?;
161-
162-
// create new FAT partition
163-
fatfs::format_volume(&fat_file, fatfs::FormatVolumeOptions::new())
164-
.context("Failed to format UEFI FAT file")?;
165-
166-
// copy EFI file to FAT filesystem
167-
let partition = fatfs::FileSystem::new(&fat_file, fatfs::FsOptions::new())
168-
.context("Failed to open FAT file system of UEFI FAT file")?;
169-
let root_dir = partition.root_dir();
170-
root_dir.create_dir("efi")?;
171-
root_dir.create_dir("efi/boot")?;
172-
let mut bootx64 = root_dir.create_file("efi/boot/bootx64.efi")?;
173-
bootx64.truncate()?;
174-
io::copy(&mut fs::File::open(&executable_path)?, &mut bootx64)?;
175-
}
176-
177-
// create gpt disk
178-
{
179-
//todo!()
180-
}
158+
create_uefi_disk_image(&executable_path, &efi_file)
159+
.context("failed to create UEFI disk image")?;
181160
}
182161
}
183162

@@ -228,7 +207,7 @@ fn main() -> anyhow::Result<()> {
228207
let mut output_bin_path = executable_path
229208
.parent()
230209
.unwrap()
231-
.join(format!("bootimage-{}-{}.bin", executable_name, kernel_name));
210+
.join(format!("bootimage-{}-{}.img", executable_name, kernel_name));
232211

233212
create_disk_image(&executable_path, &output_bin_path)
234213
.context("Failed to create bootable disk image")?;
@@ -254,6 +233,132 @@ fn main() -> anyhow::Result<()> {
254233
Ok(())
255234
}
256235

236+
fn create_uefi_disk_image(executable_path: &Path, efi_file: &Path) -> anyhow::Result<()> {
237+
fs::copy(&executable_path, &efi_file).context("failed to copy efi file to out dir")?;
238+
239+
let efi_size = fs::metadata(&efi_file)
240+
.context("failed to read metadata of efi file")?
241+
.len();
242+
243+
// create fat partition
244+
let fat_file_path = {
245+
const MB: u64 = 1024 * 1024;
246+
247+
let fat_path = efi_file.with_extension("fat");
248+
let fat_file = fs::OpenOptions::new()
249+
.read(true)
250+
.write(true)
251+
.create(true)
252+
.truncate(true)
253+
.open(&fat_path)
254+
.context("Failed to create UEFI FAT file")?;
255+
let efi_size_rounded = ((efi_size - 1) / MB + 1) * MB;
256+
fat_file
257+
.set_len(efi_size_rounded)
258+
.context("failed to set UEFI FAT file length")?;
259+
260+
// create new FAT partition
261+
let format_options = fatfs::FormatVolumeOptions::new().volume_label(*b"FOOO ");
262+
fatfs::format_volume(&fat_file, format_options)
263+
.context("Failed to format UEFI FAT file")?;
264+
265+
// copy EFI file to FAT filesystem
266+
let partition = fatfs::FileSystem::new(&fat_file, fatfs::FsOptions::new())
267+
.context("Failed to open FAT file system of UEFI FAT file")?;
268+
let root_dir = partition.root_dir();
269+
root_dir.create_dir("efi")?;
270+
root_dir.create_dir("efi/boot")?;
271+
let mut bootx64 = root_dir.create_file("efi/boot/bootx64.efi")?;
272+
bootx64.truncate()?;
273+
io::copy(&mut fs::File::open(&executable_path)?, &mut bootx64)?;
274+
275+
fat_path
276+
};
277+
278+
// create gpt disk
279+
{
280+
let image_path = efi_file.with_extension("img");
281+
let mut image = fs::OpenOptions::new()
282+
.create(true)
283+
.truncate(true)
284+
.read(true)
285+
.write(true)
286+
.open(&image_path)
287+
.context("failed to create UEFI disk image")?;
288+
289+
let partition_size: u64 = fs::metadata(&fat_file_path)
290+
.context("failed to read metadata of UEFI FAT partition")?
291+
.len();
292+
let image_size = partition_size + 1024 * 64;
293+
image
294+
.set_len(image_size)
295+
.context("failed to set length of UEFI disk image")?;
296+
297+
// Create a protective MBR at LBA0
298+
let mbr = gpt::mbr::ProtectiveMBR::with_lb_size(
299+
u32::try_from((image_size / 512) - 1).unwrap_or(0xFF_FF_FF_FF),
300+
);
301+
mbr.overwrite_lba0(&mut image)
302+
.context("failed to write protective MBR")?;
303+
304+
// create new GPT in image file
305+
let block_size = gpt::disk::LogicalBlockSize::Lb512;
306+
let block_size_bytes: u64 = block_size.into();
307+
let mut disk = gpt::GptConfig::new()
308+
.writable(true)
309+
.initialized(false)
310+
.logical_block_size(block_size)
311+
.create_from_device(Box::new(&mut image), None)
312+
.context("failed to open UEFI disk image")?;
313+
disk.update_partitions(Default::default())
314+
.context("failed to initialize GPT partition table")?;
315+
316+
// add add EFI system partition
317+
let partition_id = disk
318+
.add_partition("boot", partition_size, gpt::partition_types::EFI, 0)
319+
.context("failed to add boot partition")?;
320+
321+
let partition = disk
322+
.partitions()
323+
.get(&partition_id)
324+
.ok_or_else(|| anyhow!("Partition doesn't exist after adding it"))?;
325+
let created_partition_size: u64 =
326+
(partition.last_lba - partition.first_lba + 1u64) * block_size_bytes;
327+
if created_partition_size != partition_size {
328+
bail!(
329+
"Created partition has invalid size (size is {:?}, expected {})",
330+
created_partition_size,
331+
partition_size
332+
);
333+
}
334+
let start_offset = partition
335+
.bytes_start(block_size)
336+
.context("failed to retrieve partition start offset")?;
337+
338+
// Write the partition table
339+
disk.write()
340+
.context("failed to write GPT partition table to UEFI image file")?;
341+
342+
image
343+
.seek(io::SeekFrom::Start(start_offset))
344+
.context("failed to seek to boot partiiton start")?;
345+
let bytes_written = io::copy(
346+
&mut File::open(&fat_file_path).context("failed to open fat image")?,
347+
&mut image,
348+
)
349+
.context("failed to write boot partition content")?;
350+
if bytes_written != partition_size {
351+
bail!(
352+
"Invalid number of partition bytes written (expected {}, got {})",
353+
partition_size,
354+
bytes_written
355+
);
356+
}
357+
}
358+
359+
Ok(())
360+
}
361+
257362
fn bios_run(bin_path: &Path) -> anyhow::Result<Option<ExitCode>> {
258363
let mut qemu = Command::new("qemu-system-x86_64");
259364
qemu.arg("-drive")

0 commit comments

Comments
 (0)