ircu2/ircd/ircd_signal.c

302 lines
7.7 KiB
C

/*
* IRC - Internet Relay Chat, ircd/ircd_signal.c
* Copyright (C) 1990 Jarkko Oikarinen and
* University of Oulu, Computing Center
*
* 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file
* @brief Signal handlers for ircu.
* @version $Id: ircd_signal.c 1633 2006-03-25 03:46:56Z entrope $
*/
#include "config.h"
#include "ircd.h"
#include "ircd_alloc.h"
#include "ircd_events.h"
#include "ircd_log.h"
#include "ircd_signal.h"
#include "s_conf.h"
#include "ssl.h"
/* #include <assert.h> -- Now using assert in ircd_log.h */
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
/** Records a function to be called when a child process terminates. */
struct ChildRecord {
struct ChildRecord *next;
SigChldCallBack call;
void *datum;
pid_t cpid;
};
/** Counts various types of signals that we receive.
* Using volatile sig_atomic_t for signal-safe access from handlers.
*/
static volatile sig_atomic_t SignalCounter_alrm = 0; /**< Received SIGALRM count. */
static volatile sig_atomic_t SignalCounter_hup = 0; /**< Received SIGHUP count. */
static volatile sig_atomic_t SignalCounter_chld = 0; /**< Received SIGCHLD count. */
static volatile sig_atomic_t SignalCounter_usr1 = 0; /**< Received SIGUSR1 count. */
/** Event generator for SIGHUP. */
static struct Signal sig_hup;
/** Event generator for SIGINT. */
static struct Signal sig_int;
/** Event generator for SIGTERM. */
static struct Signal sig_term;
/** Event generator for SIGCHLD. */
static struct Signal sig_chld;
/** Event generator for SIGUSR1. */
static struct Signal sig_usr1;
/** List of active child process callback requests. */
static struct ChildRecord *children;
/** List of inactive (free) child records. */
static struct ChildRecord *crec_freelist;
/* Make sure we have a definition for SIGCHLD. */
#if !defined(SIGCHLD)
# define SIGCHLD SIGCLD
#endif
/** Signal handler for SIGALRM.
* @param[in] sig Signal number (ignored).
*/
static void sigalrm_handler(int sig)
{
++SignalCounter_alrm;
}
/** Signal callback for SIGTERM.
* @param[in] ev Signal event descriptor.
*/
static void sigterm_callback(struct Event* ev)
{
assert(0 != ev_signal(ev));
assert(ET_SIGNAL == ev_type(ev));
assert(SIGTERM == sig_signal(ev_signal(ev)));
assert(SIGTERM == ev_data(ev));
server_die("received signal SIGTERM");
}
/** Signal callback for SIGHUP.
* @param[in] ev Signal event descriptor.
*/
static void sighup_callback(struct Event* ev)
{
assert(0 != ev_signal(ev));
assert(ET_SIGNAL == ev_type(ev));
assert(SIGHUP == sig_signal(ev_signal(ev)));
assert(SIGHUP == ev_data(ev));
++SignalCounter_hup;
rehash(&me, 1);
}
/** Signal callback for SIGUSR1.
* @param[in] ev Signal event descriptor.
*/
static void sigusr1_callback(struct Event* ev)
{
assert(0 != ev_signal(ev));
assert(ET_SIGNAL == ev_type(ev));
assert(SIGUSR1 == sig_signal(ev_signal(ev)));
assert(SIGUSR1 == ev_data(ev));
++SignalCounter_usr1;
#ifdef USE_SSL
ssl_reinit(1);
#endif
}
/** Signal callback for SIGINT.
* @param[in] ev Signal event descriptor.
*/
static void sigint_callback(struct Event* ev)
{
assert(0 != ev_signal(ev));
assert(ET_SIGNAL == ev_type(ev));
assert(SIGINT == sig_signal(ev_signal(ev)));
assert(SIGINT == ev_data(ev));
#ifdef RESTART_ON_SIGINT
server_restart("caught signal: SIGINT");
#else
server_die("received signal SIGINT (Ctrl-C)");
#endif
}
/** Allocate a child callback record.
* @return Newly allocated callback record.
*/
static struct ChildRecord *alloc_crec(void)
{
struct ChildRecord *crec;
if (crec_freelist)
{
crec = crec_freelist;
crec_freelist = crec->next;
}
else
{
crec = MyCalloc(1, sizeof(*crec));
}
memset(crec, 0, sizeof(*crec));
crec->next = NULL;
return crec;
}
/** Release \a crec, which is after \a prev.
* @param[in] crec Child process callback record to release.
*/
static void release_crec(struct ChildRecord *crec)
{
memset(crec, 0, sizeof(*crec));
crec->next = crec_freelist;
crec_freelist = crec;
}
/** Register a function to be called when a child process terminates.
* @param[in] child Child process ID.
* @param[in] call Function to call when process \a child terminates.
* @param[in] datum Additional data parameter to pass to \a call.
*/
void register_child(pid_t child, SigChldCallBack call, void *datum)
{
struct ChildRecord *crec;
crec = alloc_crec();
/* Link into #children list. */
crec->next = children;
children = crec;
/* Fill in user fields. */
crec->call = call;
crec->datum = datum;
crec->cpid = child;
}
/** Unregister all callbacks for a child process, optionally calling
* them first.
* @param[in] child Child process ID to unregister.
* @param[in] do_call If non-zero, make the callbacks.
* @param[in] status If \a do_call is non-zero, the child's exit status.
*/
static void do_unregister_child(pid_t child, int do_call, int status)
{
struct ChildRecord *crec = children;
struct ChildRecord *prev = NULL;
while (crec != NULL)
{
if (crec->cpid == child)
{
if (do_call)
crec->call(child, crec->datum, status);
if (prev)
prev->next = crec->next;
else
children = crec->next;
release_crec(crec);
}
else
prev = crec;
crec = prev ? prev->next : children;
}
}
/** Unregister all callbacks for a child process.
* @param[in] child Child process ID to unregister.
*/
void unregister_child(pid_t child)
{
do_unregister_child(child, 0, 0);
}
/** Signal handler for SIGCHLD.
* @param[in] ev Signal event descriptor.
*/
static void sigchld_callback(struct Event *ev)
{
pid_t cpid;
int status;
++SignalCounter_chld;
do {
cpid = waitpid(-1, &status, WNOHANG);
if (cpid > 0)
do_unregister_child(cpid, 1, status);
} while (cpid > 0);
}
/** Register all necessary signal handlers. */
void setup_signals(void)
{
struct sigaction act;
act.sa_handler = SIG_IGN;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGPIPE);
sigaddset(&act.sa_mask, SIGALRM);
sigaddset(&act.sa_mask, SIGUSR2);
#ifdef SIGWINCH
sigaddset(&act.sa_mask, SIGWINCH);
sigaction(SIGWINCH, &act, 0);
#endif
sigaction(SIGPIPE, &act, 0);
sigaction(SIGUSR2, &act, 0);
act.sa_handler = sigalrm_handler;
sigaction(SIGALRM, &act, 0);
signal_add(&sig_hup, sighup_callback, 0, SIGHUP);
signal_add(&sig_int, sigint_callback, 0, SIGINT);
signal_add(&sig_term, sigterm_callback, 0, SIGTERM);
signal_add(&sig_chld, sigchld_callback, 0, SIGCHLD);
signal_add(&sig_usr1, sigusr1_callback, 0, SIGUSR1);
#ifdef HAVE_RESTARTABLE_SYSCALLS
/* siginterrupt is deprecated on modern systems (sa_flags=0 already
* prevents SA_RESTART), but kept for compatibility with ancient systems.
*/
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
siginterrupt(SIGALRM, 1);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#endif
}
/** Kill and clean up all child processes. */
void reap_children(void)
{
/* Send SIGTERM to all children in process group. Sleep for a
* second to let them exit before we try to clean them up.
*/
kill(0, SIGTERM);
sleep(1);
sigchld_callback(NULL);
}