Add a proper vfs driver into the kernel which handles file operations,

fix munmap number, remove the draw calls, make address space an option,
copy only the top 256 bytes of the HHDM and map the ELF code inside
userspace, add safe copying from userspace
This commit is contained in:
csd4ni3l
2026-05-09 11:36:48 +02:00
parent 674d68cdc5
commit 925b2bc8d2
17 changed files with 409 additions and 71 deletions
+3 -3
View File
@@ -22,12 +22,12 @@ pub fn init<'a>(
#[cfg(target_arch = "x86_64")]
pub fn enter_usermode(user_rip: u64, user_rsp: u64) {
return enter_usermode_x86_64(user_rip, user_rsp);
enter_usermode_x86_64(user_rip, user_rsp);
}
#[cfg(target_arch = "x86_64")]
pub fn run_elf(entry_point: *const u8, heap_base: u64) {
run_elf_x86_64(entry_point, heap_base);
pub fn run_elf(file_bytes: &[u8]) {
run_elf_x86_64(file_bytes);
}
pub fn get_allocator<'a>() -> &'static impl GlobalAlloc {
+75 -22
View File
@@ -1,3 +1,5 @@
#![allow(dead_code)]
use core::{
alloc::{GlobalAlloc, Layout},
ptr::null_mut,
@@ -12,11 +14,12 @@ use x86_64::{
use crate::{
arch::arch::{FRAME_ALLOCATOR, get_allocator, infinite_idle, sleep},
driver::{
graphics::{framebuffer::with_framebuffer, primitives::rectangle_filled},
fs::vfs::{vfs_close, vfs_lseek, vfs_open, vfs_read},
graphics::framebuffer::with_framebuffer,
keyboard::{KeyboardEvent, process_scancodes},
timer::TIMER,
},
mm::usercopy::copy_to_user,
mm::usercopy::{copy_cstr_from_user, copy_to_user},
print, println,
task::scheduler::{SCHEDULER, current_pid},
util::align_up,
@@ -29,7 +32,7 @@ const CLOSE: usize = 3;
const STAT: usize = 4;
const LSEEK: usize = 8;
const MMAP: usize = 9;
const MUNMAP: usize = 9;
const MUNMAP: usize = 11;
const BRK: usize = 12;
const GETPID: usize = 39;
const FORK: usize = 57;
@@ -45,9 +48,8 @@ const CLOCK_GETTIME: usize = 228;
const EXIT_GROUP: usize = 231;
const KBD_READ: usize = 666;
const SLEEP: usize = 909090; // zzz haha
const DRAW_PIXEL: usize = 5555;
const FRAMEBUFFER_SWAP: usize = 6666;
const DRAW_BUFFER: usize = 7777;
pub const MAP_FRAMEBUFFER: usize = 5555;
pub const FRAMEBUFFER_SWAP: usize = 6666;
pub unsafe fn malloc(size: usize, align: usize) -> *mut u8 {
let align = if align < 1 {
@@ -84,6 +86,64 @@ pub unsafe fn memset(ptr: *mut u8, val: u8, count: usize) {
unsafe { core::ptr::write_bytes(ptr, val, count) };
}
fn map_framebuffer() -> isize {
0
}
fn read(ptr: isize, size: isize, nmemb: isize, fd: isize) -> isize {
let pid = current_pid().unwrap_or(0);
if pid == 0 {
return -1;
}
SCHEDULER
.with_process(pid, |process| {
let len = size * nmemb;
let to_read = vfs_read(fd as i64, len as usize);
if let Some(read_ptr) = to_read {
let address_space = process.address_space.as_mut().unwrap();
if copy_to_user(
&mut address_space.mapper,
ptr as *mut u8,
read_ptr.0,
read_ptr.1,
)
.is_err()
{
return -1;
};
return (read_ptr.1 as isize) / size;
} else {
return -1;
}
})
.unwrap_or(-1)
}
fn open(path: isize, mode: isize) -> isize {
let pid = current_pid().unwrap_or(0);
if pid == 0 {
return -1;
}
SCHEDULER
.with_process(pid, |process| {
let address_space = process.address_space.as_mut().unwrap();
let path = copy_cstr_from_user(&mut address_space.mapper, path as *const u8, 256)?;
let mode = copy_cstr_from_user(&mut address_space.mapper, mode as *const u8, 16)?;
Ok::<isize, isize>(vfs_open(&path, &mode) as isize)
})
.unwrap_or(Err(-1))
.unwrap_or(-1)
}
fn close(fd: isize) -> isize {
vfs_close(fd as i64) as isize
}
fn kbd_read(user_ptr: *mut KeyboardEvent, max_events: isize) -> isize {
process_scancodes();
if max_events <= 0 || user_ptr.is_null() {
@@ -99,8 +159,9 @@ fn kbd_read(user_ptr: *mut KeyboardEvent, max_events: isize) -> isize {
return SCHEDULER
.with_process(pid as u64, |process| {
let to_copy = (max_events as usize).min(process.kbd_buffer.len());
let address_space = process.address_space.as_mut().unwrap();
if let Ok(_) = copy_to_user(
&mut process.address_space.mapper,
&mut address_space.mapper,
user_ptr as *mut u8,
process.kbd_buffer.as_ptr() as *const u8,
to_copy * size_of::<KeyboardEvent>(),
@@ -153,9 +214,9 @@ pub unsafe fn sbrk(increment: isize) -> isize {
// TODO: do not use x86_64 only
let virt_addr = VirtAddr::new(addr);
let page = Page::<Size4KiB>::containing_address(virt_addr);
let address_space = process.address_space.as_mut().unwrap();
unsafe {
process
.address_space
address_space
.mapper
.map_to(
page,
@@ -198,6 +259,7 @@ pub unsafe extern "C" fn syscall_dispatch(
interrupts::enable();
match num {
BRK => unsafe { sbrk(arg0) },
READ => read(arg0, arg1, arg2, arg3) as isize,
WRITE => {
let buf_ptr = arg1 as *const u8;
let len = arg2 as usize;
@@ -216,6 +278,9 @@ pub unsafe extern "C" fn syscall_dispatch(
with_framebuffer(|fb| fb.swap());
0
}
OPEN => open(arg0, arg1),
CLOSE => close(arg0),
LSEEK => vfs_lseek(arg0 as i64, arg1 as i64, arg2 as i32) as isize,
EXIT => {
println!("Program exit: {}", arg0);
with_framebuffer(|fb| fb.swap());
@@ -226,19 +291,7 @@ pub unsafe extern "C" fn syscall_dispatch(
0
}
CLOCK_GETTIME => TIMER.now().elapsed() as isize,
DRAW_PIXEL => {
with_framebuffer(|mut fb| {
rectangle_filled(&mut fb, arg0 as usize, arg1 as usize, 4, 4, arg2 as u32);
});
0
}
DRAW_BUFFER => {
with_framebuffer(|fb| {
unsafe { fb.load_from_ptr(arg0 as *const u32, arg1 as usize, arg2 as usize) };
fb.swap();
});
0
}
MAP_FRAMEBUFFER => map_framebuffer(),
KBD_READ => kbd_read(arg0 as *mut KeyboardEvent, arg1),
FRAMEBUFFER_SWAP => {
with_framebuffer(|fb| {
+20 -10
View File
@@ -4,22 +4,28 @@ use x86_64::{
structures::paging::{FrameAllocator, Mapper, Page, PageTableFlags, PhysFrame, Size4KiB},
};
use crate::{arch::arch::FRAME_ALLOCATOR, task::scheduler::SCHEDULER};
use crate::{
arch::arch::FRAME_ALLOCATOR, driver::elf::loader::load_file, mm::address_space::AddressSpace,
println, task::scheduler::SCHEDULER,
};
pub fn run_elf_x86_64(entry_point: *const u8, heap_base: u64) {
pub fn run_elf_x86_64(file_bytes: &[u8]) {
let stack_base: u64 = 0x0000_7fff_0000_0000;
let page_count = 4096; // 16 mib
let page_size = 0x1000u64;
let stack_top = stack_base + (page_count as u64 * page_size);
if let Some(mut address_space) = AddressSpace::new() {
address_space.use_address_space();
let (entry_point, heap_base) = load_file(&mut address_space.mapper, file_bytes);
println!("Entry point: {:?}", entry_point);
let process_pid = SCHEDULER
.spawn_process(entry_point as u64, stack_top, heap_base)
.unwrap();
SCHEDULER.with_process(process_pid, |process| {
process.address_space.use_address_space()
});
let mut frames: Vec<PhysFrame<Size4KiB>> = Vec::new();
let mut frame_allocator = FRAME_ALLOCATOR.lock();
@@ -31,9 +37,7 @@ pub fn run_elf_x86_64(entry_point: *const u8, heap_base: u64) {
let page = Page::<Size4KiB>::containing_address(virt_addr);
unsafe {
SCHEDULER.with_process(process_pid, |process| {
process
.address_space
address_space
.mapper
.map_to(
page,
@@ -45,10 +49,16 @@ pub fn run_elf_x86_64(entry_point: *const u8, heap_base: u64) {
)
.unwrap()
.flush();
});
}
}
drop(frame_allocator);
SCHEDULER.with_process(process_pid, |process| {
process.address_space = Some(address_space)
});
SCHEDULER.run_process(process_pid, entry_point);
} else {
return;
};
}
+1 -1
View File
@@ -19,7 +19,7 @@ fn align_up(addr: usize, align: usize) -> usize {
#[global_allocator]
pub static ALLOCATOR: Locked<LinkedListAllocator> = Locked::new(LinkedListAllocator::new());
pub const HEAP_START: usize = 0x_4444_4444_0000;
pub const HEAP_START: usize = 0xffffffff90000000;
pub const HEAP_SIZE: usize = 64 * 1024 * 1024; // 64 MiB
pub struct LinkedNode {
+1 -2
View File
@@ -1,7 +1,6 @@
use core::arch::asm;
use lazy_static::lazy_static;
use x86_64::instructions::tlb::{self, flush_all};
use x86_64::instructions::tlb::flush_all;
use crate::arch::x86_64::gdt::{GDT, TSS};
+2
View File
@@ -1,5 +1,7 @@
// this is not made by me, but AI, im not going to type these out...
#![allow(dead_code)]
// e_ident indices
pub const EI_MAG0: usize = 0;
pub const EI_MAG1: usize = 1;
+1 -1
View File
@@ -30,7 +30,7 @@ pub fn load_file(mapper: &mut OffsetPageTable, elf_bytes: &[u8]) -> (*const u8,
let elf_header_ptr = elf_bytes.as_ptr() as *const Elf64Ehdr;
return match unsafe { elf_header.e_type } {
return match elf_header.e_type {
ET_EXEC => unsafe { load_program(mapper, elf_header_ptr, elf_bytes, false) },
ET_DYN => unsafe { load_program(mapper, elf_header_ptr, elf_bytes, true) },
ET_REL => return (null(), 0),
+1
View File
@@ -0,0 +1 @@
pub mod vfs;
+208
View File
@@ -0,0 +1,208 @@
use core::ptr::{null, null_mut};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct FILE {
pub data: *const u8, // pointer to the file's data
pub size: usize, // total size
pub cursor: usize, // current position
pub writable: bool, // is this a write buffer?
pub write_buf: *mut u8, // for writable fake files
pub write_cap: usize,
pub fd: i64,
}
impl FILE {
pub const fn zeroed() -> FILE {
FILE {
data: null(),
size: 0,
cursor: 0,
writable: false,
write_buf: null_mut(),
write_cap: 0,
fd: -1,
}
}
}
struct FakeFileEntry {
name: &'static str,
data: &'static [u8],
}
pub type Fd = i64;
const MAX_FD: usize = 16;
fn fd_ok(fd: Fd) -> bool {
fd >= 0 && (fd as usize) < MAX_FD
}
static DOOM_WAD: &[u8] = include_bytes!("../../../../assets/doom1.wad");
static DOOM_ELF: &[u8] = include_bytes!("../../../../assets/doomgeneric");
static HELLOWORLD_ELF: &[u8] = include_bytes!("../../../../assets/helloworld.elf");
static FILES: &[FakeFileEntry] = &[
FakeFileEntry {
name: "testfile",
data: b"Hello, World!",
},
FakeFileEntry {
name: "helloworld.elf",
data: HELLOWORLD_ELF,
},
FakeFileEntry {
name: "doomgeneric",
data: DOOM_ELF,
},
FakeFileEntry {
name: "doom1.wad",
data: DOOM_WAD,
},
FakeFileEntry {
name: "default.cfg",
data: b"",
},
FakeFileEntry {
name: "doom.cfg",
data: b"",
},
];
static mut FILE_POOL: [FILE; 16] = [FILE::zeroed(); 16];
static mut FILE_POOL_USED: [bool; 16] = [false; 16];
pub unsafe fn get_file_pool_slot() -> (*mut FILE, i64) {
unsafe {
for i in 0..16 {
if !FILE_POOL_USED[i] {
FILE_POOL_USED[i] = true;
return (&mut FILE_POOL[i], i as i64);
}
}
(null_mut(), -1)
}
}
unsafe fn file_mut(fd: Fd) -> Option<&'static mut FILE> {
if !fd_ok(fd) {
return None;
}
let idx = fd as usize;
if unsafe { !FILE_POOL_USED[idx] } {
return None;
}
return unsafe { Some(&mut FILE_POOL[idx]) };
}
#[unsafe(no_mangle)]
pub fn vfs_open(name: &str, _mode: &str) -> Fd {
for entry in FILES {
if entry.name.contains(name) {
let (slot, fd) = unsafe { get_file_pool_slot() };
if slot.is_null() {
return -1;
}
unsafe {
(*slot).data = entry.data.as_ptr();
(*slot).size = entry.data.len();
(*slot).cursor = 0;
(*slot).writable = false;
(*slot).write_buf = null_mut();
(*slot).write_cap = 0;
(*slot).fd = fd;
}
return fd;
}
}
-1
}
#[unsafe(no_mangle)]
pub fn vfs_close(fd: Fd) -> i32 {
if !fd_ok(fd) {
return -1;
}
unsafe {
let idx = fd as usize;
if !FILE_POOL_USED[idx] {
return -1;
}
FILE_POOL_USED[idx] = false;
FILE_POOL[idx] = FILE::zeroed();
}
0
}
#[unsafe(no_mangle)]
pub fn vfs_write(ptr: *mut u8, size: usize, count: usize, fp: *mut FILE) -> usize {
if ptr.is_null() || fp.is_null() || unsafe { (*fp).fd < 0 || (*fp).fd >= 16 } {
return 0;
}
count
}
#[unsafe(no_mangle)]
pub fn vfs_read(fd: Fd, len: usize) -> Option<(*const u8, usize)> {
unsafe {
let f = file_mut(fd)?;
if f.cursor > f.size {
return Some((f.data, 0));
}
let available = f.size - f.cursor;
let to_read = len.min(available);
let src = f.data.add(f.cursor);
f.cursor = f.cursor.saturating_add(to_read);
Some((src, to_read))
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vfs_lseek(fd: Fd, offset: i64, whence: i32) -> i64 {
let f = match unsafe { file_mut(fd) } {
Some(f) => f,
None => return -1,
};
let new_pos = match whence {
0 => {
if offset < 0 {
return -1;
}
offset as usize
}
1 => {
let cur = f.cursor as i64;
let pos = cur.saturating_add(offset);
if pos < 0 {
return -1;
}
pos as usize
}
2 => {
let end = f.size as i64;
let pos = end.saturating_add(offset);
if pos < 0 {
return -1;
}
pos as usize
}
_ => return -1,
};
if new_pos > f.size {
return -1;
}
f.cursor = new_pos;
f.cursor as i64
}
+1
View File
@@ -1,4 +1,5 @@
pub mod elf;
pub mod fs;
pub mod graphics;
pub mod keyboard;
pub mod mouse;
+2 -7
View File
@@ -102,11 +102,9 @@ unsafe extern "C" fn kmain() -> ! {
// removed by the linker.
assert!(BASE_REVISION.is_supported());
let mut mapper;
if let Some(hhdm_response) = HHDM_REQUEST.get_response() {
if let Some(memory_map_response) = MEMORY_MAP_REQUEST.get_response() {
mapper = init(hhdm_response, memory_map_response);
init(hhdm_response, memory_map_response);
} else {
kernel_crash(); // Could not get required info from Limine's memory map.
}
@@ -130,12 +128,9 @@ unsafe extern "C" fn kmain() -> ! {
println!("Could not get date at boot. Will default to 0.")
}
let (entry_point, heap_base) = load_file(&mut mapper, INIT_ELF_BYTES);
println!("Entry point: {:?}", entry_point);
with_framebuffer(|fb| fb.swap());
run_elf(entry_point, heap_base);
run_elf(INIT_ELF_BYTES);
loop {}
}
+1 -1
View File
@@ -29,7 +29,7 @@ impl AddressSpace {
let new_pml4_ptr =
physical_to_virt_pointer(new_pml4.start_address(), frame_allocator.hhdm_offset);
for i in 0..512 {
for i in 256..512 {
let val = core::ptr::read(cur_pml4_ptr.add(i));
core::ptr::write(new_pml4_ptr.add(i), val);
}
+72
View File
@@ -1,3 +1,7 @@
use alloc::{
string::{String, ToString},
vec::Vec,
};
use x86_64::{
VirtAddr,
structures::paging::{OffsetPageTable, PageTableFlags, Translate, mapper::TranslateResult},
@@ -37,3 +41,71 @@ pub fn copy_to_user(
unsafe { core::ptr::copy_nonoverlapping(src, buf, len) };
Ok(())
}
pub fn copy_from_user(
mapper: &mut OffsetPageTable,
buf: *mut u8,
src: *const u8,
len: usize,
) -> Result<(), isize> {
if len == 0 {
return Ok(());
}
if buf.is_null() || src.is_null() {
return Err(-14);
}
let start = src as u64;
let end = start
.checked_add(len as u64)
.ok_or(-1)
.map_err(|err| err as isize)?;
let mut page_addr = start & !0xFFF;
while page_addr < end {
let translate_result = mapper.translate(VirtAddr::new(page_addr));
#[allow(non_shorthand_field_patterns)]
if let TranslateResult::Mapped {
frame: _,
offset: _,
flags: flags,
} = translate_result
{
if !flags.contains(PageTableFlags::USER_ACCESSIBLE) {
return Err(-13);
}
} else {
return Err(-1);
}
page_addr += 0x1000;
}
unsafe { core::ptr::copy_nonoverlapping(src, buf, len) };
Ok(())
}
pub fn copy_cstr_from_user(
mapper: &mut OffsetPageTable,
user_ptr: *const u8,
max_len: usize,
) -> Result<String, isize> {
if user_ptr.is_null() {
return Err(-14);
}
let mut buf: Vec<u8> = Vec::with_capacity(64);
for i in 0..max_len {
let mut byte = 0u8;
copy_from_user(mapper, &mut byte as *mut u8, unsafe { user_ptr.add(i) }, 1)?;
if byte == 0 {
return core::str::from_utf8(&buf)
.map(|s| s.to_string())
.map_err(|_| -84);
}
buf.push(byte);
}
Err(-36)
}
+5 -7
View File
@@ -16,7 +16,7 @@ pub struct Process {
pub heap_base: u64,
pub heap_end: u64,
pub kbd_buffer: Vec<KeyboardEvent>,
pub address_space: AddressSpace,
pub address_space: Option<AddressSpace>,
pub user_entry: u64,
}
impl Process {
@@ -26,18 +26,16 @@ impl Process {
stack_top: u64,
heap_base: u64,
heap_end: u64,
) -> Option<Process> {
let address_space = AddressSpace::new()?;
Some(Process {
) -> Process {
Process {
pid,
stack_top,
state: ProcessState::Ready,
heap_base,
heap_end,
kbd_buffer: Vec::new(),
address_space,
address_space: None,
user_entry,
})
}
}
}
+1 -2
View File
@@ -39,7 +39,7 @@ impl Locked<Scheduler> {
let mut guard = without_interrupts(|| self.lock());
let pid = guard.next_pid;
guard.next_pid += 1;
let process = Process::new(pid, entry_point, stack_top, heap_base, heap_base)?;
let process = Process::new(pid, entry_point, stack_top, heap_base, heap_base);
guard.processes.insert(pid, process);
Some(pid)
@@ -52,7 +52,6 @@ impl Locked<Scheduler> {
};
set_current_pid(Some(pid));
enter_usermode(entry_point as u64, (stack_top & !0xF) - 8);
}