forked from AbleOS/ableos
Merge branch 'master' into master
This commit is contained in:
commit
bea92d996c
1704
Cargo.lock
generated
1704
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,7 @@ version = "0.2.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
hbvm = { git = "https://git.ablecorp.us/ableos/holey-bytes" }
|
hbvm = { git = "https://git.ablecorp.us/ableos/holey-bytes" }
|
||||||
hbasm = { git = "https://git.ablecorp.us/ableos/holey-bytes" }
|
# hbasm = { git = "https://git.ablecorp.us/ableos/holey-bytes" }
|
||||||
|
|
||||||
embedded-graphics = "0.7.1"
|
embedded-graphics = "0.7.1"
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ SECTIONS
|
||||||
. = ALIGN(4K);
|
. = ALIGN(4K);
|
||||||
PROVIDE(_initial_kernel_heap_start = .);
|
PROVIDE(_initial_kernel_heap_start = .);
|
||||||
/* PROVIDE(_initial_kernel_heap_size = 1024 * 1024); */
|
/* PROVIDE(_initial_kernel_heap_size = 1024 * 1024); */
|
||||||
PROVIDE(_initial_kernel_heap_size = 1024 * 4096);
|
PROVIDE(_initial_kernel_heap_size = 1024 * 4096 * 100);
|
||||||
. += _initial_kernel_heap_size;
|
. += _initial_kernel_heap_size;
|
||||||
} :data
|
} :data
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,49 +63,26 @@ pub enum HostError {
|
||||||
// // pub fn rpc_register(_engine: &mut Engine) {}
|
// // pub fn rpc_register(_engine: &mut Engine) {}
|
||||||
// // pub fn rpc_call(_engine: &mut Engine) {}
|
// // pub fn rpc_call(_engine: &mut Engine) {}
|
||||||
|
|
||||||
use log::error;
|
use {hbvm::vm::mem::HandlePageFault, log::error};
|
||||||
|
|
||||||
use hbvm::vm::{
|
|
||||||
mem::{Memory, MemoryAccessReason, PageSize},
|
|
||||||
trap::HandleTrap,
|
|
||||||
value::Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// AbleOS HBVM traphandler
|
/// AbleOS HBVM traphandler
|
||||||
pub struct TrapHandler;
|
pub struct TrapHandler;
|
||||||
impl HandleTrap for TrapHandler {
|
impl HandlePageFault for TrapHandler {
|
||||||
fn page_fault(
|
fn page_fault(
|
||||||
&mut self,
|
&mut self,
|
||||||
mar: MemoryAccessReason,
|
reason: hbvm::vm::mem::MemoryAccessReason,
|
||||||
memory: &mut Memory,
|
memory: &mut hbvm::vm::mem::Memory,
|
||||||
vaddr: u64,
|
vaddr: u64,
|
||||||
size: PageSize,
|
size: hbvm::vm::mem::PageSize,
|
||||||
dataptr: *mut u8,
|
dataptr: *mut u8,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
error!(
|
error!(
|
||||||
"MemoryAccessReason: {}
|
"REASON: {}
|
||||||
Memory: {:?}
|
memory: {:?}
|
||||||
VAddr: {}
|
vaddr: {}
|
||||||
Size: {:?}
|
size: {:?}
|
||||||
DataPTR: {:?}",
|
Dataptr {:?}",
|
||||||
mar, memory, vaddr, size, dataptr
|
reason, memory, vaddr, size, dataptr
|
||||||
);
|
);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invalid_op(&mut self, _: &mut [Value; 256], _: &mut usize, _: &mut Memory, _: u8) -> bool
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
log::trace!("Invalid opcode");
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ecall(&mut self, _: &mut [Value; 256], _: &mut usize, _: &mut Memory)
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
log::trace!("ableOS system call made");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
//! AbleOS Kernel Entrypoint
|
//! AbleOS Kernel Entrypoint
|
||||||
|
|
||||||
use crate::capabilities;
|
|
||||||
|
|
||||||
// use crate::arch::sloop;
|
// use crate::arch::sloop;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
arch::logging::SERIAL_CONSOLE,
|
|
||||||
bootmodules::{build_cmd, BootModules},
|
bootmodules::{build_cmd, BootModules},
|
||||||
|
capabilities,
|
||||||
device_tree::DeviceTree,
|
device_tree::DeviceTree,
|
||||||
scheduler::Scheduler,
|
scheduler::Scheduler,
|
||||||
|
|
||||||
},
|
},
|
||||||
alloc::format,
|
alloc::format,
|
||||||
log::{debug, info, trace},
|
log::{debug, info, trace},
|
||||||
|
@ -43,69 +40,12 @@ pub fn kmain(cmdline: &str, boot_modules: BootModules) -> ! {
|
||||||
|
|
||||||
capabilities::example();
|
capabilities::example();
|
||||||
|
|
||||||
// TODO: change this to a driver
|
let mut sched = Scheduler::new();
|
||||||
{
|
// AHEM that isn't a valid HBVM program
|
||||||
let mut prog = alloc::vec![];
|
sched.new_process(boot_modules[0].bytes.clone());
|
||||||
let mut code = alloc::string::String::new();
|
sched.new_process(boot_modules[1].bytes.clone());
|
||||||
|
|
||||||
let mut sc = SERIAL_CONSOLE.lock();
|
|
||||||
loop {
|
|
||||||
match sc.receive() {
|
|
||||||
b'\r' => {
|
|
||||||
code.push('\n');
|
|
||||||
|
|
||||||
sc.send(b'\r');
|
|
||||||
sc.send(b'\n');
|
|
||||||
|
|
||||||
match hbasm::assembly(&code, &mut prog) {
|
|
||||||
Ok(_) => {
|
|
||||||
use hbvm::validate::validate;
|
|
||||||
match validate(&prog) {
|
|
||||||
Err(_e) => {
|
|
||||||
// log::error!("Program validation error: {e:?}");
|
|
||||||
}
|
|
||||||
Ok(_) => {
|
|
||||||
|
|
||||||
// log::info!("valid program");
|
|
||||||
// use {crate::host::TrapHandler, hbvm::vm::Vm};
|
|
||||||
let mut sched = Scheduler::new();
|
|
||||||
sched.new_process(prog.clone());
|
|
||||||
sched.scheduler_run();
|
|
||||||
// let mut vm;
|
|
||||||
// unsafe {
|
|
||||||
// vm = Vm::new_unchecked(&prog, TrapHandler);
|
|
||||||
// vm.memory.insert_test_page();
|
|
||||||
// }
|
|
||||||
// log::info!("Program interrupt: {:?}", vm.run());
|
|
||||||
// log::debug!("{:?}", vm.registers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sc.send(b'>');
|
|
||||||
}
|
|
||||||
Err(_e) => {
|
|
||||||
// log::error!(
|
|
||||||
// "Error {:?} at {:?} (`{}`)",
|
|
||||||
// e.kind,
|
|
||||||
// e.span.clone(),
|
|
||||||
// &code[e.span],
|
|
||||||
// );
|
|
||||||
for x in "err".as_bytes() {
|
|
||||||
sc.send(*x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code = alloc::string::String::new();
|
|
||||||
}
|
|
||||||
byte => {
|
|
||||||
code.push(byte as char);
|
|
||||||
|
|
||||||
sc.send(byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
sched.run();
|
||||||
// sloop();
|
// sloop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,16 +16,17 @@ impl log::Log for Logger {
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
fn log(&self, record: &log::Record) {
|
||||||
let lvl = record.level();
|
let lvl = record.level();
|
||||||
|
let lvl_color = match lvl {
|
||||||
|
Level::Error => "160",
|
||||||
|
Level::Warn => "172",
|
||||||
|
Level::Info => "47",
|
||||||
|
Level::Debug => "25",
|
||||||
|
Level::Trace => "103",
|
||||||
|
};
|
||||||
|
let module = record.module_path().unwrap_or_default();
|
||||||
|
let line = record.line().unwrap_or_default();
|
||||||
crate::arch::log(format_args!(
|
crate::arch::log(format_args!(
|
||||||
"\x1b[38;5;{}m{lvl}\x1b[0m [{}]: {}\r\n",
|
"\x1b[38;5;{lvl_color}m{lvl}\x1b[0m [{module}:{line}]: {}\r\n",
|
||||||
match lvl {
|
|
||||||
Level::Error => "160",
|
|
||||||
Level::Warn => "172",
|
|
||||||
Level::Info => "47",
|
|
||||||
Level::Debug => "25",
|
|
||||||
Level::Trace => "103",
|
|
||||||
},
|
|
||||||
record.module_path().unwrap_or_default(),
|
|
||||||
record.args(),
|
record.args(),
|
||||||
))
|
))
|
||||||
.expect("write to serial console");
|
.expect("write to serial console");
|
||||||
|
|
|
@ -4,21 +4,19 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
use {crate::host::TrapHandler, hbvm::vm::Vm};
|
use {crate::host::TrapHandler, hbvm::vm::Vm};
|
||||||
|
const TIMER_QUOTIENT: usize = 100;
|
||||||
|
|
||||||
pub struct Scheduler<'a> {
|
pub struct Scheduler<'a> {
|
||||||
data: VecDeque<Vm<'a, TrapHandler>>,
|
data: VecDeque<Vm<'a, TrapHandler, TIMER_QUOTIENT>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: This is a very simple schduler and it sucks and should be replaced with a better one
|
// NOTE: This is a very simple schduler and it sucks and should be replaced with a better one
|
||||||
// Written By Yours Truly: Munir
|
// Written By Yours Truly: Munir
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl Scheduler<'_> {
|
impl Scheduler<'_> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: VecDeque::new(),
|
data: VecDeque::new(),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new_process(&mut self, program: Vec<u8>) {
|
pub fn new_process(&mut self, program: Vec<u8>) {
|
||||||
|
@ -41,10 +39,19 @@ impl Scheduler<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scheduler_run(&mut self) -> Option<u32> {
|
pub fn run(&mut self) -> ! {
|
||||||
loop {
|
loop {
|
||||||
|
// If there are no programs to run then sleep.
|
||||||
|
if self.data.is_empty() {
|
||||||
|
use crate::arch::sloop;
|
||||||
|
sloop();
|
||||||
|
}
|
||||||
|
|
||||||
let mut prog = self.data.pop_front().unwrap();
|
let mut prog = self.data.pop_front().unwrap();
|
||||||
prog.run().unwrap();
|
prog.run().unwrap();
|
||||||
|
|
||||||
|
// log::info!("VM registers {:?}", prog.registers);
|
||||||
|
log::info!("Scheduled program");
|
||||||
self.data.push_back(prog);
|
self.data.push_back(prog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,16 @@ version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.10"
|
cpio_reader = "0.1"
|
||||||
|
derive_more = "0.99"
|
||||||
|
env_logger = "0.10"
|
||||||
error-stack = "0.2"
|
error-stack = "0.2"
|
||||||
fatfs = "0.3"
|
fatfs = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
rpm = "0.11"
|
||||||
|
zstd = "0.12"
|
||||||
|
|
||||||
|
[dependencies.reqwest]
|
||||||
|
version = "0.11"
|
||||||
|
default-features = false
|
||||||
|
features = ["rustls-tls", "blocking"]
|
||||||
|
|
BIN
repbuild/inf_loop.hb
Normal file
BIN
repbuild/inf_loop.hb
Normal file
Binary file not shown.
|
@ -18,8 +18,8 @@ TERM_BACKDROP=008080
|
||||||
# Setting a default resolution for the framebuffer
|
# Setting a default resolution for the framebuffer
|
||||||
RESOLUTION=1024x768x24
|
RESOLUTION=1024x768x24
|
||||||
|
|
||||||
MODULE_PATH=boot:///background.bmp
|
MODULE_PATH=boot:///inf_loop.hb
|
||||||
MODULE_CMDLINE="diskid=123456789"
|
MODULE_CMDLINE="diskid=123456789"
|
||||||
|
|
||||||
MODULE_PATH=boot:///background.bmp
|
MODULE_PATH=boot:///inf_loop.hb
|
||||||
MODULE_CMDLINE=""
|
MODULE_CMDLINE=""
|
|
@ -1,9 +1,14 @@
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use {
|
use {
|
||||||
error_stack::{bail, report, Context, IntoReport, Result, ResultExt},
|
derive_more::{Deref, DerefMut, Display},
|
||||||
|
error_stack::{bail, ensure, report, Context, IntoReport, Result, ResultExt},
|
||||||
fatfs::{FileSystem, FormatVolumeOptions, FsOptions, ReadWriteSeek},
|
fatfs::{FileSystem, FormatVolumeOptions, FsOptions, ReadWriteSeek},
|
||||||
std::{fmt::Display, fs::File, io, path::Path, process::Command},
|
std::{
|
||||||
|
fmt::Display,
|
||||||
|
fs::File,
|
||||||
|
io::{self, Write},
|
||||||
|
path::Path,
|
||||||
|
process::Command,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
|
@ -110,12 +115,18 @@ fn get_fs() -> Result<FileSystem<impl ReadWriteSeek>, io::Error> {
|
||||||
&mut File::open("repbuild/background.bmp")?,
|
&mut File::open("repbuild/background.bmp")?,
|
||||||
&mut fs.root_dir().create_file("background.bmp")?,
|
&mut fs.root_dir().create_file("background.bmp")?,
|
||||||
)?;
|
)?;
|
||||||
|
io::copy(
|
||||||
|
&mut File::open("repbuild/inf_loop.hb")?,
|
||||||
|
&mut fs.root_dir().create_file("inf_loop.hb")?,
|
||||||
|
)?;
|
||||||
|
|
||||||
drop(bootdir);
|
drop(bootdir);
|
||||||
Ok(fs)
|
Ok(fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(release: bool, target: Target) -> Result<(), Error> {
|
fn build(release: bool, target: Target) -> Result<(), Error> {
|
||||||
|
fetch_ovmf().change_context(Error::OvmfFetch)?;
|
||||||
|
|
||||||
let fs = get_fs().change_context(Error::Io)?;
|
let fs = get_fs().change_context(Error::Io)?;
|
||||||
let mut com = Command::new("cargo");
|
let mut com = Command::new("cargo");
|
||||||
com.current_dir("kernel");
|
com.current_dir("kernel");
|
||||||
|
@ -124,11 +135,8 @@ fn build(release: bool, target: Target) -> Result<(), Error> {
|
||||||
com.arg("-r");
|
com.arg("-r");
|
||||||
}
|
}
|
||||||
|
|
||||||
match target {
|
if target == Target::Riscv64Virt {
|
||||||
Target::Riscv64Virt => {
|
com.args(["--target", "targets/riscv64-virt-ableos.json"]);
|
||||||
com.args(["--target", "targets/riscv64-virt-ableos.json"]);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match com.status() {
|
match com.status() {
|
||||||
|
@ -165,10 +173,7 @@ fn run(release: bool, target: Target) -> Result<(), Error> {
|
||||||
if target == Target::X86_64 {
|
if target == Target::X86_64 {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
com.args([
|
com.args([
|
||||||
"-bios",
|
"-bios", "target/OVMF_CODE.fd",
|
||||||
std::env::var("REPBUILD_QEMU_FIRMWARE_PATH")
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or("/usr/share/ovmf/x64/OVMF_CODE.fd"),
|
|
||||||
"-drive", "file=target/disk.img,format=raw",
|
"-drive", "file=target/disk.img,format=raw",
|
||||||
"-m", "4G",
|
"-m", "4G",
|
||||||
// "-serial", "stdio",
|
// "-serial", "stdio",
|
||||||
|
@ -217,33 +222,121 @@ fn run(release: bool, target: Target) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
enum Target {
|
enum Target {
|
||||||
X86_64,
|
X86_64,
|
||||||
Riscv64Virt,
|
Riscv64Virt,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Display)]
|
||||||
enum Error {
|
enum Error {
|
||||||
|
#[display(fmt = "Failed to build the kernel")]
|
||||||
Build,
|
Build,
|
||||||
|
#[display(fmt = "Missing or invalid subcommand (available: build, run)")]
|
||||||
InvalidSubCom,
|
InvalidSubCom,
|
||||||
|
#[display(fmt = "IO Error")]
|
||||||
Io,
|
Io,
|
||||||
|
#[display(fmt = "Failed to spawn a process")]
|
||||||
ProcessSpawn,
|
ProcessSpawn,
|
||||||
|
#[display(fmt = "Failed to fetch UEFI firmware")]
|
||||||
|
OvmfFetch,
|
||||||
|
#[display(fmt = "QEMU Error: {}", "fmt_qemu_err(*_0)")]
|
||||||
Qemu(Option<i32>),
|
Qemu(Option<i32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context for Error {}
|
impl Context for Error {}
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt_qemu_err(e: Option<i32>) -> impl Display {
|
||||||
match self {
|
struct W(Option<i32>);
|
||||||
Self::Build => f.write_str("failed to build the kernel"),
|
impl Display for W {
|
||||||
Self::InvalidSubCom => {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str("missing or invalid subcommand (available: build, run)")
|
if let Some(c) = self.0 {
|
||||||
|
c.fmt(f)
|
||||||
|
} else {
|
||||||
|
f.write_str("Interrupted by signal")
|
||||||
}
|
}
|
||||||
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"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
W(e)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue