summaryrefslogtreecommitdiff
path: root/kernel/sys_signal.cc
blob: 8b459d54c6517c5fd63af1ac60907a18b115067e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2026 Richard Qin */

/*
 * sys_signal.cc — Signal-related system calls
 *
 * Implements:
 *   sys_kill           (SYS_kill = 129)
 *   sys_rt_sigaction   (SYS_rt_sigaction = 134)
 *   sys_rt_sigprocmask (SYS_rt_sigprocmask = 135)
 *   sys_rt_sigreturn   (SYS_rt_sigreturn = 139)
 *
 * Reference: Linux kernel/signal.c, arch/riscv/kernel/signal.c
 */

#include <lume/signal.h>
#include <lume/signal_types.h>
#include <lume/signal_nr.h>
#include <lume/task.h>
#include <lume/errno.h>
#include <lume/uaccess.h>
#include <lume/kprintf.h>
#include <lume/klog.h>
#include <lume/new.h>
#include <lume/pgrp.h>

/* From signal.cc */
extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task);

/* From task.cc */
extern list_node g_all_tasks;
extern Spinlock g_task_list_lock;

/* ================================================================== */
/*  Helper: find task by PID                                          */
/* ================================================================== */

static TaskControlBlock* find_task_by_pid(int32 pid)
{
    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_kill — send a signal to a process                             */
/* ================================================================== */

/*
 * Deliver `sig` to the thread-group leader represented by `sig_st`.
 * sighand is allocated eagerly at fork/exec time, so we just trust
 * that the leader has one; send_signal returns -ESRCH if not.
 */
static int deliver_to_group(signal_struct* sig_st, int sig,
                            kernel_siginfo* info)
{
    if (!sig_st || !sig_st->leader) return 0;
    if (send_signal(sig, info, sig_st->leader) == 0) return 1;
    return 0;
}

int64 sys_kill(uint64 a0, uint64 a1, uint64, uint64, uint64, uint64)
{
    int32 pid = static_cast<int32>(a0);
    int   sig = static_cast<int>(a1);

    if (sig < 0 || sig > _NSIG) return -EINVAL;

    TaskControlBlock* curr = current_task();
    if (!curr) return -ESRCH;

    kernel_siginfo info{};
    info.si_signo = sig;
    info.si_code  = 0;  /* SI_USER */
    info._sifields._kill._pid = curr->pid;
    info._sifields._kill._uid = 0;

    /* pid > 0: deliver to that thread group's leader. */
    if (pid > 0) {
        TaskControlBlock* target = find_task_by_pid(pid);
        if (!target) return -ESRCH;
        /* Group signal: prefer the thread-group leader if available. */
        if (target->signal && target->signal->leader)
            target = target->signal->leader;
        if (sig == 0) return 0;
        return send_signal(sig, &info, target);
    }

    /* pid == 0: every member of caller's process group. */
    if (pid == 0) {
        if (!curr->signal) return -ESRCH;
        if (sig == 0) return 0;
        int delivered = 0;
        int32 target_pgid = curr->signal->pgid;
        for_each_pgrp_member(target_pgid, [&](signal_struct* s) {
            delivered += deliver_to_group(s, sig, &info);
        });
        return delivered > 0 ? 0 : -ESRCH;
    }

    /* pid == -1: every process the caller may signal, except init.
     * We approximate "every" with "every thread-group leader on every
     * pgrp bucket".  This is rare in practice (BusyBox doesn't use it
     * during normal shell operation). */
    if (pid == -1) {
        if (sig == 0) return 0;
        int delivered = 0;
        LockGuard guard(g_pgrp_lock);
        for (auto& bucket : g_pgrp_hash) {
            list_node* node = bucket.next;
            while (node != &bucket) {
                list_node* next = node->next;
                auto* s = list_entry<signal_struct,
                                     &signal_struct::pgrp_link>(node);
                if (s->leader && s->leader->pid != 1)
                    delivered += deliver_to_group(s, sig, &info);
                node = next;
            }
        }
        return delivered > 0 ? 0 : -ESRCH;
    }

    /* pid < -1: every member of pgrp |pid|. */
    int32 target_pgid = -pid;
    if (sig == 0) {
        bool any = false;
        for_each_pgrp_member(target_pgid, [&](signal_struct*) { any = true; });
        return any ? 0 : -ESRCH;
    }
    int delivered = 0;
    for_each_pgrp_member(target_pgid, [&](signal_struct* s) {
        delivered += deliver_to_group(s, sig, &info);
    });
    return delivered > 0 ? 0 : -ESRCH;
}

/* ================================================================== */
/*  sys_tkill / sys_tgkill — send a signal to a specific thread       */
/* ================================================================== */

static int64 do_tkill(int32 tgid, int32 tid, int sig)
{
    if (sig < 0 || sig > _NSIG) return -EINVAL;
    if (tid <= 0) return -EINVAL;
    TaskControlBlock* target = find_task_by_pid(tid);
    if (!target) return -ESRCH;
    /* tgid == -1 from tkill means "any tgid"; otherwise enforce match. */
    if (tgid != -1 && target->tgid != tgid) return -ESRCH;

    if (sig == 0) return 0;

    kernel_siginfo info{};
    info.si_signo = sig;
    info.si_code  = 0;  /* SI_TKILL would be -6; userland rarely checks */
    info._sifields._kill._pid = current_task() ? current_task()->pid : 0;
    info._sifields._kill._uid = 0;
    return send_signal(sig, &info, target);
}

int64 sys_tkill(uint64 a0, uint64 a1, uint64, uint64, uint64, uint64)
{
    return do_tkill(-1,
                    static_cast<int32>(a0),
                    static_cast<int>(a1));
}

int64 sys_tgkill(uint64 a0, uint64 a1, uint64 a2, uint64, uint64, uint64)
{
    return do_tkill(static_cast<int32>(a0),
                    static_cast<int32>(a1),
                    static_cast<int>(a2));
}

/* ================================================================== */
/*  sys_rt_sigaction — get/set signal disposition                     */
/* ================================================================== */

/*
 * User-space sigaction struct layout (RISC-V LP64):
 *   uint64 sa_handler;
 *   uint64 sa_flags;
 *   uint64 sa_restorer;     (SA_RESTORER)
 *   sigset_t sa_mask;       (8 bytes on LP64)
 */
struct user_sigaction {
    uint64   sa_handler;
    uint64   sa_flags;
    uint64   sa_restorer;
    sigset_t sa_mask;
};

int64 sys_rt_sigaction(uint64 a0, uint64 a1, uint64 a2, uint64 a3,
                       uint64, uint64)
{
    int sig            = static_cast<int>(a0);
    uint64 act_uaddr   = a1;   /* const struct sigaction __user * */
    uint64 oact_uaddr  = a2;   /* struct sigaction __user * */
    uint64 sigsetsize  = a3;

    if (sig < 1 || sig > _NSIG) return -EINVAL;
    if (sig_kernel_only(sig))   return -EINVAL;
    if (sigsetsize != sizeof(sigset_t)) return -EINVAL;

    TaskControlBlock* curr = current_task();
    if (!curr || !curr->sighand) return -EINVAL;

    /* All reads/writes of the action table happen under siglock —
     * Linux's do_sigaction() also takes the same lock (kernel/signal.c). */
    user_sigaction old{};
    user_sigaction act{};
    bool have_new = (act_uaddr != 0);

    if (have_new) {
        int err = copy_from_user(&act, act_uaddr, sizeof(act));
        if (err < 0) return -EFAULT;
    }

    {
        LockGuard guard(curr->sighand->siglock);
        kernel_sigaction* ksa = &curr->sighand->action[sig - 1];
        old.sa_handler  = ksa->sa_handler;
        old.sa_flags    = ksa->sa_flags;
        old.sa_restorer = ksa->sa_restorer;
        old.sa_mask     = ksa->sa_mask;

        if (have_new) {
            ksa->sa_handler  = act.sa_handler;
            ksa->sa_flags    = act.sa_flags;
            ksa->sa_restorer = act.sa_restorer;
            ksa->sa_mask     = act.sa_mask;
            ksa->sa_mask.del(SIGKILL);
            ksa->sa_mask.del(SIGSTOP);
        }
    }

    if (oact_uaddr != 0) {
        int err = copy_to_user(oact_uaddr, &old, sizeof(old));
        if (err < 0) return -EFAULT;
    }
    return 0;
}

/* ================================================================== */
/*  sys_rt_sigprocmask — change signal blocked mask                   */
/* ================================================================== */

int64 sys_rt_sigprocmask(uint64 a0, uint64 a1, uint64 a2, uint64 a3,
                         uint64, uint64)
{
    int    how        = static_cast<int>(a0);
    uint64 set_uaddr  = a1;
    uint64 oset_uaddr = a2;
    uint64 sigsetsize = a3;

    if (sigsetsize != sizeof(sigset_t)) return -EINVAL;

    TaskControlBlock* curr = current_task();
    if (!curr || !curr->sighand) return -EINVAL;

    sigset_t newset{};
    bool have_new = (set_uaddr != 0);
    if (have_new) {
        int err = copy_from_user(&newset, set_uaddr, sizeof(sigset_t));
        if (err < 0) return -EFAULT;
        newset.del(SIGKILL);
        newset.del(SIGSTOP);
    }

    sigset_t old{};
    {
        /* Linux: kernel/signal.c:do_sigprocmask() also holds siglock
         * across read+update so the snapshot is atomic vs. concurrent
         * signal delivery. */
        LockGuard guard(curr->sighand->siglock);
        old = curr->blocked;
        if (have_new) {
            switch (how) {
            case SIG_BLOCK:
                curr->blocked = curr->blocked | newset;
                break;
            case SIG_UNBLOCK:
                curr->blocked = curr->blocked & ~newset;
                break;
            case SIG_SETMASK:
                curr->blocked = newset;
                break;
            default:
                return -EINVAL;
            }
        }
    }

    if (oset_uaddr != 0) {
        int err = copy_to_user(oset_uaddr, &old, sizeof(sigset_t));
        if (err < 0) return -EFAULT;
    }
    return 0;
}

/* ================================================================== */
/*  sys_rt_sigreturn — restore context after signal handler           */
/* ================================================================== */

/* Arch-specific: implemented in arch/riscv/signal.cc */
extern int64 arch_sigreturn(arch::TrapFrame* tf);

int64 sys_rt_sigreturn(uint64, uint64, uint64, uint64, uint64, uint64)
{
    TaskControlBlock* curr = current_task();
    if (!curr || !curr->trapframe) return -EINVAL;

    return arch_sigreturn(curr->trapframe);
}

/* ================================================================== */
/*  sys_rt_sigtimedwait (SYS_rt_sigtimedwait = 137)                   */
/* ================================================================== */
#include <lume/timer.h>
#include <lume/sched.h>

struct linux_timespec {
    int64 tv_sec;
    int64 tv_nsec;
};

int64 sys_rt_sigtimedwait(uint64 /* set_ptr */, uint64 /* info_ptr */, uint64 timeout_ptr, uint64 /* sigsetsize */, uint64, uint64)
{
    /*
     * COMPATIBILITY STUB:
     * LumeOS lacks full real-time signal queues and timed waiting mechanism.
     * When BusyBox init runs out of children to reap, it calls this to wait 
     * for incoming signals (like SIGCHLD) with a timeout. 
     * We simulate the timeout by yielding the CPU, then return -EAGAIN (Try again) 
     * which tells user space that the timeout expired without receiving a signal.
     */
    if (timeout_ptr != 0) {
        linux_timespec req;
        if (copy_from_user(&req, timeout_ptr, sizeof(req)) == 0) {
            uint64 target = g_jiffies + (req.tv_sec * 100) + (req.tv_nsec / 10000000);
            while (g_jiffies < target) {
                schedule();
            }
        }
    }
    return -EAGAIN;
}