use error_stack::{bail, report, Context, IntoReport, Result, ResultExt}; use fatfs::{FileSystem, FormatVolumeOptions, FsOptions, ReadWriteSeek}; use nix::fcntl::FallocateFlags; use std::{fmt::Display, fs::File, io, os::fd::AsRawFd, path::Path, process::Command}; fn main() -> Result<(), Error> { env_logger::init(); let mut args = std::env::args(); args.next(); match args.next().as_deref() { Some("build" | "b") => build( args.next() .map(|x| x == "-r" || x == "--release") .unwrap_or_default(), ) .change_context(Error::Build), Some("run" | "r") => { build( args.next() .map(|x| x == "-r" || x == "--release") .unwrap_or_default(), )?; run() } Some("help" | "h") => { println!(concat!( "AbleOS RepBuild\n", "Subcommands:\n", " build (b): Build a bootable disk image\n", " help (h): Print this message\n", " run (r): Build and run AbleOS in QEMU\n\n", "Options for build and run:\n", " -r: build in release mode", ),); Ok(()) } _ => Err(report!(Error::InvalidSubCom)), } } fn get_fs() -> Result, io::Error> { let path = Path::new("target/disk.img"); match std::fs::metadata(path) { Err(e) if e.kind() == io::ErrorKind::NotFound => (), Err(e) => bail!(e), Ok(_) => { return FileSystem::new( File::options().read(true).write(true).open(path)?, FsOptions::new(), ) .into_report() } } let mut img = File::options() .read(true) .write(true) .create(true) .open(path)?; img.set_len(1024 * 1024 * 64)?; fatfs::format_volume(&mut img, FormatVolumeOptions::new())?; let fs = FileSystem::new(img, FsOptions::new())?; let bootdir = fs.root_dir().create_dir("efi")?.create_dir("boot")?; io::copy( &mut File::open("limine/BOOTX64.EFI")?, &mut bootdir.create_file("bootx64.efi")?, )?; io::copy( &mut File::open(Path::new("repbuild/limine.cfg"))?, &mut fs.root_dir().create_file("limine.cfg")?, )?; drop(bootdir); Ok(fs) } fn build(release: bool) -> Result<(), Error> { let fs = get_fs().change_context(Error::Io)?; let mut com = Command::new("cargo"); com.current_dir("kernel"); com.args(["b"]); if release { com.arg("-r"); } com.status().into_report().change_context(Error::Build)?; (|| -> std::io::Result<_> { io::copy( &mut File::open( Path::new("target/x86_64-ableos") .join(if release { "release" } else { "debug" }) .join("kernel"), )?, &mut fs.root_dir().create_file("kernel")?, ) .map(|_| ()) })() .into_report() .change_context(Error::Io) } fn run() -> Result<(), Error> { let mut com = Command::new("qemu-system-x86_64"); #[rustfmt::skip] com.args([ "-bios", "/usr/share/OVMF/OVMF_CODE.fd", "-drive", "file=target/disk.img,format=raw", "-m", "4G", "-serial", "stdio", "-smp", "cores=2", ]); #[cfg(target_os = "linux")] { com.args(["-enable-kvm", "-cpu", "host"]); } match com .status() .into_report() .change_context(Error::ProcessSpawn)? { s if s.success() => Ok(()), s => Err(report!(Error::Qemu(s.code()))), } } #[derive(Debug)] enum Error { Build, InvalidSubCom, Io, ProcessSpawn, Qemu(Option), } impl Context for Error {} impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Build => f.write_str("failed to build the kernel"), Self::InvalidSubCom => { f.write_str("missing or invalid subcommand (available: build, run)") } Self::Io => f.write_str("IO error"), Self::ProcessSpawn => f.write_str("failed to spawn a process"), Self::Qemu(Some(c)) => write!(f, "QEMU Error: {c}"), Self::Qemu(None) => write!(f, "QEMU Error: interrupted by signal"), } } }