diff --git a/xtask/src/build.rs b/xtask/src/build.rs index 295e476..a4a6407 100644 --- a/xtask/src/build.rs +++ b/xtask/src/build.rs @@ -1,6 +1,7 @@ use std::{ ffi::OsStr, - fs, + fs::{self, File, OpenOptions}, + io::{self, Seek, Write}, path::{Path, PathBuf}, process::{Command, Output, Stdio}, }; @@ -9,29 +10,39 @@ use eyre::{WrapErr, eyre}; use crate::Context; +const SECTOR_SIZE: usize = 512; + const NASM_COMMON_FLAGS: &[&str] = &["-werror"]; pub fn build(ctx: &Context) -> Result<(), eyre::Error> { - let stage_1_src_dir = ctx.workspace.join("stage_1"); - let stage_2_src_dir = ctx.workspace.join("stage_2"); - let stage_3_src_dir = ctx.workspace.join("stage_3"); + let s1_src_dir = ctx.workspace.join("stage_1"); + let s2_src_dir = ctx.workspace.join("stage_2"); + let s3_src_dir = ctx.workspace.join("stage_3"); let build_dir = ctx.workspace.join("build"); - let stage_1_build_dir = build_dir.join("stage_1"); - let stage_2_build_dir = build_dir.join("stage_2"); + let s1_build_dir = build_dir.join("stage_1"); + let s2_build_dir = build_dir.join("stage_2"); mkdir_if_missing(&build_dir)?; - mkdir_if_missing(&stage_1_build_dir)?; - mkdir_if_missing(&stage_2_build_dir)?; + mkdir_if_missing(&s1_build_dir)?; + mkdir_if_missing(&s2_build_dir)?; + + println!("building stage 1"); + let s1_bin_path = build_stage_1(ctx, &s1_build_dir, &s1_src_dir)?; + + println!("building stage 2"); + let s2_bin_path = build_stage_2(ctx, &s2_build_dir, &s2_src_dir)?; + + println!("building stage 3"); + let s3_bin_path = build_stage_3(ctx, &s3_src_dir)?; - build_stage_1(ctx, &stage_1_build_dir, &stage_1_src_dir)?; - build_stage_2(ctx, &stage_2_build_dir, &stage_2_src_dir)?; - build_stage_3(ctx, &stage_3_src_dir)?; + println!("creating stage 2/3 blob"); + make_s23_blob(&build_dir, &s2_bin_path, &s3_bin_path)?; Ok(()) } -fn build_stage_1(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result<(), eyre::Error> { +fn build_stage_1(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result { let src_paths = ls_with_extension(src_dir, "s")?; let [src_path] = &*src_paths else { @@ -41,8 +52,8 @@ fn build_stage_1(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result<(), )); }; - let out_path = build_dir.join("s1.bin"); - + let bin_path = build_dir.join("s1.bin"); + let include_dir = ctx.workspace.join("include"); Command::new("nasm") @@ -51,16 +62,16 @@ fn build_stage_1(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result<(), .arg("-I") .arg(&include_dir) .arg("-o") - .arg(&out_path) + .arg(&bin_path) .arg(src_path) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .run_ok()?; - Ok(()) + Ok(bin_path) } -fn build_stage_2(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result<(), eyre::Error> { +fn build_stage_2(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result { let include_dir = ctx.workspace.join("include"); let src_paths = ls_with_extension(src_dir, "s")?; let mut obj_paths = Vec::new(); @@ -69,9 +80,9 @@ fn build_stage_2(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result<(), let Some(file_stem) = src_path.file_stem() else { continue; }; - + let obj_path = build_dir.join(file_stem).with_extension("o"); - + Command::new("nasm") .args(["-f", "elf"]) .args(NASM_COMMON_FLAGS) @@ -89,22 +100,22 @@ fn build_stage_2(ctx: &Context, build_dir: &Path, src_dir: &Path) -> Result<(), let linker_file_path = src_dir.join("link.ld"); let bin_path = build_dir.join("s2.bin"); - + Command::new("ld") .args(["-m", "elf_i386"]) .arg("-T") .arg(&linker_file_path) .arg("-o") - .arg(bin_path) + .arg(&bin_path) .args(&obj_paths) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .run_ok()?; - - Ok(()) + + Ok(bin_path) } -fn build_stage_3(ctx: &Context, stage_3_src_dir: &Path) -> Result<(), eyre::Error> { +fn build_stage_3(ctx: &Context, stage_3_src_dir: &Path) -> Result { Command::new(ctx.cargo) .arg("build") .arg("--release") @@ -113,29 +124,72 @@ fn build_stage_3(ctx: &Context, stage_3_src_dir: &Path) -> Result<(), eyre::Erro .stderr(Stdio::inherit()) .run_ok()?; - Ok(()) + let bin_path = { + let mut bin_path = stage_3_src_dir.to_owned(); + bin_path.extend(["target", "protected32", "release", "stage_3"]); + bin_path + }; + + Ok(bin_path) } -fn mkdir_if_missing(path: &Path) -> Result<(), eyre::Error> { - let exists = fs::exists(path) - .wrap_err_with(|| format!("failed to check if {} exists", path.to_string_lossy()))?; +fn make_s23_blob( + build_dir: &Path, s2_bin_path: &Path, s3_bin_path: &Path, +) -> Result<(), eyre::Error> { + let out_path = build_dir.join("s23.bin"); + + let mut out_file = OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(&out_path) + .wrap_io_err(IoOp::Open, &out_path)?; - if !exists { - fs::create_dir(path) - .wrap_err_with(|| format!("failed to create {}", path.to_string_lossy()))?; + let mut buf = [0u8; SECTOR_SIZE]; + + let s2_len_sectors = read_sectors_from_to(s2_bin_path, &mut out_file, &out_path, &mut buf)?; + if s2_len_sectors == 0 { + return Err(eyre!("empty stage 2")); } + let s3_offset_sectors_16 = u16::try_from(s2_len_sectors).wrap_err("stage 3 offset overflow")?; + + let s3_len_sectors = read_sectors_from_to(s3_bin_path, &mut out_file, &out_path, &mut buf)?; + if s3_len_sectors == 0 { + return Err(eyre!("empty stage 3")); + } + let s3_len_sectors_16 = u16::try_from(s3_len_sectors).wrap_err("stage 3 length overflow")?; + + out_file + .seek(io::SeekFrom::Start(0)) + .wrap_io_err(IoOp::Seek, &out_path)?; + + // Write the stage 2 data section fields + out_file + .write_all(&s3_offset_sectors_16.to_le_bytes()) + .wrap_io_err(IoOp::Write, &out_path)?; + out_file + .write_all(&s3_len_sectors_16.to_le_bytes()) + .wrap_io_err(IoOp::Write, &out_path)?; + out_file.flush().wrap_io_err(IoOp::Flush, &out_path)?; + + Ok(()) +} + +fn mkdir_if_missing(path: &Path) -> Result<(), eyre::Error> { + if !fs::exists(path).wrap_io_err(IoOp::Stat, path)? { + fs::create_dir(path).wrap_io_err(IoOp::Open, path)?; + } Ok(()) } fn ls_with_extension(path: &Path, ext: &str) -> Result, eyre::Error> { - let read_dir = fs::read_dir(path) - .wrap_err_with(|| format!("failed to read {}", path.to_string_lossy()))?; + let read_dir = fs::read_dir(path).wrap_io_err(IoOp::Read, path)?; let mut paths = Vec::new(); for dir in read_dir { - let dir = dir.wrap_err_with(|| format!("failed to read {}", path.to_string_lossy()))?; + let dir = dir.wrap_io_err(IoOp::Read, path)?; if dir.path().extension() == Some(OsStr::new(ext)) { paths.push(dir.path()); @@ -145,6 +199,57 @@ fn ls_with_extension(path: &Path, ext: &str) -> Result, eyre::Error Ok(paths) } +fn read_retry_eintr(r: &mut T, buf: &mut [u8]) -> Result +where + T: io::Read, +{ + loop { + match r.read(buf) { + Ok(n) => return Ok(n), + Err(err) => match err.kind() { + io::ErrorKind::Interrupted => (), + _ => return Err(err), + }, + } + } +} + +fn read_sectors_from_to( + src_path: &Path, dst_file: &mut File, dst_path: &Path, buf: &mut [u8], +) -> Result { + let mut total_read = 0u64; + + let mut src_file = File::open(src_path).wrap_io_err(IoOp::Open, src_path)?; + + loop { + match read_retry_eintr(&mut src_file, buf).wrap_io_err(IoOp::Read, src_path)? { + 0 => break, + n => { + total_read += n as u64; + dst_file + .write_all(&buf[..n]) + .wrap_io_err(IoOp::Write, dst_path)?; + } + } + } + + let remainder = (total_read % (SECTOR_SIZE as u64)) as usize; + let padding = (SECTOR_SIZE - remainder) % SECTOR_SIZE; + + if padding != 0 { + // Pad with 0xf4 since it encodes HLT, in case we accidentally jump to it. + buf[..padding].fill(0xf4); + dst_file + .write_all(&buf[..padding]) + .wrap_io_err(IoOp::Write, dst_path)?; + } + + let total_bytes = total_read + padding as u64; + assert!(total_bytes % SECTOR_SIZE as u64 == 0); + + Ok(total_bytes / SECTOR_SIZE as u64) +} + trait CommandExt { fn run_ok(&mut self) -> Result; } @@ -162,3 +267,39 @@ impl CommandExt for Command { Ok(output) } } + +trait WrapIoError { + fn wrap_io_err(self, op: IoOp, path: &Path) -> Result; +} + +impl WrapIoError for Result +where + Self: WrapErr, +{ + fn wrap_io_err(self, op: IoOp, path: &Path) -> Result { + self.wrap_err_with(|| format!("failed to {} {}", op.name(), path.to_string_lossy())) + } +} + +#[derive(Clone, Copy)] +enum IoOp { + Read, + Write, + Stat, + Open, + Seek, + Flush, +} + +impl IoOp { + fn name(self) -> &'static str { + match self { + IoOp::Read => "read", + IoOp::Write => "write", + IoOp::Stat => "stat", + IoOp::Open => "open", + IoOp::Seek => "seek", + IoOp::Flush => "flush", + } + } +}