diff --git a/README.md b/README.md index daefcc0..2de1eb6 100644 --- a/README.md +++ b/README.md @@ -12,22 +12,23 @@ Welcome to **Hexium OS**, an experimental operating system written in Rust. This - :electric_plug: Serial Support - :brain: Memory Management - :file_cabinet: In-memory File System -- :dart: Task State Segment (TSS) +- :dart: Task State Segment - :wrench: Heap allocator - :keyboard: Keyboard Driver -- :clock8: Multitasking +- :clock8: Multitasking (Unavailable) +- :card_file_box: Virtual Filesystem - :x: Shell - :x: ACPI/AML Shutdown - :x: CpuId Support - :x: Mouse Driver -- :x: Graphical Interface (GUI) +- :x: Graphical Interface - :x: ELF Loader - :x: Network Driver - :x: Audio Driver - :x: FAT32 Support - :x: OpenGL-like API -- :x: Integrated Development Environment (IDE) - :x: C/C++ Compiler +- :x: Rust Standard Library - :x: Processes - :x: Installation Setup - :x: Web Browser @@ -36,7 +37,9 @@ Welcome to **Hexium OS**, an experimental operating system written in Rust. This ## **⚙️ Building** -This project requires a nightly version of Rust because it uses some unstable features. You might need to run `rustup update nightly --force` to update to the latest nightly even if some components such as `rustfmt` are missing it. +This project requires a nightly version of Rust because it uses some unstable features. You might need to run `rustup update nightly --force` to update to the latest nightly even if some components such as `rustfmt` are missing it. Additionally, ensure you have `rustc` and `cargo` version 1.86 or higher installed. + +You will also need `xorriso`, a tool for creating ISO images. You can build the project by running: @@ -58,12 +61,11 @@ make run ```bash /initrd/ # The initial ramdisk -/userspace/ # Userspace programs /kernel/src/ # Kernel source code /kernel/target/ # Kernel output directory /limine # Limine and UEFI binaries (generated) /ovmf # Virtual firmware (generated) -/scripts # Build & helper scripts +/tools # Build & helper scripts/tools ``` [QEMU]: https://www.qemu.org/ diff --git a/initrd/welcome.txt b/initrd/welcome.txt index 186809c..03537e9 100644 --- a/initrd/welcome.txt +++ b/initrd/welcome.txt @@ -1 +1 @@ -Welcome to Hexium OS +Welcome to Hexium OS \ No newline at end of file diff --git a/kernel/src/fs/memfs.rs b/kernel/src/fs/memfs.rs deleted file mode 100644 index 828f8fb..0000000 --- a/kernel/src/fs/memfs.rs +++ /dev/null @@ -1,72 +0,0 @@ -use alloc::collections::BTreeMap; -use alloc::string::{String, ToString}; -use alloc::vec::Vec; -use crate::fs::vfs::FileSystem; -use crate::fs::vfs::FileType; -use crate::fs::vfs::VNode; - - -pub struct MemFS { - files: BTreeMap>, // Store file data in memory - mounted: bool, -} - -impl MemFS { - pub fn new() -> Self { - MemFS { - files: BTreeMap::new(), - mounted: false, - } - } -} - -impl FileSystem for MemFS { - fn mount(&mut self, _path: &str) -> Result<(), ()> { - self.mounted = true; - Ok(()) - } - - fn unmount(&mut self) -> Result<(), String> { - self.mounted = false; - self.files.clear(); - Ok(()) - } - - fn create(&mut self, path: &str, file_type: FileType) -> Result { - if self.files.contains_key(path) { - return Err("File already exists".to_string()); - } - self.files.insert(path.to_string(), Vec::new()); - Ok(VNode::new(path.to_string(), file_type)) - } - - fn open(&self, path: &str) -> Result { - if self.files.contains_key(path) { - Ok(VNode::new(path.to_string(), FileType::File)) - } else { - Err("File not found".to_string()) - } - } - - fn read(&self, file: &VNode, buf: &mut [u8], offset: usize) -> Result { - if let Some(data) = self.files.get(&file.file_name) { - let len = buf.len().min(data.len().saturating_sub(offset)); - buf[..len].copy_from_slice(&data[offset..offset + len]); - Ok(len) - } else { - Err("File not found".to_string()) - } - } - - fn write(&mut self, file: &VNode, buf: &[u8], offset: usize) -> Result { - if let Some(data) = self.files.get_mut(&file.file_name) { - if offset + buf.len() > data.len() { - data.resize(offset + buf.len(), 0); - } - data[offset..offset + buf.len()].copy_from_slice(buf); - Ok(buf.len()) - } else { - Err("File not found".to_string()) - } - } -} diff --git a/kernel/src/fs/mod.rs b/kernel/src/fs/mod.rs index e6bacd2..001cf72 100644 --- a/kernel/src/fs/mod.rs +++ b/kernel/src/fs/mod.rs @@ -1,3 +1 @@ -pub mod memfs; pub mod ramfs; -pub mod vfs; diff --git a/kernel/src/fs/ramfs.rs b/kernel/src/fs/ramfs.rs index ef49071..da8848e 100644 --- a/kernel/src/fs/ramfs.rs +++ b/kernel/src/fs/ramfs.rs @@ -1,62 +1,58 @@ use crate::{ - boot, fs::vfs::{FileSystem, FileType, VNode, VFS}, info, print, trace, utils + boot, + hal::vfs::{Vfs, VfsError, Vnode, VnodeOps, VnodeType}, + print, trace, utils, +}; +use alloc::sync::Arc; +use alloc::{ + format, + string::{String, ToString}, }; -use alloc::boxed::Box; -use alloc::string::{String, ToString}; pub struct RamFs { - //files: Vec, archive: &'static [u8], } impl RamFs { pub fn new(archive: &'static [u8]) -> Self { - RamFs { - //files: Vec::new(), - archive, - } + RamFs { archive } } } -impl FileSystem for RamFs { - fn mount(&mut self, _path: &str) -> Result<(), ()> { - info!("RamFs mounted"); - Ok(()) - } - - fn unmount(&mut self) -> Result<(), String> { - info!("RamFs unmounted"); - Ok(()) - } - - fn open(&self, path: &str) -> Result { - if let Some((_size, _content)) = tar_lookup(self.archive, path) { - Ok(VNode::new(path.to_string(), FileType::File)) - } else { - Err("File not found".to_string()) +impl VnodeOps for RamFs { + fn lookup(&self, path: &str) -> Result { + match tar_lookup(self.archive, path) { + Some((_size, _content, vtype)) => Ok(Vnode { + name: path.to_string(), + vtype, + ops: Arc::new(RamFs::new(self.archive)), + }), + None => Err(VfsError::NotFound), } } - fn read(&self, file: &VNode, buf: &mut [u8], offset: usize) -> Result { - if let Some((_size, content)) = tar_lookup(self.archive, &file.file_name) { - let len = buf.len().min(content.len().saturating_sub(offset)); - buf[..len].copy_from_slice(&content[offset..offset + len]); + fn read( + &self, + file: &Vnode, + buf: &mut [u8], + offset: usize, + _length: usize, + ) -> Result { + if let Some((size, content, _)) = tar_lookup(self.archive, &file.name) { + let start = offset.min(size); + let end = (offset + buf.len()).min(size); + let len = end - start; + if len > 0 { + buf[..len].copy_from_slice(&content[start..end]); + } Ok(len) } else { Err("File not found".to_string()) } } - - fn write(&mut self, _file: &VNode, _buf: &[u8], _offset: usize) -> Result { - Err("Write operation not supported".to_string()) - } - - fn create(&mut self, _path: &str, _file_type: FileType) -> Result { - Err("Create operation not supported".to_string()) - } } -pub fn init(vfs: &mut VFS) { +pub fn init(vfs: &Vfs) { if let Some(module_response) = boot::MODULE_REQUEST.get_response() { let modules = module_response.modules(); if !modules.is_empty() { @@ -68,19 +64,26 @@ pub fn init(vfs: &mut VFS) { } let archive = unsafe { - core::slice::from_raw_parts( - modules[0].addr() as *const u8, - (modules[0].size() as u64).try_into().unwrap(), - ) + core::slice::from_raw_parts(modules[0].addr() as *const u8, modules[0].size() as usize) }; - let ramfs = Box::new(RamFs::new(archive)); - vfs.mount_fs(ramfs); + let ramfs = RamFs::new(archive); + vfs.mount("/ramdisk", Arc::new(ramfs)); } } -pub fn tar_lookup<'a>(archive: &'a [u8], filename: &str) -> Option<(usize, &'a [u8])> { +pub fn tar_lookup<'a>( + archive: &'a [u8], + filename: &'a str, +) -> Option<(usize, &'a [u8], VnodeType)> { let mut ptr = 0; + // Ensure filename starts with "./" + let normalized_filename = if filename.starts_with("./") { + filename.to_string() + } else { + format!("./{}", filename) + }; + while ptr + 257 < archive.len() { if &archive[ptr + 257..ptr + 262] != b"ustar" { break; @@ -90,16 +93,24 @@ pub fn tar_lookup<'a>(archive: &'a [u8], filename: &str) -> Option<(usize, &'a [ let name_len = header_filename.iter().position(|&x| x == 0).unwrap_or(100); let file_name = &header_filename[..name_len]; + let typeflag = archive[ptr + 156]; + let vtype = if typeflag == b'5' { + VnodeType::Directory + } else { + VnodeType::Regular + }; + let filesize = utils::octal_to_binrary(&archive[ptr + 124..ptr + 135]); - if file_name == filename.as_bytes() { + if file_name == normalized_filename.as_bytes() { return Some(( filesize as usize, &archive[ptr + 512..ptr + 512 + filesize as usize], + vtype, )); } - ptr = ptr + ((((filesize as usize) + 511) / 512) + 1) * 512; + ptr += (((filesize as usize + 511) / 512) + 1) * 512; } None } diff --git a/kernel/src/fs/vfs.rs b/kernel/src/fs/vfs.rs deleted file mode 100644 index e9bf57c..0000000 --- a/kernel/src/fs/vfs.rs +++ /dev/null @@ -1,77 +0,0 @@ -use alloc::boxed::Box; -use alloc::string::{String, ToString}; - -pub trait FileSystem { - fn mount(&mut self, path: &str) -> Result<(), ()>; - fn unmount(&mut self) -> Result<(), String>; - fn create(&mut self, path: &str, file_type: FileType) -> Result; - fn open(&self, path: &str) -> Result; - fn read(&self, file: &VNode, buf: &mut [u8], offset: usize) -> Result; - fn write(&mut self, file: &VNode, buf: &[u8], offset: usize) -> Result; -} - -pub struct VNode { - pub file_name: String, - pub file_type: FileType, -} - -impl VNode { - pub fn new(file_name: String, file_type: FileType) -> Self { - VNode { file_name, file_type } - } - - pub fn default() -> Self { - VNode::new("unnamed_vnode".to_string(),FileType::File) - } -} - -pub enum FileType { - File = 0, - Directory = 1, -} - -pub struct VFS { - pub fs: Option>, -} - -impl VFS { - pub fn new(fs: Option>) -> Self { - VFS { fs } - } - - pub fn mount_fs(&mut self, fs: Box) { - self.fs = Some(fs); - } - - pub fn unmount_fs(&mut self) -> Result<(), String> { - if let Some(fs) = &mut self.fs { - fs.unmount() - } else { - Err("No file system mounted".to_string()) - } - } - - pub fn create_file(&mut self, path: &str, file_type: FileType) -> Result { - if let Some(fs) = &mut self.fs { - fs.create(path, file_type) - } else { - Err("No file system mounted".to_string()) - } - } - - pub fn open_file(&mut self, path: &str) -> Result { - if let Some(fs) = &self.fs { - fs.open(path) - } else { - Err("No file system mounted".to_string()) - } - } - - pub fn read_file(&mut self, file: &VNode, buf: &mut [u8], offset: usize) -> Result { - if let Some(fs) = &self.fs { - fs.read(file, buf, offset) - } else { - Err("No file system mounted".to_string()) - } - } -} diff --git a/kernel/src/hal/mod.rs b/kernel/src/hal/mod.rs new file mode 100644 index 0000000..10d397e --- /dev/null +++ b/kernel/src/hal/mod.rs @@ -0,0 +1 @@ +pub mod vfs; diff --git a/kernel/src/hal/vfs.rs b/kernel/src/hal/vfs.rs new file mode 100644 index 0000000..ccdd20c --- /dev/null +++ b/kernel/src/hal/vfs.rs @@ -0,0 +1,103 @@ +use crate::trace; +use alloc::{ + collections::btree_map::BTreeMap, + string::{String, ToString}, + sync::Arc, +}; +use spin::RwLock; + +#[derive(Debug)] +pub enum VfsError { + NotFound, +} + +pub struct Vnode { + pub name: String, + pub vtype: VnodeType, + pub ops: Arc, +} + +pub trait VnodeOps { + fn lookup(&self, path: &str) -> Result; + fn read( + &self, + file: &Vnode, + buf: &mut [u8], + offset: usize, + length: usize, + ) -> Result; +} + +pub enum VnodeType { + Regular, + Directory, +} + +pub struct Vfs { + mounts: RwLock>>, +} + +impl Vfs { + pub fn new() -> Self { + trace!("Creating new VFS instance"); + Vfs { + mounts: RwLock::new(BTreeMap::new()), + } + } + + pub fn mount(&self, mount_point: &str, ops: Arc) { + self.mounts.write().insert(String::from(mount_point), ops); + } + + pub fn unmount(&self, mount_point: &str) -> Result<(), VfsError> { + let mut mounts = self.mounts.write(); + if mounts.remove(mount_point).is_some() { + Ok(()) + } else { + Err(VfsError::NotFound) + } + } + + fn find_fs(&self, path: &str) -> Result<(Arc, String), VfsError> { + let mounts = self.mounts.read(); + let mut best_match: Option<(&str, &Arc)> = None; + + for (key, fs) in mounts.iter() { + if path.starts_with(key) { + match best_match { + Some((best_key, _)) if key.len() <= best_key.len() => {} + _ => best_match = Some((key.as_str(), fs)), + } + } + } + + if let Some((key, fs)) = best_match { + let relative_path = path.strip_prefix(key).unwrap_or("").trim_start_matches('/'); + Ok((fs.clone(), relative_path.to_string())) + } else { + Err(VfsError::NotFound) + } + } + + pub fn lookuppn(&self, full_path: &str) -> Result { + let (ops, rel_path) = self.find_fs(full_path)?; + + let vtype = if rel_path.is_empty() || full_path.ends_with('/') { + VnodeType::Directory + } else { + VnodeType::Regular + }; + + let display_path = if rel_path.is_empty() { + full_path.strip_prefix('/').unwrap_or(full_path) + } else { + &rel_path + }; + + Ok(Vnode { + name: display_path.to_string(), + vtype, + ops, + }) + } +} diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index f1497dc..5e0b5a5 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -3,7 +3,7 @@ #![feature(abi_x86_interrupt)] #![feature(custom_test_frameworks)] #![test_runner(crate::test_runner)] -#![reexport_test_harness_main="test_main"] +#![reexport_test_harness_main = "test_main"] extern crate alloc; @@ -14,6 +14,7 @@ pub mod boot; pub mod devices; pub mod drivers; pub mod fs; +pub mod hal; pub mod interrupts; pub mod log; pub mod memory; @@ -28,42 +29,41 @@ pub fn init() { interrupts::init(); memory::init(); - let mut vfs = fs::vfs::VFS::new(None); - fs::ramfs::init(&mut vfs); + let mut vfs = hal::vfs::Vfs::new(); + fs::ramfs::init(&vfs); print_startup_message(&mut vfs); // Issue#30: Commented out for now as the code doesn't run past this section. Will return it back. - // let mut executor = crate::task::executor::Executor::new(); - // let _ = executor.spawn(crate::task::Task::new(devices::keyboard::trace_keypresses())); - // executor.run(); - - //vfs.unmount_fs(); + //let mut executor = crate::task::executor::Executor::new(); + //let _ = executor.spawn(crate::task::Task::new(devices::keyboard::trace_keypresses())); + //executor.run(); } -fn print_startup_message(vfs: &mut fs::vfs::VFS) -> [u8; 128] { - let mut buffer = [0u8; 128]; +fn print_startup_message(vfs: &hal::vfs::Vfs) { + let file: hal::vfs::Vnode = match vfs.lookuppn("/ramdisk/welcome.txt") { + Ok(file) => file, + Err(err) => { + error!("File lookup error: {:?}", err); + return; + } + }; - match vfs.open_file("./welcome.txt") { - Ok(vnode) => match vfs.read_file(&vnode, &mut buffer, 0) { - Ok(_bytes_read) => {} - Err(err) => { - error!("Error reading file: {}", err); - } - }, + let mut buffer = [0u8; 64]; + + match file.ops.read(&file, &mut buffer, 0, 64) { + Ok(_) => {} Err(err) => { - error!("File not found: {}", err); + error!("File read error: {:?}", err); } } info!( - "Hexium OS kernel v{} succesfully initialized at {}", + "Hexium OS kernel v{} successfully initialized at {}", env!("CARGO_PKG_VERSION"), unsafe { rtc::read_rtc() } ); info!("{}", String::from_utf8_lossy(&buffer)); - - buffer } pub fn hlt_loop() -> ! { @@ -83,7 +83,7 @@ pub fn test_panic_handler(info: &PanicInfo) -> ! { serial_println!("[failed]"); serial_println!("Error: {}", info); exit_qemu(QemuExitCode::Failed); - loop{} + loop {} } pub fn test_runner(tests: &[&dyn Testable]) { @@ -96,7 +96,6 @@ pub fn test_runner(tests: &[&dyn Testable]) { exit_qemu(QemuExitCode::Success); } - #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum QemuExitCode { @@ -118,13 +117,14 @@ pub trait Testable { } impl Testable for T -where T : Fn(), +where + T: Fn(), { fn run(&self) { serial_print!("{}...\t", core::any::type_name::()); self(); serial_println!("[ok]"); - } + } } #[cfg(test)] @@ -138,4 +138,4 @@ unsafe extern "C" fn kmain() -> ! { #[panic_handler] fn panic(info: &PanicInfo) -> ! { test_panic_handler(info) -} \ No newline at end of file +} diff --git a/kernel/src/log.rs b/kernel/src/log.rs index d2e8e46..813ba70 100644 --- a/kernel/src/log.rs +++ b/kernel/src/log.rs @@ -13,20 +13,43 @@ pub enum LogLevel { // Define a macro that accepts the log level and message. #[macro_export] macro_rules! log { - // Log level and a formatted message ($level:expr, $($arg:tt)*) => {{ - let (label, color_code) = match $level { - $crate::log::LogLevel::None => ("NONE ", "\x1b[90m"), // Bright Black (Gray), if needed - $crate::log::LogLevel::Trace => ("TRACE", "\x1b[95m"), // Bright Magenta - $crate::log::LogLevel::Debug => ("DEBUG", "\x1b[94m"), // Bright Blue - $crate::log::LogLevel::Info => ("INFO ", "\x1b[92m"), // Bright Green - $crate::log::LogLevel::Warn => ("WARN ", "\x1b[93m"), // Bright Yellow - $crate::log::LogLevel::Error => ("ERROR", "\x1b[91m"), // Bright Red - $crate::log::LogLevel::Fatal => ("FATAL", "\x1b[91m"), // Bright Red (same as ERROR) - $crate::log::LogLevel::Panic => ("PANIC", "\x1b[97;41m"), // White text on Red background + let (label, color_code, show_location) = match $level { + $crate::log::LogLevel::None => ("NONE ", "\x1b[90m", false), + $crate::log::LogLevel::Trace => ("TRACE", "\x1b[95m", false), + $crate::log::LogLevel::Debug => ("DEBUG", "\x1b[94m", false), + $crate::log::LogLevel::Info => ("INFO ", "\x1b[92m", false), + + $crate::log::LogLevel::Warn => { + #[cfg(debug_assertions)] + { ("WARN ", "\x1b[93m", true) } + + #[cfg(not(debug_assertions))] + { ("WARN ", "\x1b[93m", false) } + } + + $crate::log::LogLevel::Error => ("ERROR", "\x1b[91m", true), + $crate::log::LogLevel::Fatal => ("FATAL", "\x1b[91m", true), + $crate::log::LogLevel::Panic => ("PANIC", "\x1b[97;41m", true), }; - $crate::println!("{}[{}]\x1b[0m {}", color_code, label, format_args!($($arg)*)); + if show_location { + $crate::println!( + "{}[{}]\x1b[0m {}:{}: {}", + color_code, + label, + file!(), + line!(), + format_args!($($arg)*) + ); + } else { + $crate::println!( + "{}[{}]\x1b[0m {}", + color_code, + label, + format_args!($($arg)*) + ); + } }}; } @@ -44,7 +67,7 @@ macro_rules! debug { { $crate::log!($crate::log::LogLevel::Debug, $($arg)*); } - + #[cfg(not(debug_assertions))] {} }; diff --git a/kernel/src/main.rs b/kernel/src/main.rs index b42e72d..1bf53de 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -5,11 +5,14 @@ #![reexport_test_harness_main = "test_main"] use core::panic::PanicInfo; +#[cfg(not(test))] use hexium_os::{boot, hlt_loop, init, panic_log}; +#[cfg(test)] +use hexium_os::{boot, init}; #[test_case] fn test_example() { - assert_eq!(1+1, 2); + assert_eq!(1 + 1, 2); } #[cfg(test)] @@ -26,14 +29,14 @@ unsafe extern "C" fn kmain() -> ! { unsafe extern "C" fn kmain() -> ! { assert!(boot::BASE_REVISION.is_supported()); - /* - Issue#30: The lines at the end of this comment below do not seem to have an effect after the init method above - however calling them above the init method causes a boot-loop. + /* + Issue#30: The lines at the end of this comment below do not seem to have an effect after the init method above + however calling them above the init method causes a boot-loop. NOTE: Calling them after the init method after the executor code has been commented back in, will cause them not to be run as the executor code seems to block the 'thread'. print!("Test"); println!("Test2"); - */ + */ init(); @@ -43,7 +46,7 @@ unsafe extern "C" fn kmain() -> ! { #[cfg(not(test))] #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use hexium_os::utils::registers::{print_register_dump, get_registers}; + use hexium_os::utils::registers::{get_registers, print_register_dump}; panic_log!("{}\n", info); print_register_dump(&get_registers()); loop {} @@ -53,4 +56,4 @@ fn panic(info: &PanicInfo) -> ! { #[panic_handler] fn panic(info: &PanicInfo) -> ! { hexium_os::test_panic_handler(info) -} \ No newline at end of file +} diff --git a/kernel/src/writer.rs b/kernel/src/writer.rs index 8fbd1a6..e4494ee 100644 --- a/kernel/src/writer.rs +++ b/kernel/src/writer.rs @@ -1,4 +1,4 @@ -use crate::{utils::types::option_to_c_void, boot}; +use crate::{boot, utils::types::option_to_c_void}; use core::fmt; use core::ptr; use lazy_static::lazy_static; @@ -79,14 +79,17 @@ impl Writer { impl fmt::Write for Writer { fn write_str(&mut self, s: &str) -> fmt::Result { self.write_string(s); - + Ok(()) } } #[macro_export] macro_rules! print { - ($($arg:tt)*) => ($crate::writer::_print(format_args!($($arg)*))); + ($($arg:tt)*) => {{ + $crate::writer::_print(format_args!($($arg)*)); + $crate::serial_print!("{}", format_args!($($arg)*)); + }}; } #[macro_export] @@ -103,4 +106,4 @@ pub fn _print(args: fmt::Arguments) { interrupts::without_interrupts(|| { WRITER.lock().write_fmt(args).unwrap(); }); -} \ No newline at end of file +} diff --git a/kernel/tests/writer.rs b/kernel/tests/writer.rs index 222e152..359dbb0 100644 --- a/kernel/tests/writer.rs +++ b/kernel/tests/writer.rs @@ -2,23 +2,22 @@ #![no_main] #![feature(custom_test_frameworks)] #![test_runner(hexium_os::test_runner)] -#![reexport_test_harness_main="test_main"] +#![reexport_test_harness_main = "test_main"] use core::panic::PanicInfo; -use hexium_os::{println, init}; +use hexium_os::{init, println}; #[unsafe(no_mangle)] unsafe extern "C" fn kmain() -> ! { init(); // Issue#30: Not sure why it's absence causes an loop running of test_println_long test. - test_main(); - loop{} + test_main(); + loop {} } #[panic_handler] fn panic(info: &PanicInfo) -> ! { - hexium_os::test_panic_handler(info); - loop{} + hexium_os::test_panic_handler(info); } #[test_case] @@ -49,4 +48,4 @@ fn test_println_long_more() { // let screen_char = WRITER.lock().write_char(c).buffer.chars[BUFFER_HEIGHT - 2][i].read(); // assert_eq!(char::from(screen_char.ascii_character), c); // } -// } \ No newline at end of file +// }