/* * Copyright (c) 2011 Jiri Zarevucky * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @addtogroup libposix * @{ */ /** @file Signal handling. */ #define LIBPOSIX_INTERNAL #include "signal.h" #include "internal/common.h" #include "limits.h" #include "stdlib.h" #include "string.h" #include "errno.h" #include "libc/fibril_synch.h" #include "libc/task.h" /* This file implements a fairly dumb and incomplete "simulation" of * POSIX signals. Since HelenOS doesn't support signals and mostly doesn't * have any equivalent functionality, most of the signals are useless. The * main purpose of this implementation is thus to help port applications using * signals with minimal modification, but if the application uses signals for * anything non-trivial, it's quite probable it won't work properly even if * it builds without problems. */ /* Used to serialize signal handling. */ static FIBRIL_MUTEX_INITIALIZE(_signal_mutex); static LIST_INITIALIZE(_signal_queue); static posix_sigset_t _signal_mask = 0; #define DEFAULT_HANDLER { .sa_handler = SIG_DFL, \ .sa_mask = 0, .sa_flags = 0, .sa_sigaction = NULL } /* Actions associated with each signal number. */ static struct posix_sigaction _signal_actions[_TOP_SIGNAL + 1] = { DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER, DEFAULT_HANDLER }; /** * Default signal handler. Executes the default action for each signal, * as reasonable within HelenOS. * * @param signo Signal number. */ void __posix_default_signal_handler(int signo) { switch (signo) { case SIGABRT: abort(); case SIGQUIT: fprintf(stderr, "Quit signal raised. Exiting.\n"); exit(EXIT_FAILURE); case SIGINT: fprintf(stderr, "Interrupt signal caught. Exiting.\n"); exit(EXIT_FAILURE); case SIGTERM: fprintf(stderr, "Termination signal caught. Exiting.\n"); exit(EXIT_FAILURE); case SIGSTOP: fprintf(stderr, "Stop signal caught, but unsupported. Ignoring.\n"); break; case SIGKILL: /* This will only occur when raise or similar is called. */ /* Commit suicide. */ task_kill(task_get_id()); /* Should not be reached. */ abort(); case SIGFPE: case SIGBUS: case SIGILL: case SIGSEGV: posix_psignal(signo, "Hardware exception raised by user code"); abort(); case SIGSYS: case SIGXCPU: case SIGXFSZ: case SIGTRAP: case SIGHUP: case SIGPIPE: case SIGPOLL: case SIGURG: case SIGTSTP: case SIGTTIN: case SIGTTOU: posix_psignal(signo, "Unsupported signal caught"); abort(); case SIGCHLD: case SIGUSR1: case SIGUSR2: case SIGALRM: case SIGVTALRM: case SIGPROF: case SIGCONT: /* ignored */ break; } } /** * Just an empty function to get an unique pointer value for comparison. * * @param signo Signal number. */ void __posix_hold_signal_handler(int signo) { /* Nothing */ } /** * Empty function to be used as ignoring handler. * * @param signo Signal number. */ void __posix_ignore_signal_handler(int signo) { /* Nothing */ } /** * Clear the signal set. * * @param set Pointer to the signal set. * @return Always returns zero. */ int posix_sigemptyset(posix_sigset_t *set) { assert(set != NULL); *set = 0; return 0; } /** * Fill the signal set (i.e. add all signals). * * @param set Pointer to the signal set. * @return Always returns zero. */ int posix_sigfillset(posix_sigset_t *set) { assert(set != NULL); *set = UINT32_MAX; return 0; } /** * Add a signal to the set. * * @param set Pointer to the signal set. * @param signo Signal number to add. * @return Always returns zero. */ int posix_sigaddset(posix_sigset_t *set, int signo) { assert(set != NULL); *set |= (1 << signo); return 0; } /** * Delete a signal from the set. * * @param set Pointer to the signal set. * @param signo Signal number to remove. * @return Always returns zero. */ int posix_sigdelset(posix_sigset_t *set, int signo) { assert(set != NULL); *set &= ~(1 << signo); return 0; } /** * Inclusion test for a signal set. * * @param set Pointer to the signal set. * @param signo Signal number to query. * @return 1 if the signal is in the set, 0 otherwise. */ int posix_sigismember(const posix_sigset_t *set, int signo) { assert(set != NULL); return (*set & (1 << signo)) != 0; } /** * Unsafe variant of the sigaction() function. * Doesn't do any checking of its arguments and * does not deal with thread-safety. * * @param sig * @param act * @param oact */ static void _sigaction_unsafe(int sig, const struct posix_sigaction *restrict act, struct posix_sigaction *restrict oact) { if (oact != NULL) { memcpy(oact, &_signal_actions[sig], sizeof(struct posix_sigaction)); } if (act != NULL) { memcpy(&_signal_actions[sig], act, sizeof(struct posix_sigaction)); } } /** * Sets a new action for the given signal number. * * @param sig Signal number to set action for. * @param act If not NULL, contents of this structure are * used as the new action for the signal. * @param oact If not NULL, the original action associated with the signal * is stored in the structure pointer to. * @return -1 with errno set on failure, 0 on success. */ int posix_sigaction(int sig, const struct posix_sigaction *restrict act, struct posix_sigaction *restrict oact) { if (sig > _TOP_SIGNAL || (act != NULL && (sig == SIGKILL || sig == SIGSTOP))) { errno = EINVAL; return -1; } if (sig > _TOP_CATCHABLE_SIGNAL) { posix_psignal(sig, "WARNING: registering handler for a partially" " or fully unsupported signal. This handler may only be" " invoked by the raise() function, which may not be what" " the application developer intended"); } fibril_mutex_lock(&_signal_mutex); _sigaction_unsafe(sig, act, oact); fibril_mutex_unlock(&_signal_mutex); return 0; } /** * Sets a new handler for the given signal number. * * @param sig Signal number to set handler for. * @param func Handler function. * @return SIG_ERR on failure, original handler on success. */ void (*posix_signal(int sig, void (*func)(int)))(int) { struct posix_sigaction new = { .sa_handler = func, .sa_mask = 0, .sa_flags = 0, .sa_sigaction = NULL }; struct posix_sigaction old; if (posix_sigaction(sig, func == NULL ? NULL : &new, &old) == 0) { return old.sa_handler; } else { return SIG_ERR; } } typedef struct { link_t link; int signo; posix_siginfo_t siginfo; } signal_queue_item; /** * Queue blocked signal. * * @param signo Signal number. * @param siginfo Additional information about the signal. */ static void _queue_signal(int signo, posix_siginfo_t *siginfo) { assert(signo >= 0 && signo <= _TOP_SIGNAL); assert(siginfo != NULL); signal_queue_item *item = malloc(sizeof(signal_queue_item)); link_initialize(&(item->link)); item->signo = signo; memcpy(&item->siginfo, siginfo, sizeof(posix_siginfo_t)); list_append(&(item->link), &_signal_queue); } /** * Executes an action associated with the given signal. * * @param signo Signal number. * @param siginfo Additional information about the circumstances of this raise. * @return 0 if the action has been successfully executed. -1 if the signal is * blocked. */ static int _raise_sigaction(int signo, posix_siginfo_t *siginfo) { assert(signo >= 0 && signo <= _TOP_SIGNAL); assert(siginfo != NULL); fibril_mutex_lock(&_signal_mutex); struct posix_sigaction action = _signal_actions[signo]; if (posix_sigismember(&_signal_mask, signo) || action.sa_handler == SIG_HOLD) { _queue_signal(signo, siginfo); fibril_mutex_unlock(&_signal_mutex); return -1; } /* Modifying signal mask is unnecessary, * signal handling is serialized. */ if ((action.sa_flags & SA_RESETHAND) && signo != SIGILL && signo != SIGTRAP) { _signal_actions[signo] = (struct posix_sigaction) DEFAULT_HANDLER; } if (action.sa_flags & SA_SIGINFO) { assert(action.sa_sigaction != NULL); action.sa_sigaction(signo, siginfo, NULL); } else { assert(action.sa_handler != NULL); action.sa_handler(signo); } fibril_mutex_unlock(&_signal_mutex); return 0; } /** * Raise all unblocked previously queued signals. */ static void _dequeue_unblocked_signals() { link_t *iterator = _signal_queue.head.next; link_t *next; while (iterator != &(_signal_queue).head) { next = iterator->next; signal_queue_item *item = list_get_instance(iterator, signal_queue_item, link); if (!posix_sigismember(&_signal_mask, item->signo) && _signal_actions[item->signo].sa_handler != SIG_HOLD) { list_remove(&(item->link)); _raise_sigaction(item->signo, &(item->siginfo)); free(item); } iterator = next; } } /** * Raise a signal for the calling process. * * @param sig Signal number. * @return -1 with errno set on failure, 0 on success. */ int posix_raise(int sig) { if (sig >= 0 && sig <= _TOP_SIGNAL) { posix_siginfo_t siginfo = { .si_signo = sig, .si_code = SI_USER }; return _raise_sigaction(sig, &siginfo); } else { errno = EINVAL; return -1; } } /** * Raises a signal for a selected process. * * @param pid PID of the process for which the signal shall be raised. * @param signo Signal to raise. * @return -1 with errno set on failure (possible errors include unsupported * action, invalid signal number, lack of permissions, etc.), 0 on success. */ int posix_kill(posix_pid_t pid, int signo) { if (pid < 1) { // TODO errno = ENOTSUP; return -1; } if (signo > _TOP_SIGNAL) { errno = EINVAL; return -1; } if (pid == (posix_pid_t) task_get_id()) { return posix_raise(signo); } switch (signo) { case SIGKILL: task_kill(pid); break; default: /* Nothing else supported yet. */ errno = ENOTSUP; return -1; } return 0; } /** * Send a signal to a process group. Always fails at the moment because of * lack of this functionality in HelenOS. * * @param pid PID of the process group. * @param sig Signal number. * @return -1 on failure, 0 on success (see kill()). */ int posix_killpg(posix_pid_t pid, int sig) { assert(pid > 1); return posix_kill(-pid, sig); } /** * Outputs information about the signal to the standard error stream. * * @param pinfo SigInfo struct to write. * @param message String to output alongside human-readable signal description. */ void posix_psiginfo(const posix_siginfo_t *pinfo, const char *message) { assert(pinfo != NULL); posix_psignal(pinfo->si_signo, message); // TODO: print si_code } /** * Outputs information about the signal to the standard error stream. * * @param signum Signal number. * @param message String to output alongside human-readable signal description. */ void posix_psignal(int signum, const char *message) { char *sigmsg = posix_strsignal(signum); if (message == NULL || *message == '\0') { fprintf(stderr, "%s\n", sigmsg); } else { fprintf(stderr, "%s: %s\n", message, sigmsg); } } /** * Manipulate the signal mask of the calling thread. * * @param how What to do with the mask. * @param set Signal set to work with. * @param oset If not NULL, the original signal mask is coppied here. * @return 0 success, errorcode on failure. */ int posix_thread_sigmask(int how, const posix_sigset_t *restrict set, posix_sigset_t *restrict oset) { fibril_mutex_lock(&_signal_mutex); if (oset != NULL) { *oset = _signal_mask; } if (set != NULL) { switch (how) { case SIG_BLOCK: _signal_mask |= *set; break; case SIG_UNBLOCK: _signal_mask &= ~*set; break; case SIG_SETMASK: _signal_mask = *set; break; default: fibril_mutex_unlock(&_signal_mutex); return EINVAL; } } _dequeue_unblocked_signals(); fibril_mutex_unlock(&_signal_mutex); return 0; } /** * Manipulate the signal mask of the process. * * @param how What to do with the mask. * @param set Signal set to work with. * @param oset If not NULL, the original signal mask is coppied here. * @return 0 on success, -1 with errno set on failure. */ int posix_sigprocmask(int how, const posix_sigset_t *restrict set, posix_sigset_t *restrict oset) { int result = posix_thread_sigmask(how, set, oset); if (result != 0) { errno = result; return -1; } return 0; } /** @} */