gambas-source-code/main/gbx/gbx_signal.c

303 lines
6.3 KiB
C
Raw Normal View History

/***************************************************************************
gbx_signal.c
(c) 2000-2012 Benoît Minisini <gambas@users.sourceforge.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
***************************************************************************/
#define __GBX_SIGNAL_C
#include "gb_error.h"
#include "gb_array.h"
#include "gbx_api.h"
#include "gbx_signal.h"
//#define DEBUG_ME 1
static SIGNAL_HANDLER *_handlers = NULL;
static int _pipe[2] = { -1, -1 };
static volatile int _count = 0;
static int _raising_callback = 0;
void SIGNAL_install(SIGNAL_HANDLER *handler, int signum, void (*callback)(int, siginfo_t *, void *))
{
struct sigaction action;
#ifdef DEBUG_ME
fprintf(stderr, "SIGNAL_install: %d %p\n", signum, callback);
#endif
handler->signum = signum;
action.sa_flags = SA_SIGINFO;
sigemptyset(&action.sa_mask);
action.sa_sigaction = callback;
if (sigaction(signum, NULL, &handler->old_action) != 0 || sigaction(signum, &action, NULL) != 0)
ERROR_panic("Cannot install signal handler: %s", strerror(errno));
}
void SIGNAL_uninstall(SIGNAL_HANDLER *handler, int signum)
{
#ifdef DEBUG_ME
fprintf(stderr, "SIGNAL_uninstall: %d\n", signum);
#endif
if (sigaction(signum, &handler->old_action, NULL) != 0)
ERROR_panic("Cannot uninstall signal handler");
while (handler->callbacks)
SIGNAL_unregister(handler->signum, handler->callbacks);
}
void SIGNAL_previous(SIGNAL_HANDLER *handler, int signum, siginfo_t *info, void *context)
{
if (handler->old_action.sa_handler != SIG_DFL && handler->old_action.sa_handler != SIG_IGN)
{
if (handler->old_action.sa_flags & SA_SIGINFO)
{
//fprintf(stderr, "Calling old action %p\n", _old_SIGCHLD_action.sa_sigaction);
(*handler->old_action.sa_sigaction)(signum, info, context);
}
else
{
//fprintf(stderr, "Calling old handler %p\n", _old_SIGCHLD_action.sa_handler);
(*handler->old_action.sa_handler)(signum);
}
}
}
static SIGNAL_HANDLER *find_handler(int signum)
{
int i;
for (i = 0; i < ARRAY_count(_handlers); i++)
{
if (_handlers[i].signum == signum)
return &_handlers[i];
}
return NULL;
}
static SIGNAL_HANDLER *add_handler(void)
{
if (!_handlers)
ARRAY_create_inc(&_handlers, 1);
return ARRAY_add_void(&_handlers);
}
static void handle_signal(int signum, siginfo_t *info, void *context)
{
char buffer;
int save_errno;
save_errno = errno;
if (_count)
{
buffer = signum;
for(;;)
{
if (write(_pipe[1], &buffer, 1) == 1)
break;
if (errno != EINTR)
ERROR_panic("Cannot write signal #%d into signal pipe: %s", signum, strerror(errno));
}
}
SIGNAL_previous(find_handler(signum), signum, info, context);
errno = save_errno;
}
static bool _must_purge_callbacks = FALSE;
static int _purge_signum;
static SIGNAL_HANDLER *_purge_handler;
static void purge_callbacks(void)
{
SIGNAL_CALLBACK *cb, *next_cb;
_raising_callback--;
if (_raising_callback)
return;
if (_must_purge_callbacks)
{
cb = _purge_handler->callbacks;
while (cb)
{
next_cb = cb->next;
if (!cb->callback)
SIGNAL_unregister(_purge_signum, cb);
cb = next_cb;
}
_must_purge_callbacks = FALSE;
}
}
void SIGNAL_raise_callbacks(int fd, int type, void *data)
{
SIGNAL_HANDLER *handler;
SIGNAL_CALLBACK *cb;
char signum;
/*old = signal(SIGCHLD, signal_child);*/
if (read(fd, &signum, 1) != 1)
return;
handler = find_handler(signum);
_raising_callback++;
_purge_signum = signum;
_purge_handler = handler;
ON_ERROR(purge_callbacks)
{
cb = handler->callbacks;
while (cb)
{
if (cb->callback)
(*cb->callback)((int)signum, cb->data);
cb = cb->next;
}
}
END_ERROR
purge_callbacks();
}
SIGNAL_CALLBACK *SIGNAL_register(int signum, void (*callback)(int, intptr_t), intptr_t data)
{
SIGNAL_HANDLER *handler;
SIGNAL_CALLBACK *cb;
if (!_count)
{
if (pipe(_pipe) != 0)
ERROR_panic("Cannot create signal handler pipes: %s", strerror(errno));
fcntl(_pipe[0], F_SETFD, FD_CLOEXEC);
fcntl(_pipe[1], F_SETFD, FD_CLOEXEC);
// Allows to read the signal pipe without blocking
fcntl(_pipe[0], F_SETFL, fcntl(_pipe[0], F_GETFL) | O_NONBLOCK);
GB_Watch(_pipe[0], GB_WATCH_READ, (void *)SIGNAL_raise_callbacks, 0);
}
_count++;
handler = find_handler(signum);
if (!handler)
{
handler = add_handler();
SIGNAL_install(handler, signum, handle_signal);
}
ALLOC(&cb, sizeof(SIGNAL_CALLBACK), "SIGNAL_register_callback");
cb->prev = NULL;
cb->next = handler->callbacks;
cb->callback = callback;
cb->data = data;
handler->callbacks = cb;
#ifdef DEBUG_ME
fprintf(stderr, "SIGNAL_register: %d -> %p (%p)\n", signum, cb, cb->callback);
#endif
return cb;
}
void SIGNAL_unregister(int signum, SIGNAL_CALLBACK *cb)
{
SIGNAL_HANDLER *handler = find_handler(signum);
if (!handler)
return;
if (_raising_callback)
{
#ifdef DEBUG_ME
fprintf(stderr, "SIGNAL_unregister: disable %d %p (%p)\n", signum, cb, cb->callback);
#endif
cb->callback = NULL;
_must_purge_callbacks = TRUE;
return;
}
#ifdef DEBUG_ME
fprintf(stderr, "SIGNAL_unregister: remove %d %p (%p)\n", signum, cb, cb->callback);
#endif
if (cb->prev)
cb->prev->next = cb->next;
if (cb->next)
cb->next->prev = cb->prev;
if (cb == handler->callbacks)
handler->callbacks = cb->next;
FREE(&cb, "SIGNAL_unregister_callback");
_count--;
if (_count == 0)
{
GB_Watch(_pipe[0], GB_WATCH_NONE, NULL, 0);
close(_pipe[0]);
close(_pipe[1]);
_pipe[0] = -1;
_pipe[1] = -1;
}
}
void SIGNAL_exit(void)
{
int i;
SIGNAL_HANDLER *handler;
if (_handlers)
{
for (i = 0; i < ARRAY_count(_handlers); i++)
{
handler = &_handlers[i];
SIGNAL_uninstall(handler, handler->signum);
}
ARRAY_delete(&_handlers);
}
}
int SIGNAL_get_fd(void)
{
return _pipe[0];
}