302 lines
7.7 KiB
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);
|
|
}
|