summaryrefslogtreecommitdiff
path: root/na/kernel/sys_misc.cc
diff options
context:
space:
mode:
Diffstat (limited to 'na/kernel/sys_misc.cc')
-rw-r--r--na/kernel/sys_misc.cc360
1 files changed, 360 insertions, 0 deletions
diff --git a/na/kernel/sys_misc.cc b/na/kernel/sys_misc.cc
new file mode 100644
index 0000000..48eb47b
--- /dev/null
+++ b/na/kernel/sys_misc.cc
@@ -0,0 +1,360 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * Miscellaneous System Calls — busybox bootstrap set
+ *
+ * Each syscall follows the Linux kernel semantics exactly.
+ * Reference: Linux kernel/sys.c, kernel/signal.c, kernel/time/posix-timers.c
+ */
+
+#include <lume/types.h>
+#include <lume/task.h>
+#include <lume/errno.h>
+#include <lume/kprintf.h>
+#include <lume/klog.h>
+#include <lume/pid.h>
+#include <lume/pgrp.h>
+#include <lume/signal_types.h>
+#include <lume/tty.h>
+#include <lume/uaccess.h>
+#include <arch/cpu.h>
+
+/* ======================================================================
+ * sys_reboot — Linux kernel/reboot.c
+ *
+ * Validates magic numbers and performs the requested action.
+ * For now, we only support LINUX_REBOOT_CMD_CAD_ON/OFF (ignore)
+ * and reject everything else with -EPERM (no CAP_SYS_BOOT).
+ * ====================================================================== */
+
+static constexpr uint32 LINUX_REBOOT_MAGIC1 = 0xfee1dead;
+static constexpr uint32 LINUX_REBOOT_MAGIC2 = 672274793; /* 0x28121969 */
+static constexpr uint32 LINUX_REBOOT_MAGIC2A = 85072278;
+static constexpr uint32 LINUX_REBOOT_MAGIC2B = 369367448;
+static constexpr uint32 LINUX_REBOOT_MAGIC2C = 537993216;
+
+int64 sys_reboot(uint64 magic1, uint64 magic2, uint64 cmd,
+ uint64, uint64, uint64)
+{
+ /* Linux: kernel/reboot.c — validate magic numbers first */
+ if (static_cast<uint32>(magic1) != LINUX_REBOOT_MAGIC1)
+ return -EINVAL;
+ if (static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2 &&
+ static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2A &&
+ static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2B &&
+ static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2C)
+ return -EINVAL;
+
+ /* We don't have CAP_SYS_BOOT yet — reject with -EPERM.
+ * This matches Linux behavior for unprivileged callers.
+ * Busybox handles this gracefully and continues. */
+ (void)cmd;
+ printk(KERN_INFO "reboot: cmd=%llu (not implemented, returning -EPERM)\n", cmd);
+ return -EPERM;
+}
+
+/* ======================================================================
+ * sys_prctl — Linux kernel/sys.c
+ *
+ * Process control. Most options are optional for early boot.
+ * We implement the minimum set busybox needs.
+ * ====================================================================== */
+
+/* prctl option constants (from <linux/prctl.h>) */
+static constexpr int PR_SET_NAME = 15;
+static constexpr int PR_GET_NAME = 16;
+static constexpr int PR_SET_PDEATHSIG = 1;
+static constexpr int PR_GET_PDEATHSIG = 2;
+
+int64 sys_prctl(uint64 option, uint64 arg2, uint64 arg3,
+ uint64 arg4, uint64 arg5, uint64)
+{
+ switch (static_cast<int>(option)) {
+ case PR_SET_PDEATHSIG:
+ /* Linux: set signal delivered when parent dies.
+ * Silently accept — we don't track pdeath_signal yet. */
+ return 0;
+ case PR_GET_PDEATHSIG: {
+ /* Linux: return current pdeath_signal (0 = none) */
+ int32 zero = 0;
+ if (copy_to_user(arg2, &zero, sizeof(zero)) != 0)
+ return -EFAULT;
+ return 0;
+ }
+ case PR_SET_NAME: {
+ /* Linux: set comm (task name), max 16 chars */
+ /* TODO: copy_from_user the string */
+ return 0;
+ }
+ case PR_GET_NAME: {
+ /* Linux: get comm (task name) */
+ TaskControlBlock* curr = current_task();
+ if (!curr) return -ESRCH;
+ const char* name = curr->name ? curr->name : "";
+ uint64 len = 0;
+ while (name[len] && len < 15) len++;
+ char buf[16] = {};
+ for (uint64 i = 0; i < len; i++) buf[i] = name[i];
+ if (copy_to_user(arg2, buf, 16) != 0)
+ return -EFAULT;
+ return 0;
+ }
+ default:
+ /* Unknown prctl — Linux returns -EINVAL for unrecognized options */
+ return -EINVAL;
+ }
+}
+
+/* ======================================================================
+ * Session / Process-Group syscalls
+ *
+ * All four operate on the caller's signal_struct (or a target task's).
+ * Identity lives in signal_struct, so these become straightforward reads
+ * and pgrp-hash updates. We mirror Linux kernel/sys.c semantics closely.
+ * ====================================================================== */
+
+static TaskControlBlock* find_task_by_pid(int32 pid)
+{
+ extern list_node g_all_tasks;
+ extern Spinlock g_task_list_lock;
+ LockGuard guard(g_task_list_lock);
+ list_node* node = g_all_tasks.next;
+ while (node != &g_all_tasks) {
+ auto* task = list_entry<TaskControlBlock, &TaskControlBlock::all_link>(node);
+ if (task->pid == pid) return task;
+ node = node->next;
+ }
+ return nullptr;
+}
+
+/*
+ * sys_setsid — Linux kernel/sys.c:ksys_setsid()
+ *
+ * Caller becomes leader of a new session+pgrp. Refused if a pgrp with
+ * id == caller pid already exists with members other than the caller —
+ * that's Linux's "process group id already exists" check, which prevents
+ * collisions when a non-leader tries to setsid.
+ */
+int64 sys_setsid(uint64, uint64, uint64, uint64, uint64, uint64)
+{
+ TaskControlBlock* curr = current_task();
+ if (!curr || !curr->signal) return -ESRCH;
+
+ /* Detect collision: another pgrp already uses our pid as pgid. */
+ bool conflict = false;
+ for_each_pgrp_member(curr->pid, [&](signal_struct* sig) {
+ if (sig != curr->signal) conflict = true;
+ });
+ if (conflict) return -EPERM;
+
+ /* Move to new pgrp (creates one if missing), update sid, drop ctty. */
+ change_pgid(curr->signal, curr->pid);
+
+ /* Detach the controlling terminal. If this task is the session
+ * leader that owns the tty (tty->session == caller's old sid),
+ * also clear the tty's session/pgrp so the next session-leader
+ * that opens the device can claim it. Without this, tty->session
+ * permanently sticks to the abandoned sid and every subsequent
+ * tty_acquire_ctty() bails with "owned by another session" —
+ * which is exactly what kept BusyBox's child sh from picking the
+ * console back up after init's setsid().
+ *
+ * Linux's behavior is similar in spirit (disassociate_ctty when
+ * the leader exits), but it triggers from exit, not setsid. Our
+ * simpler "clear-on-give-up" matches the way our acquisition path
+ * is written. */
+ tty_struct* old_tty = nullptr;
+ int32 old_sid = 0;
+ {
+ LockGuard guard(curr->signal->lock);
+ old_tty = curr->signal->tty;
+ old_sid = curr->signal->sid;
+ curr->signal->sid = curr->pid;
+ curr->signal->tty = nullptr;
+ curr->signal->tty_old_pgrp = 0;
+ }
+ if (old_tty) {
+ LockGuard guard(old_tty->lock);
+ if (old_tty->session == old_sid) {
+ old_tty->session = 0;
+ old_tty->pgrp = 0;
+ }
+ }
+ return curr->pid;
+}
+
+/*
+ * sys_setpgid — Linux kernel/sys.c:ksys_setpgid()
+ *
+ * Move a process between groups within the same session. Allowed:
+ * - caller targeting self or a child
+ * - target is not a session leader
+ * - target stays in caller's session
+ * - target hasn't exec()'d (we don't track did_exec yet, so skip)
+ * - new pgid is either == target's pid (new group) or names a pgrp
+ * that already exists in the same session
+ */
+int64 sys_setpgid(uint64 pid, uint64 pgid, uint64, uint64, uint64, uint64)
+{
+ TaskControlBlock* curr = current_task();
+ if (!curr || !curr->signal) return -ESRCH;
+
+ int32 target_pid = static_cast<int32>(pid);
+ int32 target_pgid = static_cast<int32>(pgid);
+
+ if (target_pid == 0) target_pid = curr->pid;
+ if (target_pgid == 0) target_pgid = target_pid;
+ if (target_pgid < 0) return -EINVAL;
+
+ TaskControlBlock* target = (target_pid == curr->pid)
+ ? curr
+ : find_task_by_pid(target_pid);
+ if (!target || !target->signal) return -ESRCH;
+
+ /* Target must be caller or a child of caller. */
+ if (target != curr && target->parent != curr) return -ESRCH;
+
+ /* Same session as caller. */
+ if (target->signal->sid != curr->signal->sid) return -EPERM;
+
+ /* Cannot move a session leader into a different pgrp. */
+ if (target->signal->sid == target->pid) return -EPERM;
+
+ /* If target_pgid != target_pid, the pgrp must already exist in
+ * the same session. pgrp existence == any signal_struct with
+ * pgid == target_pgid AND sid == caller's sid. */
+ if (target_pgid != target->pid) {
+ bool found = false;
+ for_each_pgrp_member(target_pgid, [&](signal_struct* sig) {
+ if (sig->sid == curr->signal->sid) found = true;
+ });
+ if (!found) return -EPERM;
+ }
+
+ change_pgid(target->signal, target_pgid);
+ return 0;
+}
+
+/*
+ * sys_getpgid — return process group ID of `pid` (0 = self).
+ */
+int64 sys_getpgid(uint64 pid, uint64, uint64, uint64, uint64, uint64)
+{
+ TaskControlBlock* curr = current_task();
+ if (!curr || !curr->signal) return -ESRCH;
+
+ if (pid == 0) return curr->signal->pgid;
+
+ TaskControlBlock* target = find_task_by_pid(static_cast<int32>(pid));
+ if (!target || !target->signal) return -ESRCH;
+ return target->signal->pgid;
+}
+
+/*
+ * sys_getsid — return session ID of `pid` (0 = self).
+ */
+int64 sys_getsid(uint64 pid, uint64, uint64, uint64, uint64, uint64)
+{
+ TaskControlBlock* curr = current_task();
+ if (!curr || !curr->signal) return -ESRCH;
+
+ if (pid == 0) return curr->signal->sid;
+
+ TaskControlBlock* target = find_task_by_pid(static_cast<int32>(pid));
+ if (!target || !target->signal) return -ESRCH;
+
+ /* Linux: -EPERM if target's session differs from caller's and we're
+ * in a different namespace. We have one namespace, so always allow. */
+ return target->signal->sid;
+}
+
+/* ======================================================================
+ * sys_geteuid / sys_getegid — Linux kernel/sys.c
+ *
+ * Return effective UID/GID. We run everything as root (0).
+ * ====================================================================== */
+
+int64 sys_geteuid(uint64, uint64, uint64, uint64, uint64, uint64)
+{
+ /* Linux: return current_euid(). We are always root. */
+ return 0;
+}
+
+int64 sys_getegid(uint64, uint64, uint64, uint64, uint64, uint64)
+{
+ /* Linux: return current_egid(). We are always root. */
+ return 0;
+}
+
+/* ======================================================================
+ * sys_sigaltstack — Linux kernel/signal.c
+ *
+ * Set/get alternate signal stack. For now, accept and ignore
+ * (busybox doesn't critically need it for shell operation).
+ * ====================================================================== */
+
+int64 sys_sigaltstack(uint64 uss_ptr, uint64 uoss_ptr,
+ uint64, uint64, uint64, uint64)
+{
+ /* Linux: if uoss != NULL, return current alt stack info.
+ * We don't have alt stacks yet — report SS_DISABLE. */
+ if (uoss_ptr != 0) {
+ /* stack_t { void* ss_sp; int ss_flags; size_t ss_size; }
+ * On RISC-V LP64: 8 + 4 + padding + 8 = 24 bytes typically,
+ * but the ABI packs it as {uint64, int32, pad, uint64}. */
+ struct {
+ uint64 ss_sp;
+ int32 ss_flags;
+ int32 _pad;
+ uint64 ss_size;
+ } oss = { 0, 2 /* SS_DISABLE */, 0, 0 };
+ if (copy_to_user(uoss_ptr, &oss, sizeof(oss)) != 0)
+ return -EFAULT;
+ }
+
+ /* uss: silently accept (we'll need it for proper signal handling later) */
+ (void)uss_ptr;
+ return 0;
+}
+
+/* ======================================================================
+ * sys_clock_gettime — Linux kernel/time/posix-timers.c
+ *
+ * Get the current time for a given clock.
+ * We use the RISC-V cycle counter as a monotonic source.
+ * ====================================================================== */
+
+static constexpr int CLOCK_REALTIME = 0;
+static constexpr int CLOCK_MONOTONIC = 1;
+
+int64 sys_clock_gettime(uint64 clk_id, uint64 tp_ptr,
+ uint64, uint64, uint64, uint64)
+{
+ if (tp_ptr == 0) return -EFAULT;
+
+ /* Read RISC-V time CSR (mtime mapped to SBI) */
+ uint64 ticks = arch::cpu::read_time();
+
+ /* Convert to seconds + nanoseconds.
+ * QEMU virt timebase-frequency is 10MHz. */
+ constexpr uint64 kTimebaseHz = 10'000'000ULL;
+ uint64 sec = ticks / kTimebaseHz;
+ uint64 nsec = (ticks % kTimebaseHz) * (1'000'000'000ULL / kTimebaseHz);
+
+ struct timespec64 {
+ int64 tv_sec;
+ int64 tv_nsec;
+ };
+
+ timespec64 ts;
+ ts.tv_sec = static_cast<int64>(sec);
+ ts.tv_nsec = static_cast<int64>(nsec);
+
+ (void)clk_id; /* We treat all clocks as monotonic for now */
+
+ if (copy_to_user(tp_ptr, &ts, sizeof(ts)) != 0)
+ return -EFAULT;
+
+ return 0;
+}