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
18 changes: 9 additions & 9 deletions Cargo.lock

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

7 changes: 2 additions & 5 deletions xtask/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@ fatfs = { version = "0.3.6", default-features = false, features = ["alloc", "std
fs-err = "3.0.0"
heck = "0.5.0"
itertools = "0.13.0"
lzma-rs = "0.3.0"
log.workspace = true
mbrman = "0.5.1"
nix = { version = "0.29.0", default-features = false, features = ["fs"] }
os_info = { version = "3.6.0", default-features = false }
ovmf-prebuilt = "0.2.0"
proc-macro2 = { version = "1.0.46", features = ["span-locations"] }
quote = "1.0.21"
regex = "1.10.2"
serde_json = "1.0.73"
sha2 = "0.10.6"
syn = { version = "2.0.0", features = ["full"] }
tar = "0.4.38"
tempfile = "3.6.0"
walkdir = "2.4.0"
ureq = "2.8.0"
25 changes: 25 additions & 0 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use arch::UefiArch;
use cargo::{Cargo, CargoAction, Feature, Package, TargetTypes};
use clap::Parser;
use itertools::Itertools;
use log::{LevelFilter, Metadata, Record};
use opt::{Action, BuildOpt, ClippyOpt, CovOpt, DocOpt, Opt, QemuOpt, TpmVersion};
use std::process::Command;
use util::run_cmd;
Expand Down Expand Up @@ -322,9 +323,33 @@ fn has_cmd(target_cmd: &str) -> bool {
run_cmd(cmd).is_ok()
}

fn install_logger() {
struct Logger;

impl log::Log for Logger {
fn enabled(&self, _: &Metadata) -> bool {
true
}

fn log(&self, record: &Record) {
println!("[{}] {}", record.level(), record.args());
}

fn flush(&self) {}
}

static LOGGER: Logger = Logger;

log::set_logger(&LOGGER)
.map(|()| log::set_max_level(LevelFilter::Info))
.unwrap();
}

fn main() -> Result<()> {
let opt = Opt::parse();

install_logger();

match &opt.action {
Action::Build(build_opt) => build(build_opt),
Action::CheckRaw(_) => check_raw::check_raw(),
Expand Down
201 changes: 39 additions & 162 deletions xtask/src/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ use crate::tpm::Swtpm;
use crate::util::command_to_string;
use crate::{net, platform};
use anyhow::{bail, Context, Result};
use ovmf_prebuilt::{FileType, Prebuilt, Source};
use regex::bytes::Regex;
use serde_json::{json, Value};
use sha2::{Digest, Sha256};
use std::env;
use std::ffi::OsString;
use std::io::{BufRead, BufReader, Cursor, Read, Write};
use std::io::{BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use tar::Archive;
use tempfile::TempDir;
use ureq::Agent;
#[cfg(target_os = "linux")]
use {std::fs::Permissions, std::os::unix::fs::PermissionsExt};

Expand All @@ -38,161 +36,41 @@ const ENV_VAR_OVMF_VARS: &str = "OVMF_VARS";
/// Environment variable for overriding the path of the OVMF shell file.
const ENV_VAR_OVMF_SHELL: &str = "OVMF_SHELL";

/// Download `url` and return the raw data.
fn download_url(url: &str) -> Result<Vec<u8>> {
let agent: Agent = ureq::AgentBuilder::new()
.user_agent("uefi-rs-ovmf-downloader")
.build();

// Limit the size of the download.
let max_size_in_bytes = 5 * 1024 * 1024;

// Download the file.
println!("downloading {url}");
let resp = agent.get(url).call()?;
let mut data = Vec::with_capacity(max_size_in_bytes);
resp.into_reader()
.take(max_size_in_bytes.try_into().unwrap())
.read_to_end(&mut data)?;
println!("received {} bytes", data.len());

Ok(data)
}

// Extract the tarball's files into `prebuilt_dir`.
//
// `tarball_data` is raw decompressed tar data.
fn extract_prebuilt(tarball_data: &[u8], prebuilt_dir: &Path) -> Result<()> {
let cursor = Cursor::new(tarball_data);
let mut archive = Archive::new(cursor);

// Extract each file entry.
for entry in archive.entries()? {
let mut entry = entry?;

// Skip directories.
if entry.size() == 0 {
continue;
impl From<UefiArch> for ovmf_prebuilt::Arch {
fn from(arch: UefiArch) -> Self {
match arch {
UefiArch::AArch64 => Self::Aarch64,
UefiArch::IA32 => Self::Ia32,
UefiArch::X86_64 => Self::X64,
}

let path = entry.path()?;
// Strip the leading directory, which is the release name.
let path: PathBuf = path.components().skip(1).collect();

let dir = path.parent().unwrap();
let dst_dir = prebuilt_dir.join(dir);
let dst_path = prebuilt_dir.join(path);
println!("unpacking to {}", dst_path.display());
fs_err::create_dir_all(dst_dir)?;
entry.unpack(dst_path)?;
}

Ok(())
}

/// Update the local copy of the prebuilt OVMF files. Does nothing if the local
/// copy is already up to date.
fn update_prebuilt() -> Result<PathBuf> {
let prebuilt_dir = Path::new(OVMF_PREBUILT_DIR);
let hash_path = prebuilt_dir.join("sha256");

// Check if the hash file already has the expected hash in it. If so, assume
// that we've already got the correct prebuilt downloaded and unpacked.
if let Ok(current_hash) = fs_err::read_to_string(&hash_path) {
if current_hash == OVMF_PREBUILT_HASH {
return Ok(prebuilt_dir.to_path_buf());
/// Get a user-provided path for the given OVMF file type.
///
/// This uses the command-line arg if present, otherwise it falls back to an
/// environment variable. If neither is present, returns `None`.
fn get_user_provided_path(file_type: FileType, opt: &QemuOpt) -> Option<PathBuf> {
let opt_path;
let var_name;
match file_type {
FileType::Code => {
opt_path = &opt.ovmf_code;
var_name = ENV_VAR_OVMF_CODE;
}
}

let base_url = "https://github.com/rust-osdev/ovmf-prebuilt/releases/download";
let url = format!(
"{base_url}/{release}/{release}-bin.tar.xz",
release = OVMF_PREBUILT_TAG
);

let data = download_url(&url)?;

// Validate the hash.
let actual_hash = format!("{:x}", Sha256::digest(&data));
if actual_hash != OVMF_PREBUILT_HASH {
bail!(
"file hash {actual_hash} does not match {}",
OVMF_PREBUILT_HASH
);
}

// Unpack the tarball.
println!("decompressing tarball");
let mut decompressed = Vec::new();
let mut compressed = Cursor::new(data);
lzma_rs::xz_decompress(&mut compressed, &mut decompressed)?;

// Clear out the existing prebuilt dir, if present.
let _ = fs_err::remove_dir_all(prebuilt_dir);

// Extract the files.
extract_prebuilt(&decompressed, prebuilt_dir)?;

// Rename the x64 directory to x86_64, to match `Arch::as_str`.
fs_err::rename(prebuilt_dir.join("x64"), prebuilt_dir.join("x86_64"))?;

// Write out the hash file. When we upgrade to a new release of
// ovmf-prebuilt, the hash will no longer match, triggering a fresh
// download.
fs_err::write(&hash_path, actual_hash)?;

Ok(prebuilt_dir.to_path_buf())
}

#[derive(Clone, Copy, Debug)]
enum OvmfFileType {
Code,
Vars,
Shell,
}

impl OvmfFileType {
fn as_str(&self) -> &'static str {
match self {
Self::Code => "code",
Self::Vars => "vars",
Self::Shell => "shell",
FileType::Vars => {
opt_path = &opt.ovmf_vars;
var_name = ENV_VAR_OVMF_VARS;
}
}

fn extension(&self) -> &'static str {
match self {
Self::Code | Self::Vars => "fd",
Self::Shell => "efi",
FileType::Shell => {
opt_path = &None;
var_name = ENV_VAR_OVMF_SHELL;
}
}

/// Get a user-provided path for the given OVMF file type.
///
/// This uses the command-line arg if present, otherwise it falls back to an
/// environment variable. If neither is present, returns `None`.
fn get_user_provided_path(self, opt: &QemuOpt) -> Option<PathBuf> {
let opt_path;
let var_name;
match self {
Self::Code => {
opt_path = &opt.ovmf_code;
var_name = ENV_VAR_OVMF_CODE;
}
Self::Vars => {
opt_path = &opt.ovmf_vars;
var_name = ENV_VAR_OVMF_VARS;
}
Self::Shell => {
opt_path = &None;
var_name = ENV_VAR_OVMF_SHELL;
}
}
if let Some(path) = opt_path {
Some(path.clone())
} else {
env::var_os(var_name).map(PathBuf::from)
}
if let Some(path) = opt_path {
Some(path.clone())
} else {
env::var_os(var_name).map(PathBuf::from)
}
}

Expand All @@ -210,8 +88,8 @@ impl OvmfPaths {
/// 1. Command-line arg
/// 2. Environment variable
/// 3. Prebuilt file (automatically downloaded)
fn find_ovmf_file(file_type: OvmfFileType, opt: &QemuOpt, arch: UefiArch) -> Result<PathBuf> {
if let Some(path) = file_type.get_user_provided_path(opt) {
fn find_ovmf_file(file_type: FileType, opt: &QemuOpt, arch: UefiArch) -> Result<PathBuf> {
if let Some(path) = get_user_provided_path(file_type, opt) {
// The user provided an exact path to use; verify that it
// exists.
if path.exists() {
Expand All @@ -224,22 +102,21 @@ impl OvmfPaths {
);
}
} else {
let prebuilt_dir = update_prebuilt()?;
let prebuilt = Prebuilt::fetch(Source {
tag: OVMF_PREBUILT_TAG,
sha256: OVMF_PREBUILT_HASH,
}, OVMF_PREBUILT_DIR)?;

Ok(prebuilt_dir.join(format!(
"{arch}/{}.{}",
file_type.as_str(),
file_type.extension()
)))
Ok(prebuilt.get_file(arch.into(), file_type))
}
}

/// Find path to OVMF files by the strategy documented for
/// [`Self::find_ovmf_file`].
fn find(opt: &QemuOpt, arch: UefiArch) -> Result<Self> {
let code = Self::find_ovmf_file(OvmfFileType::Code, opt, arch)?;
let vars = Self::find_ovmf_file(OvmfFileType::Vars, opt, arch)?;
let shell = Self::find_ovmf_file(OvmfFileType::Shell, opt, arch)?;
let code = Self::find_ovmf_file(FileType::Code, opt, arch)?;
let vars = Self::find_ovmf_file(FileType::Vars, opt, arch)?;
let shell = Self::find_ovmf_file(FileType::Shell, opt, arch)?;

Ok(Self { code, vars, shell })
}
Expand Down
Loading