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//!
5//! The thread lifecycle syscalls (`SYS_THREAD_CREATE/JOIN/EXIT`) are thin
6//! wrappers over the internal helpers in [`crate::process::thread_ops`],
7//! shared verbatim with the `/thread` VFS scheme.
8
9use super::{error::SyscallError, SyscallFrame};
10use crate::process::{
11    create_session, current_pgid, current_task_clone, current_task_id, current_tid, get_parent_pid,
12    get_pgid_by_pid, get_sid_by_pid, get_task_ids_in_tgid, kill_task, set_process_group,
13    thread_ops,
14};
15use core::sync::atomic::Ordering;
16
17/// SYS_GETPID (311): Return current process ID.
18///
19/// In Strat9, each task has a unique ID, so getpid returns the TaskId.
20pub fn sys_getpid() -> Result<u64, SyscallError> {
21    current_task_clone()
22        .map(|task| task.tgid as u64)
23        .ok_or(SyscallError::Fault)
24}
25
26/// SYS_GETTID (312): Return current thread ID.
27///
28/// In the current single-threaded silo model, TID == PID.
29pub fn sys_gettid() -> Result<u64, SyscallError> {
30    current_tid()
31        .map(|tid| tid as u64)
32        .ok_or(SyscallError::Fault)
33}
34
35/// SYS_THREAD_CREATE (341): create a userspace thread sharing current process resources.
36pub fn sys_thread_create(
37    frame: &SyscallFrame,
38    entry: u64,
39    stack_top: u64,
40    arg0: u64,
41    flags: u64,
42    tls_base: u64,
43) -> Result<u64, SyscallError> {
44    let child = thread_ops::create_user_thread(
45        thread_ops::UserEntryContext::from_frame(frame),
46        entry,
47        stack_top,
48        arg0,
49        flags,
50        tls_base,
51    )?;
52    Ok(child.tid as u64)
53}
54
55/// SYS_THREAD_JOIN (342): wait for a thread created by the current task.
56pub fn sys_thread_join(tid: u64, status_ptr: u64, flags: u64) -> Result<u64, SyscallError> {
57    if flags != 0 {
58        return Err(SyscallError::InvalidArgument);
59    }
60
61    let wait_tid = u32::try_from(tid).map_err(|_| SyscallError::InvalidArgument)?;
62    let (joined_tid, status) = thread_ops::join_task(wait_tid)?;
63    if status_ptr != 0 {
64        let out =
65            crate::memory::UserSliceWrite::new(status_ptr, 4).map_err(|_| SyscallError::Fault)?;
66        out.copy_from(&(status as i32).to_ne_bytes());
67    }
68    Ok(joined_tid as u64)
69}
70
71/// SYS_THREAD_EXIT (343): exit only the current thread.
72pub fn sys_thread_exit(exit_code: u64) -> Result<u64, SyscallError> {
73    let code = i32::try_from(exit_code).map_err(|_| SyscallError::InvalidArgument)?;
74    thread_ops::exit_current_thread(code)
75}
76
77/// SYS_PROC_GETPPID/SYS_GETPPID (309): Return parent process ID.
78pub fn sys_getppid() -> Result<u64, SyscallError> {
79    let child = current_task_id().ok_or(SyscallError::Fault)?;
80    Ok(get_parent_pid(child).map(|p| p as u64).unwrap_or(0))
81}
82
83/// SYS_GETPGID (318): Return process group id for `pid` (`0` = caller).
84pub fn sys_getpgid(pid: i64) -> Result<u64, SyscallError> {
85    if pid < 0 {
86        return Err(SyscallError::InvalidArgument);
87    }
88    if pid == 0 {
89        return current_pgid()
90            .map(|pgid| pgid as u64)
91            .ok_or(SyscallError::Fault);
92    }
93    get_pgid_by_pid(pid as u32)
94        .map(|pgid| pgid as u64)
95        .ok_or(SyscallError::NotFound)
96}
97
98/// POSIX getpgrp wrapper (equivalent to getpgid(0)).
99pub fn sys_getpgrp() -> Result<u64, SyscallError> {
100    current_pgid()
101        .map(|pgid| pgid as u64)
102        .ok_or(SyscallError::Fault)
103}
104
105/// SYS_GETSID (332): Return session id for `pid` (`0` = caller).
106pub fn sys_getsid(pid: i64) -> Result<u64, SyscallError> {
107    if pid < 0 {
108        return Err(SyscallError::InvalidArgument);
109    }
110    if pid == 0 {
111        return crate::process::current_sid()
112            .map(|sid| sid as u64)
113            .ok_or(SyscallError::Fault);
114    }
115    get_sid_by_pid(pid as u32)
116        .map(|sid| sid as u64)
117        .ok_or(SyscallError::NotFound)
118}
119
120/// SYS_SETPGID (317): set process group id.
121pub fn sys_setpgid(pid: i64, pgid: i64) -> Result<u64, SyscallError> {
122    if pid < 0 || pgid < 0 {
123        return Err(SyscallError::InvalidArgument);
124    }
125    let caller = current_task_id().ok_or(SyscallError::Fault)?;
126    let target_pid = if pid == 0 { None } else { Some(pid as u32) };
127    let new_pgid = if pgid == 0 { None } else { Some(pgid as u32) };
128    let final_pgid = set_process_group(caller, target_pid, new_pgid)?;
129    Ok(final_pgid as u64)
130}
131
132/// SYS_SETSID (319): create a new session.
133pub fn sys_setsid() -> Result<u64, SyscallError> {
134    let caller = current_task_id().ok_or(SyscallError::Fault)?;
135    create_session(caller).map(|sid| sid as u64)
136}
137
138// ========== Credentials ==============================
139
140/// SYS_GETUID (335): Return real user id.
141pub fn sys_getuid() -> Result<u64, SyscallError> {
142    let task = current_task_clone().ok_or(SyscallError::Fault)?;
143    Ok(task.uid.load(Ordering::Relaxed) as u64)
144}
145
146/// SYS_GETEUID (336): Return effective user id.
147pub fn sys_geteuid() -> Result<u64, SyscallError> {
148    let task = current_task_clone().ok_or(SyscallError::Fault)?;
149    Ok(task.euid.load(Ordering::Relaxed) as u64)
150}
151
152/// SYS_GETGID (337): Return real group id.
153pub fn sys_getgid() -> Result<u64, SyscallError> {
154    let task = current_task_clone().ok_or(SyscallError::Fault)?;
155    Ok(task.gid.load(Ordering::Relaxed) as u64)
156}
157
158/// SYS_GETEGID (338): Return effective group id.
159pub fn sys_getegid() -> Result<u64, SyscallError> {
160    let task = current_task_clone().ok_or(SyscallError::Fault)?;
161    Ok(task.egid.load(Ordering::Relaxed) as u64)
162}
163
164/// SYS_SETUID (339): Set real and effective user id (simplified: no capabilities check).
165pub fn sys_setuid(uid: u64) -> Result<u64, SyscallError> {
166    if uid > u32::MAX as u64 {
167        return Err(SyscallError::InvalidArgument);
168    }
169    let task = current_task_clone().ok_or(SyscallError::Fault)?;
170    // Privileged (uid==0) can set anything; unprivileged can only set to current uid/euid.
171    let euid = task.euid.load(Ordering::Relaxed);
172    let cur_uid = task.uid.load(Ordering::Relaxed);
173    if euid != 0 && uid as u32 != cur_uid && uid as u32 != euid {
174        return Err(SyscallError::PermissionDenied);
175    }
176    task.uid.store(uid as u32, Ordering::Relaxed);
177    task.euid.store(uid as u32, Ordering::Relaxed);
178    Ok(0)
179}
180
181/// SYS_SETGID (340): Set real and effective group id (simplified).
182pub fn sys_setgid(gid: u64) -> Result<u64, SyscallError> {
183    if gid > u32::MAX as u64 {
184        return Err(SyscallError::InvalidArgument);
185    }
186    let task = current_task_clone().ok_or(SyscallError::Fault)?;
187    let euid = task.euid.load(Ordering::Relaxed);
188    let cur_gid = task.gid.load(Ordering::Relaxed);
189    let egid = task.egid.load(Ordering::Relaxed);
190    if euid != 0 && gid as u32 != cur_gid && gid as u32 != egid {
191        return Err(SyscallError::PermissionDenied);
192    }
193    task.gid.store(gid as u32, Ordering::Relaxed);
194    task.egid.store(gid as u32, Ordering::Relaxed);
195    Ok(0)
196}
197
198// ========== Thread lifecycle helpers ================================================================================================================================================================
199
200/// SYS_SET_TID_ADDRESS (333): Store `tidptr` in the task; return current TID.
201///
202/// The kernel will write 0 to `tidptr` and call futex_wake when the thread
203/// exits. This is the mechanism used by pthreads for thread join.
204pub fn sys_set_tid_address(tidptr: u64) -> Result<u64, SyscallError> {
205    let task = current_task_clone().ok_or(SyscallError::Fault)?;
206    task.clear_child_tid.store(tidptr, Ordering::Relaxed);
207    Ok(task.tid as u64)
208}
209
210/// SYS_EXIT_GROUP (334): Exit all threads in the thread group.
211pub fn sys_exit_group(exit_code: u64) -> Result<u64, SyscallError> {
212    let current = current_task_clone().ok_or(SyscallError::Fault)?;
213    for sibling_id in get_task_ids_in_tgid(current.tgid) {
214        if sibling_id != current.id {
215            let _ = kill_task(sibling_id);
216        }
217    }
218
219    // Diverges : never returns.
220    crate::process::scheduler::exit_current_task(exit_code as i32)
221}
222
223// ========== Architecture-specific ==========================================================================================================================================================================
224
225/// x86_64 arch_prctl operation codes (Linux-compatible).
226const ARCH_SET_GS: u64 = 0x1001;
227const ARCH_SET_FS: u64 = 0x1002;
228const ARCH_GET_FS: u64 = 0x1003;
229const ARCH_GET_GS: u64 = 0x1004;
230
231/// MSR addresses for FS/GS base.
232const MSR_FS_BASE: u32 = 0xC000_0100;
233const MSR_GS_BASE: u32 = 0xC000_0101;
234
235/// SYS_ARCH_PRCTL (350): Architecture-specific process settings.
236///
237/// Supported operations:
238/// - `ARCH_SET_FS` (0x1002): Set user-space FS.base (Thread Local Storage).
239/// - `ARCH_GET_FS` (0x1003): Read current FS.base into *arg.
240pub fn sys_arch_prctl(code: u64, addr: u64) -> Result<u64, SyscallError> {
241    let task = current_task_clone().ok_or(SyscallError::Fault)?;
242    match code {
243        ARCH_SET_FS => {
244            // Store in task struct (so it survives context switches).
245            task.user_fs_base.store(addr, Ordering::Relaxed);
246            // Write to MSR immediately : we are the current task.
247            unsafe { wrmsr(MSR_FS_BASE, addr) };
248            Ok(0)
249        }
250        ARCH_GET_FS => {
251            let base = task.user_fs_base.load(Ordering::Relaxed);
252            // Write the 8-byte value back to the provided user pointer.
253            use crate::memory::UserSliceWrite;
254            let out = UserSliceWrite::new(addr, 8).map_err(|_| SyscallError::Fault)?;
255            out.copy_from(&base.to_ne_bytes());
256            Ok(0)
257        }
258        ARCH_SET_GS => {
259            // GS slot not separately stored for now.
260            unsafe { wrmsr(MSR_GS_BASE, addr) };
261            Ok(0)
262        }
263        ARCH_GET_GS => {
264            let base = unsafe { rdmsr(MSR_GS_BASE) };
265            use crate::memory::UserSliceWrite;
266            let out = UserSliceWrite::new(addr, 8).map_err(|_| SyscallError::Fault)?;
267            out.copy_from(&base.to_ne_bytes());
268            Ok(0)
269        }
270        _ => Err(SyscallError::InvalidArgument),
271    }
272}
273
274/// Write a 64-bit value to an MSR.
275///
276/// # Safety
277/// Must only be called with valid MSR addresses. Misuse causes a #GP.
278#[inline]
279unsafe fn wrmsr(msr: u32, value: u64) {
280    let lo = value as u32;
281    let hi = (value >> 32) as u32;
282    unsafe {
283        core::arch::asm!(
284            "wrmsr",
285            in("ecx") msr,
286            in("eax") lo,
287            in("edx") hi,
288            options(nostack, preserves_flags),
289        );
290    }
291}
292
293/// Read a 64-bit value from an MSR.
294///
295/// # Safety
296/// Must only be called with valid MSR addresses.
297#[inline]
298unsafe fn rdmsr(msr: u32) -> u64 {
299    let lo: u32;
300    let hi: u32;
301    unsafe {
302        core::arch::asm!(
303            "rdmsr",
304            in("ecx") msr,
305            out("eax") lo,
306            out("edx") hi,
307            options(nostack, preserves_flags),
308        );
309    }
310    lo as u64 | ((hi as u64) << 32)
311}
312
313// ========== tgkill ==================================================
314
315/// SYS_TGKILL (352): Send a signal to a specific thread in a thread group.
316///
317/// In the current single-threaded model, tgid and tid both map to a single
318/// task (pid == tid == tgid). We verify both match before delivering.
319pub fn sys_tgkill(tgid: u64, tid: u64, signum: u64) -> Result<u64, SyscallError> {
320    use crate::process::{get_task_by_pid, send_signal, Signal};
321
322    // Sanity check.
323    if signum as u32 >= 64 {
324        return Err(SyscallError::InvalidArgument);
325    }
326
327    // Resolve tgid => task.
328    let task = get_task_by_pid(tgid as u32).ok_or(SyscallError::NotFound)?;
329
330    // Verify the tid matches (single-threaded: task.tid == task.pid).
331    if task.tid as u64 != tid && task.pid as u64 != tid {
332        return Err(SyscallError::NotFound);
333    }
334
335    if signum == 0 {
336        return Ok(0); // existence check only
337    }
338
339    let sig = Signal::from_u32(signum as u32).ok_or(SyscallError::InvalidArgument)?;
340    send_signal(task.id, sig)?;
341    Ok(0)
342}