From b211cd306bd6eea9dc58cab7b6c89d1b81e09441 Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Wed, 18 Feb 2026 10:49:38 -0500 Subject: [PATCH] feat: musl libc full support - getrlimit, uname, epoll, env, pthread, and test programs Add kernel syscalls (getrlimit/prlimit64, uname, epoll_create1/ctl/wait/pwait), environment variable support (envp stack parsing, getenv), real pthread mutexes/condvars via futex, and libbreenix-libc wrappers. Includes three new musl C test programs (env_musl_test, uname_musl_test, rlimit_musl_test) that exercise these syscalls end-to-end through stock musl libc. Co-Authored-By: Ryan Breen Co-Authored-By: Claude Opus 4.6 --- kernel/src/arch_impl/aarch64/syscall_entry.rs | 9 + kernel/src/boot/test_list.rs | 5 +- kernel/src/ipc/fd.rs | 7 + kernel/src/ipc/poll.rs | 3 + kernel/src/process/manager.rs | 72 ++- kernel/src/syscall/dispatcher.rs | 9 + kernel/src/syscall/epoll.rs | 431 ++++++++++++++++++ kernel/src/syscall/fs.rs | 7 + kernel/src/syscall/handler.rs | 9 + kernel/src/syscall/handlers.rs | 97 ++++ kernel/src/syscall/mod.rs | 23 + kernel/src/syscall/pipe.rs | 4 + libs/libbreenix-libc/src/lib.rs | 372 ++++++++++++++- libs/libbreenix/src/syscall.rs | 16 + userspace/c-programs/Makefile | 13 +- userspace/c-programs/env_test.c | 60 +++ userspace/c-programs/rlimit_test.c | 50 ++ userspace/c-programs/uname_test.c | 50 ++ 18 files changed, 1212 insertions(+), 25 deletions(-) create mode 100644 kernel/src/syscall/epoll.rs create mode 100644 userspace/c-programs/env_test.c create mode 100644 userspace/c-programs/rlimit_test.c create mode 100644 userspace/c-programs/uname_test.c diff --git a/kernel/src/arch_impl/aarch64/syscall_entry.rs b/kernel/src/arch_impl/aarch64/syscall_entry.rs index a237da6f..ea54f9ff 100644 --- a/kernel/src/arch_impl/aarch64/syscall_entry.rs +++ b/kernel/src/arch_impl/aarch64/syscall_entry.rs @@ -564,6 +564,15 @@ fn dispatch_syscall_enum( // Testing/diagnostic syscalls SyscallNumber::CowStats => sys_cow_stats_aarch64(arg1), SyscallNumber::SimulateOom => sys_simulate_oom_aarch64(arg1), + // Resource limits and system info + SyscallNumber::Getrlimit => result_to_u64(crate::syscall::handlers::sys_getrlimit(arg1, arg2)), + SyscallNumber::Prlimit64 => result_to_u64(crate::syscall::handlers::sys_prlimit64(arg1, arg2, arg3, arg4)), + SyscallNumber::Uname => result_to_u64(crate::syscall::handlers::sys_uname(arg1)), + // epoll + SyscallNumber::EpollCreate1 => result_to_u64(crate::syscall::epoll::sys_epoll_create1(arg1 as u32)), + SyscallNumber::EpollCtl => result_to_u64(crate::syscall::epoll::sys_epoll_ctl(arg1 as i32, arg2 as i32, arg3 as i32, arg4)), + SyscallNumber::EpollWait => result_to_u64(crate::syscall::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, 0, 0)), + SyscallNumber::EpollPwait => result_to_u64(crate::syscall::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, arg5, arg6)), } } diff --git a/kernel/src/boot/test_list.rs b/kernel/src/boot/test_list.rs index 6d128b3c..cd4c952c 100644 --- a/kernel/src/boot/test_list.rs +++ b/kernel/src/boot/test_list.rs @@ -77,8 +77,11 @@ pub const TEST_BINARIES: &[&str] = &[ "ls_test", // Rust std library test (installed as hello_world.elf on ext2) "hello_world", - // musl libc C program (cross-compiled with musl libc for aarch64) + // musl libc C programs (cross-compiled with musl libc for aarch64) "hello_musl", + "env_musl_test", + "uname_musl_test", + "rlimit_musl_test", // Fork / CoW tests "fork_memory_test", "fork_state_test", diff --git a/kernel/src/ipc/fd.rs b/kernel/src/ipc/fd.rs index f490ee64..1df9a74a 100644 --- a/kernel/src/ipc/fd.rs +++ b/kernel/src/ipc/fd.rs @@ -133,6 +133,8 @@ pub enum FdKind { ProcfsFile { content: alloc::string::String, position: usize }, /// Procfs directory listing (for /proc and /proc/[pid]) ProcfsDirectory { path: alloc::string::String, position: u64 }, + /// Epoll instance file descriptor + Epoll(u64), } impl core::fmt::Debug for FdKind { @@ -168,6 +170,7 @@ impl core::fmt::Debug for FdKind { FdKind::FifoWrite(path, _) => write!(f, "FifoWrite({})", path), FdKind::ProcfsFile { content, position } => write!(f, "ProcfsFile(len={}, pos={})", content.len(), position), FdKind::ProcfsDirectory { path, position } => write!(f, "ProcfsDirectory(path={}, pos={})", path, position), + FdKind::Epoll(id) => write!(f, "Epoll({})", id), } } } @@ -735,6 +738,10 @@ impl Drop for FdTable { FdKind::ProcfsDirectory { .. } => { // Procfs directory doesn't need cleanup } + FdKind::Epoll(id) => { + // Clean up the epoll instance + crate::syscall::epoll::remove_instance(id); + } } } } diff --git a/kernel/src/ipc/poll.rs b/kernel/src/ipc/poll.rs index 919ae1ff..a93451a8 100644 --- a/kernel/src/ipc/poll.rs +++ b/kernel/src/ipc/poll.rs @@ -301,6 +301,9 @@ pub fn poll_fd(fd_entry: &FileDescriptor, events: i16) -> i16 { revents |= events::POLLIN; } } + FdKind::Epoll(_) => { + // Epoll fds are not directly pollable + } } revents diff --git a/kernel/src/process/manager.rs b/kernel/src/process/manager.rs index 4578e6aa..03937858 100644 --- a/kernel/src/process/manager.rs +++ b/kernel/src/process/manager.rs @@ -584,11 +584,19 @@ impl ProcessManager { // Set up argc/argv/envp/auxv on the stack following Linux ABI // The stack is now mapped, so we can write to it via physical addresses + let default_env: [&[u8]; 5] = [ + b"PATH=/bin:/sbin\0", + b"HOME=/\0", + b"TERM=vt100\0", + b"USER=root\0", + b"SHELL=/bin/bsh\0", + ]; let initial_sp = if let Some(ref page_table) = process.page_table { self.setup_argv_on_stack( page_table, user_stack_top, argv, + &default_env, loaded_elf.phdr_vaddr, loaded_elf.phnum, loaded_elf.phentsize, @@ -2576,10 +2584,18 @@ impl ProcessManager { // We need to write to the new stack pages that we just mapped // Since the new page table is not active yet, we need to translate addresses // and write via the physical frames + let default_env: [&[u8]; 5] = [ + b"PATH=/bin:/sbin\0", + b"HOME=/\0", + b"TERM=vt100\0", + b"USER=root\0", + b"SHELL=/bin/bsh\0", + ]; let initial_rsp = self.setup_argv_on_stack( &new_page_table, USER_STACK_TOP, argv, + &default_env, loaded_elf.phdr_vaddr, loaded_elf.phnum, loaded_elf.phentsize, @@ -2812,10 +2828,18 @@ impl ProcessManager { )?; } + let default_env: [&[u8]; 5] = [ + b"PATH=/bin:/sbin\0", + b"HOME=/\0", + b"TERM=vt100\0", + b"USER=root\0", + b"SHELL=/bin/bsh\0", + ]; let initial_rsp = self.setup_argv_on_stack( &new_page_table, user_stack_top, argv, + &default_env, loaded_elf.phdr_vaddr, loaded_elf.phnum, loaded_elf.phentsize, @@ -3251,6 +3275,7 @@ impl ProcessManager { /// /// 16 random bytes (for AT_RANDOM) /// argv string data (null-terminated strings) + /// envp string data (null-terminated strings) /// --- 8-byte alignment padding --- /// AT_NULL (0, 0) // auxv terminator /// AT_RANDOM (25, ptr_to_random) // pointer to 16 random bytes @@ -3260,6 +3285,9 @@ impl ProcessManager { /// AT_PHDR (3, phdr_vaddr) // address of program headers in memory /// AT_ENTRY (9, entry_point) // program entry point /// NULL (envp terminator) // 8 bytes of 0 + /// envp[m-1] pointer + /// ... + /// envp[0] pointer /// NULL (argv terminator) // 8 bytes of 0 /// argv[n-1] pointer /// ... @@ -3282,6 +3310,7 @@ impl ProcessManager { page_table: &crate::memory::process_memory::ProcessPageTable, stack_top: u64, argv: &[&[u8]], + envp: &[&[u8]], phdr_vaddr: u64, phnum: u16, phentsize: u16, @@ -3325,7 +3354,7 @@ impl ProcessManager { self.write_byte_to_stack(page_table, random_addr + i as u64, *byte)?; } - // Calculate total space needed for argv strings + // Calculate total space needed for argv and envp strings let mut total_string_space: usize = 0; for arg in argv.iter() { let len = arg.len(); @@ -3335,6 +3364,14 @@ impl ProcessManager { total_string_space += len + 1; } } + for env in envp.iter() { + let len = env.len(); + if len > 0 && env[len - 1] == 0 { + total_string_space += len; + } else { + total_string_space += len + 1; + } + } // Reserve space for strings below the random bytes cursor -= total_string_space as u64; @@ -3363,6 +3400,25 @@ impl ProcessManager { } } + // Write envp strings and collect their addresses + let mut envp_addrs: Vec = Vec::with_capacity(envp.len()); + + for env in envp.iter() { + envp_addrs.push(current_string_addr); + + for byte in env.iter() { + self.write_byte_to_stack(page_table, current_string_addr, *byte)?; + current_string_addr += 1; + } + + // Add null terminator if not present + let len = env.len(); + if len == 0 || env[len - 1] != 0 { + self.write_byte_to_stack(page_table, current_string_addr, 0)?; + current_string_addr += 1; + } + } + // --- Phase 2: Build the pointer/value section below the strings --- // Auxiliary vector entries (each is two u64 values: type, value) @@ -3370,8 +3426,8 @@ impl ProcessManager { let auxv_count = 7; let auxv_space = auxv_count * 2 * 8; // 7 entries * 2 u64s * 8 bytes - // envp: just a NULL terminator (empty environment) = 1 u64 - let envp_space = 8; + // envp: envp.len() pointers + NULL terminator + let envp_space = (envp.len() + 1) * 8; // argv: argc pointers + NULL terminator = (argc + 1) u64s let argv_space = (argc + 1) * 8; @@ -3405,11 +3461,17 @@ impl ProcessManager { self.write_u64_to_stack(page_table, write_pos, 0)?; write_pos += 8; - // 4. Write envp NULL terminator (empty environment) + // 4. Write envp pointers + for addr in envp_addrs.iter() { + self.write_u64_to_stack(page_table, write_pos, *addr)?; + write_pos += 8; + } + + // 5. Write envp NULL terminator self.write_u64_to_stack(page_table, write_pos, 0)?; write_pos += 8; - // 5. Write auxiliary vector entries + // 6. Write auxiliary vector entries // AT_ENTRY (9) - program entry point self.write_u64_to_stack(page_table, write_pos, 9)?; // AT_ENTRY write_pos += 8; diff --git a/kernel/src/syscall/dispatcher.rs b/kernel/src/syscall/dispatcher.rs index 854950d9..3936f0ec 100644 --- a/kernel/src/syscall/dispatcher.rs +++ b/kernel/src/syscall/dispatcher.rs @@ -173,5 +173,14 @@ pub fn dispatch_syscall( // Testing/diagnostic syscalls (Breenix-specific) SyscallNumber::CowStats => super::handlers::sys_cow_stats(arg1), SyscallNumber::SimulateOom => super::handlers::sys_simulate_oom(arg1), + // Resource limits and system info + SyscallNumber::Getrlimit => handlers::sys_getrlimit(arg1, arg2), + SyscallNumber::Prlimit64 => handlers::sys_prlimit64(arg1, arg2, arg3, arg4), + SyscallNumber::Uname => handlers::sys_uname(arg1), + // epoll + SyscallNumber::EpollCreate1 => super::epoll::sys_epoll_create1(arg1 as u32), + SyscallNumber::EpollCtl => super::epoll::sys_epoll_ctl(arg1 as i32, arg2 as i32, arg3 as i32, arg4), + SyscallNumber::EpollWait => super::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, 0, 0), + SyscallNumber::EpollPwait => super::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, arg5, arg6), } } diff --git a/kernel/src/syscall/epoll.rs b/kernel/src/syscall/epoll.rs new file mode 100644 index 00000000..e553cdbc --- /dev/null +++ b/kernel/src/syscall/epoll.rs @@ -0,0 +1,431 @@ +//! epoll implementation for Breenix +//! +//! Provides epoll_create1, epoll_ctl, and epoll_wait/epoll_pwait syscalls +//! for I/O event notification. +//! +//! This implementation reuses the existing poll infrastructure +//! (`ipc::poll::poll_fd`) for readiness checking, providing an epoll-compatible +//! API on top. + +use alloc::vec::Vec; +use core::sync::atomic::{AtomicU64, Ordering}; +use spin::Mutex; + +use super::errno; +use super::SyscallResult; +use crate::ipc::fd::{FdKind, FileDescriptor}; +use crate::ipc::poll; + +// ============================================================================= +// epoll constants (matching Linux ABI) +// ============================================================================= + +/// epoll_ctl operations +const EPOLL_CTL_ADD: i32 = 1; +const EPOLL_CTL_DEL: i32 = 2; +const EPOLL_CTL_MOD: i32 = 3; + +/// epoll event flags +const EPOLLIN: u32 = 0x001; +const EPOLLOUT: u32 = 0x004; +const EPOLLERR: u32 = 0x008; +const EPOLLHUP: u32 = 0x010; + +// ============================================================================= +// Data structures +// ============================================================================= + +/// epoll_event structure matching Linux ABI (packed on x86_64) +#[repr(C)] +#[cfg_attr(target_arch = "x86_64", repr(packed))] +#[derive(Clone, Copy)] +pub struct EpollEvent { + pub events: u32, + pub data: u64, +} + +/// A single entry in an epoll instance's interest list +#[derive(Clone)] +struct EpollEntry { + fd: i32, + events: u32, + data: u64, +} + +/// An epoll instance containing registered file descriptors +struct EpollInstance { + entries: Vec, +} + +impl EpollInstance { + fn new() -> Self { + EpollInstance { + entries: Vec::new(), + } + } + + fn add(&mut self, fd: i32, events: u32, data: u64) -> Result<(), i32> { + // Check for duplicate + if self.entries.iter().any(|e| e.fd == fd) { + return Err(errno::EEXIST); + } + self.entries.push(EpollEntry { fd, events, data }); + Ok(()) + } + + fn modify(&mut self, fd: i32, events: u32, data: u64) -> Result<(), i32> { + for entry in self.entries.iter_mut() { + if entry.fd == fd { + entry.events = events; + entry.data = data; + return Ok(()); + } + } + Err(errno::ENOENT) + } + + fn delete(&mut self, fd: i32) -> Result<(), i32> { + let len_before = self.entries.len(); + self.entries.retain(|e| e.fd != fd); + if self.entries.len() == len_before { + Err(errno::ENOENT) + } else { + Ok(()) + } + } +} + +// ============================================================================= +// Global epoll instance registry +// ============================================================================= + +/// Next unique epoll instance ID +static NEXT_EPOLL_ID: AtomicU64 = AtomicU64::new(1); + +/// Global registry of epoll instances, keyed by instance ID. +/// Protected by a spinlock. The Vec is small (typically <10 entries). +static EPOLL_INSTANCES: Mutex> = Mutex::new(Vec::new()); + +fn alloc_instance() -> u64 { + let id = NEXT_EPOLL_ID.fetch_add(1, Ordering::Relaxed); + let mut instances = EPOLL_INSTANCES.lock(); + instances.push((id, EpollInstance::new())); + id +} + +/// Remove an epoll instance by ID. Called from FdTable::Drop. +pub fn remove_instance(id: u64) { + let mut instances = EPOLL_INSTANCES.lock(); + instances.retain(|(iid, _)| *iid != id); +} + +fn with_instance(id: u64, f: F) -> Result +where + F: FnOnce(&mut EpollInstance) -> Result, +{ + let mut instances = EPOLL_INSTANCES.lock(); + for (iid, instance) in instances.iter_mut() { + if *iid == id { + return f(instance); + } + } + Err(errno::EBADF) +} + +// ============================================================================= +// Syscall implementations +// ============================================================================= + +/// epoll_create1(flags) -> fd +/// +/// Creates a new epoll instance. Returns a file descriptor referring to the +/// new epoll instance. The flags argument is currently ignored (EPOLL_CLOEXEC +/// would be the only valid flag). +pub fn sys_epoll_create1(_flags: u32) -> SyscallResult { + let epoll_id = alloc_instance(); + + // Get current thread to find its process and fd table + let thread_id = match crate::task::scheduler::current_thread_id() { + Some(id) => id, + None => return SyscallResult::Err(errno::ESRCH as u64), + }; + + let mut manager_guard = crate::process::manager(); + let manager = match manager_guard.as_mut() { + Some(m) => m, + None => { + remove_instance(epoll_id); + return SyscallResult::Err(errno::ENOMEM as u64); + } + }; + + let (_pid, process) = match manager.find_process_by_thread_mut(thread_id) { + Some(p) => p, + None => { + remove_instance(epoll_id); + return SyscallResult::Err(errno::ESRCH as u64); + } + }; + + match process.fd_table.alloc(FdKind::Epoll(epoll_id)) { + Ok(fd) => SyscallResult::Ok(fd as u64), + Err(e) => { + remove_instance(epoll_id); + SyscallResult::Err(e as u64) + } + } +} + +/// epoll_ctl(epfd, op, fd, event_ptr) -> 0 or -errno +/// +/// Control interface for an epoll instance. Adds, modifies, or removes +/// entries in the interest list of the epoll instance referred to by epfd. +pub fn sys_epoll_ctl(epfd: i32, op: i32, fd: i32, event_ptr: u64) -> SyscallResult { + // Look up the epoll instance ID from the fd + let thread_id = match crate::task::scheduler::current_thread_id() { + Some(id) => id, + None => return SyscallResult::Err(errno::ESRCH as u64), + }; + + let manager_guard = crate::process::manager(); + let manager = match manager_guard.as_ref() { + Some(m) => m, + None => return SyscallResult::Err(errno::ENOMEM as u64), + }; + + let (_pid, process) = match manager.find_process_by_thread(thread_id) { + Some(p) => p, + None => return SyscallResult::Err(errno::ESRCH as u64), + }; + + // Validate epfd is an epoll fd + let epoll_id = match process.fd_table.get(epfd) { + Some(entry) => match &entry.kind { + FdKind::Epoll(id) => *id, + _ => return SyscallResult::Err(errno::EINVAL as u64), + }, + None => return SyscallResult::Err(errno::EBADF as u64), + }; + + // Validate the target fd exists (except for DEL which doesn't need event) + if op != EPOLL_CTL_DEL { + if process.fd_table.get(fd).is_none() { + return SyscallResult::Err(errno::EBADF as u64); + } + } + + // Read the event structure from userspace (not needed for DEL) + let (events, data) = if op != EPOLL_CTL_DEL { + if event_ptr == 0 { + return SyscallResult::Err(errno::EFAULT as u64); + } + let event: EpollEvent = match super::userptr::copy_from_user(event_ptr as *const EpollEvent) { + Ok(e) => e, + Err(e) => return SyscallResult::Err(e), + }; + (event.events, event.data) + } else { + (0, 0) + }; + + // Drop manager guard before accessing EPOLL_INSTANCES to avoid lock ordering issues + drop(manager_guard); + + let result = match op { + EPOLL_CTL_ADD => with_instance(epoll_id, |inst| inst.add(fd, events, data)), + EPOLL_CTL_MOD => with_instance(epoll_id, |inst| inst.modify(fd, events, data)), + EPOLL_CTL_DEL => with_instance(epoll_id, |inst| inst.delete(fd)), + _ => Err(errno::EINVAL), + }; + + match result { + Ok(()) => SyscallResult::Ok(0), + Err(e) => SyscallResult::Err(e as u64), + } +} + +/// epoll_pwait(epfd, events_ptr, maxevents, timeout, sigmask_ptr, sigsetsize) -> count or -errno +/// +/// Wait for events on an epoll instance. Returns ready events in the +/// user-provided buffer. Reuses poll_fd for readiness checking. +pub fn sys_epoll_pwait( + epfd: i32, + events_ptr: u64, + maxevents: i32, + timeout: i32, + _sigmask_ptr: u64, + _sigsetsize: u64, +) -> SyscallResult { + if maxevents <= 0 { + return SyscallResult::Err(errno::EINVAL as u64); + } + if events_ptr == 0 { + return SyscallResult::Err(errno::EFAULT as u64); + } + + let maxevents = maxevents as usize; + + // Look up the epoll instance ID + let thread_id = match crate::task::scheduler::current_thread_id() { + Some(id) => id, + None => return SyscallResult::Err(errno::ESRCH as u64), + }; + + let epoll_id = { + let manager_guard = crate::process::manager(); + let manager = match manager_guard.as_ref() { + Some(m) => m, + None => return SyscallResult::Err(errno::ENOMEM as u64), + }; + let (_pid, process) = match manager.find_process_by_thread(thread_id) { + Some(p) => p, + None => return SyscallResult::Err(errno::ESRCH as u64), + }; + match process.fd_table.get(epfd) { + Some(entry) => match &entry.kind { + FdKind::Epoll(id) => *id, + _ => return SyscallResult::Err(errno::EINVAL as u64), + }, + None => return SyscallResult::Err(errno::EBADF as u64), + } + }; // manager guard dropped + + // Calculate wake-up deadline for timeouts + let deadline_ns = if timeout > 0 { + let (s, n) = crate::time::get_monotonic_time_ns(); + let now = (s as u64) * 1_000_000_000 + (n as u64); + Some(now + (timeout as u64) * 1_000_000) + } else if timeout == 0 { + Some(0) // non-blocking + } else { + None // infinite + }; + + loop { + // Snapshot the registered entries from the epoll instance + let entries: Vec = { + let instances = EPOLL_INSTANCES.lock(); + let mut found = None; + for (iid, instance) in instances.iter() { + if *iid == epoll_id { + found = Some(instance.entries.clone()); + break; + } + } + match found { + Some(e) => e, + None => return SyscallResult::Err(errno::EBADF as u64), + } + }; + + // Snapshot fd table entries for the registered fds + let fd_snapshots: Vec<(EpollEntry, Option)> = { + let manager_guard = crate::process::manager(); + let manager = match manager_guard.as_ref() { + Some(m) => m, + None => return SyscallResult::Err(errno::ENOMEM as u64), + }; + let (_pid, process) = match manager.find_process_by_thread(thread_id) { + Some(p) => p, + None => return SyscallResult::Err(errno::ESRCH as u64), + }; + + entries + .into_iter() + .map(|entry| { + let fd_entry = process.fd_table.get(entry.fd).cloned(); + (entry, fd_entry) + }) + .collect() + }; // manager guard dropped + + // Check readiness for each registered fd + let mut ready_events: Vec = Vec::new(); + for (entry, fd_entry) in fd_snapshots.iter() { + if let Some(ref fd) = fd_entry { + // Convert epoll event mask to poll events + let poll_events = epoll_to_poll_events(entry.events); + let revents = poll::poll_fd(fd, poll_events); + let epoll_revents = poll_to_epoll_events(revents); + + if epoll_revents != 0 { + ready_events.push(EpollEvent { + events: epoll_revents, + data: entry.data, + }); + if ready_events.len() >= maxevents { + break; + } + } + } + } + + // If events are ready, copy to userspace and return + if !ready_events.is_empty() { + let count = ready_events.len(); + unsafe { + let dst = events_ptr as *mut EpollEvent; + for (i, event) in ready_events.iter().enumerate() { + core::ptr::write(dst.add(i), *event); + } + } + return SyscallResult::Ok(count as u64); + } + + // No events ready - check timeout + if let Some(deadline) = deadline_ns { + if deadline == 0 { + // Non-blocking (timeout=0) + return SyscallResult::Ok(0); + } + let (s, n) = crate::time::get_monotonic_time_ns(); + let now = (s as u64) * 1_000_000_000 + (n as u64); + if now >= deadline { + return SyscallResult::Ok(0); + } + } + + // Check for pending signals that should interrupt the wait + if let Some(_eintr) = super::check_signals_for_eintr() { + return SyscallResult::Err(errno::EINTR as u64); + } + + // Yield and retry + crate::task::scheduler::yield_current(); + } +} + +// ============================================================================= +// Event mask conversion helpers +// ============================================================================= + +/// Convert epoll event mask to poll events (i16) +fn epoll_to_poll_events(epoll_events: u32) -> i16 { + let mut poll_events: i16 = 0; + if epoll_events & EPOLLIN != 0 { + poll_events |= poll::events::POLLIN; + } + if epoll_events & EPOLLOUT != 0 { + poll_events |= poll::events::POLLOUT; + } + // EPOLLERR and EPOLLHUP are always reported (output-only in poll) + poll_events +} + +/// Convert poll revents (i16) to epoll event mask +fn poll_to_epoll_events(revents: i16) -> u32 { + let mut epoll_events: u32 = 0; + if revents & poll::events::POLLIN != 0 { + epoll_events |= EPOLLIN; + } + if revents & poll::events::POLLOUT != 0 { + epoll_events |= EPOLLOUT; + } + if revents & poll::events::POLLERR != 0 { + epoll_events |= EPOLLERR; + } + if revents & poll::events::POLLHUP != 0 { + epoll_events |= EPOLLHUP; + } + epoll_events +} diff --git a/kernel/src/syscall/fs.rs b/kernel/src/syscall/fs.rs index 4f71dbab..3f796cfb 100644 --- a/kernel/src/syscall/fs.rs +++ b/kernel/src/syscall/fs.rs @@ -804,6 +804,13 @@ pub fn sys_fstat(fd: i32, statbuf: u64) -> SyscallResult { stat.st_nlink = 2; // . and .. stat.st_size = 0; } + FdKind::Epoll(_) => { + // epoll fds report as anonymous inodes + stat.st_dev = 0; + stat.st_ino = 0; + stat.st_mode = S_IFREG | 0o600; + stat.st_nlink = 1; + } } // Copy stat structure to userspace diff --git a/kernel/src/syscall/handler.rs b/kernel/src/syscall/handler.rs index 1214ae3d..e6e9af6c 100644 --- a/kernel/src/syscall/handler.rs +++ b/kernel/src/syscall/handler.rs @@ -447,6 +447,15 @@ pub extern "C" fn rust_syscall_handler(frame: &mut SyscallFrame) { // Display takeover Some(SyscallNumber::TakeOverDisplay) => super::handlers::sys_take_over_display(), Some(SyscallNumber::GiveBackDisplay) => super::handlers::sys_give_back_display(), + // Resource limits and system info + Some(SyscallNumber::Getrlimit) => super::handlers::sys_getrlimit(args.0, args.1), + Some(SyscallNumber::Prlimit64) => super::handlers::sys_prlimit64(args.0, args.1, args.2, args.3), + Some(SyscallNumber::Uname) => super::handlers::sys_uname(args.0), + // epoll + Some(SyscallNumber::EpollCreate1) => super::epoll::sys_epoll_create1(args.0 as u32), + Some(SyscallNumber::EpollCtl) => super::epoll::sys_epoll_ctl(args.0 as i32, args.1 as i32, args.2 as i32, args.3), + Some(SyscallNumber::EpollWait) => super::epoll::sys_epoll_pwait(args.0 as i32, args.1, args.2 as i32, args.3 as i32, 0, 0), + Some(SyscallNumber::EpollPwait) => super::epoll::sys_epoll_pwait(args.0 as i32, args.1, args.2 as i32, args.3 as i32, args.4, args.5), None => { log::warn!("Unknown syscall number: {} - returning ENOSYS", syscall_num); SyscallResult::Err(super::ErrorCode::NoSys as u64) diff --git a/kernel/src/syscall/handlers.rs b/kernel/src/syscall/handlers.rs index 349e9f3e..04ab507c 100644 --- a/kernel/src/syscall/handlers.rs +++ b/kernel/src/syscall/handlers.rs @@ -325,6 +325,7 @@ pub fn sys_write(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { FdKind::PtySlave(pty_num) => WriteOperation::PtySlave(*pty_num), FdKind::ProcfsFile { .. } => WriteOperation::Ebadf, FdKind::ProcfsDirectory { .. } => WriteOperation::Eisdir, + FdKind::Epoll(_) => WriteOperation::Ebadf, } // manager_guard dropped here, releasing the lock before I/O }; @@ -1590,6 +1591,10 @@ pub fn sys_read(fd: u64, buf_ptr: u64, count: u64) -> SyscallResult { log::debug!("sys_read: Cannot read from /proc directory, use getdents instead"); SyscallResult::Err(super::errno::EISDIR as u64) } + FdKind::Epoll(_) => { + // Cannot read from epoll fd directly + SyscallResult::Err(super::errno::EINVAL as u64) + } } } @@ -3743,3 +3748,95 @@ pub fn sys_simulate_oom(enable: u64) -> SyscallResult { SyscallResult::Err(38) // ENOSYS - function not implemented } } + +// ============================================================================= +// Resource Limits and System Information +// ============================================================================= + +/// Linux rlimit structure +#[repr(C)] +#[derive(Copy, Clone)] +struct Rlimit { + rlim_cur: u64, + rlim_max: u64, +} + +const RLIMIT_STACK: u64 = 3; +const RLIMIT_NOFILE: u64 = 7; +const RLIMIT_CORE: u64 = 4; +const RLIM_INFINITY: u64 = u64::MAX; + +/// getrlimit - Get resource limits +pub fn sys_getrlimit(resource: u64, rlim_ptr: u64) -> SyscallResult { + if rlim_ptr == 0 { + return SyscallResult::Err(super::errno::EFAULT as u64); + } + + let rlim = match resource { + RLIMIT_STACK => Rlimit { rlim_cur: 8 * 1024 * 1024, rlim_max: RLIM_INFINITY }, + RLIMIT_NOFILE => Rlimit { rlim_cur: 1024, rlim_max: 4096 }, + RLIMIT_CORE => Rlimit { rlim_cur: 0, rlim_max: RLIM_INFINITY }, + _ => Rlimit { rlim_cur: RLIM_INFINITY, rlim_max: RLIM_INFINITY }, + }; + + if super::userptr::copy_to_user(rlim_ptr as *mut Rlimit, &rlim).is_err() { + return SyscallResult::Err(super::errno::EFAULT as u64); + } + SyscallResult::Ok(0) +} + +/// prlimit64 - Get/set resource limits +pub fn sys_prlimit64(_pid: u64, resource: u64, _new_rlim_ptr: u64, old_rlim_ptr: u64) -> SyscallResult { + if old_rlim_ptr != 0 { + return sys_getrlimit(resource, old_rlim_ptr); + } + SyscallResult::Ok(0) +} + +/// Linux utsname structure +#[repr(C)] +#[derive(Clone, Copy)] +struct Utsname { + sysname: [u8; 65], + nodename: [u8; 65], + release: [u8; 65], + version: [u8; 65], + machine: [u8; 65], + domainname: [u8; 65], +} + +fn copy_utsname_field(field: &mut [u8; 65], value: &[u8]) { + let len = core::cmp::min(value.len(), 64); + field[..len].copy_from_slice(&value[..len]); +} + +/// uname - Get system identification +pub fn sys_uname(buf_ptr: u64) -> SyscallResult { + if buf_ptr == 0 { + return SyscallResult::Err(super::errno::EFAULT as u64); + } + + let mut utsname = Utsname { + sysname: [0u8; 65], + nodename: [0u8; 65], + release: [0u8; 65], + version: [0u8; 65], + machine: [0u8; 65], + domainname: [0u8; 65], + }; + + copy_utsname_field(&mut utsname.sysname, b"Breenix"); + copy_utsname_field(&mut utsname.nodename, b"breenix"); + copy_utsname_field(&mut utsname.release, b"0.1.0"); + copy_utsname_field(&mut utsname.version, b"Breenix 0.1"); + #[cfg(target_arch = "x86_64")] + copy_utsname_field(&mut utsname.machine, b"x86_64"); + #[cfg(target_arch = "aarch64")] + copy_utsname_field(&mut utsname.machine, b"aarch64"); + copy_utsname_field(&mut utsname.domainname, b"(none)"); + + if super::userptr::copy_to_user(buf_ptr as *mut Utsname, &utsname).is_err() { + return SyscallResult::Err(super::errno::EFAULT as u64); + } + SyscallResult::Ok(0) +} diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index 28e56dc2..76b0ba7f 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -30,6 +30,7 @@ pub mod handler; pub(crate) mod dispatcher; pub mod iovec; pub mod clone; +pub mod epoll; pub mod fifo; pub mod fs; pub mod futex; @@ -169,6 +170,15 @@ pub enum SyscallNumber { // Testing (Breenix-specific) CowStats, SimulateOom, + // Resource limits and system info + Getrlimit, + Prlimit64, + Uname, + // epoll + EpollCreate1, + EpollCtl, + EpollWait, + EpollPwait, } #[allow(dead_code)] @@ -231,6 +241,7 @@ impl SyscallNumber { 60 => Some(Self::Exit), // was Breenix 0 61 => Some(Self::Wait4), 62 => Some(Self::Kill), + 63 => Some(Self::Uname), 72 => Some(Self::Fcntl), 79 => Some(Self::Getcwd), 80 => Some(Self::Chdir), @@ -241,6 +252,7 @@ impl SyscallNumber { 87 => Some(Self::Unlink), 88 => Some(Self::Symlink), 89 => Some(Self::Readlink), + 97 => Some(Self::Getrlimit), 109 => Some(Self::SetPgid), 110 => Some(Self::Getppid), 112 => Some(Self::SetSid), @@ -272,6 +284,11 @@ impl SyscallNumber { 273 => Some(Self::SetRobustList), // NEW stub 292 => Some(Self::Dup3), 293 => Some(Self::Pipe2), + 232 => Some(Self::EpollWait), + 233 => Some(Self::EpollCtl), + 281 => Some(Self::EpollPwait), + 291 => Some(Self::EpollCreate1), + 302 => Some(Self::Prlimit64), 318 => Some(Self::GetRandom), // PTY syscalls (Breenix-specific, same on both archs) 400 => Some(Self::PosixOpenpt), @@ -299,6 +316,10 @@ impl SyscallNumber { pub fn from_u64(value: u64) -> Option { match value { // Linux ARM64 generic syscall numbers (from asm-generic/unistd.h) + // epoll + 20 => Some(Self::EpollCreate1), + 21 => Some(Self::EpollCtl), + 22 => Some(Self::EpollPwait), // I/O 17 => Some(Self::Getcwd), 23 => Some(Self::Dup), @@ -355,6 +376,7 @@ impl SyscallNumber { 155 => Some(Self::GetPgid), 156 => Some(Self::GetSid), 157 => Some(Self::SetSid), + 160 => Some(Self::Uname), // Process info 172 => Some(Self::GetPid), 173 => Some(Self::Getppid), @@ -384,6 +406,7 @@ impl SyscallNumber { 233 => Some(Self::Madvise), // Wait 260 => Some(Self::Wait4), + 261 => Some(Self::Prlimit64), // Random 278 => Some(Self::GetRandom), // PTY syscalls (Breenix-specific, same on both archs) diff --git a/kernel/src/syscall/pipe.rs b/kernel/src/syscall/pipe.rs index 34c74887..85a21ced 100644 --- a/kernel/src/syscall/pipe.rs +++ b/kernel/src/syscall/pipe.rs @@ -244,6 +244,10 @@ pub fn sys_close(fd: i32) -> SyscallResult { FdKind::ProcfsDirectory { .. } => { log::debug!("sys_close: Closed procfs directory fd={}", fd); } + FdKind::Epoll(id) => { + crate::syscall::epoll::remove_instance(id); + log::debug!("sys_close: Closed epoll fd={}", fd); + } } log::debug!("sys_close: returning to userspace fd={}", fd); SyscallResult::Ok(0) diff --git a/libs/libbreenix-libc/src/lib.rs b/libs/libbreenix-libc/src/lib.rs index caa3705e..5722d1ae 100644 --- a/libs/libbreenix-libc/src/lib.rs +++ b/libs/libbreenix-libc/src/lib.rs @@ -1168,11 +1168,17 @@ pub unsafe extern "C" fn _start() -> ! { /// Rust entry point called from _start with the stack pointer. /// -/// Extracts argc and argv from the stack and calls main(). +/// Extracts argc, argv, and envp from the stack and calls main(). +/// Stack layout: [argc] [argv[0]..argv[argc-1]] [NULL] [envp[0]..envp[n]] [NULL] [auxv...] extern "C" fn _start_rust(sp: *const u64) -> ! { unsafe { let argc = *sp as isize; let argv = sp.add(1) as *const *const u8; + + // envp starts after argv NULL terminator: sp + 1 (argc) + argc + 1 (NULL) + let envp = sp.add(1 + argc as usize + 1) as *const *const u8; + environ = envp as usize; + extern "C" { fn main(argc: isize, argv: *const *const u8) -> isize; } @@ -1362,9 +1368,55 @@ pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { 0 } -/// getenv - get environment variable (stub - always returns NULL) +/// getenv - get environment variable +/// +/// Searches the environment for a variable matching `name` and returns +/// a pointer to the value (the part after '='). Returns NULL if not found. #[no_mangle] -pub extern "C" fn getenv(_name: *const u8) -> *mut u8 { +pub unsafe extern "C" fn getenv(name: *const u8) -> *mut u8 { + if name.is_null() { + return core::ptr::null_mut(); + } + + let env_ptr = environ as *const *const u8; + if env_ptr.is_null() { + return core::ptr::null_mut(); + } + + // Get name length (walk until null byte) + let mut name_len: usize = 0; + while *name.add(name_len) != 0 { + name_len += 1; + } + if name_len == 0 { + return core::ptr::null_mut(); + } + + // Walk environ array entries + let mut i: usize = 0; + loop { + let entry = *env_ptr.add(i); + if entry.is_null() { + break; + } + + // Check if entry starts with name followed by '=' + let mut matches = true; + for j in 0..name_len { + if *entry.add(j) != *name.add(j) { + matches = false; + break; + } + } + + if matches && *entry.add(name_len) == b'=' { + // Return pointer to the character after '=' + return entry.add(name_len + 1) as *mut u8; + } + + i += 1; + } + core::ptr::null_mut() } @@ -1471,6 +1523,136 @@ pub extern "C" fn getauxval(type_: u64) -> u64 { } } +// ============================================================================= +// Resource Limits +// ============================================================================= + +/// Linux rlimit structure +#[repr(C)] +pub struct rlimit { + pub rlim_cur: u64, + pub rlim_max: u64, +} + +/// Linux utsname structure +#[repr(C)] +pub struct utsname { + pub sysname: [u8; 65], + pub nodename: [u8; 65], + pub release: [u8; 65], + pub version: [u8; 65], + pub machine: [u8; 65], + pub domainname: [u8; 65], +} + +/// getrlimit - get resource limits +#[no_mangle] +pub unsafe extern "C" fn getrlimit(resource: i32, rlim: *mut rlimit) -> i32 { + #[cfg(target_arch = "x86_64")] + let ret = libbreenix::syscall::raw::syscall2( + libbreenix::syscall::nr::GETRLIMIT, + resource as u64, + rlim as u64, + ) as i64; + #[cfg(target_arch = "aarch64")] + let ret = libbreenix::syscall::raw::syscall4( + libbreenix::syscall::nr::PRLIMIT64, + 0u64, // pid=0 means current process + resource as u64, + 0u64, // new_rlim = NULL + rlim as u64, + ) as i64; + if ret < 0 { set_errno_from_result(ret); -1 } else { 0 } +} + +/// uname - get system identification +#[no_mangle] +pub unsafe extern "C" fn uname(buf: *mut utsname) -> i32 { + let ret = libbreenix::syscall::raw::syscall1( + libbreenix::syscall::nr::UNAME, + buf as u64, + ) as i64; + if ret < 0 { set_errno_from_result(ret); -1 } else { 0 } +} + +// ============================================================================= +// epoll +// ============================================================================= + +/// epoll_event structure (matches Linux ABI) +#[repr(C)] +#[cfg_attr(target_arch = "x86_64", repr(packed))] +pub struct epoll_event { + pub events: u32, + pub data: u64, +} + +/// epoll_create - create an epoll instance (legacy, calls epoll_create1) +#[no_mangle] +pub unsafe extern "C" fn epoll_create(_size: i32) -> i32 { + epoll_create1(0) +} + +/// epoll_create1 - create an epoll instance +#[no_mangle] +pub unsafe extern "C" fn epoll_create1(flags: i32) -> i32 { + let ret = libbreenix::syscall::raw::syscall1( + libbreenix::syscall::nr::EPOLL_CREATE1, + flags as u64, + ) as i64; + if ret < 0 { set_errno_from_result(ret); -1 } else { ret as i32 } +} + +/// epoll_ctl - control interface for an epoll instance +#[no_mangle] +pub unsafe extern "C" fn epoll_ctl( + epfd: i32, + op: i32, + fd: i32, + event: *mut epoll_event, +) -> i32 { + let ret = libbreenix::syscall::raw::syscall4( + libbreenix::syscall::nr::EPOLL_CTL, + epfd as u64, + op as u64, + fd as u64, + event as u64, + ) as i64; + if ret < 0 { set_errno_from_result(ret); -1 } else { 0 } +} + +/// epoll_wait - wait for events on an epoll instance +#[no_mangle] +pub unsafe extern "C" fn epoll_wait( + epfd: i32, + events: *mut epoll_event, + maxevents: i32, + timeout: i32, +) -> i32 { + epoll_pwait(epfd, events, maxevents, timeout, core::ptr::null()) +} + +/// epoll_pwait - wait for events on an epoll instance with signal mask +#[no_mangle] +pub unsafe extern "C" fn epoll_pwait( + epfd: i32, + events: *mut epoll_event, + maxevents: i32, + timeout: i32, + sigmask: *const u8, +) -> i32 { + let ret = libbreenix::syscall::raw::syscall6( + libbreenix::syscall::nr::EPOLL_PWAIT, + epfd as u64, + events as u64, + maxevents as u64, + timeout as u64, + sigmask as u64, + 8u64, // sigsetsize + ) as i64; + if ret < 0 { set_errno_from_result(ret); -1 } else { ret as i32 } +} + // ============================================================================= // Signal Handling // ============================================================================= @@ -2069,6 +2251,7 @@ const CLONE_CHILD_SETTID: u64 = 0x01000000; /// Futex operation codes const FUTEX_WAIT: u32 = 0; +const FUTEX_WAKE: u32 = 1; /// Thread start info passed through the heap to the child thread #[repr(C)] @@ -2310,11 +2493,53 @@ pub extern "C" fn pthread_setname_np(_thread: usize, _name: *const u8) -> i32 { } // ============================================================================= -// Pthread Mutex Functions (no-op stubs for single-threaded) +// Pthread Mutex Functions (futex-based) // ============================================================================= +// +// Mutex state word (u32 at the start of pthread_mutex_t): +// 0 = unlocked +// 1 = locked, no waiters +// 2 = locked, one or more waiters +// +// This is a standard futex-based mutex following the Drepper "Futexes Are Tricky" pattern. + +/// Helper: perform a futex_wait syscall on `addr`. Blocks if *addr == expected. +#[inline] +unsafe fn futex_wait(addr: *const u32, expected: u32) { + libbreenix::syscall::raw::syscall6( + libbreenix::syscall::nr::FUTEX, + addr as u64, + FUTEX_WAIT as u64, + expected as u64, + 0, // no timeout + 0, + 0, + ); +} + +/// Helper: perform a futex_wake syscall on `addr`. Wakes up to `count` waiters. +#[inline] +unsafe fn futex_wake(addr: *const u32, count: u32) { + libbreenix::syscall::raw::syscall6( + libbreenix::syscall::nr::FUTEX, + addr as u64, + FUTEX_WAKE as u64, + count as u64, + 0, + 0, + 0, + ); +} #[no_mangle] -pub extern "C" fn pthread_mutex_init(_mutex: *mut u8, _attr: *const u8) -> i32 { +pub extern "C" fn pthread_mutex_init(mutex: *mut u8, _attr: *const u8) -> i32 { + if mutex.is_null() { + return EINVAL; + } + // Zero-initialize the mutex state word + unsafe { + core::ptr::write_volatile(mutex as *mut u32, 0); + } 0 } @@ -2324,20 +2549,80 @@ pub extern "C" fn pthread_mutex_destroy(_mutex: *mut u8) -> i32 { } #[no_mangle] -pub extern "C" fn pthread_mutex_lock(_mutex: *mut u8) -> i32 { +pub extern "C" fn pthread_mutex_lock(mutex: *mut u8) -> i32 { + if mutex.is_null() { + return EINVAL; + } + let state = mutex as *mut u32; + unsafe { + // Fast path: try CAS 0 -> 1 (unlocked -> locked, no waiters) + let old = atomic_cmpxchg_u32(state, 0, 1); + if old == 0 { + return 0; // Acquired + } + + // Slow path: set state to 2 (locked with waiters) and wait + // If old was 1, swap to 2 so unlock knows to wake + let mut c = old; + if c != 2 { + c = atomic_xchg_u32(state, 2); + } + while c != 0 { + futex_wait(state, 2); + c = atomic_xchg_u32(state, 2); + } + } 0 } #[no_mangle] -pub extern "C" fn pthread_mutex_trylock(_mutex: *mut u8) -> i32 { - 0 +pub extern "C" fn pthread_mutex_trylock(mutex: *mut u8) -> i32 { + if mutex.is_null() { + return EINVAL; + } + let state = mutex as *mut u32; + unsafe { + let old = atomic_cmpxchg_u32(state, 0, 1); + if old == 0 { 0 } else { EBUSY } + } } #[no_mangle] -pub extern "C" fn pthread_mutex_unlock(_mutex: *mut u8) -> i32 { +pub extern "C" fn pthread_mutex_unlock(mutex: *mut u8) -> i32 { + if mutex.is_null() { + return EINVAL; + } + let state = mutex as *mut u32; + unsafe { + // Atomically decrement. If old state was 2 (waiters), we need to wake. + let old = atomic_xchg_u32(state, 0); + if old == 2 { + // There were waiters - wake one + futex_wake(state, 1); + } + } 0 } +/// Atomic compare-and-exchange for u32. Returns the previous value. +#[inline] +unsafe fn atomic_cmpxchg_u32(ptr: *mut u32, expected: u32, desired: u32) -> u32 { + use core::sync::atomic::{AtomicU32, Ordering}; + let atomic = &*(ptr as *const AtomicU32); + match atomic.compare_exchange(expected, desired, Ordering::Acquire, Ordering::Relaxed) { + Ok(v) => v, + Err(v) => v, + } +} + +/// Atomic exchange for u32. Returns the previous value. +#[inline] +unsafe fn atomic_xchg_u32(ptr: *mut u32, val: u32) -> u32 { + use core::sync::atomic::{AtomicU32, Ordering}; + let atomic = &*(ptr as *const AtomicU32); + atomic.swap(val, Ordering::AcqRel) +} + #[no_mangle] pub extern "C" fn pthread_mutexattr_init(_attr: *mut u8) -> i32 { 0 @@ -2354,11 +2639,23 @@ pub extern "C" fn pthread_mutexattr_settype(_attr: *mut u8, _kind: i32) -> i32 { } // ============================================================================= -// Pthread Condition Variable Functions (no-op stubs) +// Pthread Condition Variable Functions (futex-based) // ============================================================================= +// +// Uses a sequence counter pattern: +// - The first u32 of pthread_cond_t is a sequence number. +// - signal/broadcast increments the sequence and wakes waiters. +// - wait reads the sequence, releases the mutex, then futex_waits on the +// sequence value. On wakeup it re-acquires the mutex. #[no_mangle] -pub extern "C" fn pthread_cond_init(_cond: *mut u8, _attr: *const u8) -> i32 { +pub extern "C" fn pthread_cond_init(cond: *mut u8, _attr: *const u8) -> i32 { + if cond.is_null() { + return EINVAL; + } + unsafe { + core::ptr::write_volatile(cond as *mut u32, 0); + } 0 } @@ -2368,27 +2665,68 @@ pub extern "C" fn pthread_cond_destroy(_cond: *mut u8) -> i32 { } #[no_mangle] -pub extern "C" fn pthread_cond_signal(_cond: *mut u8) -> i32 { +pub extern "C" fn pthread_cond_signal(cond: *mut u8) -> i32 { + if cond.is_null() { + return EINVAL; + } + let seq = cond as *mut u32; + unsafe { + // Increment sequence counter + let atomic = &*(seq as *const core::sync::atomic::AtomicU32); + atomic.fetch_add(1, core::sync::atomic::Ordering::Release); + // Wake one waiter + futex_wake(seq, 1); + } 0 } #[no_mangle] -pub extern "C" fn pthread_cond_broadcast(_cond: *mut u8) -> i32 { +pub extern "C" fn pthread_cond_broadcast(cond: *mut u8) -> i32 { + if cond.is_null() { + return EINVAL; + } + let seq = cond as *mut u32; + unsafe { + let atomic = &*(seq as *const core::sync::atomic::AtomicU32); + atomic.fetch_add(1, core::sync::atomic::Ordering::Release); + // Wake all waiters + futex_wake(seq, u32::MAX); + } 0 } #[no_mangle] -pub extern "C" fn pthread_cond_wait(_cond: *mut u8, _mutex: *mut u8) -> i32 { +pub extern "C" fn pthread_cond_wait(cond: *mut u8, mutex: *mut u8) -> i32 { + if cond.is_null() || mutex.is_null() { + return EINVAL; + } + let seq = cond as *mut u32; + unsafe { + // Read current sequence before releasing mutex + let atomic = &*(seq as *const core::sync::atomic::AtomicU32); + let current_seq = atomic.load(core::sync::atomic::Ordering::Acquire); + + // Release the mutex + pthread_mutex_unlock(mutex); + + // Block until sequence changes (signal/broadcast increments it) + futex_wait(seq, current_seq); + + // Re-acquire the mutex before returning + pthread_mutex_lock(mutex); + } 0 } #[no_mangle] pub unsafe extern "C" fn pthread_cond_timedwait( - _cond: *mut u8, - _mutex: *mut u8, + cond: *mut u8, + mutex: *mut u8, _abstime: *const u8, ) -> i32 { - 0 + // For now, delegate to regular wait (no timeout support yet). + // Real timedwait would pass a timeout to futex_wait. + pthread_cond_wait(cond, mutex) } #[no_mangle] diff --git a/libs/libbreenix/src/syscall.rs b/libs/libbreenix/src/syscall.rs index 981521d4..f58ab2be 100644 --- a/libs/libbreenix/src/syscall.rs +++ b/libs/libbreenix/src/syscall.rs @@ -129,6 +129,15 @@ pub mod nr { // Display takeover (Breenix-specific) pub const TAKE_OVER_DISPLAY: u64 = 431; pub const GIVE_BACK_DISPLAY: u64 = 432; + // Resource limits and system info + pub const GETRLIMIT: u64 = 97; + pub const PRLIMIT64: u64 = 302; + pub const UNAME: u64 = 63; + // epoll + pub const EPOLL_WAIT: u64 = 232; + pub const EPOLL_CTL: u64 = 233; + pub const EPOLL_PWAIT: u64 = 281; + pub const EPOLL_CREATE1: u64 = 291; // Testing syscalls (Breenix-specific) pub const COW_STATS: u64 = 500; pub const SIMULATE_OOM: u64 = 501; @@ -263,6 +272,13 @@ pub mod nr { // Display takeover (Breenix-specific) pub const TAKE_OVER_DISPLAY: u64 = 431; pub const GIVE_BACK_DISPLAY: u64 = 432; + // Resource limits and system info (ARM64 has no getrlimit; use prlimit64) + pub const PRLIMIT64: u64 = 261; + pub const UNAME: u64 = 160; + // epoll + pub const EPOLL_CREATE1: u64 = 20; + pub const EPOLL_CTL: u64 = 21; + pub const EPOLL_PWAIT: u64 = 22; // Testing syscalls (Breenix-specific) pub const COW_STATS: u64 = 500; pub const SIMULATE_OOM: u64 = 501; diff --git a/userspace/c-programs/Makefile b/userspace/c-programs/Makefile index 491a2a25..8ac7787e 100644 --- a/userspace/c-programs/Makefile +++ b/userspace/c-programs/Makefile @@ -45,7 +45,7 @@ LDFLAGS := -T $(LINKER_SCRIPT) --static \ AARCH64_DIR := $(BREENIX_ROOT)/userspace/programs/aarch64 # Programs -PROGRAMS := hello_musl +PROGRAMS := hello_musl env_musl_test uname_musl_test rlimit_musl_test .PHONY: all install clean @@ -57,8 +57,17 @@ all: $(addsuffix .elf,$(PROGRAMS)) hello_musl.elf: hello.o $(LLD) $(LDFLAGS) $< -o $@ +env_musl_test.elf: env_test.o + $(LLD) $(LDFLAGS) $< -o $@ + +uname_musl_test.elf: uname_test.o + $(LLD) $(LDFLAGS) $< -o $@ + +rlimit_musl_test.elf: rlimit_test.o + $(LLD) $(LDFLAGS) $< -o $@ + install: all - cp hello_musl.elf $(AARCH64_DIR)/hello_musl.elf + for prog in $(PROGRAMS); do cp $$prog.elf $(AARCH64_DIR)/$$prog.elf; done clean: rm -f *.o *.elf diff --git a/userspace/c-programs/env_test.c b/userspace/c-programs/env_test.c new file mode 100644 index 00000000..3746e070 --- /dev/null +++ b/userspace/c-programs/env_test.c @@ -0,0 +1,60 @@ +#include +#include +#include + +extern char **environ; + +int main(int argc, char *argv[]) { + int pass = 0; + int fail = 0; + const char *val; + + /* Test 1: getenv("PATH") returns non-null and contains "/bin" */ + val = getenv("PATH"); + if (val != NULL && strstr(val, "/bin") != NULL) { + printf("PASS: getenv(\"PATH\") = \"%s\"\n", val); + pass++; + } else { + printf("FAIL: getenv(\"PATH\") = %s\n", val ? val : "(null)"); + fail++; + } + + /* Test 2: getenv("HOME") returns "/home" */ + val = getenv("HOME"); + if (val != NULL && strcmp(val, "/home") == 0) { + printf("PASS: getenv(\"HOME\") = \"%s\"\n", val); + pass++; + } else { + printf("FAIL: getenv(\"HOME\") = %s\n", val ? val : "(null)"); + fail++; + } + + /* Test 3: getenv("TERM") returns non-null */ + val = getenv("TERM"); + if (val != NULL) { + printf("PASS: getenv(\"TERM\") = \"%s\"\n", val); + pass++; + } else { + printf("FAIL: getenv(\"TERM\") = (null)\n"); + fail++; + } + + /* Test 4: walk environ[] and count variables */ + int count = 0; + if (environ != NULL) { + for (int i = 0; environ[i] != NULL; i++) { + printf(" environ[%d] = \"%s\"\n", i, environ[i]); + count++; + } + } + if (count >= 3) { + printf("PASS: environ has %d variables\n", count); + pass++; + } else { + printf("FAIL: environ has only %d variables (expected >= 3)\n", count); + fail++; + } + + printf("\nenv_test: %d passed, %d failed\n", pass, fail); + return fail > 0 ? 1 : 0; +} diff --git a/userspace/c-programs/rlimit_test.c b/userspace/c-programs/rlimit_test.c new file mode 100644 index 00000000..f97506d4 --- /dev/null +++ b/userspace/c-programs/rlimit_test.c @@ -0,0 +1,50 @@ +#include +#include + +int main(int argc, char *argv[]) { + int pass = 0; + int fail = 0; + struct rlimit rlim; + int ret; + + /* Test 1: getrlimit(RLIMIT_STACK) - expect 8MB (8388608) */ + ret = getrlimit(RLIMIT_STACK, &rlim); + if (ret == 0) { + printf(" RLIMIT_STACK: cur=%lu, max=%lu\n", + (unsigned long)rlim.rlim_cur, + (unsigned long)rlim.rlim_max); + if (rlim.rlim_cur == 8388608) { + printf("PASS: RLIMIT_STACK cur = 8388608 (8MB)\n"); + pass++; + } else { + printf("FAIL: RLIMIT_STACK cur = %lu (expected 8388608)\n", + (unsigned long)rlim.rlim_cur); + fail++; + } + } else { + printf("FAIL: getrlimit(RLIMIT_STACK) returned %d\n", ret); + fail++; + } + + /* Test 2: getrlimit(RLIMIT_NOFILE) - expect 1024 */ + ret = getrlimit(RLIMIT_NOFILE, &rlim); + if (ret == 0) { + printf(" RLIMIT_NOFILE: cur=%lu, max=%lu\n", + (unsigned long)rlim.rlim_cur, + (unsigned long)rlim.rlim_max); + if (rlim.rlim_cur == 1024) { + printf("PASS: RLIMIT_NOFILE cur = 1024\n"); + pass++; + } else { + printf("FAIL: RLIMIT_NOFILE cur = %lu (expected 1024)\n", + (unsigned long)rlim.rlim_cur); + fail++; + } + } else { + printf("FAIL: getrlimit(RLIMIT_NOFILE) returned %d\n", ret); + fail++; + } + + printf("\nrlimit_test: %d passed, %d failed\n", pass, fail); + return fail > 0 ? 1 : 0; +} diff --git a/userspace/c-programs/uname_test.c b/userspace/c-programs/uname_test.c new file mode 100644 index 00000000..f43bc109 --- /dev/null +++ b/userspace/c-programs/uname_test.c @@ -0,0 +1,50 @@ +#include +#include +#include + +int main(int argc, char *argv[]) { + int pass = 0; + int fail = 0; + struct utsname buf; + + /* Test 1: uname() returns 0 */ + int ret = uname(&buf); + if (ret == 0) { + printf("PASS: uname() returned 0\n"); + pass++; + } else { + printf("FAIL: uname() returned %d\n", ret); + fail++; + /* Can't check fields if uname failed */ + printf("\nuname_test: %d passed, %d failed\n", pass, fail); + return 1; + } + + /* Print all fields */ + printf(" sysname: %s\n", buf.sysname); + printf(" nodename: %s\n", buf.nodename); + printf(" release: %s\n", buf.release); + printf(" version: %s\n", buf.version); + printf(" machine: %s\n", buf.machine); + + /* Test 2: sysname == "Breenix" */ + if (strcmp(buf.sysname, "Breenix") == 0) { + printf("PASS: sysname = \"%s\"\n", buf.sysname); + pass++; + } else { + printf("FAIL: sysname = \"%s\" (expected \"Breenix\")\n", buf.sysname); + fail++; + } + + /* Test 3: machine == "aarch64" */ + if (strcmp(buf.machine, "aarch64") == 0) { + printf("PASS: machine = \"%s\"\n", buf.machine); + pass++; + } else { + printf("FAIL: machine = \"%s\" (expected \"aarch64\")\n", buf.machine); + fail++; + } + + printf("\nuname_test: %d passed, %d failed\n", pass, fail); + return fail > 0 ? 1 : 0; +}