#![no_std] #![no_main] use lancer_core::ipc::{MAX_IPC_BYTES, unpack_bytes}; use lancer_core::packet_ring::{PacketRingReader, PacketRingWriter}; use lancer_user::path::{ build_resolved_line, bytes_eq, extract_filename, first_word, skip_leading_spaces, }; use lancer_user::print; use lancer_user::show; use lancer_user::syscall; const INPUT_ENDPOINT: u64 = 1; const NETSTACK_RING_BASE_SLOT: u64 = 64; const NETSTACK_RING_FRAME_COUNT: u64 = 2; const SLOT_CHILD_PROC: u64 = 4; const SLOT_CHILD_SCHED: u64 = 5; const SLOT_CHILD_ENDPOINT: u64 = 6; const SLOT_CHILD_NOTIF: u64 = 7; const TAG_ENDPOINT: u64 = 1; const TAG_NOTIFICATION: u64 = 2; const TAG_SCHED_CONTEXT: u64 = 5; const TAG_FRAME: u64 = 11; const RIGHTS_ALL: u64 = 0b1111; const SLOT_UNTYPED: u64 = 28; const BOOT_PROCESS_COUNT: u64 = 7; const NETSOCK_MAP_VADDR: u64 = 0x5000_0000; const NETSOCK_RING_HALF: usize = 2048; const NETSOCK_SLOT_SIZE: u32 = 64; const SLOT_NETSOCK_MEM: u64 = 8; const SLOT_SHELL_NOTIF: u64 = 9; const SLOT_NETSTACK_NOTIF: u64 = 10; const VFS_RING_BASE_SLOT: u64 = 128; const VFS_RING_FRAME_COUNT_SHELL: u64 = 16; const SLOT_VFS_CLIENT_NOTIF: u64 = 12; const SLOT_VFS_NOTIF: u64 = 13; const SLOT_CHILD_NETSOCK_NOTIF: u64 = 14; const SLOT_VFS_PROC: u64 = 15; const SLOT_CHILD_PROC_B: u64 = 16; const SLOT_CHILD_SCHED_B: u64 = 17; const SLOT_CHILD_ENDPOINT_B: u64 = 18; const SLOT_NETSOCK_MEM_B: u64 = 19; const SLOT_CHILD_NETSOCK_NOTIF_B: u64 = 20; const SLOT_TEMP_CLIENT_RING: u64 = 200; const SLOT_TEMP_CLIENT_NOTIF: u64 = 216; const SLOT_TEMP_CLIENT_RING_B: u64 = 220; const SLOT_TEMP_CLIENT_NOTIF_B: u64 = 236; const VFS_RING_VADDR: u64 = 0x7000_0000; const VFS_CLIENT_INIT_VADDR: u64 = 0x4000_0000; const NETSTACK_RING_VADDR: u64 = 0x6000_0000; const NETSTACK_RING_HALF: usize = 4096; const VFS_RING_HALF: usize = 4096; const VFS_RING_SLOT_SIZE: u32 = 48; const VFS_RING_PAGES: u64 = 16; const VFS_CLIENT_RING_BASE_SLOT: u64 = 160; const VFS_CLIENT_SLOT_STRIDE: u64 = 32; const INPUT_RING_VADDR: u64 = 0x3000_0000; const INPUT_RING_SIZE: usize = 4096; const NOTIFY_BIT_CHILD_DEATH: u64 = 0x01; struct LineBuf { data: [u8; 128], len: usize, } impl LineBuf { const fn new() -> Self { Self { data: [0u8; 128], len: 0, } } fn push(&mut self, b: u8) -> bool { match self.len < self.data.len() { true => { self.data[self.len] = b; self.len += 1; true } false => false, } } fn pop(&mut self) -> bool { match self.len > 0 { true => { self.len -= 1; true } false => false, } } fn clear(&mut self) { self.len = 0; } fn as_bytes(&self) -> &[u8] { &self.data[..self.len] } } const MAX_VFS_CLIENTS: usize = 8; struct VfsClientTracker { free: [bool; MAX_VFS_CLIENTS], } impl VfsClientTracker { const fn new() -> Self { Self { free: [false, true, true, true, true, true, true, true], } } fn alloc(&mut self) -> Option { (1..MAX_VFS_CLIENTS as u8) .find(|&i| self.free[i as usize]) .inspect(|&i| { self.free[i as usize] = false; }) } fn release(&mut self, idx: u8) { if (idx as usize) < MAX_VFS_CLIENTS && idx > 0 { self.free[idx as usize] = true; } } } fn create_vfs_client(client_idx: u8, child_proc_slot: u64, ring_tmp: u64, notif_tmp: u64) -> bool { let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_FRAME, 0, ring_tmp, VFS_RING_PAGES); if r < 0 { return false; } let map_ok = (0..VFS_RING_PAGES) .all(|i| syscall::frame_map(ring_tmp + i, VFS_CLIENT_INIT_VADDR + i * 4096, 1) >= 0); if !map_ok { (0..VFS_RING_PAGES).for_each(|i| { syscall::frame_unmap(ring_tmp + i, VFS_CLIENT_INIT_VADDR + i * 4096); }); (0..VFS_RING_PAGES).for_each(|i| { syscall::cap_revoke(ring_tmp + i); }); return false; } let base = VFS_CLIENT_INIT_VADDR as *mut u8; let _ = unsafe { PacketRingWriter::init(base, VFS_RING_HALF, VFS_RING_SLOT_SIZE) }; let _ = unsafe { PacketRingWriter::init( base.wrapping_add(VFS_RING_HALF), VFS_RING_HALF, VFS_RING_SLOT_SIZE, ) }; (0..VFS_RING_PAGES).for_each(|i| { syscall::frame_unmap(ring_tmp + i, VFS_CLIENT_INIT_VADDR + i * 4096); }); let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_NOTIFICATION, 0, notif_tmp, 1); if r < 0 { (0..VFS_RING_PAGES).for_each(|i| { syscall::cap_revoke(ring_tmp + i); }); return false; } let child_ring_ok = (0..VFS_RING_PAGES) .all(|i| syscall::cap_grant(ring_tmp + i, child_proc_slot, 11 + i, RIGHTS_ALL) >= 0); if !child_ring_ok { syscall::cap_revoke(notif_tmp); (0..VFS_RING_PAGES).for_each(|i| { syscall::cap_revoke(ring_tmp + i); }); return false; } let r = syscall::cap_grant(notif_tmp, child_proc_slot, 12 + VFS_RING_PAGES, RIGHTS_ALL); if r < 0 { syscall::cap_revoke(notif_tmp); (0..VFS_RING_PAGES).for_each(|i| { syscall::cap_revoke(ring_tmp + i); }); return false; } let r = syscall::cap_grant( SLOT_VFS_NOTIF, child_proc_slot, 13 + VFS_RING_PAGES, RIGHTS_ALL, ); if r < 0 { syscall::cap_revoke(notif_tmp); (0..VFS_RING_PAGES).for_each(|i| { syscall::cap_revoke(ring_tmp + i); }); return false; } let vfs_ring_base = VFS_CLIENT_RING_BASE_SLOT + (client_idx as u64) * VFS_CLIENT_SLOT_STRIDE; let vfs_notif_slot = vfs_ring_base + VFS_RING_PAGES; let vfs_ring_ok = (0..VFS_RING_PAGES).all(|i| { syscall::cap_grant(ring_tmp + i, SLOT_VFS_PROC, vfs_ring_base + i, RIGHTS_ALL) >= 0 }); if !vfs_ring_ok { syscall::cap_revoke(notif_tmp); (0..VFS_RING_PAGES).for_each(|i| { syscall::cap_revoke(ring_tmp + i); }); return false; } let r = syscall::cap_grant(notif_tmp, SLOT_VFS_PROC, vfs_notif_slot, RIGHTS_ALL); if r < 0 { syscall::cap_revoke(notif_tmp); (0..VFS_RING_PAGES).for_each(|i| { syscall::cap_revoke(ring_tmp + i); }); return false; } let mut fs_client = unsafe { lancer_user::fs::FsClient::reattach( VFS_RING_VADDR as usize, SLOT_VFS_NOTIF as u8, SLOT_VFS_CLIENT_NOTIF as u8, ) }; let reg_result = fs_client.extended_raw(lancer_vfs_proto::EXT_REGISTER_CLIENT, client_idx as u64, 0); reg_result.is_ok() } fn destroy_vfs_client(client_idx: u8) { let mut fs_client = unsafe { lancer_user::fs::FsClient::reattach( VFS_RING_VADDR as usize, SLOT_VFS_NOTIF as u8, SLOT_VFS_CLIENT_NOTIF as u8, ) }; let _ = fs_client.extended_raw( lancer_vfs_proto::EXT_UNREGISTER_CLIENT, client_idx as u64, 0, ); } fn print_prompt(cwd: &[u8], cwd_len: usize) { let path = &cwd[..cwd_len]; print!("lancer:"); match cwd_len <= 32 { true => { if let Ok(s) = core::str::from_utf8(path) { print!("{s}"); } } false => { let tail = &path[cwd_len - 29..]; print!("..."); if let Ok(s) = core::str::from_utf8(tail) { print!("{s}"); } } } print!("> "); } fn find_module_by_name(name: &[u8]) -> Option { let mut dummy = [0u8; 0]; let total = syscall::module_info(u64::MAX, &mut dummy); if total < 0 { return None; } let mut path_buf = [0u8; 128]; (BOOT_PROCESS_COUNT..total as u64).find(|&i| { path_buf = [0u8; 128]; let path_len = syscall::module_info(i, &mut path_buf); match path_len < 0 { true => false, false => { let used = (path_len as usize).min(path_buf.len()); let path = &path_buf[..used]; let filename = extract_filename(path); bytes_eq(filename, name) } } }) } struct ChildNetsock { tx: PacketRingWriter, rx: PacketRingReader, } fn setup_child_netsock( line: &[u8], has_netring: bool, child_proc_slot: u64, mem_slot: u64, notif_slot: u64, ) -> Option { let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_FRAME, 0, mem_slot, 1); if r < 0 { return None; } let r = syscall::frame_map(mem_slot, NETSOCK_MAP_VADDR, 1); if r < 0 { syscall::cap_revoke(mem_slot); return None; } let init_to_child = NETSOCK_MAP_VADDR as *mut u8; let child_to_init = (NETSOCK_MAP_VADDR + NETSOCK_RING_HALF as u64) as *mut u8; let tx = unsafe { PacketRingWriter::init(init_to_child, NETSOCK_RING_HALF, NETSOCK_SLOT_SIZE) }; let _ = unsafe { PacketRingWriter::init(child_to_init, NETSOCK_RING_HALF, NETSOCK_SLOT_SIZE) }; let rx = unsafe { PacketRingReader::attach(child_to_init, NETSOCK_RING_HALF) }; let _ = tx.try_push(line); let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_NOTIFICATION, 0, notif_slot, 1); if r < 0 { syscall::frame_unmap(mem_slot, NETSOCK_MAP_VADDR); syscall::cap_revoke(mem_slot); return None; } let r = syscall::cap_grant(mem_slot, child_proc_slot, 3, RIGHTS_ALL); if r < 0 { syscall::cap_revoke(notif_slot); syscall::frame_unmap(mem_slot, NETSOCK_MAP_VADDR); syscall::cap_revoke(mem_slot); return None; } let r = syscall::cap_grant(notif_slot, child_proc_slot, 4, RIGHTS_ALL); if r < 0 { syscall::cap_revoke(notif_slot); syscall::frame_unmap(mem_slot, NETSOCK_MAP_VADDR); syscall::cap_revoke(mem_slot); return None; } if has_netring { let r = syscall::cap_grant(SLOT_SHELL_NOTIF, child_proc_slot, 5, 0b0010); if r < 0 { syscall::cap_revoke(notif_slot); syscall::frame_unmap(mem_slot, NETSOCK_MAP_VADDR); syscall::cap_revoke(mem_slot); return None; } } Some(ChildNetsock { tx, rx }) } fn cleanup_child_netsock(mem_slot: u64, notif_slot: u64) { syscall::frame_unmap(mem_slot, NETSOCK_MAP_VADDR); syscall::cap_revoke(mem_slot); syscall::cap_revoke(notif_slot); } fn relay_child_to_netstack(child_rx: &PacketRingReader, netstack_tx: &PacketRingWriter) -> bool { let mut buf = [0u8; 64]; let mut relayed = false; core::iter::from_fn(|| match netstack_tx.has_space() { false => None, true => match child_rx.try_pop(&mut buf[1..]) { Some(n) if n > 0 => { buf[0] = 0; let _ = netstack_tx.try_push(&buf[..1 + n]); relayed = true; Some(()) } _ => None, }, }) .take(16) .count(); if relayed { syscall::notify_signal(SLOT_NETSTACK_NOTIF, 4); } relayed } fn relay_netstack_to_child(netstack_rx: &PacketRingReader, child_tx: &PacketRingWriter) -> bool { let mut buf = [0u8; 128]; let mut relayed = false; core::iter::from_fn(|| match netstack_rx.try_pop(&mut buf) { Some(n) if n >= 2 => match child_tx.try_push(&buf[1..n]) { true => { relayed = true; Some(()) } false => None, }, _ => None, }) .take(16) .count(); if relayed { syscall::notify_signal(SLOT_CHILD_NETSOCK_NOTIF, 1); } relayed } #[allow(clippy::too_many_arguments)] fn spawn_module( module_idx: u64, line: &[u8], has_netring: bool, has_vfs: bool, netstack_tx: Option<&PacketRingWriter>, netstack_rx: Option<&PacketRingReader>, vfs_tracker: &mut VfsClientTracker, remote: bool, ) -> Option { let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_ENDPOINT, 0, SLOT_CHILD_ENDPOINT, 1); if r < 0 { print!("error: failed to create endpoint\n"); return None; } let child_pid = syscall::proc_create(SLOT_UNTYPED, SLOT_CHILD_PROC); if child_pid < 0 { print!("error: failed to create process\n"); syscall::cap_revoke(SLOT_CHILD_ENDPOINT); return None; } let r = syscall::proc_load_module(SLOT_CHILD_PROC, module_idx); if r < 0 { print!("error: failed to load module\n"); syscall::proc_destroy(SLOT_CHILD_PROC); syscall::cap_revoke(SLOT_CHILD_ENDPOINT); return None; } let r = syscall::cap_grant(SLOT_CHILD_ENDPOINT, SLOT_CHILD_PROC, 2, RIGHTS_ALL); if r < 0 { print!("error: failed to grant endpoint\n"); syscall::proc_destroy(SLOT_CHILD_PROC); syscall::cap_revoke(SLOT_CHILD_ENDPOINT); return None; } let vfs_client_idx = match has_vfs { true => match vfs_tracker.alloc() { None => None, Some(idx) => { syscall::cap_revoke(SLOT_TEMP_CLIENT_RING); syscall::cap_revoke(SLOT_TEMP_CLIENT_NOTIF); match create_vfs_client( idx, SLOT_CHILD_PROC, SLOT_TEMP_CLIENT_RING, SLOT_TEMP_CLIENT_NOTIF, ) { true => Some(idx), false => { vfs_tracker.release(idx); None } } } }, false => None, }; let _ = syscall::cap_grant(SLOT_UNTYPED, SLOT_CHILD_PROC, 30, RIGHTS_ALL); let netsock = setup_child_netsock( line, has_netring, SLOT_CHILD_PROC, SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF, ); let death_notif_slot = match has_netring { true => SLOT_SHELL_NOTIF, false => { let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_NOTIFICATION, 0, SLOT_CHILD_NOTIF, 1); if r < 0 { if netsock.is_some() { cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); } if let Some(idx) = vfs_client_idx { destroy_vfs_client(idx); vfs_tracker.release(idx); } syscall::proc_destroy(SLOT_CHILD_PROC); syscall::cap_revoke(SLOT_CHILD_ENDPOINT); return None; } SLOT_CHILD_NOTIF } }; syscall::proc_bind_death_notif(SLOT_CHILD_PROC, death_notif_slot, NOTIFY_BIT_CHILD_DEATH); let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_SCHED_CONTEXT, 0, SLOT_CHILD_SCHED, 1); if r < 0 { print!("error: failed to create sched context\n"); if netsock.is_some() { cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); } if let Some(idx) = vfs_client_idx { destroy_vfs_client(idx); vfs_tracker.release(idx); } syscall::proc_destroy(SLOT_CHILD_PROC); if !has_netring { syscall::cap_revoke(SLOT_CHILD_NOTIF); } syscall::cap_revoke(SLOT_CHILD_ENDPOINT); return None; } let r = syscall::sched_configure(SLOT_CHILD_SCHED, 10_000_000, 10_000_000, 100); if r < 0 { print!("error: failed to configure sched context\n"); if netsock.is_some() { cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); } if let Some(idx) = vfs_client_idx { destroy_vfs_client(idx); vfs_tracker.release(idx); } syscall::proc_destroy(SLOT_CHILD_PROC); syscall::cap_revoke(SLOT_CHILD_SCHED); if !has_netring { syscall::cap_revoke(SLOT_CHILD_NOTIF); } syscall::cap_revoke(SLOT_CHILD_ENDPOINT); return None; } let r = syscall::sched_attach(SLOT_CHILD_SCHED, child_pid as u64); if r < 0 { print!("error: failed to attach sched context\n"); if netsock.is_some() { cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); } if let Some(idx) = vfs_client_idx { destroy_vfs_client(idx); vfs_tracker.release(idx); } syscall::proc_destroy(SLOT_CHILD_PROC); syscall::cap_revoke(SLOT_CHILD_SCHED); if !has_netring { syscall::cap_revoke(SLOT_CHILD_NOTIF); } syscall::cap_revoke(SLOT_CHILD_ENDPOINT); return None; } let bootstrap_val = match remote { true => 2, false => 0, }; let r = syscall::proc_start(SLOT_CHILD_PROC, bootstrap_val); if r < 0 { print!("error: failed to start process\n"); if netsock.is_some() { cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); } if let Some(idx) = vfs_client_idx { destroy_vfs_client(idx); vfs_tracker.release(idx); } syscall::proc_destroy(SLOT_CHILD_PROC); syscall::cap_revoke(SLOT_CHILD_SCHED); if !has_netring { syscall::cap_revoke(SLOT_CHILD_NOTIF); } syscall::cap_revoke(SLOT_CHILD_ENDPOINT); return None; } let can_relay = has_netring && netsock.is_some() && netstack_tx.is_some() && netstack_rx.is_some(); let had_output = match (can_relay, remote) { (true, false) => { let child_netsock = netsock.unwrap(); let ns_tx = netstack_tx.unwrap(); let ns_rx = netstack_rx.unwrap(); run_relay_loop(&child_netsock, ns_tx, ns_rx); cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); true } (true, true) => { let child_netsock = netsock.unwrap(); let ns_tx = netstack_tx.unwrap(); let ns_rx = netstack_rx.unwrap(); run_relay_loop_remote(&child_netsock, ns_tx, ns_rx); cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); true } (_, true) => { run_simple_wait_remote(death_notif_slot); if netsock.is_some() { cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); } false } (_, false) => { run_simple_wait(death_notif_slot); if netsock.is_some() { cleanup_child_netsock(SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF); } false } }; if let Some(idx) = vfs_client_idx { destroy_vfs_client(idx); vfs_tracker.release(idx); } syscall::cap_revoke(SLOT_CHILD_SCHED); if !has_netring { syscall::cap_revoke(SLOT_CHILD_NOTIF); } syscall::cap_revoke(SLOT_CHILD_ENDPOINT); Some(had_output) } fn run_simple_wait(death_notif_slot: u64) { loop { let (_, bits) = syscall::notify_wait(death_notif_slot); if bits & NOTIFY_BIT_CHILD_DEATH != 0 { syscall::proc_destroy(SLOT_CHILD_PROC); break; } } } fn run_simple_wait_remote(death_notif_slot: u64) { syscall::ntfn_bind(death_notif_slot); loop { let (status, msg) = syscall::recv(SLOT_CHILD_ENDPOINT); match status == syscall::BOUND_NOTIFICATION_BADGE { true => { if msg[1] & NOTIFY_BIT_CHILD_DEATH != 0 { syscall::proc_destroy(SLOT_CHILD_PROC); break; } } false if status >= 0 => { let _ = syscall::send(2, msg); } _ => {} } } syscall::ntfn_unbind(); } fn drain_stale_netstack_replies(netstack_rx: &PacketRingReader) { let mut discard = [0u8; 128]; core::iter::from_fn(|| netstack_rx.try_pop(&mut discard).map(|_| ())) .take(64) .count(); } fn run_relay_loop( child_netsock: &ChildNetsock, netstack_tx: &PacketRingWriter, netstack_rx: &PacketRingReader, ) { drain_stale_netstack_replies(netstack_rx); loop { let (_, bits) = syscall::notify_wait(SLOT_SHELL_NOTIF); relay_child_to_netstack(&child_netsock.rx, netstack_tx); relay_netstack_to_child(netstack_rx, &child_netsock.tx); if bits & NOTIFY_BIT_CHILD_DEATH != 0 { relay_child_to_netstack(&child_netsock.rx, netstack_tx); syscall::proc_destroy(SLOT_CHILD_PROC); break; } } } fn run_relay_loop_remote( child_netsock: &ChildNetsock, netstack_tx: &PacketRingWriter, netstack_rx: &PacketRingReader, ) { drain_stale_netstack_replies(netstack_rx); syscall::ntfn_bind(SLOT_SHELL_NOTIF); loop { let (status, msg) = syscall::recv(SLOT_CHILD_ENDPOINT); match status == syscall::BOUND_NOTIFICATION_BADGE { true => { let bits = msg[1]; relay_child_to_netstack(&child_netsock.rx, netstack_tx); relay_netstack_to_child(netstack_rx, &child_netsock.tx); if bits & NOTIFY_BIT_CHILD_DEATH != 0 { relay_child_to_netstack(&child_netsock.rx, netstack_tx); syscall::proc_destroy(SLOT_CHILD_PROC); break; } } false if status >= 0 => { let _ = syscall::send(2, msg); } _ => {} } } syscall::ntfn_unbind(); } #[allow(clippy::too_many_arguments)] fn spawn_conc_child( module_idx: u64, args: &[u8], proc_slot: u64, sched_slot: u64, endpoint_slot: u64, death_notif_slot: u64, death_bit: u64, vfs_client_idx: u8, vfs_tracker: &mut VfsClientTracker, netsock_mem: u64, netsock_notif: u64, vfs_ring_tmp: u64, vfs_notif_tmp: u64, ) -> bool { let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_ENDPOINT, 0, endpoint_slot, 1); if r < 0 { return false; } let child_pid = syscall::proc_create(SLOT_UNTYPED, proc_slot); if child_pid < 0 { syscall::cap_revoke(endpoint_slot); return false; } let r = syscall::proc_load_module(proc_slot, module_idx); if r < 0 { syscall::proc_destroy(proc_slot); syscall::cap_revoke(endpoint_slot); return false; } let r = syscall::cap_grant(endpoint_slot, proc_slot, 2, RIGHTS_ALL); if r < 0 { syscall::proc_destroy(proc_slot); syscall::cap_revoke(endpoint_slot); return false; } match create_vfs_client(vfs_client_idx, proc_slot, vfs_ring_tmp, vfs_notif_tmp) { true => {} false => { vfs_tracker.release(vfs_client_idx); syscall::proc_destroy(proc_slot); syscall::cap_revoke(endpoint_slot); return false; } } let _ = syscall::cap_grant(SLOT_UNTYPED, proc_slot, 30, RIGHTS_ALL); let netsock = setup_child_netsock(args, false, proc_slot, netsock_mem, netsock_notif); if netsock.is_none() { destroy_vfs_client(vfs_client_idx); vfs_tracker.release(vfs_client_idx); syscall::cap_revoke(vfs_ring_tmp); syscall::cap_revoke(vfs_notif_tmp); syscall::proc_destroy(proc_slot); syscall::cap_revoke(endpoint_slot); return false; } syscall::proc_bind_death_notif(proc_slot, death_notif_slot, death_bit); let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_SCHED_CONTEXT, 0, sched_slot, 1); if r < 0 { cleanup_child_netsock(netsock_mem, netsock_notif); destroy_vfs_client(vfs_client_idx); vfs_tracker.release(vfs_client_idx); syscall::cap_revoke(vfs_ring_tmp); syscall::cap_revoke(vfs_notif_tmp); syscall::proc_destroy(proc_slot); syscall::cap_revoke(endpoint_slot); return false; } let r = syscall::sched_configure(sched_slot, 10_000_000, 10_000_000, 100); if r < 0 { cleanup_child_netsock(netsock_mem, netsock_notif); destroy_vfs_client(vfs_client_idx); vfs_tracker.release(vfs_client_idx); syscall::cap_revoke(vfs_ring_tmp); syscall::cap_revoke(vfs_notif_tmp); syscall::proc_destroy(proc_slot); syscall::cap_revoke(sched_slot); syscall::cap_revoke(endpoint_slot); return false; } let r = syscall::sched_attach(sched_slot, child_pid as u64); if r < 0 { cleanup_child_netsock(netsock_mem, netsock_notif); destroy_vfs_client(vfs_client_idx); vfs_tracker.release(vfs_client_idx); syscall::cap_revoke(vfs_ring_tmp); syscall::cap_revoke(vfs_notif_tmp); syscall::proc_destroy(proc_slot); syscall::cap_revoke(sched_slot); syscall::cap_revoke(endpoint_slot); return false; } let r = syscall::proc_start(proc_slot, 0); if r < 0 { cleanup_child_netsock(netsock_mem, netsock_notif); destroy_vfs_client(vfs_client_idx); vfs_tracker.release(vfs_client_idx); syscall::cap_revoke(vfs_ring_tmp); syscall::cap_revoke(vfs_notif_tmp); syscall::proc_destroy(proc_slot); syscall::cap_revoke(sched_slot); syscall::cap_revoke(endpoint_slot); return false; } syscall::frame_unmap(netsock_mem, NETSOCK_MAP_VADDR); true } fn cleanup_conc_child( proc_slot: u64, sched_slot: u64, endpoint_slot: u64, vfs_client_idx: u8, vfs_tracker: &mut VfsClientTracker, ) { destroy_vfs_client(vfs_client_idx); vfs_tracker.release(vfs_client_idx); syscall::proc_destroy(proc_slot); syscall::cap_revoke(sched_slot); syscall::cap_revoke(endpoint_slot); } fn run_conctest(vfs_tracker: &mut VfsClientTracker) { let module_idx = match find_module_by_name(b"fstest") { Some(idx) => idx, None => { show!(rsh, error, "fstest module not found"); return; } }; let vfs_idx_a = match vfs_tracker.alloc() { Some(idx) => idx, None => { show!(rsh, error, "no free vfs client slots"); return; } }; let vfs_idx_b = match vfs_tracker.alloc() { Some(idx) => idx, None => { vfs_tracker.release(vfs_idx_a); show!(rsh, error, "no free vfs client slots for b"); return; } }; let r = syscall::untyped_retype(SLOT_UNTYPED, TAG_NOTIFICATION, 0, SLOT_CHILD_NOTIF, 1); if r < 0 { vfs_tracker.release(vfs_idx_b); vfs_tracker.release(vfs_idx_a); show!(rsh, error, "failed to create death notif"); return; } syscall::cap_revoke(SLOT_TEMP_CLIENT_RING); syscall::cap_revoke(SLOT_TEMP_CLIENT_NOTIF); syscall::cap_revoke(SLOT_NETSOCK_MEM); syscall::cap_revoke(SLOT_CHILD_NETSOCK_NOTIF); let ok_a = spawn_conc_child( module_idx, b"fstest concurrent-a", SLOT_CHILD_PROC, SLOT_CHILD_SCHED, SLOT_CHILD_ENDPOINT, SLOT_CHILD_NOTIF, 0x01, vfs_idx_a, vfs_tracker, SLOT_NETSOCK_MEM, SLOT_CHILD_NETSOCK_NOTIF, SLOT_TEMP_CLIENT_RING, SLOT_TEMP_CLIENT_NOTIF, ); if !ok_a { vfs_tracker.release(vfs_idx_b); syscall::cap_revoke(SLOT_CHILD_NOTIF); show!(rsh, error, "failed to spawn child a"); return; } let ok_b = spawn_conc_child( module_idx, b"fstest concurrent-b", SLOT_CHILD_PROC_B, SLOT_CHILD_SCHED_B, SLOT_CHILD_ENDPOINT_B, SLOT_CHILD_NOTIF, 0x02, vfs_idx_b, vfs_tracker, SLOT_NETSOCK_MEM_B, SLOT_CHILD_NETSOCK_NOTIF_B, SLOT_TEMP_CLIENT_RING_B, SLOT_TEMP_CLIENT_NOTIF_B, ); if !ok_b { cleanup_conc_child( SLOT_CHILD_PROC, SLOT_CHILD_SCHED, SLOT_CHILD_ENDPOINT, vfs_idx_a, vfs_tracker, ); syscall::cap_revoke(SLOT_TEMP_CLIENT_RING); syscall::cap_revoke(SLOT_TEMP_CLIENT_NOTIF); syscall::cap_revoke(SLOT_NETSOCK_MEM); syscall::cap_revoke(SLOT_CHILD_NETSOCK_NOTIF); syscall::cap_revoke(SLOT_CHILD_NOTIF); show!(rsh, error, "failed to spawn child b"); return; } let mut deaths: u64 = 0; core::iter::from_fn(|| match deaths & 0x03 == 0x03 { true => None, false => { let (_, bits) = syscall::notify_wait(SLOT_CHILD_NOTIF); deaths |= bits; Some(()) } }) .take(100) .count(); let both_dead = deaths & 0x03 == 0x03; if !both_dead { show!(rsh, error, "timed out waiting for children"); } cleanup_conc_child( SLOT_CHILD_PROC, SLOT_CHILD_SCHED, SLOT_CHILD_ENDPOINT, vfs_idx_a, vfs_tracker, ); cleanup_conc_child( SLOT_CHILD_PROC_B, SLOT_CHILD_SCHED_B, SLOT_CHILD_ENDPOINT_B, vfs_idx_b, vfs_tracker, ); syscall::cap_revoke(SLOT_TEMP_CLIENT_RING); syscall::cap_revoke(SLOT_TEMP_CLIENT_NOTIF); syscall::cap_revoke(SLOT_TEMP_CLIENT_RING_B); syscall::cap_revoke(SLOT_TEMP_CLIENT_NOTIF_B); syscall::cap_revoke(SLOT_NETSOCK_MEM); syscall::cap_revoke(SLOT_CHILD_NETSOCK_NOTIF); syscall::cap_revoke(SLOT_NETSOCK_MEM_B); syscall::cap_revoke(SLOT_CHILD_NETSOCK_NOTIF_B); syscall::cap_revoke(SLOT_CHILD_NOTIF); if !both_dead { return; } let mut fs_client = unsafe { lancer_user::fs::FsClient::reattach( VFS_RING_VADDR as usize, SLOT_VFS_NOTIF as u8, SLOT_VFS_CLIENT_NOTIF as u8, ) }; let mut pass = 0u32; let mut fail = 0u32; let mut buf_a = [0u8; 64]; match fs_client.open(0, b"conc_a", lancer_vfs_proto::FsRights::READ) { Ok(h) => { match fs_client.read(h, 0, &mut buf_a) { Ok(n) => match &buf_a[..n] == b"alpha_data" { true => pass += 1, false => { show!(rsh, error, "conc_a mismatch"); fail += 1; } }, Err(_) => { show!(rsh, error, "read conc_a failed"); fail += 1; } } let _ = fs_client.close(h); let _ = fs_client.unlink(0, b"conc_a"); } Err(_) => { show!(rsh, error, "open conc_a failed"); fail += 1; } } let mut buf_b = [0u8; 64]; match fs_client.open(0, b"conc_b", lancer_vfs_proto::FsRights::READ) { Ok(h) => { match fs_client.read(h, 0, &mut buf_b) { Ok(n) => match &buf_b[..n] == b"bravo_data" { true => pass += 1, false => { show!(rsh, error, "conc_b mismatch"); fail += 1; } }, Err(_) => { show!(rsh, error, "read conc_b failed"); fail += 1; } } let _ = fs_client.close(h); let _ = fs_client.unlink(0, b"conc_b"); } Err(_) => { show!(rsh, error, "open conc_b failed"); fail += 1; } } match fail { 0 => show!(rsh, "results: {pass} pass, {fail} fail - ok"), _ => show!(rsh, error, "results: {pass} pass, {fail} fail - failed"), } } #[allow(clippy::too_many_arguments)] fn process_line( line: &[u8], has_netring: bool, has_vfs: bool, netstack_tx: Option<&PacketRingWriter>, netstack_rx: Option<&PacketRingReader>, cwd: &mut [u8; 256], cwd_len: &mut usize, vfs_tracker: &mut VfsClientTracker, remote: bool, ) { if line.is_empty() { return; } let cmd = first_word(line); if bytes_eq(cmd, b"exit") { syscall::exit(); } if bytes_eq(cmd, b"pwd") { if let Ok(s) = core::str::from_utf8(&cwd[..*cwd_len]) { print!("{s}\n"); } return; } if bytes_eq(cmd, b"conctest") { match has_vfs { true => run_conctest(vfs_tracker), false => show!(rsh, warn, "vfs not available"), } return; } let mut resolved = [0u8; 256]; let resolved_len = build_resolved_line(&cwd[..], *cwd_len, line, &mut resolved); let child_line = &resolved[..resolved_len]; if bytes_eq(cmd, b"cd") { let orig_rest = skip_leading_spaces(&line[cmd.len()..]); if first_word(orig_rest).is_empty() { return; } let after_cmd = &resolved[cmd.len()..resolved_len]; let rest = skip_leading_spaces(after_cmd); let target = first_word(rest); match find_module_by_name(b"cd") { Some(idx) => { if let Some(false) = spawn_module( idx, child_line, false, has_vfs, None, None, vfs_tracker, remote, ) { let t_len = target.len().min(256); cwd[..t_len].copy_from_slice(&target[..t_len]); *cwd_len = t_len; } } None => print!("cd: command not found\n"), } return; } match find_module_by_name(cmd) { Some(idx) => { let _ = spawn_module( idx, child_line, has_netring, has_vfs, netstack_tx, netstack_rx, vfs_tracker, remote, ); } None => match core::str::from_utf8(cmd) { Ok(s) => print!("{s}: command not found\n"), Err(_) => print!("(invalid): command not found\n"), }, } } #[allow(clippy::too_many_arguments)] fn process_input_byte( b: u8, line: &mut LineBuf, has_netring: bool, has_vfs: bool, netstack_tx: Option<&PacketRingWriter>, netstack_rx: Option<&PacketRingReader>, cwd: &mut [u8; 256], cwd_len: &mut usize, vfs_tracker: &mut VfsClientTracker, remote: bool, ) { match b { 0x0D | 0x0A => { print!("\n"); process_line( line.as_bytes(), has_netring, has_vfs, netstack_tx, netstack_rx, cwd, cwd_len, vfs_tracker, remote, ); line.clear(); print_prompt(cwd, *cwd_len); } 0x7F | 0x08 => { if line.pop() { print!("\x08 \x08"); } } 0x20..=0x7E => { if line.push(b) { let ch = [b]; if let Ok(s) = core::str::from_utf8(&ch) { print!("{s}"); } } } _ => {} } } #[unsafe(no_mangle)] pub extern "C" fn lancer_main() -> ! { let remote = !lancer_user::io::debug_print_enabled(); let has_netring = (0..NETSTACK_RING_FRAME_COUNT).all(|i| { syscall::frame_map( NETSTACK_RING_BASE_SLOT + i, NETSTACK_RING_VADDR + i * 4096, 1, ) >= 0 }); let (netstack_tx, netstack_rx) = match has_netring { true => { let rx_base = NETSTACK_RING_VADDR as *mut u8; let tx_base = (NETSTACK_RING_VADDR + NETSTACK_RING_HALF as u64) as *mut u8; let _ = unsafe { PacketRingWriter::init(rx_base, NETSTACK_RING_HALF, 128) }; let tx = unsafe { PacketRingWriter::init(tx_base, NETSTACK_RING_HALF, 128) }; let rx = unsafe { PacketRingReader::attach(rx_base, NETSTACK_RING_HALF) }; show!(rsh, "netstack ring mapped"); (Some(tx), Some(rx)) } false => { show!(rsh, warn, "netstack ring not available"); (None, None) } }; let has_vfs = (0..VFS_RING_FRAME_COUNT_SHELL) .all(|i| syscall::frame_map(VFS_RING_BASE_SLOT + i, VFS_RING_VADDR + i * 4096, 1) >= 0); match has_vfs { true => show!(rsh, "vfs ring mapped"), false => show!(rsh, warn, "vfs not available"), } let mut cwd = [0u8; 256]; cwd[0] = b'/'; let mut cwd_len: usize = 1; let mut vfs_tracker = VfsClientTracker::new(); print_prompt(&cwd, cwd_len); let mut line = LineBuf::new(); match remote { true => { let r = syscall::frame_map(1, INPUT_RING_VADDR, 1); if r < 0 { show!(rsh, error, "input ring frame_map failed"); syscall::exit(); } let input_rx = unsafe { PacketRingReader::attach(INPUT_RING_VADDR as *mut u8, INPUT_RING_SIZE) }; loop { let (_, _bits) = syscall::notify_wait(SLOT_SHELL_NOTIF); let mut buf = [0u8; 64]; core::iter::from_fn(|| { input_rx.try_pop(&mut buf).map(|n| { (0..n).fold((), |(), i| { process_input_byte( buf[i], &mut line, has_netring, has_vfs, netstack_tx.as_ref(), netstack_rx.as_ref(), &mut cwd, &mut cwd_len, &mut vfs_tracker, true, ); }); }) }) .take(64) .count(); } } false => loop { let (_status, msg) = syscall::recv(INPUT_ENDPOINT); let mut raw_bytes = [0u8; MAX_IPC_BYTES]; let count = unpack_bytes(&msg, &mut raw_bytes); (0..count).fold((), |(), i| { process_input_byte( raw_bytes[i], &mut line, has_netring, has_vfs, netstack_tx.as_ref(), netstack_rx.as_ref(), &mut cwd, &mut cwd_len, &mut vfs_tracker, false, ); }); }, } }