Skip to main content

strat9_kernel/syscall/
process.rs

1//! Process and thread management syscalls.
2//!
3//! Implements PID/TID retrieval per the Strat9-OS ABI.
4
5use super::{error::SyscallError, SyscallFrame};
6use crate::process::{
7    block_current_task, create_session, current_pgid, current_task_clone, current_task_id,
8    current_tid, get_child_task_id_by_tid, get_parent_pid, get_pgid_by_pid, get_sid_by_pid,
9    get_task_ids_in_tgid, kill_task,
10    scheduler::add_task_with_parent,
11    set_process_group,
12    task::{CpuContext, ExtendedState, KernelStack, SyncUnsafeCell, Task},
13    WaitChildResult,
14};
15use alloc::{boxed::Box, sync::Arc};
16use core::{mem::offset_of, sync::atomic::Ordering};
17
18#[repr(C)]
19#[derive(Clone, Copy)]
20struct ThreadUserContext {
21    entry: u64,
22    stack_top: u64,
23    arg0: u64,
24    user_cs: u64,
25    user_rflags: u64,
26    user_ss: u64,
27}
28
29const THREAD_OFF_ENTRY: usize = offset_of!(ThreadUserContext, entry);
30const THREAD_OFF_STACK_TOP: usize = offset_of!(ThreadUserContext, stack_top);
31const THREAD_OFF_ARG0: usize = offset_of!(ThreadUserContext, arg0);
32const THREAD_OFF_USER_CS: usize = offset_of!(ThreadUserContext, user_cs);
33const THREAD_OFF_USER_RFLAGS: usize = offset_of!(ThreadUserContext, user_rflags);
34const THREAD_OFF_USER_SS: usize = offset_of!(ThreadUserContext, user_ss);
35
36/// Performs the thread child start operation.
37extern "C" fn thread_child_start(ctx_ptr: u64) -> ! {
38    // SAFETY: `ctx_ptr` is allocated with Box::into_raw in `build_user_thread_task`
39    // and passed as immutable bootstrap data for this task only.
40    let boxed = unsafe { Box::from_raw(ctx_ptr as *mut ThreadUserContext) };
41    let ctx = *boxed;
42    // SAFETY: Assembly routine performs an iretq into userspace with validated context.
43    unsafe { thread_iret_from_ctx(&ctx as *const ThreadUserContext) }
44}
45
46/// Performs the thread iret from ctx operation.
47#[unsafe(naked)]
48unsafe extern "C" fn thread_iret_from_ctx(_ctx: *const ThreadUserContext) -> ! {
49    core::arch::naked_asm!(
50        // Mask IRQs before touching GS. The user RFLAGS frame re-enables IF.
51        "cli",
52        "mov rsi, rdi",
53        // Build iret frame: SS, RSP, RFLAGS, CS, RIP
54        "mov r8, [rsi + {off_user_ss}]",
55        "push r8",
56        "mov r8, [rsi + {off_stack_top}]",
57        "push r8",
58        "mov r8, [rsi + {off_user_rflags}]",
59        "push r8",
60        "mov r8, [rsi + {off_user_cs}]",
61        "push r8",
62        "mov r8, [rsi + {off_entry}]",
63        "push r8",
64        // Argument convention for userspace entry: rdi = arg0
65        "mov rdi, [rsi + {off_arg0}]",
66        // Child thread returns 0 if entry routine ever reads rax.
67        "xor rax, rax",
68        "swapgs",
69        "iretq",
70        off_entry = const THREAD_OFF_ENTRY,
71        off_stack_top = const THREAD_OFF_STACK_TOP,
72        off_arg0 = const THREAD_OFF_ARG0,
73        off_user_cs = const THREAD_OFF_USER_CS,
74        off_user_rflags = const THREAD_OFF_USER_RFLAGS,
75        off_user_ss = const THREAD_OFF_USER_SS,
76    );
77}
78
79/// Performs the build user thread task operation.
80fn build_user_thread_task(
81    parent: &Arc<Task>,
82    bootstrap_ctx: Box<ThreadUserContext>,
83    tls_base: u64,
84) -> Result<Arc<Task>, SyscallError> {
85    let kernel_stack =
86        KernelStack::allocate(Task::DEFAULT_STACK_SIZE).map_err(|_| SyscallError::OutOfMemory)?;
87    let context = CpuContext::new(thread_child_start as *const () as u64, &kernel_stack);
88    let (pid, tid, _) = Task::allocate_process_ids();
89
90    let parent_fpu = unsafe { &*parent.fpu_state.get() };
91    let mut child_fpu = ExtendedState::new();
92    child_fpu.copy_from(parent_fpu);
93    let interrupt_frame = crate::syscall::SyscallFrame {
94        r15: 0,
95        r14: 0,
96        r13: 0,
97        r12: 0,
98        rbp: 0,
99        rbx: 0,
100        r11: bootstrap_ctx.user_rflags,
101        r10: 0,
102        r9: 0,
103        r8: 0,
104        rsi: 0,
105        rdi: bootstrap_ctx.arg0,
106        rdx: 0,
107        rcx: bootstrap_ctx.entry,
108        rax: 0,
109        iret_rip: bootstrap_ctx.entry,
110        iret_cs: bootstrap_ctx.user_cs,
111        iret_rflags: bootstrap_ctx.user_rflags,
112        iret_rsp: bootstrap_ctx.stack_top,
113        iret_ss: bootstrap_ctx.user_ss,
114    };
115
116    let task = Arc::new(Task {
117        id: crate::process::TaskId::new(),
118        pid,
119        tid,
120        tgid: parent.tgid,
121        pgid: core::sync::atomic::AtomicU32::new(parent.pgid.load(Ordering::Relaxed)),
122        sid: core::sync::atomic::AtomicU32::new(parent.sid.load(Ordering::Relaxed)),
123        uid: core::sync::atomic::AtomicU32::new(parent.uid.load(Ordering::Relaxed)),
124        euid: core::sync::atomic::AtomicU32::new(parent.euid.load(Ordering::Relaxed)),
125        gid: core::sync::atomic::AtomicU32::new(parent.gid.load(Ordering::Relaxed)),
126        egid: core::sync::atomic::AtomicU32::new(parent.egid.load(Ordering::Relaxed)),
127        state: core::sync::atomic::AtomicU8::new(crate::process::TaskState::Ready as u8),
128        priority: parent.priority,
129        context: SyncUnsafeCell::new(context),
130        resume_kind: SyncUnsafeCell::new(crate::process::task::ResumeKind::RetFrame),
131        interrupt_rsp: core::sync::atomic::AtomicU64::new(0),
132        kernel_stack,
133        user_stack: None,
134        name: "user-thread",
135        process: parent.process.clone(),
136        pending_signals: crate::process::signal::SignalSet::new(),
137        blocked_signals: parent.blocked_signals.clone(),
138        irq_signal_delivery_blocked: core::sync::atomic::AtomicBool::new(false),
139        signal_stack: SyncUnsafeCell::new(None),
140        itimers: crate::process::timer::ITimers::new(),
141        wake_pending: core::sync::atomic::AtomicBool::new(false),
142        wake_deadline_ns: core::sync::atomic::AtomicU64::new(0),
143        trampoline_entry: core::sync::atomic::AtomicU64::new(0),
144        trampoline_stack_top: core::sync::atomic::AtomicU64::new(0),
145        trampoline_arg0: core::sync::atomic::AtomicU64::new(0),
146        ticks: core::sync::atomic::AtomicU64::new(0),
147        sched_policy: SyncUnsafeCell::new(parent.sched_policy()),
148        home_cpu: core::sync::atomic::AtomicUsize::new(usize::MAX),
149        vruntime: core::sync::atomic::AtomicU64::new(parent.vruntime()),
150        fair_rq_generation: core::sync::atomic::AtomicU64::new(0),
151        fair_on_rq: core::sync::atomic::AtomicBool::new(false),
152        clear_child_tid: core::sync::atomic::AtomicU64::new(0),
153        robust_list_head: core::sync::atomic::AtomicU64::new(0),
154        robust_list_len: core::sync::atomic::AtomicUsize::new(0),
155        user_fs_base: core::sync::atomic::AtomicU64::new(tls_base),
156        fpu_state: SyncUnsafeCell::new(child_fpu),
157        xcr0_mask: core::sync::atomic::AtomicU64::new(parent.xcr0_mask.load(Ordering::Relaxed)),
158        rt_link: intrusive_collections::LinkedListLink::new(),
159    });
160
161    // CpuContext initial stack layout: r15, r14, r13(arg), r12(entry), rbp, rbx, ret
162    // Seed r13 with bootstrap context pointer for `thread_child_start`.
163    unsafe {
164        let ctx = &mut *task.context.get();
165        let frame = ctx.saved_rsp as *mut u64;
166        *frame.add(2) = Box::into_raw(bootstrap_ctx) as u64;
167    }
168
169    task.seed_interrupt_frame(interrupt_frame);
170
171    Ok(task)
172}
173
174/// SYS_GETPID (311): Return current process ID.
175///
176/// In Strat9, each task has a unique ID, so getpid returns the TaskId.
177pub fn sys_getpid() -> Result<u64, SyscallError> {
178    current_task_clone()
179        .map(|task| task.tgid as u64)
180        .ok_or(SyscallError::Fault)
181}
182
183/// SYS_GETTID (312): Return current thread ID.
184///
185/// In the current single-threaded silo model, TID == PID.
186pub fn sys_gettid() -> Result<u64, SyscallError> {
187    current_tid()
188        .map(|tid| tid as u64)
189        .ok_or(SyscallError::Fault)
190}
191
192/// SYS_THREAD_CREATE (341): create a userspace thread sharing current process resources.
193pub fn sys_thread_create(
194    frame: &SyscallFrame,
195    entry: u64,
196    stack_top: u64,
197    arg0: u64,
198    flags: u64,
199    tls_base: u64,
200) -> Result<u64, SyscallError> {
201    const USER_TOP_EXCLUSIVE: u64 = 0x0000_8000_0000_0000;
202
203    if flags != 0 {
204        return Err(SyscallError::InvalidArgument);
205    }
206
207    if entry == 0
208        || stack_top == 0
209        || entry >= USER_TOP_EXCLUSIVE
210        || stack_top >= USER_TOP_EXCLUSIVE
211        || (stack_top & 0x7) != 0
212    // 8-byte alignment (x86_64 entry: 8 mod 16 is valid)
213    {
214        return Err(SyscallError::InvalidArgument);
215    }
216
217    let parent = current_task_clone().ok_or(SyscallError::Fault)?;
218    if parent.is_kernel() {
219        return Err(SyscallError::PermissionDenied);
220    }
221
222    let user_ctx = Box::new(ThreadUserContext {
223        entry,
224        stack_top,
225        arg0,
226        user_cs: frame.iret_cs,
227        user_rflags: frame.iret_rflags | (1 << 9),
228        user_ss: frame.iret_ss,
229    });
230
231    let child = build_user_thread_task(&parent, user_ctx, tls_base)?;
232    let tid = child.tid as u64;
233    add_task_with_parent(child, parent.id);
234    Ok(tid)
235}
236
237/// SYS_THREAD_JOIN (342): wait for a thread created by the current task.
238pub fn sys_thread_join(tid: u64, status_ptr: u64, flags: u64) -> Result<u64, SyscallError> {
239    if flags != 0 {
240        return Err(SyscallError::InvalidArgument);
241    }
242
243    let wait_tid = u32::try_from(tid).map_err(|_| SyscallError::InvalidArgument)?;
244    let current = current_task_clone().ok_or(SyscallError::Fault)?;
245    if wait_tid == current.tid {
246        return Err(SyscallError::InvalidArgument);
247    }
248
249    let parent_id = current_task_id().ok_or(SyscallError::Fault)?;
250    let child_id = get_child_task_id_by_tid(parent_id, wait_tid).ok_or(SyscallError::NotFound)?;
251
252    loop {
253        match crate::process::try_wait_child(parent_id, Some(child_id)) {
254            WaitChildResult::Reaped { status, .. } => {
255                if status_ptr != 0 {
256                    let out = crate::memory::UserSliceWrite::new(status_ptr, 4)
257                        .map_err(|_| SyscallError::Fault)?;
258                    out.copy_from(&(status as i32).to_ne_bytes());
259                }
260                return Ok(wait_tid as u64);
261            }
262            WaitChildResult::NoChildren => return Err(SyscallError::NotFound),
263            WaitChildResult::StillRunning => block_current_task(),
264        }
265    }
266}
267
268/// SYS_THREAD_EXIT (343): exit only the current thread.
269pub fn sys_thread_exit(exit_code: u64) -> Result<u64, SyscallError> {
270    let code = i32::try_from(exit_code).map_err(|_| SyscallError::InvalidArgument)?;
271    crate::process::scheduler::exit_current_task(code)
272}
273
274/// SYS_PROC_GETPPID/SYS_GETPPID (309): Return parent process ID.
275pub fn sys_getppid() -> Result<u64, SyscallError> {
276    let child = current_task_id().ok_or(SyscallError::Fault)?;
277    Ok(get_parent_pid(child).map(|p| p as u64).unwrap_or(0))
278}
279
280/// SYS_GETPGID (318): Return process group id for `pid` (`0` = caller).
281pub fn sys_getpgid(pid: i64) -> Result<u64, SyscallError> {
282    if pid < 0 {
283        return Err(SyscallError::InvalidArgument);
284    }
285    if pid == 0 {
286        return current_pgid()
287            .map(|pgid| pgid as u64)
288            .ok_or(SyscallError::Fault);
289    }
290    get_pgid_by_pid(pid as u32)
291        .map(|pgid| pgid as u64)
292        .ok_or(SyscallError::NotFound)
293}
294
295/// POSIX getpgrp wrapper (equivalent to getpgid(0)).
296pub fn sys_getpgrp() -> Result<u64, SyscallError> {
297    current_pgid()
298        .map(|pgid| pgid as u64)
299        .ok_or(SyscallError::Fault)
300}
301
302/// SYS_GETSID (332): Return session id for `pid` (`0` = caller).
303pub fn sys_getsid(pid: i64) -> Result<u64, SyscallError> {
304    if pid < 0 {
305        return Err(SyscallError::InvalidArgument);
306    }
307    if pid == 0 {
308        return crate::process::current_sid()
309            .map(|sid| sid as u64)
310            .ok_or(SyscallError::Fault);
311    }
312    get_sid_by_pid(pid as u32)
313        .map(|sid| sid as u64)
314        .ok_or(SyscallError::NotFound)
315}
316
317/// SYS_SETPGID (317): set process group id.
318pub fn sys_setpgid(pid: i64, pgid: i64) -> Result<u64, SyscallError> {
319    if pid < 0 || pgid < 0 {
320        return Err(SyscallError::InvalidArgument);
321    }
322    let caller = current_task_id().ok_or(SyscallError::Fault)?;
323    let target_pid = if pid == 0 { None } else { Some(pid as u32) };
324    let new_pgid = if pgid == 0 { None } else { Some(pgid as u32) };
325    let final_pgid = set_process_group(caller, target_pid, new_pgid)?;
326    Ok(final_pgid as u64)
327}
328
329/// SYS_SETSID (319): create a new session.
330pub fn sys_setsid() -> Result<u64, SyscallError> {
331    let caller = current_task_id().ok_or(SyscallError::Fault)?;
332    create_session(caller).map(|sid| sid as u64)
333}
334
335// ========== Credentials ==============================
336
337/// SYS_GETUID (335): Return real user id.
338pub fn sys_getuid() -> Result<u64, SyscallError> {
339    let task = current_task_clone().ok_or(SyscallError::Fault)?;
340    Ok(task.uid.load(Ordering::Relaxed) as u64)
341}
342
343/// SYS_GETEUID (336): Return effective user id.
344pub fn sys_geteuid() -> Result<u64, SyscallError> {
345    let task = current_task_clone().ok_or(SyscallError::Fault)?;
346    Ok(task.euid.load(Ordering::Relaxed) as u64)
347}
348
349/// SYS_GETGID (337): Return real group id.
350pub fn sys_getgid() -> Result<u64, SyscallError> {
351    let task = current_task_clone().ok_or(SyscallError::Fault)?;
352    Ok(task.gid.load(Ordering::Relaxed) as u64)
353}
354
355/// SYS_GETEGID (338): Return effective group id.
356pub fn sys_getegid() -> Result<u64, SyscallError> {
357    let task = current_task_clone().ok_or(SyscallError::Fault)?;
358    Ok(task.egid.load(Ordering::Relaxed) as u64)
359}
360
361/// SYS_SETUID (339): Set real and effective user id (simplified: no capabilities check).
362pub fn sys_setuid(uid: u64) -> Result<u64, SyscallError> {
363    if uid > u32::MAX as u64 {
364        return Err(SyscallError::InvalidArgument);
365    }
366    let task = current_task_clone().ok_or(SyscallError::Fault)?;
367    // Privileged (uid==0) can set anything; unprivileged can only set to current uid/euid.
368    let euid = task.euid.load(Ordering::Relaxed);
369    let cur_uid = task.uid.load(Ordering::Relaxed);
370    if euid != 0 && uid as u32 != cur_uid && uid as u32 != euid {
371        return Err(SyscallError::PermissionDenied);
372    }
373    task.uid.store(uid as u32, Ordering::Relaxed);
374    task.euid.store(uid as u32, Ordering::Relaxed);
375    Ok(0)
376}
377
378/// SYS_SETGID (340): Set real and effective group id (simplified).
379pub fn sys_setgid(gid: u64) -> Result<u64, SyscallError> {
380    if gid > u32::MAX as u64 {
381        return Err(SyscallError::InvalidArgument);
382    }
383    let task = current_task_clone().ok_or(SyscallError::Fault)?;
384    let euid = task.euid.load(Ordering::Relaxed);
385    let cur_gid = task.gid.load(Ordering::Relaxed);
386    let egid = task.egid.load(Ordering::Relaxed);
387    if euid != 0 && gid as u32 != cur_gid && gid as u32 != egid {
388        return Err(SyscallError::PermissionDenied);
389    }
390    task.gid.store(gid as u32, Ordering::Relaxed);
391    task.egid.store(gid as u32, Ordering::Relaxed);
392    Ok(0)
393}
394
395// ========== Thread lifecycle helpers ================================================================================================================================================================
396
397/// SYS_SET_TID_ADDRESS (333): Store `tidptr` in the task; return current TID.
398///
399/// The kernel will write 0 to `tidptr` and call futex_wake when the thread
400/// exits. This is the mechanism used by pthreads for thread join.
401pub fn sys_set_tid_address(tidptr: u64) -> Result<u64, SyscallError> {
402    let task = current_task_clone().ok_or(SyscallError::Fault)?;
403    task.clear_child_tid.store(tidptr, Ordering::Relaxed);
404    Ok(task.tid as u64)
405}
406
407/// SYS_EXIT_GROUP (334): Exit all threads in the thread group.
408pub fn sys_exit_group(exit_code: u64) -> Result<u64, SyscallError> {
409    let current = current_task_clone().ok_or(SyscallError::Fault)?;
410    for sibling_id in get_task_ids_in_tgid(current.tgid) {
411        if sibling_id != current.id {
412            let _ = kill_task(sibling_id);
413        }
414    }
415
416    // Diverges : never returns.
417    crate::process::scheduler::exit_current_task(exit_code as i32)
418}
419
420// ========== Architecture-specific ==========================================================================================================================================================================
421
422/// x86_64 arch_prctl operation codes (Linux-compatible).
423const ARCH_SET_GS: u64 = 0x1001;
424const ARCH_SET_FS: u64 = 0x1002;
425const ARCH_GET_FS: u64 = 0x1003;
426const ARCH_GET_GS: u64 = 0x1004;
427
428/// MSR addresses for FS/GS base.
429const MSR_FS_BASE: u32 = 0xC000_0100;
430const MSR_GS_BASE: u32 = 0xC000_0101;
431
432/// SYS_ARCH_PRCTL (350): Architecture-specific process settings.
433///
434/// Supported operations:
435/// - `ARCH_SET_FS` (0x1002): Set user-space FS.base (Thread Local Storage).
436/// - `ARCH_GET_FS` (0x1003): Read current FS.base into *arg.
437pub fn sys_arch_prctl(code: u64, addr: u64) -> Result<u64, SyscallError> {
438    let task = current_task_clone().ok_or(SyscallError::Fault)?;
439    match code {
440        ARCH_SET_FS => {
441            // Store in task struct (so it survives context switches).
442            task.user_fs_base.store(addr, Ordering::Relaxed);
443            // Write to MSR immediately : we are the current task.
444            unsafe { wrmsr(MSR_FS_BASE, addr) };
445            Ok(0)
446        }
447        ARCH_GET_FS => {
448            let base = task.user_fs_base.load(Ordering::Relaxed);
449            // Write the 8-byte value back to the provided user pointer.
450            use crate::memory::UserSliceWrite;
451            let out = UserSliceWrite::new(addr, 8).map_err(|_| SyscallError::Fault)?;
452            out.copy_from(&base.to_ne_bytes());
453            Ok(0)
454        }
455        ARCH_SET_GS => {
456            // GS slot not separately stored for now.
457            unsafe { wrmsr(MSR_GS_BASE, addr) };
458            Ok(0)
459        }
460        ARCH_GET_GS => {
461            let base = unsafe { rdmsr(MSR_GS_BASE) };
462            use crate::memory::UserSliceWrite;
463            let out = UserSliceWrite::new(addr, 8).map_err(|_| SyscallError::Fault)?;
464            out.copy_from(&base.to_ne_bytes());
465            Ok(0)
466        }
467        _ => Err(SyscallError::InvalidArgument),
468    }
469}
470
471/// Write a 64-bit value to an MSR.
472///
473/// # Safety
474/// Must only be called with valid MSR addresses. Misuse causes a #GP.
475#[inline]
476unsafe fn wrmsr(msr: u32, value: u64) {
477    let lo = value as u32;
478    let hi = (value >> 32) as u32;
479    unsafe {
480        core::arch::asm!(
481            "wrmsr",
482            in("ecx") msr,
483            in("eax") lo,
484            in("edx") hi,
485            options(nostack, preserves_flags),
486        );
487    }
488}
489
490/// Read a 64-bit value from an MSR.
491///
492/// # Safety
493/// Must only be called with valid MSR addresses.
494#[inline]
495unsafe fn rdmsr(msr: u32) -> u64 {
496    let lo: u32;
497    let hi: u32;
498    unsafe {
499        core::arch::asm!(
500            "rdmsr",
501            in("ecx") msr,
502            out("eax") lo,
503            out("edx") hi,
504            options(nostack, preserves_flags),
505        );
506    }
507    lo as u64 | ((hi as u64) << 32)
508}
509
510// ========== tgkill ==================================================
511
512/// SYS_TGKILL (352): Send a signal to a specific thread in a thread group.
513///
514/// In the current single-threaded model, tgid and tid both map to a single
515/// task (pid == tid == tgid). We verify both match before delivering.
516pub fn sys_tgkill(tgid: u64, tid: u64, signum: u64) -> Result<u64, SyscallError> {
517    use crate::process::{get_task_by_pid, send_signal, Signal};
518
519    // Sanity check.
520    if signum as u32 >= 64 {
521        return Err(SyscallError::InvalidArgument);
522    }
523
524    // Resolve tgid → task.
525    let task = get_task_by_pid(tgid as u32).ok_or(SyscallError::NotFound)?;
526
527    // Verify the tid matches (single-threaded: task.tid == task.pid).
528    if task.tid as u64 != tid && task.pid as u64 != tid {
529        return Err(SyscallError::NotFound);
530    }
531
532    if signum == 0 {
533        return Ok(0); // existence check only
534    }
535
536    let sig = Signal::from_u32(signum as u32).ok_or(SyscallError::InvalidArgument)?;
537    send_signal(task.id, sig)?;
538    Ok(0)
539}