/* jabberd - a dispatcher program for jabber 2.x
Copyright (C) 2007 Sergey Poznyakoff
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 3 of the License, 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, see . */
#include "jabberd.h"
#include
#include
#define TYPE_CORE 0
#define TYPE_TRANSPORT 1
#define TYPE_RETR 2
struct prog
{
struct prog *next; /* Next program in the list */
int type;
pid_t pid; /* PID */
char *tag; /* Entry tag (for diagnostics purposes) */
char **depend;
union
{
struct
{
int argc;
char **argv; /* Command line arguments */
int retr[2];
char *pidfile; /* Pidfile location */
time_t timestamp; /* Time of last startup */
size_t count; /* Number of failed starts since timestamp */
int disabled; /* 1 if this entry is disabled */
} p;
struct
{
struct prog *master;
} r;
} v;
};
#define IS_PROG(p) ((p)->type == TYPE_CORE || (p)->type == TYPE_TRANSPORT)
static struct prog *proghead, *progtail;
static size_t prognum;
static void prog_stop (struct prog *prog, int sig);
void
link_prog (struct prog *pp, int prepend)
{
if (prepend)
{
pp->next = proghead;
proghead = pp;
if (!progtail)
progtail = pp;
}
else
{
pp->next = NULL;
if (progtail)
progtail->next = pp;
else
proghead = pp;
progtail = pp;
}
}
void
register_retr (int type, struct prog *master, pid_t pid)
{
struct prog *pp;
static char *retrstr[2] = { "stdout", "stderr" };
size_t taglen = strlen (master->tag) + 1 + strlen (retrstr[type]);
char *tag = emalloc (taglen + 1);
strcpy (tag, master->tag);
strcat (tag, "/");
strcat (tag, retrstr[type]);
for (pp = proghead; pp; pp = pp->next)
if (pp->type == TYPE_RETR && pp->v.r.master == master
&& strcmp (pp->tag, tag) == 0)
{
free (tag);
prog_stop (pp, SIGKILL);
break;
}
if (!pp)
{
pp = emalloc (sizeof(*pp) + taglen + 1);
memset (pp, 0, sizeof(*pp));
pp->type = TYPE_RETR;
pp->tag = (char *) (pp + 1);
strcpy (pp->tag, tag);
free (tag);
pp->v.r.master = master;
link_prog (pp, 1);
}
pp->pid = pid;
}
void
register_prog (int type, char *tag, char **argv, int retr[2], char **depv,
char *pidfile)
{
struct prog *p, *newp;
char *pstr;
int i;
size_t size = 0;
int dep_all = 0;
int depc = 0;
if (depv)
{
if (depv[0] && depv[1] == NULL)
{
if (strcmp (depv[0], "all") == 0)
{
dep_all = 1;
for (p = proghead; p; p = p->next)
if (p->type == TYPE_CORE)
{
depc++;
size += strlen (p->tag) + 1;
}
}
else if (strcmp (depv[0], "none") == 0)
depv = NULL;
}
if (depc == 0)
for (depc = 0; depv[depc]; depc++)
;
size += sizeof (depv[0]) * (depc + 1);
}
for (i = 0; argv[i]; i++)
size += strlen (argv[i]);
size += i + sizeof (argv[0]) * (i + 1) + sizeof (*newp)
+ (tag ? (strlen (tag) + 1) : 0);
if (pidfile)
size += strlen (pidfile) + 1;
newp = emalloc (size);
memset (newp, 0, sizeof(*newp));
newp->type = type;
newp->pid = 0;
newp->v.p.argc = i;
newp->v.p.argv = (char **) (newp + 1);
pstr = (char*) (newp->v.p.argv + i + 1);
for (i = 0; argv[i]; i++)
{
strcpy (pstr, argv[i]);
newp->v.p.argv[i] = pstr;
pstr += strlen (pstr) + 1;
}
newp->v.p.argv[i] = NULL;
if (tag)
{
newp->tag = strcpy (pstr, tag);
pstr += strlen (pstr) + 1;
}
else
newp->tag = newp->v.p.argv[0];
if (pidfile)
{
newp->v.p.pidfile = strcpy (pstr, pidfile);
pstr += strlen (pstr) + 1;
}
else
newp->v.p.pidfile = NULL;
if (depv)
{
newp->depend = (char**) pstr;
pstr = (char*) (newp->depend + depc + 1);
if (dep_all)
{
depc = 0;
for (p = proghead; p; p = p->next)
if (p->type == TYPE_CORE)
{
newp->depend[depc++] = pstr;
strcpy (pstr, p->tag);
pstr += strlen (pstr) + 1;
}
}
else
{
for (depc = 0; depv[depc]; depc++)
{
newp->depend[depc] = pstr;
strcpy (pstr, p->tag);
pstr += strlen (pstr) + 1;
}
}
depv[depc] = NULL;
}
else
newp->depend = NULL;
newp->v.p.retr[0] = retr[0];
newp->v.p.retr[1] = retr[1];
link_prog (newp, 0);
}
void
register_transport (char *tag, char **argv, int retr[2], char **depv,
char *pidfile)
{
static char *std_dep[] = { "all", NULL };
if (!depv)
depv = std_dep;
register_prog (TYPE_TRANSPORT, tag, argv, retr, depv, pidfile);
}
void
register_jabber_process (char *cmd, char *cfg)
{
int retr[2] = { -1, -1 };
char *argv[5];
int argc = 0;
argv[argc++] = cmd;
if (cfg)
{
argv[argc++] = "-c";
argv[argc++] = cfg;
}
if (debug_level > 2)
{
argv[argc++] = "-D";
retr[RETR_ERR] = LOG_DEBUG;
}
argv[argc] = NULL;
register_prog (TYPE_CORE, NULL, argv, retr, NULL, NULL);
}
static struct prog *
find_prog (pid_t pid)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (prog->pid == pid)
break;
return prog;
}
size_t
progman_running_count ()
{
size_t size = 0;
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (prog->pid > 0)
size++;
return size;
}
RETSIGTYPE
retr_exit (int sig)
{
exit (0);
}
int
open_retranslator (struct prog *master, int type)
{
int p[2];
FILE *fp;
char *buf = NULL;
size_t size = 0;
pid_t pid;
if (master->v.p.retr[type] == -1)
return -1;
pipe (p);
switch (pid = fork ())
{
case 0:
/* Retranslator process */
syslog_setup ();
signal_setup (retr_exit);
close (p[1]);
fp = fdopen (p[0], "r");
if (fp == NULL)
exit (1);
openlog (master->tag, LOG_PID, log_facility);
while (getline (&buf, &size, fp) > 0)
logmsg (master->v.p.retr[type], "%s", buf);
exit (0);
case -1:
logmsg (LOG_CRIT, "cannot run retranslator `%s': fork failed: %s",
master->tag, strerror (errno));
return -1;
default:
register_retr (type, master, pid);
close (p[0]);
return p[1];
}
}
static void
prog_start (struct prog *prog)
{
int i;
pid_t pid;
time_t now;
int retr[2];
time (&now);
if (prog->v.p.timestamp + TESTTIME > now)
prog->v.p.count++;
else
{
prog->v.p.count = 0;
prog->v.p.timestamp = now;
}
if (prog->v.p.count > MAXSPAWN)
{
int old_alarm;
logmsg(LOG_ERR, "%s is respawning too fast, disabled for %d minutes",
prog->tag, SLEEPTIME / 60);
prog->v.p.timestamp = now;
prog->v.p.disabled = 1;
old_alarm = alarm (0);
if (old_alarm > SLEEPTIME || old_alarm <= 0)
old_alarm = SLEEPTIME;
alarm (old_alarm);
return;
}
logmsg (LOG_DEBUG, "starting %s", prog->tag);
if (prog->v.p.pidfile)
{
if (unlink (prog->v.p.pidfile) && errno != ENOENT)
logmsg (LOG_ERR, "%s: cannot remove pidfile `%s': %s",
prog->tag, prog->v.p.pidfile, strerror (errno));
}
retr[RETR_OUT] = open_retranslator (prog, RETR_OUT);
retr[RETR_ERR] = open_retranslator (prog, RETR_ERR);
switch (pid = fork ())
{
/* The child branch. */
case 0:
if (retr[RETR_OUT] == -1)
{
close (1);
open ("/dev/null", O_RDWR);
}
else if (retr[RETR_OUT] != 1)
{
close (1);
dup2 (retr[RETR_OUT], 1);
}
if (retr[RETR_ERR] == -1)
{
close (2);
open ("/dev/null", O_RDWR);
}
else if (retr[RETR_ERR] != 1)
{
close (2);
dup2 (retr[RETR_ERR], 2);
}
/* Close unneded descripitors */
for (i = getmaxfd (); i > 2; i--)
close (i);
signal_setup (SIG_DFL);
execvp(prog->v.p.argv[0], prog->v.p.argv);
openlog (syslog_tag, LOG_PID, log_facility);
logmsg (LOG_CRIT, "cannot start `%s': %s", prog->tag,
strerror (errno));
exit (1);
case -1:
logmsg (LOG_CRIT, "cannot run `%s': fork failed: %s",
prog->tag, strerror (errno));
break;
default:
prog->pid = pid;
}
}
void
progman_start ()
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_PROG (prog))
prog_start (prog);
}
void
progman_wake_disabled ()
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_PROG (prog) && prog->v.p.disabled)
{
prog->v.p.disabled = 0;
prog->v.p.count = 0;
prog->v.p.timestamp = 0;
prog_start (prog);
}
}
static void
prog_stop (struct prog *prog, int sig)
{
if (prog->pid > 0)
{
logmsg (LOG_DEBUG, "Stopping %s",
prog->tag, (unsigned long) prog->pid);
kill (prog->pid, sig);
}
}
static void
prog_stop_recursive (struct prog *prog, int sig)
{
if (!prog)
return;
prog_stop_recursive (prog->next, sig);
prog_stop (prog, sig);
}
void
progman_stop ()
{
unsigned long i;
prog_stop_recursive (proghead, SIGTERM);
for (i = 0; i < shutdown_timeout; i++)
{
progman_cleanup (1);
if (progman_running_count () == 0)
return;
sleep (1);
}
prog_stop_recursive (proghead, SIGKILL);
}
static void
print_status (char *tag, pid_t pid, int status, int expect_term)
{
if (WIFEXITED (status))
{
if (WEXITSTATUS (status) == 0)
logmsg (LOG_DEBUG,
"%s (%lu) exited successfully",
tag, (unsigned long) pid);
else
logmsg (LOG_ERR,
"%s (%lu) failed with status %d",
tag, (unsigned long) pid,
WEXITSTATUS (status));
}
else if (WIFSIGNALED (status))
{
int prio;
if (expect_term && WTERMSIG (status) == SIGTERM)
prio = LOG_DEBUG;
else
prio = LOG_ERR;
logmsg(prio,
"%s (%lu) terminated on signal %d",
tag, (unsigned long) pid,
WTERMSIG (status));
}
else if (WIFSTOPPED (status))
logmsg(LOG_ERR,
"%s (%lu) stopped on signal %d",
tag, (unsigned long) pid,
WSTOPSIG (status));
#ifdef WCOREDUMP
else if (WCOREDUMP (status))
logmsg (LOG_ERR,
"%s (%lu) dumped core",
tag, (unsigned long) pid);
#endif
else
logmsg(LOG_ERR,
"%s (%lu) terminated with unrecognized status",
tag, (unsigned long) pid);
}
void
prog_stop_dependent (struct prog *prog)
{
struct prog *p;
for (p = proghead; p; p = p->next)
if (p->depend)
{
int i;
for (i = 0; p->depend[i]; i++)
if (strcmp (prog->tag, p->depend[i]) == 0)
prog_stop (p, SIGTERM);
}
}
void
progman_cleanup (int expect_term)
{
pid_t pid;
int status;
while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
{
struct prog *prog = find_prog (pid);
if (!prog)
{
logmsg (LOG_NOTICE, "unknown child %lu finished",
(unsigned long) pid);
continue;
}
print_status (prog->tag, prog->pid, status, expect_term);
prog->pid = 0;
if (IS_PROG (prog))
{
prog_stop_dependent (prog);
if (!expect_term)
prog_start (prog);
}
}
}
void
progman_stop_component (const char *name)
{
struct prog *prog;
logmsg (LOG_INFO, "stopping component `%s'", name);
for (prog = proghead; prog; prog = prog->next)
if (IS_PROG (prog) && strcmp (prog->tag, name) == 0)
{
if (prog->v.p.disabled)
logmsg (LOG_INFO, "stopping component `%s': component not started",
name);
else
prog_stop (prog, SIGTERM);
}
}
void
progman_dump_stats (const char *filename)
{
FILE *fp;
struct prog *prog;
char *s;
int rc;
logmsg (LOG_INFO, "dumping statistics to `%s'", filename);
fp = fopen (filename, "w");
if (!fp)
{
logmsg (LOG_ERR, "cannot open file `%s' for writing: %s",
filename, strerror (errno));
return;
}
for (prog = proghead; prog; prog = prog->next)
{
switch (prog->type)
{
case TYPE_CORE:
case TYPE_TRANSPORT:
fprintf (fp, "%s %s ",
prog->type == TYPE_CORE ? "core" : "transport",
prog->tag);
if (prog->pid)
fprintf (fp, "%lu", (unsigned long) prog->pid);
else if (prog->v.p.disabled)
{
char buf[48];
time_t t = prog->v.p.timestamp + SLEEPTIME;
strftime (buf, sizeof buf, "%c",
localtime (&t));
fprintf (fp, "[disabled; scheduled for %s]", buf);
}
else
fprintf (fp, "[not running]");
if (rc = argcv_string (prog->v.p.argc, prog->v.p.argv, &s))
{
logmsg (LOG_ERR, "cannot convert argument list: %s",
strerror (rc));
}
else
{
fprintf (fp, " %s", s);
free (s);
}
fputc ('\n', fp);
break;
case TYPE_RETR:
fprintf (fp, "retranslator %s %lu\n", prog->tag,
(unsigned long) prog->pid);
}
}
fclose (fp);
}