#![no_std] #![no_main] use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; use liblbos::characters; use crate::terminal::{print, println}; use liblbos::fs::{DirectoryReader, FileSystem}; static NO_EXIT: AtomicBool = AtomicBool::new(false); static CURRENT_DIR_PATH: AtomicPtr = AtomicPtr::new(0 as *mut _); static CURRENT_DIR_PATH_LEN: AtomicUsize = AtomicUsize::new(0); struct Environment<'a> { pub current_directory_path: &'a [u8], } #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { liblbos::syscalls::write_terminal(b"abort\n"); loop {} } fn cd(new_path: &str, environment: &mut Environment<'_>) { let old_path = environment.current_directory_path; let abs_new_path = if new_path.starts_with('/') { // absolute path, replaces old path let new_path_buf = liblbos::syscalls::alloc_blocks(new_path.len().div_ceil(512)); let new_path_buf = unsafe { core::slice::from_raw_parts_mut(new_path_buf as *mut _, new_path.len()) }; // copy { for (i, &x) in new_path.as_bytes().iter().enumerate() { new_path_buf[i] = x; } } new_path_buf } else { // todo: ".." is currently handled on the ktask level, however... // todo: we should evaluate this and instead modify the absolute path to remove the ".." // todo: otherwise, we're bound to end up with a path like "/a/b/../../c" which is not nice // relative path, append let new_path_len = old_path.len() + new_path.len() + 1; let new_path_buf = liblbos::syscalls::alloc_blocks(new_path_len.div_ceil(512)); let new_path_buf = unsafe { core::slice::from_raw_parts_mut(new_path_buf as *mut _, new_path_len) }; // copy old path { for (i, &x) in old_path.iter().enumerate() { new_path_buf[i] = x; } } // copy new path { for (i, &x) in new_path.as_bytes().iter().enumerate() { new_path_buf[old_path.len() + i] = x; } } // add separator new_path_buf[old_path.len() + new_path.len()] = b'/'; new_path_buf }; // check to make sure the new path is valid { let mut fs = FileSystem::empty(); if liblbos::ktask::ktask_fsopen(&mut fs) != 0 { println("failed to open fs"); // free liblbos::syscalls::free_blocks(abs_new_path.as_ptr() as usize, abs_new_path.len().div_ceil(512)); return; } let mut dir = DirectoryReader::empty(); if liblbos::ktask::ktask_fsopendir(&fs, &mut dir, abs_new_path) != 0 { println("invalid dir"); // free liblbos::syscalls::free_blocks(abs_new_path.as_ptr() as usize, abs_new_path.len().div_ceil(512)); return; } } // it is valid, update everything { // free old path liblbos::syscalls::free_blocks(CURRENT_DIR_PATH.load(Ordering::Relaxed) as usize, CURRENT_DIR_PATH_LEN.load(Ordering::Relaxed).div_ceil(512)); } CURRENT_DIR_PATH.store(abs_new_path.as_ptr() as *mut _, Ordering::Relaxed); CURRENT_DIR_PATH_LEN.store(abs_new_path.len(), Ordering::Relaxed); environment.current_directory_path = unsafe { core::slice::from_raw_parts_mut(CURRENT_DIR_PATH.load(Ordering::Relaxed) as *mut _, CURRENT_DIR_PATH_LEN.load(Ordering::Relaxed)) }; } fn lsdir(env: &Environment<'_>) { let mut fs = FileSystem::empty(); if liblbos::ktask::ktask_fsopen(&mut fs) != 0 { println("failed to open fs"); return; } let mut dir = DirectoryReader::empty(); if liblbos::ktask::ktask_fsopendir(&fs, &mut dir, env.current_directory_path) != 0 { println("failed to open dir"); return; } let mut record = liblbos::fs::Record { name: [0; 12], record_type: 0, target: 0, total_size_bytes: 0, }; while liblbos::ktask::ktask_fsreaddir(&fs, &mut dir, &mut record) == 0 { print("\n"); if record.record_type == 0 { println("unexpected eod"); break; } print("- "); let name = &record.name[..record.name.iter().position(|&x| x == 0).unwrap_or(11)]; liblbos::syscalls::write_terminal( name ); if record.record_type == liblbos::fs::RecordType::Directory as u8 { liblbos::syscalls::write_terminal(&[b' ', characters::C_FOLDER as u8]); } else if &name[name.len() - 3..] == b"DDI" { liblbos::syscalls::write_terminal(&[b' ', characters::C_BLOOD as u8]); } else { liblbos::syscalls::write_terminal(&[b' ', characters::C_FILE as u8]); } } print("\n"); } fn attempt_run_file(env: &Environment<'_>, name: &str) { print("\n"); if name.len() > 11 { println("filename too long"); return; } let mut fs = FileSystem::empty(); if liblbos::ktask::ktask_fsopen(&mut fs) != 0 { println("failed to open fs"); return; } let mut dir = DirectoryReader::empty(); if liblbos::ktask::ktask_fsopendir(&fs, &mut dir, env.current_directory_path) != 0 { println("failed to open dir"); return; } let mut record = liblbos::fs::Record { name: [0; 12], record_type: 0, target: 0, total_size_bytes: 0, }; fn namecmp(record: &liblbos::fs::Record, name: &[u8]) -> bool { let record_name = &record.name[..record.name.iter().position(|&x| x == 0).unwrap_or(11)]; record_name == name } while !namecmp(&record, name.as_bytes()) { if liblbos::ktask::ktask_fsreaddir(&fs, &mut dir, &mut record) != 0 { println("file not found"); return; } if record.record_type == 0 { println("unexpected eod"); return; } if record.record_type == liblbos::fs::RecordType::Directory as u8 && namecmp(&record, name.as_bytes()) { println("not a file"); return; } } // found, load the file let mut reader = liblbos::fs::FileReader::empty(); if liblbos::ktask::ktask_fsopenfile(&fs, &record, &mut reader) != 0 { println("failed to open file"); return; } let size = record.total_size_bytes as usize; let buf = liblbos::syscalls::alloc_blocks(size.div_ceil(512)); if liblbos::ktask::ktask_fsreadfile(&fs, &mut reader, unsafe { core::slice::from_raw_parts_mut(buf as *mut _, size) }) != 0 { println("failed to read file"); } let e = liblbos::Environment { current_directory_path: CURRENT_DIR_PATH.load(Ordering::Relaxed) as usize, current_directory_path_len: CURRENT_DIR_PATH_LEN.load(Ordering::Relaxed), }; let mut task_setup = liblbos::TaskSetup { epc: 0, stack_block_count: 8, ddi_first_addr: 0, ddi_size: 0, environment: &e as *const _ as usize, wait_for_task_exit: true, }; if liblbos::ktask::ktask_loadddifile(buf, size, &mut task_setup as *mut _ as usize) != 0 { println("failed to load ddi"); liblbos::syscalls::free_blocks(buf, size.div_ceil(512)); return; } liblbos::syscalls::free_blocks(buf, size.div_ceil(512)); liblbos::syscalls::create_task(task_setup); // yeah we're extending its lifetime #[allow(clippy::drop_non_drop)] drop(e); println("\ntask exited\n"); } fn execute(cmd: &str, environment: &mut Environment<'_>) { print("\n"); let mut split = cmd.split_ascii_whitespace(); if let Some(cmd) = split.next() { match cmd { "ls" => { lsdir(environment); } "cd" => { if let Some(path) = split.next() { cd(path, environment); } else { println("cd requires a path"); } } "exit" => { if NO_EXIT.load(Ordering::Relaxed) { println("cannot exit as task 1"); } else { if CURRENT_DIR_PATH.load(Ordering::Relaxed) as usize != 0 { // free liblbos::syscalls::free_blocks(CURRENT_DIR_PATH.load(Ordering::Relaxed) as usize, CURRENT_DIR_PATH_LEN.load(Ordering::Relaxed).div_ceil(512)); } liblbos::syscalls::exit(); } } _ => { attempt_run_file(environment, cmd); } } } } fn hex(mut n: u8) -> [u8; 2] { let mut buf = [0u8; 2]; for i in (0..2).rev() { let num = n & 0b1111; n >>= 4; if num < 10 { buf[i] = b'0' + num; } else { buf[i] = b'a' + (num - 10); } } buf } mod arch; mod terminal; fn main_wrapper() { let tid = liblbos::syscalls::current_task(); NO_EXIT.store(tid == 1, Ordering::Relaxed); let environment = liblbos::syscalls::environment_pointer(); let mut environment = if environment == 0 { // no environment, construct it ourself static DEFAULT_CURRENT_DIRECTORY: &[u8] = b"/"; let environment = Environment { current_directory_path: DEFAULT_CURRENT_DIRECTORY, }; environment } else { let env = unsafe { &mut *(environment as *mut liblbos::Environment) }; let current_directory_path = unsafe { core::slice::from_raw_parts(env.current_directory_path as *const u8, env.current_directory_path_len) }; let environment = Environment { current_directory_path, }; environment }; // replace the current path with one that we allocate ourself, so that we can modify it later { let path = environment.current_directory_path; let len = environment.current_directory_path.len(); CURRENT_DIR_PATH.store(liblbos::syscalls::alloc_blocks(len.div_ceil(512)) as *mut u8, Ordering::Relaxed); CURRENT_DIR_PATH_LEN.store(len, Ordering::Relaxed); { // copy let dest = unsafe { core::slice::from_raw_parts_mut(CURRENT_DIR_PATH.load(Ordering::Relaxed) as *mut _, len) }; for (i, &x) in path.iter().enumerate() { dest[i] = x; } } environment.current_directory_path = unsafe { core::slice::from_raw_parts_mut(CURRENT_DIR_PATH.load(Ordering::Relaxed) as *mut u8, CURRENT_DIR_PATH_LEN.load(Ordering::Relaxed)) }; } print("\n\n"); print("turntable v"); const VERSION: &str = env!("CARGO_PKG_VERSION"); print(VERSION); print("\n"); println("(c) 2025\nReal Microsoft, LLC"); print("> "); let mut cmdbuf = [0u8; 256]; let mut cmdbuf_len = 0; let mut inbuf = [0u8; 8]; loop { let read = liblbos::syscalls::read_inbuf(&mut inbuf); if read > 0 { for c in &inbuf[0..read] { if cmdbuf_len >= cmdbuf.len() { print("\x07"); } else { // backspace if *c == 0x7f && cmdbuf_len > 0 { cmdbuf_len -= 1; print("\x08 \x08"); } else if *c == b'\r' { execute(unsafe { core::str::from_utf8_unchecked(&cmdbuf[0..cmdbuf_len]) }, &mut environment); cmdbuf_len = 0; print("> "); break; } else if !c.is_ascii_control() { cmdbuf[cmdbuf_len] = *c; cmdbuf_len += 1; liblbos::syscalls::write_terminal(&[*c]); } } } } } } #[unsafe(no_mangle)] extern "C" fn main() { main_wrapper(); if CURRENT_DIR_PATH.load(Ordering::Relaxed) as usize != 0 { // free liblbos::syscalls::free_blocks(CURRENT_DIR_PATH.load(Ordering::Relaxed) as usize, CURRENT_DIR_PATH_LEN.load(Ordering::Relaxed).div_ceil(512)); } }