/* -*- mode:c; c-file-style:"k&r"; c-basic-offset: 4; tab-width:4; indent-tabs-mode:nil; mode:auto-fill; fill-column:78; -*- */ /* vim: set ts=4 sw=4 et tw=78 fo=cqt wm=0: */ /* Copyright (C) 2014 Stony Brook University This file is part of Graphene Library OS. Graphene Library OS is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Graphene Library OS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ /* * shim_sigaction.c * * Implementation of system call "sigaction", "sigreturn", "sigprocmask", * "kill", "tkill" and "tgkill". */ #include #include #include #include #include #include #include #include #include #include int shim_do_sigaction (int signum, const struct __kernel_sigaction * act, struct __kernel_sigaction * oldact, size_t sigsetsize) { /* SIGKILL and SIGSTOP cannot be caught or ignored */ if (signum == SIGKILL || signum == SIGSTOP || signum <= 0 || signum > NUM_SIGS || sigsetsize != sizeof(__sigset_t)) return -EINVAL; struct shim_thread * cur = get_cur_thread(); int err = 0; assert(!act || (void *) act->k_sa_handler != (void *) 0x11); struct shim_signal_handle * sighdl = &cur->signal_handles[signum - 1]; lock(cur->lock); if (oldact) { if (sighdl->action) { memcpy(oldact, sighdl->action, sizeof(struct __kernel_sigaction)); } else { memset(oldact, 0, sizeof(struct __kernel_sigaction)); oldact->k_sa_handler = SIG_DFL; } } if (act) { if (!(sighdl->action)) sighdl->action = malloc(sizeof(struct __kernel_sigaction)); if (!(sighdl->action)) { err = -ENOMEM; goto out; } memcpy(sighdl->action, act, sizeof(struct __kernel_sigaction)); } err = 0; out: unlock(cur->lock); return err; } int shim_do_sigreturn (int __unused) { /* do nothing */ return 0; } int shim_do_sigprocmask (int how, const __sigset_t * set, __sigset_t * oldset) { __sigset_t * old, tmp, set_tmp; if (how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK) return -EINVAL; struct shim_thread * cur = get_cur_thread(); int err = 0; lock(cur->lock); old = get_sig_mask(cur); if (oldset) { memcpy(&tmp, old, sizeof(__sigset_t)); old = &tmp; } /* if set is NULL, then the signal mask is unchanged, but the current value of the signal mask is nevertheless returned in oldset */ if (!set) goto out; memcpy(&set_tmp, old, sizeof(__sigset_t)); switch (how) { case SIG_BLOCK: __sigorset(&set_tmp, &set_tmp, set); break; case SIG_UNBLOCK: __signotset(&set_tmp, &set_tmp, set); break; case SIG_SETMASK: memcpy(&set_tmp, set, sizeof(__sigset_t)); break; } set_sig_mask(cur, &set_tmp); out: unlock(cur->lock); if (!err && oldset) memcpy(oldset, old, sizeof(__sigset_t)); return err; } int shim_do_sigaltstack (const stack_t * ss, stack_t * oss) { if (ss && (ss->ss_flags & ~SS_DISABLE)) return -EINVAL; struct shim_thread * cur = get_cur_thread(); lock(cur->lock); if (oss) *oss = cur->signal_altstack; if (ss) { if (ss->ss_size < MINSIGSTKSZ) { unlock(cur->lock); return -ENOMEM; } cur->signal_altstack = *ss; } unlock(cur->lock); return 0; } int shim_do_sigsuspend (const __sigset_t * mask) { __sigset_t * old, tmp; struct shim_thread * cur = get_cur_thread(); lock(cur->lock); old = get_sig_mask(cur); memcpy(&tmp, old, sizeof(__sigset_t)); old = &tmp; set_sig_mask(cur, mask); cur->suspend_on_signal = true; thread_setwait(NULL, NULL); thread_sleep(NO_TIMEOUT); unlock(cur->lock); set_sig_mask(cur, old); return -EINTR; } int shim_do_sigpending (__sigset_t * set, size_t sigsetsize) { struct shim_thread * cur = get_cur_thread(); __sigemptyset(set); if (!cur->signal_logs) return 0; for (int sig = 1 ; sig <= NUM_SIGS ; sig++) { if (atomic_read(&cur->signal_logs[sig - 1].head) != atomic_read(&cur->signal_logs[sig - 1].tail)) __sigaddset(set, sig); } return 0; } struct walk_arg { struct shim_thread * current; IDTYPE sender; IDTYPE id; int sig; bool use_ipc; }; static inline void __append_signal (struct shim_thread * thread, int sig, IDTYPE sender) { debug("Thread %d killed by signal %d\n", thread->tid, sig); siginfo_t info; memset(&info, 0, sizeof(siginfo_t)); info.si_signo = sig; info.si_pid = sender; append_signal(thread, sig, &info, true); } static int __kill_proc (struct shim_thread * thread, void * arg, bool * unlocked) { struct walk_arg * warg = (struct walk_arg *) arg; int srched = 0; if (!warg->use_ipc && !thread->in_vm) return 0; if (thread->tgid != warg->id) return 0; if (warg->current == thread) return 1; /* DEP: Let's do a racy read of is_alive and in_vm. * If either of these are zero it is a stable condition, * and we can elide the lock acquire (which helps perf). */ if (!thread->is_alive) goto out; if (!thread->in_vm) { unlock(thread_list_lock); *unlocked = true; return (!ipc_pid_kill_send(warg->sender, warg->id, KILL_PROCESS, warg->sig)) ? 1 : 0; } else { lock(thread->lock); if (!thread->is_alive) goto out_locked; if (thread->in_vm) { if (warg->sig > 0) __append_signal(thread, warg->sig, warg->sender); srched = 1; } else { /* This double-check case is probably unnecessary, but keep it for now */ unlock(thread->lock); unlock(thread_list_lock); *unlocked = true; return (!ipc_pid_kill_send(warg->sender, warg->id, KILL_PROCESS, warg->sig)) ? 1 : 0; } } out_locked: unlock(thread->lock); out: return srched; } static int __kill_proc_simple (struct shim_simple_thread * sthread, void * arg, bool * unlocked) { struct walk_arg * warg = (struct walk_arg *) arg; int srched = 0; if (sthread->tgid != warg->id) return 0; lock(sthread->lock); if (sthread->is_alive) { unlock(sthread->lock); unlock(thread_list_lock); *unlocked = true; return (!ipc_pid_kill_send(warg->sender, warg->id, KILL_PROCESS, warg->sig)) ? 1 : 0; } unlock(sthread->lock); return srched; } int do_kill_proc (IDTYPE sender, IDTYPE tgid, int sig, bool use_ipc) { struct shim_thread * cur = get_cur_thread(); if (!tgid) { /* DEP: cur->tgid never changes. No lock needed */ tgid = cur->tgid; } struct walk_arg arg; arg.current = cur; arg.sender = sender; arg.id = tgid; arg.sig = sig; arg.use_ipc = use_ipc; bool srched = false; if (!walk_thread_list(__kill_proc, &arg, false)) srched = true; if (!use_ipc || srched) goto out; if (!walk_simple_thread_list(__kill_proc_simple, &arg, false)) srched = true; if (!srched && !ipc_pid_kill_send(sender, tgid, KILL_PROCESS, sig)) srched = true; out: return srched ? 0 : -ESRCH; } static int __kill_pgroup (struct shim_thread * thread, void * arg, bool * unlocked) { struct walk_arg * warg = (struct walk_arg *) arg; int srched = 0; if (!warg->use_ipc && !thread->in_vm) return 0; if (thread->pgid != warg->id) return 0; if (warg->current == thread) return 1; lock(thread->lock); if (!thread->is_alive) goto out; if (thread->in_vm) { if (warg->sig > 0) __append_signal(thread, warg->sig, warg->sender); srched = 1; } else { unlock(thread->lock); unlock(thread_list_lock); *unlocked = true; return (!ipc_pid_kill_send(warg->sender, warg->id, KILL_PGROUP, warg->sig)) ? 1 : 0; } out: unlock(thread->lock); return srched; } static int __kill_pgroup_simple (struct shim_simple_thread * sthread, void * arg, bool * unlocked) { struct walk_arg * warg = (struct walk_arg *) arg; int srched = 0; if (sthread->pgid != warg->id) return 0; lock(sthread->lock); if (sthread->is_alive) { unlock(sthread->lock); unlock(thread_list_lock); *unlocked = true; return (!ipc_pid_kill_send(warg->sender, warg->id, KILL_PGROUP, warg->sig)) ? 1 : 0; } unlock(sthread->lock); return srched; } int do_kill_pgroup (IDTYPE sender, IDTYPE pgid, int sig, bool use_ipc) { struct shim_thread * cur = get_cur_thread(); if (!pgid) { pgid = cur->pgid; } struct walk_arg arg; arg.current = cur; arg.sender = sender; arg.id = pgid; arg.sig = sig; arg.use_ipc = use_ipc; bool srched = false; if (!walk_thread_list(__kill_pgroup, &arg, false)) srched = true; if (!use_ipc || srched) goto out; if (!walk_simple_thread_list(__kill_pgroup_simple, &arg, false)) srched = true; if (!srched && !ipc_pid_kill_send(sender, pgid, KILL_PGROUP, sig)) srched = true; out: return srched ? 0 : -ESRCH; } static int __kill_all_threads (struct shim_thread * thread, void * arg, bool * unlocked) { int srched = 0; struct walk_arg * warg = (struct walk_arg *) arg; if (thread->tgid != thread->tid) return 0; if (warg->current == thread) return 1; lock(thread->lock); if (thread->in_vm) { __append_signal(thread, warg->sig, warg->sender); srched = 1; } unlock(thread->lock); return srched; } int broadcast_signal (IDTYPE sender, int sig); int kill_all_threads (struct shim_thread * cur, IDTYPE sender, int sig) { struct walk_arg arg; arg.current = cur; arg.sender = sender; arg.id = 0; arg.sig = sig; arg.use_ipc = false; walk_thread_list(__kill_all_threads, &arg, false); return 0; } int shim_do_kill (pid_t pid, int sig) { INC_PROFILE_OCCURENCE(syscall_use_ipc); if (sig < 0 || sig > NUM_SIGS) return -EINVAL; struct shim_thread * cur = get_cur_thread(); int ret = 0; bool send_to_self = false; /* If pid equals 0, then sig is sent to every process in the process group of the calling process. */ if (pid == 0) { ret = do_kill_pgroup(cur->tgid, 0, sig, true); send_to_self = true; } /* If pid equals -1, then sig is sent to every process for which the calling process has permission to send */ else if (pid == -1) { broadcast_signal(cur->tid, sig); kill_all_threads(cur, cur->tid, sig); send_to_self = true; } /* If pid is positive, then signal sig is sent to the process with the ID specified by pid. */ else if (pid > 0) { ret = do_kill_proc(cur->tid, pid, sig, true); send_to_self = (pid == cur->tgid); } /* If pid is less than -1, then sig is sent to every process in the process group whose id is -pid */ else { ret = do_kill_pgroup(cur->tid, -pid, sig, true); send_to_self = (-pid == cur->pgid); } if (send_to_self) { if (ret == -ESRCH) ret = 0; if (sig) { siginfo_t info; memset(&info, 0, sizeof(siginfo_t)); info.si_signo = sig; info.si_pid = cur->tid; deliver_signal(&info, NULL); } } return ret < 0 ? ret : 0; } int do_kill_thread (IDTYPE sender, IDTYPE tgid, IDTYPE tid, int sig, bool use_ipc) { if (sig < 0 || sig > NUM_SIGS) return -EINVAL; struct shim_thread * thread = lookup_thread(tid); int ret = 0; if (thread) { lock(thread->lock); if (thread->in_vm) { if (!tgid || thread->tgid == tgid) __append_signal(thread, sig, sender); else ret = -ESRCH; } else { unlock(thread->lock); return ipc_pid_kill_send(sender, tid, KILL_THREAD, sig); } unlock(thread->lock); return ret; } if (!use_ipc) return -ESRCH; return ipc_pid_kill_send(sender, tid, KILL_THREAD, sig); } int shim_do_tkill (pid_t tid, int sig) { INC_PROFILE_OCCURENCE(syscall_use_ipc); if (tid <= 0) return -EINVAL; struct shim_thread * cur = get_cur_thread(); if (tid == cur->tid) { if (sig) { siginfo_t info; memset(&info, 0, sizeof(siginfo_t)); info.si_signo = sig; info.si_pid = cur->tid; deliver_signal(&info, NULL); } return 0; } return do_kill_thread(cur->tgid, 0, tid, sig, true); } int shim_do_tgkill (pid_t tgid, pid_t tid, int sig) { INC_PROFILE_OCCURENCE(syscall_use_ipc); if (tgid < -1 || tgid == 0 || tid <= 0) return -EINVAL; if (tgid == -1) tgid = 0; struct shim_thread * cur = get_cur_thread(); if (tid == cur->tid) { if (sig) { siginfo_t info; memset(&info, 0, sizeof(siginfo_t)); info.si_signo = sig; info.si_pid = cur->tid; deliver_signal(&info, NULL); } return 0; } return do_kill_thread(cur->tgid, tgid, tid, sig, true); }