mirror of
https://github.com/ish-app/ish.git
synced 2026-01-18 13:57:29 +00:00
323 lines
9.2 KiB
C
323 lines
9.2 KiB
C
#include "debug.h"
|
|
#include <signal.h>
|
|
#include "kernel/calls.h"
|
|
#include "kernel/signal.h"
|
|
#include "kernel/vdso.h"
|
|
|
|
int xsave_extra = 0;
|
|
int fxsave_extra = 0;
|
|
|
|
void deliver_signal(struct task *task, int sig) {
|
|
task->pending |= 1l << sig;
|
|
if (task != current)
|
|
pthread_kill(task->thread, SIGUSR1);
|
|
}
|
|
|
|
void send_signal(struct task *task, int sig) {
|
|
struct sighand *sighand = task->sighand;
|
|
lock(&sighand->lock);
|
|
if (sighand->action[sig].handler != SIG_IGN_) {
|
|
if (task->blocked & (1l << sig))
|
|
task->queued |= (1l << sig);
|
|
else
|
|
deliver_signal(task, sig);
|
|
}
|
|
unlock(&sighand->lock);
|
|
}
|
|
|
|
void send_group_signal(dword_t pgid, int sig) {
|
|
lock(&pids_lock);
|
|
struct pid *pid = pid_get(pgid);
|
|
struct task *task;
|
|
list_for_each_entry(&pid->pgroup, task, pgroup) {
|
|
send_signal(task, sig);
|
|
}
|
|
unlock(&pids_lock);
|
|
}
|
|
|
|
static void receive_signal(struct sighand *sighand, int sig) {
|
|
if (sighand->action[sig].handler == SIG_DFL_) {
|
|
switch (sig) {
|
|
// non-fatal signals
|
|
case SIGURG_: case SIGCONT_: case SIGCHLD_:
|
|
case SIGIO_: case SIGWINCH_:
|
|
break;
|
|
|
|
// most of the rest are fatal
|
|
// some stop the process, we'll leave that as unimplemented
|
|
default:
|
|
unlock(&sighand->lock); // do_exit must be called without this lock
|
|
do_exit(sig);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// setup the frame
|
|
struct sigframe_ frame = {};
|
|
frame.sig = sig;
|
|
|
|
struct cpu_state *cpu = ¤t->cpu;
|
|
frame.sc.ax = cpu->eax;
|
|
frame.sc.bx = cpu->ebx;
|
|
frame.sc.cx = cpu->ecx;
|
|
frame.sc.dx = cpu->edx;
|
|
frame.sc.di = cpu->edi;
|
|
frame.sc.si = cpu->esi;
|
|
frame.sc.bp = cpu->ebp;
|
|
frame.sc.sp = frame.sc.sp_at_signal = cpu->esp;
|
|
frame.sc.ip = cpu->eip;
|
|
collapse_flags(cpu);
|
|
frame.sc.flags = cpu->eflags;
|
|
frame.sc.trapno = cpu->trapno;
|
|
// TODO more shit
|
|
|
|
addr_t sigreturn_addr = vdso_symbol("__kernel_rt_sigreturn");
|
|
if (sigreturn_addr == 0) {
|
|
fprintf(stderr, "sigreturn not found in vdso, this should never happen\n");
|
|
abort();
|
|
}
|
|
frame.pretcode = current->mem->vdso + sigreturn_addr;
|
|
// for legacy purposes
|
|
frame.retcode.popmov = 0xb858;
|
|
frame.retcode.nr_sigreturn = 173; // rt_sigreturn
|
|
frame.retcode.int80 = 0x80cd;
|
|
|
|
// set up registers for signal handler
|
|
cpu->eax = sig;
|
|
cpu->eip = sighand->action[sig].handler;
|
|
|
|
dword_t sp = cpu->esp;
|
|
if (sighand->altstack) {
|
|
sp = sighand->altstack;
|
|
sighand->on_altstack = true;
|
|
}
|
|
if (xsave_extra) {
|
|
// do as the kernel does
|
|
// this is superhypermega condensed version of fpu__alloc_mathframe in
|
|
// arch/x86/kernel/fpu/signal.c
|
|
sp -= xsave_extra;
|
|
sp &=~ 0x3f;
|
|
sp -= fxsave_extra;
|
|
}
|
|
sp -= sizeof(struct sigframe_);
|
|
// align sp + 4 on a 16-byte boundary because that's what the abi says
|
|
sp = ((sp + 4) & ~0xf) - 4;
|
|
cpu->esp = sp;
|
|
|
|
// install frame
|
|
// nothing we can do if this fails
|
|
// TODO do something other than nothing, like printk maybe
|
|
(void) user_put(sp, frame);
|
|
|
|
current->pending &= ~(1l << sig);
|
|
}
|
|
|
|
void receive_signals() {
|
|
struct sighand *sighand = current->sighand;
|
|
lock(&sighand->lock);
|
|
if (current->pending) {
|
|
for (int sig = 0; sig < NUM_SIGS; sig++)
|
|
if (current->pending & (1l << sig))
|
|
receive_signal(sighand, sig);
|
|
}
|
|
unlock(&sighand->lock);
|
|
}
|
|
|
|
struct sighand *sighand_new() {
|
|
struct sighand *sighand = malloc(sizeof(struct sighand));
|
|
if (sighand == NULL)
|
|
return NULL;
|
|
memset(sighand, 0, sizeof(struct sighand));
|
|
sighand->refcount = 1;
|
|
lock_init(&sighand->lock);
|
|
return sighand;
|
|
}
|
|
|
|
struct sighand *sighand_copy(struct sighand *sighand) {
|
|
struct sighand *new_sighand = sighand_new();
|
|
if (new_sighand == NULL)
|
|
return NULL;
|
|
memcpy(new_sighand->action, sighand->action, sizeof(new_sighand->action));
|
|
return new_sighand;
|
|
}
|
|
|
|
void sighand_release(struct sighand *sighand) {
|
|
if (--sighand->refcount== 0) {
|
|
free(sighand);
|
|
}
|
|
}
|
|
|
|
dword_t sys_rt_sigreturn(dword_t sig) {
|
|
struct cpu_state *cpu = ¤t->cpu;
|
|
struct sigcontext_ sc;
|
|
// skip the first two fields of the frame
|
|
// the return address was popped by the ret instruction
|
|
// the signal number was popped into ebx and passed as an argument
|
|
(void) user_get(cpu->esp, sc);
|
|
// TODO check for errors in that
|
|
cpu->eax = sc.ax;
|
|
cpu->ebx = sc.bx;
|
|
cpu->ecx = sc.cx;
|
|
cpu->edx = sc.dx;
|
|
cpu->edi = sc.di;
|
|
cpu->esi = sc.si;
|
|
cpu->ebp = sc.bp;
|
|
cpu->esp = sc.sp;
|
|
cpu->eip = sc.ip;
|
|
collapse_flags(cpu);
|
|
cpu->eflags = sc.flags;
|
|
lock(¤t->sighand->lock);
|
|
current->sighand->on_altstack = false;
|
|
unlock(¤t->sighand->lock);
|
|
return cpu->eax;
|
|
}
|
|
|
|
static int do_sigaction(int sig, const struct sigaction_ *action, struct sigaction_ *oldaction) {
|
|
if (sig >= NUM_SIGS)
|
|
return _EINVAL;
|
|
if (sig == SIGKILL_ || sig == SIGSTOP_)
|
|
return _EINVAL;
|
|
|
|
struct sighand *sighand = current->sighand;
|
|
lock(&sighand->lock);
|
|
if (oldaction)
|
|
*oldaction = sighand->action[sig];
|
|
if (action)
|
|
sighand->action[sig] = *action;
|
|
unlock(&sighand->lock);
|
|
return 0;
|
|
}
|
|
|
|
dword_t sys_rt_sigaction(dword_t signum, addr_t action_addr, addr_t oldaction_addr, dword_t sigset_size) {
|
|
if (sigset_size != sizeof(sigset_t_))
|
|
return _EINVAL;
|
|
struct sigaction_ action, oldaction;
|
|
if (action_addr != 0)
|
|
if (user_get(action_addr, action))
|
|
return _EFAULT;
|
|
STRACE("rt_sigaction(%d, 0x%x, 0x%x, %d)", signum, action_addr, oldaction_addr, sigset_size);
|
|
|
|
int err = do_sigaction(signum,
|
|
action_addr ? &action : NULL,
|
|
oldaction_addr ? &oldaction : NULL);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (oldaction_addr != 0)
|
|
if (user_put(oldaction_addr, oldaction))
|
|
return _EFAULT;
|
|
return err;
|
|
}
|
|
|
|
dword_t sys_sigaction(dword_t signum, addr_t action_addr, addr_t oldaction_addr) {
|
|
return sys_rt_sigaction(signum, action_addr, oldaction_addr, 1);
|
|
}
|
|
|
|
int do_sigprocmask(dword_t how, sigset_t_ set, sigset_t_ *oldset_out) {
|
|
struct sighand *sighand = current->sighand;
|
|
lock(&sighand->lock);
|
|
sigset_t oldset = current->blocked;
|
|
|
|
if (how == SIG_BLOCK_)
|
|
current->blocked |= set;
|
|
else if (how == SIG_UNBLOCK_)
|
|
current->blocked &= ~set;
|
|
else if (how == SIG_SETMASK_)
|
|
current->blocked = set;
|
|
else {
|
|
unlock(&sighand->lock);
|
|
return _EINVAL;
|
|
}
|
|
|
|
// transfer unblocked signals from queued to pending
|
|
sigset_t_ unblocked = oldset & ~current->blocked;
|
|
current->pending |= current->queued & unblocked;
|
|
current->queued &= ~unblocked;
|
|
unlock(&sighand->lock);
|
|
if (oldset_out != NULL)
|
|
*oldset_out = current->blocked;
|
|
return 0;
|
|
}
|
|
|
|
dword_t sys_rt_sigprocmask(dword_t how, addr_t set_addr, addr_t oldset_addr, dword_t size) {
|
|
if (size != sizeof(sigset_t_))
|
|
return _EINVAL;
|
|
|
|
sigset_t_ set, oldset;
|
|
if (user_get(set_addr, set))
|
|
return _EFAULT;
|
|
STRACE("rt_sigprocmask(%s, 0x%llx, 0x%x, %d)",
|
|
how == SIG_BLOCK_ ? "SIG_BLOCK" :
|
|
how == SIG_UNBLOCK_ ? "SIG_UNBLOCK" :
|
|
how == SIG_SETMASK_ ? "SIG_SETMASK" : "??",
|
|
(long long) set, oldset_addr, size);
|
|
|
|
int err = do_sigprocmask(how, set, &oldset);
|
|
if (err < 0)
|
|
return err;
|
|
if (oldset_addr != 0)
|
|
if (user_put(oldset_addr, oldset))
|
|
return _EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
dword_t sys_sigaltstack(addr_t ss_addr, addr_t old_ss_addr) {
|
|
STRACE("sigaltstack(0x%x, 0x%x)", ss_addr, old_ss_addr);
|
|
struct sighand *sighand = current->sighand;
|
|
lock(&sighand->lock);
|
|
if (old_ss_addr != 0) {
|
|
struct stack_t_ old_ss;
|
|
old_ss.stack = sighand->altstack;
|
|
old_ss.size = sighand->altstack_size;
|
|
old_ss.flags = 0;
|
|
if (sighand->altstack == 0)
|
|
old_ss.flags |= SS_DISABLE_;
|
|
if (sighand->on_altstack)
|
|
old_ss.flags |= SS_ONSTACK_;
|
|
if (user_put(old_ss_addr, old_ss)) {
|
|
unlock(&sighand->lock);
|
|
return _EFAULT;
|
|
}
|
|
}
|
|
if (ss_addr != 0) {
|
|
if (sighand->on_altstack) {
|
|
unlock(&sighand->lock);
|
|
return _EPERM;
|
|
}
|
|
struct stack_t_ ss;
|
|
if (user_get(ss_addr, ss)) {
|
|
unlock(&sighand->lock);
|
|
return _EFAULT;
|
|
}
|
|
if (ss.flags & SS_DISABLE_) {
|
|
sighand->altstack = 0;
|
|
} else {
|
|
sighand->altstack = ss.stack;
|
|
sighand->altstack_size = ss.size;
|
|
}
|
|
}
|
|
unlock(&sighand->lock);
|
|
return 0;
|
|
}
|
|
|
|
dword_t sys_kill(dword_t pid, dword_t sig) {
|
|
STRACE("kill(%d, %d)", pid, sig);
|
|
// TODO check permissions
|
|
// TODO process groups
|
|
lock(&pids_lock);
|
|
struct task *task = pid_get_task(pid);
|
|
if (task == NULL) {
|
|
unlock(&pids_lock);
|
|
return _ESRCH;
|
|
}
|
|
// signal zero is for testing whether a process exists
|
|
if (sig != 0)
|
|
send_signal(task, sig);
|
|
unlock(&pids_lock);
|
|
return 0;
|
|
}
|
|
|
|
dword_t sys_tkill(dword_t tid, dword_t sig) {
|
|
return sys_kill(tid, sig);
|
|
}
|