Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
64 changes: 0 additions & 64 deletions .github/workflows/deploy.yml

This file was deleted.

2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ostool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
name = "ostool"
readme = "../README.md"
repository = "https://github.com/drivercraft/ostool"
version = "0.8.12"
version = "0.8.13"

[package.metadata.binstall]
bin-dir = "{ name }-{ target }-v{ version }/{ bin }{ binary-ext }"
Expand Down
178 changes: 151 additions & 27 deletions ostool/src/run/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use std::{
ffi::OsString,
io::{self, BufReader, ErrorKind, Read, Write},
path::Path,
path::PathBuf,
Comment on lines +26 to 27
process::{Child, Stdio},
};
Expand All @@ -41,6 +42,14 @@ use crate::{
utils::PathResultExt,
};

enum UefiBootConfig {
Pflash {
code: PathBuf,
vars: PathBuf,
esp_dir: PathBuf,
},
}

/// QEMU configuration structure.
///
/// This configuration is typically loaded from a `.qemu.toml` file.
Expand Down Expand Up @@ -152,9 +161,16 @@ impl QemuRunner {
self.ctx.objcopy_output_bin()?;
}

let arch = self.detect_arch()?;
let detected_arch = self.ctx.arch.ok_or_else(|| {
anyhow!("Please specify `arch` in QEMU config or provide a valid ELF file.")
})?;
let arch = format!("{detected_arch:?}").to_lowercase();

let machine = "virt".to_string();
let machine = match detected_arch {
Architecture::X86_64 | Architecture::I386 => "q35",
_ => "virt",
}
.to_string();

let mut need_machine = true;

Expand Down Expand Up @@ -208,14 +224,35 @@ impl QemuRunner {
cmd.arg("-s").arg("-S");
}

if let Some(bios) = self.bios().await? {
cmd.arg("-bios").arg(bios);
let mut use_kernel_loader = true;
if let Some(uefi) = self.prepare_uefi().await? {
match uefi {
UefiBootConfig::Pflash {
code,
vars,
esp_dir,
} => {
cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=0,readonly=on,file={}",
code.display()
));
cmd.arg("-drive").arg(format!(
"if=pflash,format=raw,unit=1,file={}",
vars.display()
));
cmd.arg("-drive")
.arg(format!("format=raw,file=fat:rw:{}", esp_dir.display()));
Comment on lines +235 to +244
use_kernel_loader = false;
}
}
}

if let Some(bin_path) = &self.ctx.paths.artifacts.bin {
cmd.arg("-kernel").arg(bin_path);
} else if let Some(elf_path) = &self.ctx.paths.artifacts.elf {
cmd.arg("-kernel").arg(elf_path);
if use_kernel_loader {
if let Some(bin_path) = &self.ctx.paths.artifacts.bin {
cmd.arg("-kernel").arg(bin_path);
} else if let Some(elf_path) = &self.ctx.paths.artifacts.elf {
cmd.arg("-kernel").arg(elf_path);
}
}
cmd.stdout(Stdio::piped());
cmd.print_cmd();
Expand Down Expand Up @@ -261,25 +298,11 @@ impl QemuRunner {
Ok(())
}

fn detect_arch(&self) -> anyhow::Result<String> {
if let Some(arch) = &self.ctx.arch {
return Ok(format!("{:?}", arch).to_lowercase());
async fn prepare_uefi(&self) -> anyhow::Result<Option<UefiBootConfig>> {
if !self.config.uefi {
return Ok(None);
}

Err(anyhow!(
"Please specify `arch` in QEMU config or provide a valid ELF file."
))
}

async fn bios(&self) -> anyhow::Result<Option<PathBuf>> {
if self.config.uefi {
Ok(Some(self.preper_ovmf().await?))
} else {
Ok(None)
}
}

async fn preper_ovmf(&self) -> anyhow::Result<PathBuf> {
let arch =
self.ctx.arch.as_ref().ok_or_else(|| {
anyhow::anyhow!("Cannot determine architecture for OVMF preparation")
Expand All @@ -302,9 +325,110 @@ impl QemuRunner {
o => return Err(anyhow::anyhow!("OVMF is not supported for {o:?} ",)),
};

let bios_path = prebuilt.get_file(arch, FileType::Code);
let code = prebuilt.get_file(arch, FileType::Code);
let vars_template = prebuilt.get_file(arch, FileType::Vars);
let esp_dir = self.prepare_uefi_esp(arch).await?;
let vars = self.prepare_uefi_vars(&vars_template).await?;

Ok(Some(UefiBootConfig::Pflash {
code,
vars,
esp_dir,
}))
Comment on lines +328 to +337
}

async fn prepare_uefi_esp(&self, arch: Arch) -> anyhow::Result<PathBuf> {
let bin_path = self
.ctx
.paths
.artifacts
.bin
.as_ref()
.ok_or_else(|| anyhow!("UEFI boot requires a BIN artifact"))?;
let stem = bin_path
.file_stem()
.ok_or_else(|| anyhow!("invalid BIN path: {}", bin_path.display()))?;
let artifact_dir = self.uefi_artifact_dir(bin_path)?;
let esp_dir = artifact_dir.join(format!("{}.esp", stem.to_string_lossy()));
let boot_dir = esp_dir.join("EFI").join("BOOT");
fs::create_dir_all(&boot_dir)
.await
.with_path("failed to create directory", &boot_dir)?;

let boot_path = boot_dir.join(Self::default_uefi_boot_filename(arch));
fs::copy(bin_path, &boot_path).await.with_context(|| {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid booting raw BIN as an EFI application

With uefi = true, this path copies ctx.paths.artifacts.bin into EFI/BOOT/BOOT*.EFI, but in this codebase objcopy_output_bin() produces a raw -O binary image, not a PE/COFF EFI executable. Because the commit also disables the -kernel loader in UEFI mode, default workflows (to_bin = true) now hand OVMF a non-EFI file and the VM will fail to boot for existing ELF-based kernels.

Useful? React with 👍 / 👎.

format!(
"failed to copy EFI image from {} to {}",
bin_path.display(),
boot_path.display()
)
})?;

Ok(esp_dir)
}

fn uefi_artifact_dir(&self, bin_path: &Path) -> anyhow::Result<PathBuf> {
let metadata = self.ctx.metadata()?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not require Cargo metadata in UEFI artifact path

This helper unconditionally calls self.ctx.metadata() before computing the artifact directory, so UEFI runs now fail in system = "Custom" projects that do not have a Cargo.toml in manifest (a supported mode). Previously the UEFI preparation path did not depend on Cargo metadata, so this introduces a regression for non-Cargo custom build workflows.

Useful? React with 👍 / 👎.

let target_dir = metadata.target_directory.into_std_path_buf();
let target_dir = target_dir.canonicalize().unwrap_or(target_dir);
let bin_path = bin_path
.canonicalize()
.with_path("failed to canonicalize file", bin_path)?;
let artifact_dir = match bin_path.strip_prefix(&target_dir) {
Ok(relative_bin_path) => {
let artifact_parent = relative_bin_path.parent().ok_or_else(|| {
anyhow!(
"invalid BIN path under target directory: {}",
bin_path.display()
)
})?;
target_dir.join(artifact_parent)
}
Err(_) => bin_path
.parent()
.ok_or_else(|| anyhow!("invalid BIN path: {}", bin_path.display()))?
.to_path_buf(),
};

Ok(artifact_dir)
}

async fn prepare_uefi_vars(&self, vars_template: &Path) -> anyhow::Result<PathBuf> {
let bin_path = self
.ctx
.paths
.artifacts
.bin
.as_ref()
.ok_or_else(|| anyhow!("UEFI boot requires a BIN artifact"))?;
let stem = bin_path
.file_stem()
.ok_or_else(|| anyhow!("invalid BIN path: {}", bin_path.display()))?;
let artifact_dir = self.uefi_artifact_dir(bin_path)?;
fs::create_dir_all(&artifact_dir)
.await
.with_path("failed to create directory", &artifact_dir)?;

let vars = artifact_dir.join(format!("{}.vars.fd", stem.to_string_lossy()));
fs::copy(vars_template, &vars).await.with_context(|| {
format!(
"failed to copy OVMF vars from {} to {}",
vars_template.display(),
vars.display()
)
})?;

Ok(vars)
}

Ok(bios_path)
fn default_uefi_boot_filename(arch: Arch) -> &'static str {
match arch {
Arch::Aarch64 => "BOOTAA64.EFI",
Arch::Ia32 => "BOOTIA32.EFI",
Arch::LoongArch64 => "BOOTLOONGARCH64.EFI",
Arch::Riscv64 => "BOOTRISCV64.EFI",
Arch::X64 => "BOOTX64.EFI",
}
}

fn check_output(
Expand Down
Loading