mirror of
https://github.com/samba-team/samba.git
synced 2024-12-25 23:21:54 +03:00
8a9b8ac724
Signalling the main event loop will also happen from threads soon, and that will use the same mechanism. This also keeps the pipe open after the last signal handler is removed. Threaded jobs will come and go very frequently, and always setting up and tearing down the pipe for each job will be expensive. Also, this is "just" two file descriptors, and with eventfd just one. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Stefan Metzmacher <metze@samba.org> Reviewed-by: Jeremy Allison <jra@samba.org>
481 lines
13 KiB
C
481 lines
13 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
common events code for signal events
|
|
|
|
Copyright (C) Andrew Tridgell 2007
|
|
|
|
** NOTE! The following LGPL license applies to the tevent
|
|
** library. This does NOT imply that all of Samba is released
|
|
** under the LGPL
|
|
|
|
This library 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.
|
|
|
|
This library 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 library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "replace.h"
|
|
#include "system/filesys.h"
|
|
#include "system/wait.h"
|
|
#include "tevent.h"
|
|
#include "tevent_internal.h"
|
|
#include "tevent_util.h"
|
|
|
|
/* maximum number of SA_SIGINFO signals to hold in the queue.
|
|
NB. This *MUST* be a power of 2, in order for the ring buffer
|
|
wrap to work correctly. Thanks to Petr Vandrovec <petr@vandrovec.name>
|
|
for this. */
|
|
|
|
#define TEVENT_SA_INFO_QUEUE_COUNT 256
|
|
|
|
size_t tevent_num_signals(void)
|
|
{
|
|
return TEVENT_NUM_SIGNALS;
|
|
}
|
|
|
|
size_t tevent_sa_info_queue_count(void)
|
|
{
|
|
return TEVENT_SA_INFO_QUEUE_COUNT;
|
|
}
|
|
|
|
struct tevent_sigcounter {
|
|
uint32_t count;
|
|
uint32_t seen;
|
|
};
|
|
|
|
#if defined(HAVE___SYNC_FETCH_AND_ADD)
|
|
#define TEVENT_SIG_INCREMENT(s) __sync_fetch_and_add(&((s).count), 1)
|
|
#elif defined(HAVE_ATOMIC_ADD_32)
|
|
#define TEVENT_SIG_INCREMENT(s) atomic_add_32(&((s).count), 1)
|
|
#else
|
|
#define TEVENT_SIG_INCREMENT(s) (s).count++
|
|
#endif
|
|
#define TEVENT_SIG_SEEN(s, n) (s).seen += (n)
|
|
#define TEVENT_SIG_PENDING(s) ((s).seen != (s).count)
|
|
|
|
struct tevent_common_signal_list {
|
|
struct tevent_common_signal_list *prev, *next;
|
|
struct tevent_signal *se;
|
|
};
|
|
|
|
/*
|
|
the poor design of signals means that this table must be static global
|
|
*/
|
|
static struct tevent_sig_state {
|
|
struct tevent_common_signal_list *sig_handlers[TEVENT_NUM_SIGNALS+1];
|
|
struct sigaction *oldact[TEVENT_NUM_SIGNALS+1];
|
|
struct tevent_sigcounter signal_count[TEVENT_NUM_SIGNALS+1];
|
|
struct tevent_sigcounter got_signal;
|
|
#ifdef SA_SIGINFO
|
|
/* with SA_SIGINFO we get quite a lot of info per signal */
|
|
siginfo_t *sig_info[TEVENT_NUM_SIGNALS+1];
|
|
struct tevent_sigcounter sig_blocked[TEVENT_NUM_SIGNALS+1];
|
|
#endif
|
|
} *sig_state;
|
|
|
|
/*
|
|
return number of sigcounter events not processed yet
|
|
*/
|
|
static uint32_t tevent_sig_count(struct tevent_sigcounter s)
|
|
{
|
|
return s.count - s.seen;
|
|
}
|
|
|
|
/*
|
|
signal handler - redirects to registered signals
|
|
*/
|
|
static void tevent_common_signal_handler(int signum)
|
|
{
|
|
struct tevent_common_signal_list *sl;
|
|
struct tevent_context *ev = NULL;
|
|
int saved_errno = errno;
|
|
|
|
TEVENT_SIG_INCREMENT(sig_state->signal_count[signum]);
|
|
TEVENT_SIG_INCREMENT(sig_state->got_signal);
|
|
|
|
/* Write to each unique event context. */
|
|
for (sl = sig_state->sig_handlers[signum]; sl; sl = sl->next) {
|
|
if (sl->se->event_ctx && sl->se->event_ctx != ev) {
|
|
ev = sl->se->event_ctx;
|
|
tevent_common_wakeup(ev);
|
|
}
|
|
}
|
|
|
|
errno = saved_errno;
|
|
}
|
|
|
|
#ifdef SA_SIGINFO
|
|
/*
|
|
signal handler with SA_SIGINFO - redirects to registered signals
|
|
*/
|
|
static void tevent_common_signal_handler_info(int signum, siginfo_t *info,
|
|
void *uctx)
|
|
{
|
|
uint32_t count = tevent_sig_count(sig_state->signal_count[signum]);
|
|
/* sig_state->signal_count[signum].seen % TEVENT_SA_INFO_QUEUE_COUNT
|
|
* is the base of the unprocessed signals in the ringbuffer. */
|
|
uint32_t ofs = (sig_state->signal_count[signum].seen + count) %
|
|
TEVENT_SA_INFO_QUEUE_COUNT;
|
|
sig_state->sig_info[signum][ofs] = *info;
|
|
|
|
tevent_common_signal_handler(signum);
|
|
|
|
/* handle SA_SIGINFO */
|
|
if (count+1 == TEVENT_SA_INFO_QUEUE_COUNT) {
|
|
/* we've filled the info array - block this signal until
|
|
these ones are delivered */
|
|
#ifdef HAVE_UCONTEXT_T
|
|
/*
|
|
* This is the only way for this to work.
|
|
* By default signum is blocked inside this
|
|
* signal handler using a temporary mask,
|
|
* but what we really need to do now is
|
|
* block it in the callers mask, so it
|
|
* stays blocked when the temporary signal
|
|
* handler mask is replaced when we return
|
|
* from here. The callers mask can be found
|
|
* in the ucontext_t passed in as the
|
|
* void *uctx argument.
|
|
*/
|
|
ucontext_t *ucp = (ucontext_t *)uctx;
|
|
sigaddset(&ucp->uc_sigmask, signum);
|
|
#else
|
|
/*
|
|
* WARNING !!! WARNING !!!!
|
|
*
|
|
* This code doesn't work.
|
|
* By default signum is blocked inside this
|
|
* signal handler, but calling sigprocmask
|
|
* modifies the temporary signal mask being
|
|
* used *inside* this handler, which will be
|
|
* replaced by the callers signal mask once
|
|
* we return from here. See Samba
|
|
* bug #9550 for details.
|
|
*/
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigaddset(&set, signum);
|
|
sigprocmask(SIG_BLOCK, &set, NULL);
|
|
#endif
|
|
TEVENT_SIG_INCREMENT(sig_state->sig_blocked[signum]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int tevent_common_signal_list_destructor(struct tevent_common_signal_list *sl)
|
|
{
|
|
if (sig_state->sig_handlers[sl->se->signum]) {
|
|
DLIST_REMOVE(sig_state->sig_handlers[sl->se->signum], sl);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
destroy a signal event
|
|
*/
|
|
static int tevent_signal_destructor(struct tevent_signal *se)
|
|
{
|
|
struct tevent_common_signal_list *sl =
|
|
talloc_get_type_abort(se->additional_data,
|
|
struct tevent_common_signal_list);
|
|
|
|
if (se->event_ctx) {
|
|
struct tevent_context *ev = se->event_ctx;
|
|
|
|
DLIST_REMOVE(ev->signal_events, se);
|
|
}
|
|
|
|
talloc_free(sl);
|
|
|
|
if (sig_state->sig_handlers[se->signum] == NULL) {
|
|
/* restore old handler, if any */
|
|
if (sig_state->oldact[se->signum]) {
|
|
sigaction(se->signum, sig_state->oldact[se->signum], NULL);
|
|
talloc_free(sig_state->oldact[se->signum]);
|
|
sig_state->oldact[se->signum] = NULL;
|
|
}
|
|
#ifdef SA_SIGINFO
|
|
if (se->sa_flags & SA_SIGINFO) {
|
|
if (sig_state->sig_info[se->signum]) {
|
|
talloc_free(sig_state->sig_info[se->signum]);
|
|
sig_state->sig_info[se->signum] = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
add a signal event
|
|
return NULL on failure (memory allocation error)
|
|
*/
|
|
struct tevent_signal *tevent_common_add_signal(struct tevent_context *ev,
|
|
TALLOC_CTX *mem_ctx,
|
|
int signum,
|
|
int sa_flags,
|
|
tevent_signal_handler_t handler,
|
|
void *private_data,
|
|
const char *handler_name,
|
|
const char *location)
|
|
{
|
|
struct tevent_signal *se;
|
|
struct tevent_common_signal_list *sl;
|
|
sigset_t set, oldset;
|
|
int ret;
|
|
|
|
ret = tevent_common_wakeup_init(ev);
|
|
if (ret != 0) {
|
|
errno = ret;
|
|
return NULL;
|
|
}
|
|
|
|
if (signum >= TEVENT_NUM_SIGNALS) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
/* the sig_state needs to be on a global context as it can last across
|
|
multiple event contexts */
|
|
if (sig_state == NULL) {
|
|
sig_state = talloc_zero(NULL, struct tevent_sig_state);
|
|
if (sig_state == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
se = talloc(mem_ctx?mem_ctx:ev, struct tevent_signal);
|
|
if (se == NULL) return NULL;
|
|
|
|
se->event_ctx = ev;
|
|
se->signum = signum;
|
|
se->sa_flags = sa_flags;
|
|
se->handler = handler;
|
|
se->private_data = private_data;
|
|
se->handler_name = handler_name;
|
|
se->location = location;
|
|
se->additional_data = NULL;
|
|
|
|
sl = talloc(se, struct tevent_common_signal_list);
|
|
if (!sl) {
|
|
talloc_free(se);
|
|
return NULL;
|
|
}
|
|
sl->se = se;
|
|
se->additional_data = sl;
|
|
|
|
/* Ensure, no matter the destruction order, that we always have a handle on the global sig_state */
|
|
if (!talloc_reference(se, sig_state)) {
|
|
talloc_free(se);
|
|
return NULL;
|
|
}
|
|
|
|
/* only install a signal handler if not already installed */
|
|
if (sig_state->sig_handlers[signum] == NULL) {
|
|
struct sigaction act;
|
|
ZERO_STRUCT(act);
|
|
act.sa_handler = tevent_common_signal_handler;
|
|
act.sa_flags = sa_flags;
|
|
#ifdef SA_SIGINFO
|
|
if (sa_flags & SA_SIGINFO) {
|
|
act.sa_handler = NULL;
|
|
act.sa_sigaction = tevent_common_signal_handler_info;
|
|
if (sig_state->sig_info[signum] == NULL) {
|
|
sig_state->sig_info[signum] =
|
|
talloc_zero_array(sig_state, siginfo_t,
|
|
TEVENT_SA_INFO_QUEUE_COUNT);
|
|
if (sig_state->sig_info[signum] == NULL) {
|
|
talloc_free(se);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
sig_state->oldact[signum] = talloc(sig_state, struct sigaction);
|
|
if (sig_state->oldact[signum] == NULL) {
|
|
talloc_free(se);
|
|
return NULL;
|
|
}
|
|
if (sigaction(signum, &act, sig_state->oldact[signum]) == -1) {
|
|
talloc_free(sig_state->oldact[signum]);
|
|
sig_state->oldact[signum] = NULL;
|
|
talloc_free(se);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
DLIST_ADD(se->event_ctx->signal_events, se);
|
|
|
|
/* Make sure the signal doesn't come in while we're mangling list. */
|
|
sigemptyset(&set);
|
|
sigaddset(&set, signum);
|
|
sigprocmask(SIG_BLOCK, &set, &oldset);
|
|
DLIST_ADD(sig_state->sig_handlers[signum], sl);
|
|
sigprocmask(SIG_SETMASK, &oldset, NULL);
|
|
|
|
talloc_set_destructor(se, tevent_signal_destructor);
|
|
talloc_set_destructor(sl, tevent_common_signal_list_destructor);
|
|
|
|
return se;
|
|
}
|
|
|
|
struct tevent_se_exists {
|
|
struct tevent_se_exists **myself;
|
|
};
|
|
|
|
static int tevent_se_exists_destructor(struct tevent_se_exists *s)
|
|
{
|
|
*s->myself = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
check if a signal is pending
|
|
return != 0 if a signal was pending
|
|
*/
|
|
int tevent_common_check_signal(struct tevent_context *ev)
|
|
{
|
|
int i;
|
|
|
|
if (!sig_state || !TEVENT_SIG_PENDING(sig_state->got_signal)) {
|
|
return 0;
|
|
}
|
|
|
|
for (i=0;i<TEVENT_NUM_SIGNALS+1;i++) {
|
|
struct tevent_common_signal_list *sl, *next;
|
|
struct tevent_sigcounter counter = sig_state->signal_count[i];
|
|
uint32_t count = tevent_sig_count(counter);
|
|
#ifdef SA_SIGINFO
|
|
/* Ensure we null out any stored siginfo_t entries
|
|
* after processing for debugging purposes. */
|
|
bool clear_processed_siginfo = false;
|
|
#endif
|
|
|
|
if (count == 0) {
|
|
continue;
|
|
}
|
|
for (sl=sig_state->sig_handlers[i];sl;sl=next) {
|
|
struct tevent_signal *se = sl->se;
|
|
struct tevent_se_exists *exists;
|
|
|
|
next = sl->next;
|
|
|
|
/*
|
|
* We have to be careful to not touch "se"
|
|
* after it was deleted in its handler. Thus
|
|
* we allocate a child whose destructor will
|
|
* tell by nulling out itself that its parent
|
|
* is gone.
|
|
*/
|
|
exists = talloc(se, struct tevent_se_exists);
|
|
if (exists == NULL) {
|
|
continue;
|
|
}
|
|
exists->myself = &exists;
|
|
talloc_set_destructor(
|
|
exists, tevent_se_exists_destructor);
|
|
|
|
#ifdef SA_SIGINFO
|
|
if (se->sa_flags & SA_SIGINFO) {
|
|
uint32_t j;
|
|
|
|
clear_processed_siginfo = true;
|
|
|
|
for (j=0;j<count;j++) {
|
|
/* sig_state->signal_count[i].seen
|
|
* % TEVENT_SA_INFO_QUEUE_COUNT is
|
|
* the base position of the unprocessed
|
|
* signals in the ringbuffer. */
|
|
uint32_t ofs = (counter.seen + j)
|
|
% TEVENT_SA_INFO_QUEUE_COUNT;
|
|
se->handler(ev, se, i, 1,
|
|
(void*)&sig_state->sig_info[i][ofs],
|
|
se->private_data);
|
|
if (!exists) {
|
|
break;
|
|
}
|
|
}
|
|
#ifdef SA_RESETHAND
|
|
if (exists && (se->sa_flags & SA_RESETHAND)) {
|
|
talloc_free(se);
|
|
}
|
|
#endif
|
|
talloc_free(exists);
|
|
continue;
|
|
}
|
|
#endif
|
|
se->handler(ev, se, i, count, NULL, se->private_data);
|
|
#ifdef SA_RESETHAND
|
|
if (exists && (se->sa_flags & SA_RESETHAND)) {
|
|
talloc_free(se);
|
|
}
|
|
#endif
|
|
talloc_free(exists);
|
|
}
|
|
|
|
#ifdef SA_SIGINFO
|
|
if (clear_processed_siginfo && sig_state->sig_info[i] != NULL) {
|
|
uint32_t j;
|
|
for (j=0;j<count;j++) {
|
|
uint32_t ofs = (counter.seen + j)
|
|
% TEVENT_SA_INFO_QUEUE_COUNT;
|
|
memset((void*)&sig_state->sig_info[i][ofs],
|
|
'\0',
|
|
sizeof(siginfo_t));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TEVENT_SIG_SEEN(sig_state->signal_count[i], count);
|
|
TEVENT_SIG_SEEN(sig_state->got_signal, count);
|
|
|
|
#ifdef SA_SIGINFO
|
|
if (TEVENT_SIG_PENDING(sig_state->sig_blocked[i])) {
|
|
/* We'd filled the queue, unblock the
|
|
signal now the queue is empty again.
|
|
Note we MUST do this after the
|
|
TEVENT_SIG_SEEN(sig_state->signal_count[i], count)
|
|
call to prevent a new signal running
|
|
out of room in the sig_state->sig_info[i][]
|
|
ring buffer. */
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigaddset(&set, i);
|
|
TEVENT_SIG_SEEN(sig_state->sig_blocked[i],
|
|
tevent_sig_count(sig_state->sig_blocked[i]));
|
|
sigprocmask(SIG_UNBLOCK, &set, NULL);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void tevent_cleanup_pending_signal_handlers(struct tevent_signal *se)
|
|
{
|
|
struct tevent_common_signal_list *sl =
|
|
talloc_get_type_abort(se->additional_data,
|
|
struct tevent_common_signal_list);
|
|
|
|
tevent_common_signal_list_destructor(sl);
|
|
|
|
if (sig_state->sig_handlers[se->signum] == NULL) {
|
|
if (sig_state->oldact[se->signum]) {
|
|
sigaction(se->signum, sig_state->oldact[se->signum], NULL);
|
|
talloc_free(sig_state->oldact[se->signum]);
|
|
sig_state->oldact[se->signum] = NULL;
|
|
}
|
|
}
|
|
return;
|
|
}
|