2023-05-06 06:50:24 -05:00
|
|
|
use {
|
2023-07-12 20:21:33 -05:00
|
|
|
derive_more::{Deref, DerefMut, Display},
|
|
|
|
error_stack::{bail, ensure, report, Context, IntoReport, Result, ResultExt},
|
2023-05-06 06:50:24 -05:00
|
|
|
fatfs::{FileSystem, FormatVolumeOptions, FsOptions, ReadWriteSeek},
|
2023-07-12 20:21:33 -05:00
|
|
|
std::{
|
|
|
|
fmt::Display,
|
|
|
|
fs::File,
|
|
|
|
io::{self, Write},
|
|
|
|
path::Path,
|
|
|
|
process::Command,
|
|
|
|
},
|
2023-05-06 06:50:24 -05:00
|
|
|
};
|
2023-03-30 16:43:04 -05:00
|
|
|
|
|
|
|
fn main() -> Result<(), Error> {
|
|
|
|
env_logger::init();
|
|
|
|
let mut args = std::env::args();
|
|
|
|
args.next();
|
2023-06-13 21:03:09 -05:00
|
|
|
|
2023-07-08 23:21:27 -05:00
|
|
|
// let disk_meta = fs::metadata("target/disk.img").unwrap();
|
|
|
|
// let config_meta = fs::metadata("system.toml").unwrap();
|
2023-06-13 21:03:09 -05:00
|
|
|
|
2023-07-08 23:21:27 -05:00
|
|
|
// if disk_meta.modified().unwrap() < config_meta.modified().unwrap() {
|
|
|
|
// // TODO: work on adding in system.toml support
|
|
|
|
// // TODO: rebuild the disk
|
|
|
|
// }
|
2023-03-30 16:43:04 -05:00
|
|
|
|
|
|
|
match args.next().as_deref() {
|
|
|
|
Some("build" | "b") => {
|
|
|
|
let mut release = false;
|
|
|
|
let mut target = Target::X86_64;
|
|
|
|
for arg in args {
|
|
|
|
if arg == "-r" || arg == "--release" {
|
|
|
|
release = true;
|
|
|
|
}
|
|
|
|
if arg == "rv64" || arg == "riscv64" || arg == "riscv64-virt" {
|
|
|
|
target = Target::Riscv64Virt;
|
|
|
|
}
|
|
|
|
}
|
2022-08-03 02:11:51 -05:00
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
build(release, target).change_context(Error::Build)
|
|
|
|
}
|
|
|
|
Some("run" | "r") => {
|
|
|
|
let mut release = false;
|
|
|
|
let mut target = Target::X86_64;
|
|
|
|
for arg in args {
|
|
|
|
if arg == "-r" || arg == "--release" {
|
|
|
|
release = true;
|
|
|
|
}
|
|
|
|
if arg == "rv64" || arg == "riscv64" || arg == "riscv64-virt" {
|
|
|
|
target = Target::Riscv64Virt;
|
|
|
|
}
|
|
|
|
}
|
2022-08-03 02:11:51 -05:00
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
build(release, target)?;
|
|
|
|
run(release, target)
|
|
|
|
}
|
|
|
|
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",
|
|
|
|
" [target]: sets target"
|
|
|
|
),);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
_ => Err(report!(Error::InvalidSubCom)),
|
|
|
|
}
|
2022-08-03 02:11:51 -05:00
|
|
|
}
|
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
fn get_fs() -> Result<FileSystem<impl ReadWriteSeek>, 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")
|
|
|
|
.into_report()
|
|
|
|
.attach_printable("copying Limine bootloader (have you pulled the submodule?)")?,
|
|
|
|
&mut bootdir.create_file("bootx64.efi")?,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
io::copy(
|
|
|
|
&mut File::open("repbuild/limine.cfg")?,
|
|
|
|
&mut fs.root_dir().create_file("limine.cfg")?,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
io::copy(
|
|
|
|
&mut File::open("repbuild/background.bmp")?,
|
|
|
|
&mut fs.root_dir().create_file("background.bmp")?,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
drop(bootdir);
|
|
|
|
Ok(fs)
|
2022-08-03 02:11:51 -05:00
|
|
|
}
|
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
fn build(release: bool, target: Target) -> Result<(), Error> {
|
2023-07-12 20:21:33 -05:00
|
|
|
fetch_ovmf().change_context(Error::OvmfFetch)?;
|
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2023-07-12 20:21:33 -05:00
|
|
|
if target == Target::Riscv64Virt {
|
|
|
|
com.args(["--target", "targets/riscv64-virt-ableos.json"]);
|
2022-08-07 07:35:55 -05:00
|
|
|
}
|
2022-08-03 02:11:51 -05:00
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
match com.status() {
|
|
|
|
Ok(s) if s.code() != Some(0) => bail!(Error::Build),
|
|
|
|
Err(e) => bail!(report!(e).change_context(Error::Build)),
|
|
|
|
_ => (),
|
|
|
|
}
|
2022-08-03 02:11:51 -05:00
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
if target != Target::X86_64 {
|
|
|
|
return Ok(());
|
|
|
|
}
|
2022-08-07 07:35:55 -05:00
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
(|| -> 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)
|
|
|
|
}
|
2022-08-07 07:35:55 -05:00
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
fn run(release: bool, target: Target) -> Result<(), Error> {
|
|
|
|
let mut com = match target {
|
|
|
|
Target::X86_64 => Command::new("qemu-system-x86_64"),
|
|
|
|
Target::Riscv64Virt => Command::new("qemu-system-riscv64"),
|
|
|
|
};
|
|
|
|
|
|
|
|
if target == Target::X86_64 {
|
|
|
|
#[rustfmt::skip]
|
|
|
|
com.args([
|
2023-07-12 20:21:33 -05:00
|
|
|
"-bios", "target/OVMF_CODE.fd",
|
2023-03-30 16:43:04 -05:00
|
|
|
"-drive", "file=target/disk.img,format=raw",
|
|
|
|
"-m", "4G",
|
2023-05-06 06:50:24 -05:00
|
|
|
// "-serial", "stdio",
|
|
|
|
"-smp", "cores=4",
|
2023-07-12 06:03:06 -05:00
|
|
|
// "-vga", "cirrus",
|
2023-05-23 05:16:14 -05:00
|
|
|
// "-device", "ati-vga",
|
2023-07-12 06:03:06 -05:00
|
|
|
// "-device", "virtio-gpu-pci",
|
2023-05-06 06:50:24 -05:00
|
|
|
|
2023-07-12 06:03:06 -05:00
|
|
|
// "-device", "virtio-serial,id=virtio-serial0",
|
|
|
|
// "-chardev", "stdio,id=char0,mux=on",
|
|
|
|
// "-device", "virtconsole,chardev=char0",
|
|
|
|
// "-device", "virtio-mouse-pci",
|
2023-05-06 06:50:24 -05:00
|
|
|
|
|
|
|
// "-device", "ati-vga", "model=rage128p"
|
2023-03-30 16:43:04 -05:00
|
|
|
]);
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
{
|
2023-07-12 06:03:06 -05:00
|
|
|
|
2023-07-12 11:27:42 -05:00
|
|
|
//com.args(["-enable-kvm", "-cpu", "host"]);
|
2022-08-07 07:35:55 -05:00
|
|
|
}
|
2023-03-30 16:43:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if target == Target::Riscv64Virt {
|
|
|
|
#[rustfmt::skip]
|
|
|
|
com.args([
|
|
|
|
"-M", "virt",
|
|
|
|
"-m", "128M",
|
|
|
|
"-serial", "stdio",
|
|
|
|
"-kernel",
|
|
|
|
if release {
|
|
|
|
"target/riscv64-virt-ableos/release/kernel"
|
|
|
|
} else {
|
|
|
|
"target/riscv64-virt-ableos/debug/kernel"
|
2022-08-03 02:11:51 -05:00
|
|
|
}
|
2023-03-30 16:43:04 -05:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
match com
|
|
|
|
.status()
|
|
|
|
.into_report()
|
|
|
|
.change_context(Error::ProcessSpawn)?
|
|
|
|
{
|
|
|
|
s if s.success() => Ok(()),
|
|
|
|
s => Err(report!(Error::Qemu(s.code()))),
|
2022-08-07 07:35:55 -05:00
|
|
|
}
|
2022-08-03 02:11:51 -05:00
|
|
|
}
|
|
|
|
|
2023-07-12 20:21:33 -05:00
|
|
|
fn fetch_ovmf() -> Result<(), OvmfFetchError> {
|
|
|
|
const OVMF_RPM_URL: &str = "https://kojipkgs.fedoraproject.org/packages/edk2/20230524/3.fc38/noarch/edk2-ovmf-20230524-3.fc38.noarch.rpm";
|
|
|
|
|
|
|
|
let mut file = match std::fs::metadata("target/OVMF_CODE.fd") {
|
|
|
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => std::fs::OpenOptions::new()
|
|
|
|
.create(true)
|
|
|
|
.write(true)
|
|
|
|
.read(true)
|
|
|
|
.open("target/OVMF_CODE.fd")
|
|
|
|
.into_report()
|
|
|
|
.change_context(OvmfFetchError::Io)?,
|
|
|
|
Ok(_) => return Ok(()),
|
|
|
|
Err(e) => return Err(report!(e).change_context(OvmfFetchError::Io)),
|
|
|
|
};
|
|
|
|
|
|
|
|
log::info!("No OVMF found, downloading…");
|
|
|
|
let rpm = rpm::RPMPackage::parse(
|
|
|
|
&mut std::convert::identity::<reqwest::Result<_>>((|| {
|
|
|
|
reqwest::blocking::get(OVMF_RPM_URL)?.bytes()
|
|
|
|
})())
|
|
|
|
.into_report()
|
|
|
|
.change_context(OvmfFetchError::Fetch)?
|
|
|
|
.as_ref(),
|
|
|
|
)
|
|
|
|
.map_err(RpmError)
|
|
|
|
.into_report()
|
|
|
|
.change_context(OvmfFetchError::RpmParse)?;
|
|
|
|
|
|
|
|
ensure!(
|
|
|
|
rpm.metadata
|
|
|
|
.get_payload_compressor()
|
|
|
|
.map_err(RpmError)
|
|
|
|
.into_report()
|
|
|
|
.change_context(OvmfFetchError::RpmParse)?
|
|
|
|
== rpm::CompressionType::Zstd,
|
|
|
|
OvmfFetchError::UnsupportedCompression,
|
|
|
|
);
|
|
|
|
|
|
|
|
file.write_all(
|
|
|
|
cpio_reader::iter_files(
|
|
|
|
&zstd::decode_all(std::io::Cursor::new(rpm.content))
|
|
|
|
.into_report()
|
|
|
|
.change_context(OvmfFetchError::Zstd)?,
|
|
|
|
)
|
|
|
|
.find(|file| file.name() == "./usr/share/edk2/ovmf/OVMF_CODE.fd")
|
|
|
|
.ok_or_else(|| report!(OvmfFetchError::NoFileFound))?
|
|
|
|
.file(),
|
|
|
|
)
|
|
|
|
.into_report()
|
|
|
|
.change_context(OvmfFetchError::Io)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Display, Deref, DerefMut)]
|
|
|
|
struct RpmError(rpm::RPMError);
|
|
|
|
impl Context for RpmError {}
|
|
|
|
|
|
|
|
// Ehhh?? I am pretty sure they just forgot :ferrisClueless:
|
|
|
|
unsafe impl Sync for RpmError {}
|
|
|
|
unsafe impl Send for RpmError {}
|
|
|
|
|
|
|
|
#[derive(Debug, Display)]
|
|
|
|
enum OvmfFetchError {
|
|
|
|
#[display(fmt = "Failed to fetch OVMF package")]
|
|
|
|
Fetch,
|
|
|
|
#[display(fmt = "RPM parse error")]
|
|
|
|
RpmParse,
|
|
|
|
#[display(fmt = "Unsupported compression (ZSTD is the only supported one)")]
|
|
|
|
UnsupportedCompression,
|
|
|
|
#[display(fmt = "Decompression error")]
|
|
|
|
Zstd,
|
|
|
|
#[display(fmt = "Requested file not found in package")]
|
|
|
|
NoFileFound,
|
|
|
|
#[display(fmt = "IO Error")]
|
|
|
|
Io,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Context for OvmfFetchError {}
|
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
|
|
enum Target {
|
|
|
|
X86_64,
|
|
|
|
Riscv64Virt,
|
|
|
|
}
|
2022-08-03 02:11:51 -05:00
|
|
|
|
2023-07-12 20:21:33 -05:00
|
|
|
#[derive(Debug, Display)]
|
2023-03-30 16:43:04 -05:00
|
|
|
enum Error {
|
2023-07-12 20:21:33 -05:00
|
|
|
#[display(fmt = "Failed to build the kernel")]
|
2023-03-30 16:43:04 -05:00
|
|
|
Build,
|
2023-07-12 20:21:33 -05:00
|
|
|
#[display(fmt = "Missing or invalid subcommand (available: build, run)")]
|
2023-03-30 16:43:04 -05:00
|
|
|
InvalidSubCom,
|
2023-07-12 20:21:33 -05:00
|
|
|
#[display(fmt = "IO Error")]
|
2023-03-30 16:43:04 -05:00
|
|
|
Io,
|
2023-07-12 20:21:33 -05:00
|
|
|
#[display(fmt = "Failed to spawn a process")]
|
2023-03-30 16:43:04 -05:00
|
|
|
ProcessSpawn,
|
2023-07-12 20:21:33 -05:00
|
|
|
#[display(fmt = "Failed to fetch UEFI firmware")]
|
|
|
|
OvmfFetch,
|
|
|
|
#[display(fmt = "QEMU Error: {}", "fmt_qemu_err(*_0)")]
|
2023-03-30 16:43:04 -05:00
|
|
|
Qemu(Option<i32>),
|
2022-08-03 02:11:51 -05:00
|
|
|
}
|
|
|
|
|
2023-03-30 16:43:04 -05:00
|
|
|
impl Context for Error {}
|
2023-07-12 20:21:33 -05:00
|
|
|
|
|
|
|
fn fmt_qemu_err(e: Option<i32>) -> impl Display {
|
|
|
|
struct W(Option<i32>);
|
|
|
|
impl Display for W {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
if let Some(c) = self.0 {
|
|
|
|
c.fmt(f)
|
|
|
|
} else {
|
|
|
|
f.write_str("Interrupted by signal")
|
2023-03-30 16:43:04 -05:00
|
|
|
}
|
2022-08-07 07:35:55 -05:00
|
|
|
}
|
|
|
|
}
|
2023-07-12 20:21:33 -05:00
|
|
|
|
|
|
|
W(e)
|
2022-08-03 02:11:51 -05:00
|
|
|
}
|