/* This file is part of GNU Pies.
Copyright (C) 2007, 2008, 2009, 2010, 2011 Sergey Poznyakoff
GNU Pies 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, or (at your option)
any later version.
GNU Pies 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 GNU Pies. If not, see . */
#include "pies.h"
#include
enum prog_type
{
TYPE_COMPONENT,
TYPE_REDIRECTOR,
TYPE_COMMAND
};
enum prog_status
{
status_enabled, /* Component enabled. prog->pid!=0 shows if it is
actually running */
status_disabled, /* Component is disabled. */
status_listener, /* Component is an inetd listener */
status_sleeping, /* Component is sleeping. An attempt to start it will
be made at prog->v.p.timestamp + SLEEPTIME */
status_stopping, /* Component is being stopped */
status_finished, /* A "once" component has finished */
};
struct conn_class
{
const char *tag;
union pies_sockaddr_storage sa_storage;
size_t sa_len;
size_t count;
};
struct prog
{
struct prog *next, *prev;
enum prog_type type;
pid_t pid; /* PID */
char *tag; /* Entry tag (for diagnostics purposes) */
char **prereq;
int facility;
union
{
struct
{
struct component *comp;
size_t idx; /* Numeric identifier */
int socket;
struct prog *redir[2]; /* Pointers to redirectors */
time_t timestamp; /* Time of last startup */
size_t failcount; /* Number of failed starts since timestamp */
enum prog_status status; /* Current component status */
char *runlevels;
/* If status == status_listener: */
size_t num_instances; /* Number of running instances */
/* If comp->type == pies_comp_inetd && status == status_enabled */
struct prog *listener;
union pies_sockaddr_storage sa_storage;
size_t sa_len;
struct conn_class *cclass;
} p;
struct
{
struct prog *master;
} r;
struct
{
char *command;
} c;
} v;
};
#define IS_COMPONENT(p) ((p)->type == TYPE_COMPONENT)
static size_t numcomp;
static struct prog *proghead, *progtail;
static pies_depmap_t depmap;
static int recompute_alarm;
static struct grecs_symtab *conn_tab;
void
progman_iterate_comp (int (*fun) (struct component *, void *), void *data)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog)
&& !(prog->v.p.comp->mode == pies_comp_inetd
&& prog->v.p.listener)
&& fun (prog->v.p.comp, data))
break;
}
void
progman_sysvinit_enable (int (*fun) (struct component *, int, void *),
void *data)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog) && is_sysvinit (prog->v.p.comp))
{
int rc = fun (prog->v.p.comp, prog->v.p.status == status_finished,
data);
if (rc < 0)
continue;
if (rc)
prog->v.p.status = status_enabled;
else
prog->v.p.status = status_disabled;
debug (1, ("%s: %s", prog->tag,
prog->v.p.status == status_enabled ?
"enabled" : "disabled"));
}
}
static struct prog *
prog_lookup_by_pid (pid_t pid)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (prog->pid == pid)
break;
return prog;
}
static struct prog *
prog_lookup_by_socket (int fd)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog) && prog->v.p.socket == fd)
break;
return prog;
}
struct prog *
prog_lookup_by_tag (const char *tag)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (strcmp (prog->tag, tag) == 0)
break;
return prog;
}
struct prog *
prog_lookup_by_service (const char *service)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog)
&& prog->v.p.comp->service
&& strcmp (prog->v.p.comp->service, service) == 0)
break;
return prog;
}
struct component *
progman_lookup_component (const char *tag)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog) && strcmp (prog->tag, tag) == 0)
return prog->v.p.comp;
return NULL;
}
struct component *
progman_lookup_tcpmux (const char *service, const char *master)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog)
&& ISCF_TCPMUX (prog->v.p.comp->flags)
&& !(prog->v.p.comp->flags & CF_DISABLED)
&& prog->v.p.comp->service
&& strcasecmp (prog->v.p.comp->service, service) == 0
&& prog->v.p.comp->tcpmux
&& strcmp (prog->v.p.comp->tcpmux, master) == 0)
return prog->v.p.comp;
return NULL;
}
struct prog *
prog_lookup_by_idx (unsigned idx)
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog) && prog->v.p.idx == idx)
break;
return prog;
}
static void prog_stop (struct prog *prog, int sig);
static int prog_start_prerequisites (struct prog *prog);
void
link_prog (struct prog *pp, int prepend)
{
if (prepend)
{
if (proghead)
proghead->prev = pp;
pp->prev = NULL;
pp->next = proghead;
proghead = pp;
if (!progtail)
progtail = pp;
}
else
{
pp->next = NULL;
pp->prev = progtail;
if (progtail)
progtail->next = pp;
else
proghead = pp;
progtail = pp;
}
}
void
unlink_prog (struct prog *pp)
{
struct prog *x;
if ((x = pp->prev))
x->next = pp->next;
else
proghead = pp->next;
if ((x = pp->next))
x->prev = pp->prev;
else
progtail = pp->prev;
}
void
destroy_prog (struct prog **pp)
{
struct prog *p = *pp;
switch (p->type)
{
case TYPE_COMPONENT:
break;
case TYPE_REDIRECTOR:
{
struct prog *master = p->v.r.master;
if (p == master->v.p.redir[0])
master->v.p.redir[0] = NULL;
else if (p == master->v.p.redir[1])
master->v.p.redir[1] = NULL;
/* else
logmsg (LOG_NOTICE, _("orphan redirector: %s"), p->tag);*/
}
break;
case TYPE_COMMAND:
free (p->v.c.command);
}
unlink_prog (*pp);
free (*pp);
*pp = NULL;
}
static char *
redir_tag (struct prog *master, int type)
{
static char *redirstr[2] = { "stdout", "stderr" };
char *str;
if (type < ARRAY_SIZE(redirstr))
str = xasprintf ("%s/%s", master->tag, redirstr[type]);
else
str = xasprintf ("%s/%d", master->tag, type);
return str;
}
static struct prog *
register_redir (int type, struct prog *master)
{
char *tag = redir_tag (master, type);
char *pstr;
struct prog *pp = xzalloc (sizeof (*pp) + strlen (tag) + 1 +
2 * sizeof (char**));
pp->type = TYPE_REDIRECTOR;
pstr = (char *) (pp + 1);
pp->tag = pstr;
strcpy (pp->tag, tag);
pstr += strlen (pp->tag) + 1;
free (tag);
pp->v.r.master = master;
pp->prereq = (char**)pstr;
pp->prereq[0] = master->tag;
pp->prereq[1] = NULL;
link_prog (pp, 1);
return pp;
}
void
update_redir (int type, struct prog *master, pid_t pid)
{
struct prog *pp;
if (master->v.p.redir[type])
{
pp = master->v.p.redir[type];
prog_stop (pp, SIGKILL); /* Just in case */
}
pp = register_redir (type, master);
master->v.p.redir[type] = pp;
pp->pid = pid;
}
static struct prog *
register_prog0 (struct component *comp, unsigned index)
{
struct prog *newp;
newp = xzalloc (sizeof (*newp));
newp->type = TYPE_COMPONENT;
newp->tag = comp->tag;
newp->pid = 0;
newp->facility = comp->facility;
newp->v.p.comp = comp;
newp->v.p.idx = index;
newp->v.p.socket = -1;
if (comp->flags & CF_DISABLED)
newp->v.p.status = status_disabled;
else if (comp->mode == pies_comp_inetd)
newp->v.p.status = status_listener;
if (comp->mode != pies_comp_exec)
comp->redir[RETR_OUT].type = redir_null;
link_prog (newp, 0);
return newp;
}
void
register_prog (struct component *comp)
{
register_prog0 (comp, numcomp++);
}
void
register_command (char *tag, char *command, pid_t pid)
{
struct prog *newp = xzalloc (sizeof (*newp));
newp = xzalloc (sizeof (*newp));
newp->type = TYPE_COMMAND;
newp->tag = tag;
newp->pid = pid;
newp->v.c.command = command;
link_prog (newp, 0);
}
void
prog_rebuild_prerequisites (struct prog *prog)
{
struct component *comp = prog->v.p.comp;
struct prog *p;
int dep_all = 0;
size_t depc = 0;
if (comp->prereq)
{
depc = grecs_list_size (comp->prereq);
if (depc == 1)
{
const char *item = grecs_list_index (comp->prereq, 0);
if (strcmp (item, "all") == 0)
{
dep_all = 1;
for (p = proghead; p; p = p->next)
if (p->type == TYPE_COMPONENT)
depc++;
}
else if (strcmp (item, "none") == 0)
{
grecs_list_free (comp->prereq);
comp->prereq = NULL;
}
}
}
if (depc == 0)
return;
prog->prereq = xcalloc (depc + 1, sizeof (prog->prereq[0]));
depc = 0;
if (comp->prereq)
{
if (dep_all)
{
for (p = proghead; p; p = p->next)
if (p->type == TYPE_COMPONENT)
prog->prereq[depc++] = p->tag;
}
else
{
struct grecs_list_entry *ep;
for (ep = comp->prereq->head; ep; ep = ep->next)
prog->prereq[depc++] = (char*) ep->data;
}
}
prog->prereq[depc] = NULL;
}
int
progman_running_p ()
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
{
if (IS_COMPONENT (prog) && is_comp_wait (prog->v.p.comp) &&
prog->pid > 0)
{
debug (1, ("COMP %s still running", prog->tag));
return 1;
}
}
return 0;
}
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
redir_exit (int sig)
{
_exit (0);
}
int
redirect_to_file (struct prog *master, int stream)
{
struct passwd *pw;
int fd = open (master->v.p.comp->redir[stream].v.file, O_RDWR|O_CREAT,
0644 & ~master->v.p.comp->umask);
if (fd == -1)
{
logmsg (LOG_ERR, _("cannot open output file %s: %s"),
master->v.p.comp->redir[stream].v.file,
strerror (errno));
return -1;
}
/* Fix file ownership */
if (master->v.p.comp->privs.user
&& (pw = getpwnam (master->v.p.comp->privs.user)) != NULL)
chown (master->v.p.comp->redir[stream].v.file, pw->pw_uid, pw->pw_gid);
return fd;
}
static void
close_fds (fd_set *fdset)
{
int i;
for (i = getmaxfd (); i >= 0; i--)
{
if (fdset && FD_ISSET (i, fdset))
continue;
close (i);
}
}
int
open_redirector (struct prog *master, int stream)
{
int p[2];
FILE *fp;
char *buf = NULL;
size_t size = 0;
pid_t pid;
int prio;
char *tag;
fd_set fdset;
switch (master->v.p.comp->redir[stream].type)
{
case redir_null:
return -1;
case redir_file:
return redirect_to_file (master, stream);
case redir_syslog:
break;
}
pipe (p);
switch (pid = fork ())
{
case 0:
/* Redirector process */
tag = redir_tag (master, stream);
mf_proctitle_format ("%s redirector", tag);
free (tag);
FD_ZERO (&fdset);
FD_SET (p[0], &fdset);
close_fds (&fdset);
diag_setup (0);
signal_setup (redir_exit);
close (p[1]);
fp = fdopen (p[0], "r");
if (fp == NULL)
_exit (1);
openlog (master->tag, LOG_PID, master->facility);
prio = master->v.p.comp->redir[stream].v.prio;
while (getline (&buf, &size, fp) > 0)
syslog (prio, "%s", buf);
_exit (0);
case -1:
logmsg (LOG_CRIT,
_("cannot run redirector `%s': fork failed: %s"),
master->tag, strerror (errno));
return -1;
default:
debug (1, (_("redirector for %s started, pid=%lu"),
master->tag, (unsigned long) pid));
update_redir (stream, master, pid);
close (p[0]);
return p[1];
}
}
static unsigned
conn_class_hasher (void *data, unsigned long n_buckets)
{
struct conn_class *pcclass = data;
unsigned char const *tag = (unsigned char const *)pcclass->tag;
size_t value = 0;
unsigned char ch;
size_t len;
while ((ch = *tag++))
value = (value * 31 + ch) % n_buckets;
for (tag = (const unsigned char *)&pcclass->sa_storage,
len = pcclass->sa_len;
len;
tag++, len--)
value = (value * 31 + *tag) % n_buckets;
return value;
}
/* Compare two strings for equality. */
static int
conn_class_compare (void const *data1, void const *data2)
{
struct conn_class const *p1 = data1;
struct conn_class const *p2 = data2;
return !(p1->sa_len == p2->sa_len &&
memcmp (&p1->sa_storage, &p2->sa_storage, p1->sa_len) == 0 &&
strcmp (p1->tag, p2->tag) == 0);
}
static void
conn_class_free (void *data)
{
free (data);
}
static int
conn_class_copy(void *a, void *b)
{
memcpy (a, b, sizeof(struct conn_class));
memset (b, 0, sizeof(struct conn_class));
return 0;
}
static struct conn_class *
conn_class_lookup (const char *tag,
union pies_sockaddr_storage const *sa_storage_ptr,
size_t sa_len)
{
int install = 1;
struct conn_class *probe, *ret;
probe = xmalloc (sizeof (probe[0]));
probe->tag = tag;
probe->sa_storage = *sa_storage_ptr;
probe->sa_len = sa_len;
switch (probe->sa_storage.s.sa_family)
{
case AF_INET:
probe->sa_storage.s_in.sin_port = 0;
break;
case AF_UNIX:
break;
default:
logmsg (LOG_ERR, _("unexpected socket family: %d"),
probe->sa_storage.s.sa_family);
break;
}
probe->count = 0;
if (!conn_tab)
{
conn_tab = grecs_symtab_create(sizeof (struct conn_class),
conn_class_hasher,
conn_class_compare,
conn_class_copy,
NULL,
conn_class_free);
if (!conn_tab)
xalloc_die ();
}
ret = grecs_symtab_lookup_or_install (conn_tab, probe, &install);
if (!ret)
{
logmsg (LOG_CRIT, "cannot allocate conn_class: %s",
strerror (errno));
abort ();
}
free (probe);
return ret;
}
static void
conn_class_report (int prio, struct conn_class *pcclass)
{
char *s = sockaddr_to_astr ((struct sockaddr *)&pcclass->sa_storage,
pcclass->sa_len);
logmsg (prio, _("connections in class %s/%s: %lu"),
pcclass->tag, s, (unsigned long)pcclass->count);
free (s);
}
static void
conn_class_remove (struct conn_class *pcclass)
{
if (!conn_tab)
{
logmsg (LOG_CRIT, "attempt to free unexisting conn_class %p", pcclass);
conn_class_report (LOG_CRIT, pcclass);
abort ();
}
grecs_symtab_remove (conn_tab, pcclass);
}
extern char **environ; /* Environment */
static size_t envsize; /* Size of environ. If it is not 0, then we
have allocated space for the environ. */
#define ENV_PROTO "PROTO"
#define ENV_SOCKTYPE "SOCKTYPE"
#define ENV_LOCALIP "LOCALIP"
#define ENV_LOCALPORT "LOCALPORT"
#define ENV_LOCALHOST "LOCALHOST"
#define ENV_REMOTEIP "REMOTEIP"
#define ENV_REMOTEPORT "REMOTEPORT"
#define ENV_REMOTEHOST "REMOTEHOST"
static char *sockenv_hint[] = {
"-" ENV_PROTO,
"-" ENV_SOCKTYPE,
"-" ENV_LOCALIP,
"-" ENV_LOCALPORT,
"-" ENV_LOCALHOST,
"-" ENV_REMOTEIP,
"-" ENV_REMOTEPORT,
"-" ENV_REMOTEHOST,
NULL
};
#define DEBUG_ENVIRON(l) \
do \
if (debug_level >= (l)) \
{ \
int i; \
logmsg_printf (LOG_DEBUG, "environment: "); \
for (i = 0; environ[i]; i++) \
logmsg_printf (LOG_DEBUG, "%s ", environ[i]); \
logmsg_printf (LOG_DEBUG, "\n"); \
} \
while (0)
static void
add_env (const char *name, const char *value)
{
size_t i;
size_t namelen = strlen (name);
char *p;
for (i = 0; environ[i]; i++)
{
if (!strncmp (environ[i], name, namelen) && environ[i][namelen] == '=')
break;
}
if (environ[i] == NULL)
{
if (envsize == 0)
{
char **new_env;
envsize = i + 4;
new_env = xcalloc (envsize, sizeof new_env[0]);
for (i = 0; new_env[i] = environ[i]; i++)
;
environ = new_env;
}
else if (i == envsize)
{
envsize += 4;
environ = xrealloc (environ,
envsize * sizeof environ[0]);
}
environ[i+1] = NULL;
}
if (!value)
value = "";
p = xmalloc (namelen + 1 + strlen (value) + 1);
strcpy (p, name);
p[namelen] = '=';
strcpy (p + namelen + 1, value);
environ[i] = p;
}
static char *
find_env (const char *name, int val)
{
if (environ)
{
int nlen = strcspn (name, "+=");
int i;
for (i = 0; environ[i]; i++)
{
size_t elen = strcspn (environ[i], "=");
if (elen == nlen && memcmp (name, environ[i], nlen) == 0)
return val ? environ[i] + elen + 1 : environ[i];
}
}
return NULL;
}
static int
locate_unset (char **env, const char *name)
{
int i;
int nlen = strcspn (name, "=");
for (i = 0; env[i]; i++)
{
if (env[i][0] == '-')
{
size_t elen = strcspn (env[i] + 1, "=");
if (elen == nlen && memcmp (name, env[i] + 1, nlen) == 0)
{
if (env[i][nlen + 1])
return strcmp (name + nlen, env[i] + 1 + nlen) == 0;
else
return 1;
}
}
}
return 0;
}
static char *
env_concat (const char *name, size_t namelen, const char *a, const char *b)
{
char *res;
size_t len;
if (a && b)
{
res = xmalloc (namelen + 1 + strlen (a) + strlen (b) + 1);
strcpy (res + namelen + 1, a);
strcat (res, b);
}
else if (a)
{
len = strlen (a);
if (c_ispunct (a[len-1]))
len--;
res = xmalloc (namelen + 1 + len + 1);
memcpy (res + namelen + 1, a, len);
res[namelen + 1 + len] = 0;
}
else /* if (a == NULL) */
{
if (c_ispunct (b[0]))
b++;
res = xmalloc (namelen + 1 + len + 1);
strcpy (res + namelen + 1, b);
}
memcpy (res, name, namelen);
res[namelen] = '=';
return res;
}
static void
environ_setup (char **hint)
{
char **old_env = environ;
char **new_env;
size_t count, i, n;
if (!hint)
return;
if (strcmp (hint[0], "-") == 0)
{
old_env = NULL;
hint++;
}
/* Count new environment size */
count = 0;
if (old_env)
for (i = 0; old_env[i]; i++)
count++;
for (i = 0; hint[i]; i++)
count++;
/* Allocate new environment. */
new_env = xcalloc (count + 1, sizeof new_env[0]);
/* Populate the environment. */
n = 0;
if (old_env)
for (i = 0; old_env[i]; i++)
{
if (!locate_unset (hint, old_env[i]))
new_env[n++] = old_env[i];
}
for (i = 0; hint[i]; i++)
{
char *p;
if (hint[i][0] == '-')
{
/* Skip unset directives. */
continue;
}
if ((p = strchr (hint[i], '=')))
{
if (p == hint[i])
continue; /* Ignore erroneous entry */
if (p[-1] == '+')
new_env[n++] = env_concat (hint[i], p - hint[i] - 1,
find_env(hint[i], 1), p + 1);
else if (p[1] == '+')
new_env[n++] = env_concat (hint[i], p - hint[i],
p + 2, find_env(hint[i], 1));
else
new_env[n++] = hint[i];
}
else
{
p = find_env (hint[i], 0);
if (p)
new_env[n++] = p;
}
}
new_env[n] = NULL;
if (envsize)
free (environ);
environ = new_env;
envsize = count + 1;
}
/* Pass socket information in environment variables. */
void
prog_sockenv (struct prog *prog)
{
char buf[INT_BUFSIZE_BOUND (uintmax_t)];
char *p;
struct hostent *host;
union pies_sockaddr_storage sa_server;
socklen_t len = sizeof (sa_server);
union pies_sockaddr_storage *sa_client = &prog->v.p.sa_storage;
socklen_t cltlen = prog->v.p.sa_len;
const char *proto = NULL;
if (!(prog->v.p.comp->flags & CF_SOCKENV))
return;
if (socket_type_to_str (prog->v.p.comp->socket_type, &proto))
proto = umaxtostr (prog->v.p.comp->socket_type, buf);
add_env (ENV_SOCKTYPE, proto);
if (prog->v.p.comp->socket_url)
proto = prog->v.p.comp->socket_url->proto_s;
else if (prog->v.p.listener)
proto = prog->v.p.listener->v.p.comp->socket_url->proto_s;
else if (ISCF_TCPMUX (prog->v.p.comp->flags))
proto = "TCP";
add_env (ENV_PROTO, proto);
if (getsockname (prog->v.p.socket,
(struct sockaddr *) &sa_server, &len) < 0)
logmsg (LOG_WARNING, "getsockname(): %s", strerror (errno));
else if (sa_server.s.sa_family == AF_INET)
{
p = inet_ntoa (sa_server.s_in.sin_addr);
if (p)
add_env (ENV_LOCALIP, p);
p = umaxtostr (ntohs (sa_server.s_in.sin_port), buf);
add_env (ENV_LOCALPORT, p);
if (prog->v.p.comp->flags & CF_RESOLVE)
{
if ((host = gethostbyaddr ((char *) &sa_server.s_in.sin_addr,
sizeof (sa_server.s_in.sin_addr),
AF_INET)) == NULL)
logmsg (LOG_WARNING, "gethostbyaddr: %s", strerror (errno));
else
add_env (ENV_LOCALHOST, host->h_name);
}
}
if (cltlen > 0 && sa_client->s.sa_family == AF_INET)
{
p = inet_ntoa (sa_client->s_in.sin_addr);
if (p)
add_env (ENV_REMOTEIP, p);
p = umaxtostr (ntohs (sa_client->s_in.sin_port), buf);
add_env (ENV_REMOTEPORT, p);
if (prog->v.p.comp->flags & CF_RESOLVE)
{
if ((host = gethostbyaddr ((char *) &sa_client->s_in.sin_addr,
sizeof (sa_client->s_in.sin_addr),
AF_INET)) == NULL)
logmsg (LOG_WARNING, "gethostbyaddr: %s",
strerror (errno));
else
add_env (ENV_REMOTEHOST, host->h_name);
}
}
/* FIXME: $REMOTEINFO ? */
DEBUG_ENVIRON (4);
}
static int
check_rate (struct prog *prog, unsigned testtime, size_t max_count)
{
time_t now;
time (&now);
if (prog->v.p.timestamp + testtime > now)
prog->v.p.failcount++;
else
{
prog->v.p.failcount = 0;
prog->v.p.timestamp = now;
}
if (prog->v.p.failcount > max_count)
{
prog->v.p.timestamp = now;
prog->v.p.status = status_sleeping;
recompute_alarm = 1;
return 1;
}
return 0;
}
static int
check_spawn_rate (struct prog *prog)
{
if (check_rate (prog, TESTTIME, MAXSPAWN))
{
logmsg (LOG_NOTICE,
ngettext ("%s is respawning too fast, disabled for %d minute",
"%s is respawning too fast, disabled for %d minutes",
SLEEPTIME / 60),
prog->tag, SLEEPTIME / 60);
return 1;
}
return 0;
}
static int
check_connection_rate (struct prog *prog)
{
size_t max_rate = prog->v.p.comp->max_rate
? prog->v.p.comp->max_rate : default_max_rate;
if (max_rate && check_rate (prog, 60, max_rate))
{
logmsg (LOG_NOTICE,
ngettext ("%s is starting too often, disabled for %d minute",
"%s is starting too often, disabled for %d minutes",
SLEEPTIME / 60),
prog->tag, SLEEPTIME / 60);
return 1;
}
return 0;
}
static int
prog_open_socket (struct prog *prog)
{
prog->v.p.socket = create_socket (prog->v.p.comp->socket_url,
prog->v.p.comp->socket_type,
prog->v.p.comp->privs.user,
prog->v.p.comp->umask);
if (prog->v.p.socket == -1)
{
prog->v.p.status = status_disabled;
return 1;
}
if (listen (prog->v.p.socket, 8))
{
logmsg (LOG_ERR, "listen: %s", strerror (errno));
close (prog->v.p.socket);
prog->v.p.socket = -1;
prog->v.p.status = status_disabled;
return 1;
}
return 0;
}
static void
prog_start_prologue (struct prog *prog)
{
if (prog->v.p.comp->dir)
{
debug (1, (_("chdir %s"), prog->v.p.comp->dir));
if (chdir (prog->v.p.comp->dir))
logmsg (LOG_ERR, _("%s: cannot change to directory %s: %s"),
prog->tag, prog->v.p.comp->dir, strerror (errno));
}
environ_setup (prog->v.p.comp->env ?
prog->v.p.comp->env :
((prog->v.p.comp->flags & CF_SOCKENV) ? sockenv_hint : NULL));
DEBUG_ENVIRON (4);
pies_priv_setup (&prog->v.p.comp->privs);
if (prog->v.p.comp->umask)
umask (prog->v.p.comp->umask);
set_limits (prog->tag,
prog->v.p.comp->limits ?
prog->v.p.comp->limits : pies_limits);
if (debug_level >= 1)
{
int i;
struct component *comp = prog->v.p.comp;
logmsg_printf (LOG_DEBUG, "executing");
for (i = 0; i < comp->argc; i++)
logmsg_printf (LOG_DEBUG, " %s",
quotearg (comp->argv[i]));
logmsg_printf (LOG_DEBUG, "\n");
}
}
static void
prog_execute (struct prog *prog)
{
if (prog->v.p.comp->builtin)
{
mf_proctitle_format ("inetd %s",
prog->v.p.comp->builtin->service);
prog->v.p.comp->builtin->fun (0, prog->v.p.comp);
_exit (0);
}
execvp (prog->v.p.comp->program ?
prog->v.p.comp->program : prog->v.p.comp->argv[0],
prog->v.p.comp->argv);
openlog (log_tag, LOG_PID, prog->v.p.comp->facility);
syslog (LOG_CRIT, _("cannot start `%s': %s"), prog->tag,
strerror (errno));
_exit (EX_SOFTWARE);
}
void
progman_run_comp (struct component *comp, int fd,
union pies_sockaddr_storage *sa, socklen_t salen)
{
struct prog *prog = register_prog0 (comp, -1);
prog->v.p.socket = fd;
prog->v.p.sa_storage = *sa;
prog->v.p.sa_len = salen;
prog->v.p.cclass = conn_class_lookup (comp->tag, sa, salen);
prog_start_prologue (prog);
prog_sockenv (prog);
prog_execute (prog);
}
static int
console_open (int mode)
{
int i, fd;
for (i = 0; i < 5; i++)
{
fd = open (console_device, mode | O_NONBLOCK);
if (fd >= 0)
{
fcntl (fd, F_SETFL, mode);
return fd;
}
}
return -1;
}
static void
console_stty ()
{
struct termios tty;
int fd;
if ((fd = console_open (O_RDWR|O_NOCTTY)) < 0)
{
logmsg (LOG_CRIT, "can't open %s", console_device);
return;
}
tcgetattr (fd, &tty);
tty.c_cflag &= CBAUD|CBAUDEX|CSIZE|CSTOPB|PARENB|PARODD;
tty.c_cflag |= HUPCL|CLOCAL|CREAD;
tty.c_cc[VINTR] = 3; /* ctrl('c') */
tty.c_cc[VQUIT] = 28; /* ctrl('\\') */
tty.c_cc[VERASE] = 127;
tty.c_cc[VKILL] = 24; /* ctrl('x') */
tty.c_cc[VEOF] = 4; /* ctrl('d') */
tty.c_cc[VTIME] = 0;
tty.c_cc[VMIN] = 1;
tty.c_cc[VSTART] = 17; /* ctrl('q') */
tty.c_cc[VSTOP] = 19; /* ctrl('s') */
tty.c_cc[VSUSP] = 26; /* ctrl('z') */
/*
* Set pre and post processing
*/
tty.c_iflag = IGNPAR|ICRNL|IXON|IXANY;
tty.c_oflag = OPOST|ONLCR;
tty.c_lflag = ISIG|ICANON|ECHO|ECHOCTL|ECHOPRT|ECHOKE;
/*
* Now set the terminal line.
* We don't care about non-transmitted output data
* and non-read input data.
*/
tcsetattr (fd, TCSANOW, &tty);
tcflush(fd, TCIOFLUSH);
close (fd);
}
static void
prog_start (struct prog *prog)
{
pid_t pid;
int redir[2];
fd_set fdset;
if (prog->pid > 0 || !IS_COMPONENT (prog))
return;
if (is_sysvinit (prog->v.p.comp))
{
if (!init_process)
{
if (prog->v.p.status != status_disabled)
{
logmsg (LOG_NOTICE, "disabling sysvinit component %s",
prog->tag);
prog->v.p.status = status_disabled;
}
return;
}
debug (1, ("ok to start %s", prog->v.p.comp->tag));
}
/* This call returns 1 in two cases: Either prog is marked as disabled,
in which case there's nothing more to do, or one or more of its
prerequisites are in status_stopping. In the latter case either the
components in question will exit or a SIGALRM will get delivered. In
both cases, it will cause further processing of `prog'. */
if (prog_start_prerequisites (prog))
return;
switch (prog->v.p.comp->mode)
{
case pies_comp_exec:
if (check_spawn_rate (prog))
return;
break;
case pies_comp_pass_fd:
if (check_spawn_rate (prog))
return;
debug (1, (_("unlinking %s"), prog->v.p.comp->pass_fd_socket));
if (unlink (prog->v.p.comp->pass_fd_socket) && errno != ENOENT)
{
logmsg (LOG_ERR, _("cannot unlink %s: %s"),
prog->v.p.comp->pass_fd_socket,
strerror (errno));
return;
}
if (prog_open_socket (prog))
return;
break;
case pies_comp_accept:
if (check_spawn_rate (prog))
return;
if (prog_open_socket (prog))
return;
break;
case pies_comp_inetd:
/* Wait until an incoming connection is requested */
if (prog->v.p.socket == -1)
return;
}
debug (1, (_("starting %s"), prog->tag));
if (prog->v.p.comp->rmfile)
{
debug (1, (_("unlinking %s"), prog->v.p.comp->rmfile));
if (unlink (prog->v.p.comp->rmfile) && errno != ENOENT)
logmsg (LOG_ERR, _("%s: cannot remove file `%s': %s"),
prog->tag, prog->v.p.comp->rmfile, strerror (errno));
}
if (prog->v.p.comp->builtin && prog->v.p.comp->builtin->single_process)
{
prog->v.p.comp->builtin->fun (prog->v.p.socket, prog->v.p.comp);
return;
}
if (!init_process)
{
redir[RETR_OUT] = open_redirector (prog, RETR_OUT);
redir[RETR_ERR] = open_redirector (prog, RETR_ERR);
}
switch (pid = fork ())
{
/* The child branch. */
case 0:
signal_setup (SIG_DFL);
setsid ();
prog_start_prologue (prog);
switch (prog->v.p.comp->mode)
{
case pies_comp_accept:
case pies_comp_inetd:
prog_sockenv (prog);
dup2 (prog->v.p.socket, 0);
dup2 (prog->v.p.socket, 1);
close (prog->v.p.socket);
prog->v.p.socket = -1;
break;
default:
if (init_process)
{
int fd = console_open (O_RDWR|O_NOCTTY);
if (fd < 0)
{
logmsg (LOG_CRIT, "open(%s): %s",
console_device, strerror (errno));
fd = open("/dev/null", O_RDWR);
}
if (fd != 0)
dup2 (fd, 0);
if (fd != 1)
dup2 (fd, 1);
if (fd != 2)
dup2 (fd, 2);
}
else
{
if (redir[RETR_OUT] == -1)
{
close (1);
open ("/dev/null", O_WRONLY);
}
else if (redir[RETR_OUT] != 1)
{
dup2 (redir[RETR_OUT], 1);
}
}
break;
}
if (!init_process)
{
if (redir[RETR_ERR] == -1)
{
if (!DIAG_OUTPUT (DIAG_TO_STDERR))
{
close (2);
open ("/dev/null", O_WRONLY);
}
}
else if (redir[RETR_ERR] != 1)
{
dup2 (redir[RETR_ERR], 2);
}
}
/* Close unneeded descripitors */
FD_ZERO (&fdset);
FD_SET (0, &fdset);
FD_SET (1, &fdset);
FD_SET (2, &fdset);
if (prog->v.p.comp->mode == pies_comp_pass_fd)
FD_SET (prog->v.p.socket, &fdset);
close_fds (&fdset);
prog_execute (prog);
case -1:
logmsg (LOG_CRIT,
_("cannot run `%s': fork failed: %s"),
prog->tag, strerror (errno));
break;
default:
if (prog->v.p.comp->mode == pies_comp_pass_fd)
{
pass_fd (prog->v.p.comp->pass_fd_socket, prog->v.p.socket,
prog->v.p.comp->pass_fd_timeout ?
prog->v.p.comp->pass_fd_timeout : DEFAULT_PASS_FD_TIMEOUT);
/* FIXME: Error code */;
}
if (prog->v.p.comp->flags & CF_WAIT)
{
disable_socket (prog->v.p.socket);
}
else if (prog->v.p.comp->mode == pies_comp_accept ||
prog->v.p.comp->mode == pies_comp_inetd ||
prog->v.p.comp->mode == pies_comp_pass_fd)
close (prog->v.p.socket);
else if (is_sysvinit (prog->v.p.comp))
sysvinit_acct (SYSV_ACCT_PROC_START, "", prog->tag, pid, "");
prog->pid = pid;
prog->v.p.status = status_enabled;
debug (1, (_("%s started, pid=%lu"), prog->tag, (unsigned long) pid));
}
}
int
check_acl (pies_acl_t acl, struct sockaddr *s, socklen_t salen)
{
struct acl_input input;
int rc;
if (!acl)
return 0;
input.addr = s;
input.addrlen = salen;
input.user = NULL;
input.groups = NULL;
rc = pies_acl_check (acl, &input, 1);
if (rc == 0)
{
char *p = sockaddr_to_astr (s, salen);
logmsg (LOG_ERR, _("access from %s blocked"), p);
free (p);
return 1;
}
return 0;
}
void
fd_report (int fd, const char *msg)
{
size_t len;
if (!msg)
return;
for (len = strlen (msg); len; )
{
ssize_t rc = write (fd, msg, len);
if (rc == -1)
{
logmsg (LOG_ERR,
_("error writing to socket: %s"),
strerror (errno));
break;
}
else if (rc == 0)
break;
len -= rc;
msg += rc;
}
}
static int
_prog_accept (struct prog *p)
{
int fd;
struct prog *pinst;
union pies_sockaddr_storage addr;
socklen_t addrlen = sizeof addr;
struct conn_class *pcclass;
fd = accept (p->v.p.socket, (struct sockaddr*) &addr, &addrlen);
if (fd == -1)
{
logmsg (LOG_ERR, _("accept failed: %s"), strerror (errno));
return 1;
}
if (debug_level >= 1)
{
char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
logmsg (LOG_DEBUG, _("%s wants %s"), s, p->tag);
free (s);
}
if (check_acl (p->v.p.comp->acl, (struct sockaddr *)&addr, addrlen)
|| check_acl (pies_acl, (struct sockaddr *)&addr, addrlen))
{
fd_report (fd, p->v.p.comp->access_denied_message);
close (fd);
return 1;
}
if (p->v.p.comp->max_instances &&
p->v.p.num_instances >= p->v.p.comp->max_instances)
{
char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
logmsg (LOG_ERR,
_("%s: access from %s denied: too many instances running"),
p->tag, s);
free (s);
fd_report (fd, p->v.p.comp->max_instances_message);
close (fd);
return 1;
}
pcclass = conn_class_lookup (p->tag, &addr, addrlen);
if (p->v.p.comp->max_ip_connections &&
pcclass->count >= p->v.p.comp->max_ip_connections)
{
char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
logmsg (LOG_ERR,
_("%s: access from %s denied: "
"too many connections from that ip"),
p->tag, s);
free (s);
fd_report (fd, p->v.p.comp->max_ip_connections_message);
close (fd);
return 1;
}
if (check_connection_rate (p))
{
disable_socket (p->v.p.socket);
close (fd);
return 1;
}
pcclass->count++;
if (debug_level > 1)
conn_class_report (LOG_DEBUG, pcclass);
pinst = register_prog0 (p->v.p.comp, -1);
pinst->v.p.socket = fd;
pinst->v.p.listener = p;
pinst->v.p.sa_storage = addr;
pinst->v.p.sa_len = addrlen;
pinst->v.p.cclass = pcclass;
prog_start (pinst);
close (fd);
pinst->v.p.socket = -1;
p->v.p.num_instances++;
return 0;
}
static int
_prog_wait (struct prog *p)
{
struct prog *pinst;
debug (1, (_("someone wants %s"), p->tag));
if (p->v.p.comp->max_instances
&& p->v.p.num_instances >= p->v.p.comp->max_instances)
{
logmsg (LOG_ERR,
_("%s: too many instances running, dropping connection"),
p->tag);
return 1;
}
pinst = register_prog0 (p->v.p.comp, -1);
pinst->v.p.socket = p->v.p.socket;
pinst->v.p.listener = p;
prog_start (pinst);
pinst->v.p.socket = -1;
p->v.p.num_instances++;
return 0;
}
int
progman_accept (int socket)
{
struct prog *p = prog_lookup_by_socket (socket);
if (!p)
{
logmsg (LOG_EMERG,
_("INTERNAL ERROR: no matching prog for fd %d"), socket);
return 1;
}
if (p->v.p.comp->socket_type == SOCK_STREAM
&& !(p->v.p.comp->flags & CF_WAIT))
return _prog_accept (p);
return _prog_wait (p);
}
void
component_fixup_depend (struct component *comp)
{
struct grecs_list_entry *ep;
if (comp->depend == NULL)
return;
for (ep = comp->depend->head; ep; ep = ep->next)
{
const char *tag = ep->data;
struct component *tgt;
tgt = progman_lookup_component (tag);
if (!tgt)
{
logmsg (LOG_ERR,
_("component %s declares dependency target %s, "
"which is not declared"),
comp->tag, tag);
continue;
}
if (!tgt->prereq)
{
tgt->prereq = grecs_list_create();
}
/* FIXME: memory allocation */
grecs_list_append (tgt->prereq, xstrdup (comp->tag));
}
grecs_list_free (comp->depend);
comp->depend = NULL;
}
void
fixup_prerequisites ()
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog))
component_fixup_depend (prog->v.p.comp);
}
void
rebuild_prerequisites ()
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog))
prog_rebuild_prerequisites (prog);
}
void
print_dep (struct prog *prog)
{
pies_depmap_pos_t pos;
size_t n;
logmsg_printf (LOG_NOTICE, "%s -> ", prog->tag);
for (n = depmap_first (depmap, depmap_col, prog->v.p.idx, &pos);
n != (size_t)-1;
n = depmap_next (depmap, pos))
{
struct prog *dp = prog_lookup_by_idx (n);
logmsg_printf (LOG_NOTICE, "%s -> ", dp->tag);
}
logmsg_printf (LOG_NOTICE, "%s\n", prog->tag);
}
void
progman_dump_prereq ()
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
if (prog->prereq)
{
int i;
printf ("%s:", prog->tag);
for (i = 0; prog->prereq[i]; i++)
printf (" %s", prog->prereq[i]);
printf ("\n");
}
}
void
progman_dump_depmap ()
{
struct prog *prog;
size_t i, j;
printf ("%s:\n", _("Dependency map"));
printf (" ");
for (i = 0; i < numcomp; i++)
printf (" %2lu", (unsigned long)i);
printf ("\n");
for (i = 0; i < numcomp; i++)
{
printf ("%2lu ", (unsigned long)i);
for (j = 0; j < numcomp; j++)
printf (" %c ", depmap_isset (depmap, i, j) ? 'X' : ' ');
printf ("\n");
}
printf ("\n%s:\n", _("Legend"));
for (i = 0; i < numcomp; i++)
{
prog = prog_lookup_by_idx (i);
if (prog)
printf ("%2lu: %s\n", (unsigned long)i, prog->tag);
}
}
int
progman_build_depmap ()
{
int rc = 0;
size_t i;
struct prog *prog;
pies_depmap_t dp;
fixup_prerequisites ();
rebuild_prerequisites ();
depmap = depmap_alloc (numcomp);
for (prog = proghead; prog; prog = prog->next)
if (prog->prereq)
{
for (i = 0; prog->prereq[i]; i++)
{
struct prog *dep = prog_lookup_by_tag (prog->prereq[i]);
if (!dep)
{
prog->v.p.status = status_disabled;
logmsg (LOG_ERR, _("component %s depends on %s, "
"which is not declared"),
prog->tag, prog->prereq[i]);
rc++;
}
else
depmap_set (depmap, prog->v.p.idx, dep->v.p.idx);
}
}
dp = depmap_copy (depmap);
depmap_tc (dp);
for (i = 0; i < numcomp; i++)
if (depmap_isset (dp, i, i))
{
prog = prog_lookup_by_idx (i);
logmsg (LOG_ERR, _("component %s depends on itself"), prog->tag);
print_dep (prog);
prog->v.p.status = status_disabled;
rc++;
}
free (dp);
return rc;
}
void
progman_create_sockets ()
{
struct prog *prog;
for (prog = proghead; prog; prog = prog->next)
{
if (IS_COMPONENT (prog))
{
struct component *comp = prog->v.p.comp;
if (comp->mode == pies_comp_inetd && !ISCF_TCPMUX (comp->flags))
{
int fd = create_socket (comp->socket_url,
comp->socket_type,
comp->privs.user, comp->umask);
if (fd == -1)
prog->v.p.status = status_disabled;
else if (register_socket (comp->socket_type, fd))
{
close (fd);
prog->v.p.status = status_disabled;
}
else
prog->v.p.socket = fd;
}
}
}
}
void
progman_recompute_alarm ()
{
struct prog *prog;
time_t now = time (NULL);
time_t alarm_time = 0, x;
recompute_alarm = 0;
debug (2, ("Recomputing alarm settings"));
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog))
{
switch (prog->v.p.status)
{
case status_sleeping:
x = SLEEPTIME - (now - prog->v.p.timestamp);
if (alarm_time == 0 || x < alarm_time)
alarm_time = x;
break;
case status_stopping:
x = shutdown_timeout - (now - prog->v.p.timestamp);
if (alarm_time == 0 || x < alarm_time)
alarm_time = x;
break;
default:
break;
}
}
debug (2, ("alarm=%lu", (unsigned long)alarm_time));
if (alarm_time)
alarm (alarm_time);
}
void
progman_start ()
{
struct prog *prog;
recompute_alarm = 0;
debug (1, ("starting components"));
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog))
{
if (prog->v.p.comp->mode == pies_comp_inetd)
{
if (prog->v.p.comp->flags & CF_DISABLED)
disable_socket (prog->v.p.socket);
else
{
prog->v.p.status = status_listener;
enable_socket (prog->v.p.socket);
}
}
else if ((prog->v.p.status == status_enabled && prog->pid == 0)
|| prog->v.p.status == status_sleeping)
prog_start (prog);
}
}
static void
check_stopping (struct prog *prog, time_t now)
{
if (now - prog->v.p.timestamp >= shutdown_timeout)
{
if (prog->pid == 0)
logmsg (LOG_EMERG,
_("INTERNAL ERROR: attempting to kill unexisting process %s"),
prog->tag);
else if (prog->v.p.comp->flags & CF_SIGGROUP)
kill (-prog->pid, SIGKILL);
else
kill (prog->pid, SIGKILL);
}
else
recompute_alarm = 1;
}
void
progman_wake_sleeping (int onalrm)
{
struct prog *prog;
time_t now = time (NULL);
debug (1, (_("managing sleeping/stopping components")));
for (prog = proghead; prog; prog = prog->next)
if (IS_COMPONENT (prog))
{
switch (prog->v.p.status)
{
case status_sleeping:
if (now - prog->v.p.timestamp >= SLEEPTIME)
{
if (prog->v.p.comp->mode == pies_comp_inetd)
{
prog->v.p.status = status_listener;
enable_socket (prog->v.p.socket);
}
else
{
prog->v.p.status = status_enabled;
prog->v.p.failcount = 0;
prog->v.p.timestamp = 0;
prog_start (prog);
}
}
/* If there is no alarm pending, recompute next alarm.
This allows to cope with eventual clock inaccuracies. */
if (onalrm)
recompute_alarm = 1;
break;
case status_stopping:
check_stopping (prog, now);
break;
case status_enabled:
if (prog->pid == 0)
prog_start (prog);
break;
default:
break;
}
}
if (recompute_alarm)
progman_recompute_alarm ();
}
static int
prog_start_prerequisites (struct prog *prog)
{
int i;
int ret;
if (prog->v.p.status == status_disabled)
return 1;
if (!prog->prereq)
return 0; /* Ok to startup */
debug (1, ("starting prerequisites of %s", prog->tag));
ret = 0;
for (i = 0; prog->prereq[i]; i++)
{
struct prog *dp = prog_lookup_by_tag (prog->prereq[i]);
if (!IS_COMPONENT (dp)) /* Skip redirectors */
continue;
if (prog->v.p.comp->flags & CF_PRECIOUS)
continue;
switch (dp->v.p.status)
{
case status_enabled:
if (prog->pid != 0)
continue;
break;
case status_disabled:
prog->v.p.status = status_disabled;
return 1;
case status_listener:
continue;
case status_sleeping:
/* FIXME: What to do in this case? */
break;
case status_stopping:
check_stopping (dp, time (NULL));
ret = 1;
continue;
case status_finished:
continue;
}
prog_start (dp);
if (dp->v.p.comp->mode == pies_comp_once ||
!(dp->v.p.status == status_enabled && dp->pid))
ret = 1;
}
return ret;
}
void
prog_stop_redirectors (struct prog *prog)
{
if (prog->v.p.redir[RETR_OUT])
prog_stop (prog->v.p.redir[RETR_OUT], SIGTERM);
if (prog->v.p.redir[RETR_ERR])
prog_stop (prog->v.p.redir[RETR_ERR], SIGTERM);
}
void
prog_stop_dependents (struct prog *prog)
{
pies_depmap_pos_t pos;
size_t n;
int warned = 0;
prog_stop_redirectors (prog);
for (n = depmap_first (depmap, depmap_row, prog->v.p.idx, &pos);
n != (size_t)-1;
n = depmap_next (depmap, pos))
{
struct prog *dp = prog_lookup_by_idx (n);
if (!dp) /* should not happen */
continue;
if (!warned && dp->pid)
{
debug (1, ("stopping dependencies of %s", prog->tag));
warned = 1;
}
prog_stop (dp, SIGTERM);
}
free (pos);
}
static void
prog_stop (struct prog *prog, int sig)
{
if (prog->pid == 0)
return;
if (prog->type == TYPE_COMPONENT)
{
if (prog->v.p.status == status_enabled)
{
prog->v.p.status = status_stopping;
prog->v.p.timestamp = time (NULL);
recompute_alarm = 1;
}
}
debug (1, ("stopping %s (%lu)", prog->tag, (unsigned long) prog->pid));
if (prog->type == TYPE_COMPONENT && prog->v.p.comp->flags & CF_SIGGROUP)
kill (-prog->pid, sig);
else
kill (prog->pid, sig);
}
static void
prog_stop_all (int sig)
{
struct prog *prog;
debug (1, ("stopping all components (signal %d)", sig));
for (prog = progtail; prog; prog = prog->prev)
if (IS_COMPONENT (prog)
&& (prog->v.p.status == status_enabled
|| prog->v.p.status == status_stopping))
prog_stop (prog, sig);
}
void
progman_stop ()
{
unsigned long i;
prog_stop_all (SIGTERM);
for (i = 0; i < shutdown_timeout; i++)
{
progman_cleanup (1);
if (progman_running_count () == 0)
return;
sleep (1);
}
prog_stop_all (SIGKILL);
}
static void
print_status (const char *tag, pid_t pid, int status, int expect_term)
{
if (WIFEXITED (status))
{
if (WEXITSTATUS (status) == 0)
debug (1, (_("%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);
}
static void propagate_child_exit (pid_t) ATTRIBUTE_NORETURN;
static void
propagate_child_exit (pid_t pid)
{
int wait_status;
while (waitpid (pid, &wait_status, 0) == -1)
if (errno != EINTR)
{
logmsg (LOG_ERR, _("waitpid failed: %s"), strerror (errno));
exit (EX_OSERR);
}
if (WIFSIGNALED (wait_status))
{
int sig = WTERMSIG (wait_status);
signal (sig, SIG_DFL);
raise (sig);
}
else if (WIFEXITED (wait_status))
exit (WEXITSTATUS (wait_status));
exit (127);
}
char *
wordsplit_string (struct wordsplit const *ws)
{
char *ret, *p;
size_t count = ws->ws_wordc + ws->ws_offs;
char **argv = xcalloc (count, sizeof (argv[0]));
size_t i;
size_t len = 0;
for (i = 0; i < count; i++)
{
argv[i] = quotearg_n (i, ws->ws_wordv[i]);
len += strlen (argv[i]);
}
len += count;
ret = xmalloc (len);
for (i = 0, p = ret; i < count; i++)
{
strcpy (p, argv[i]);
p += strlen (argv[i]);
*p++ = ' ';
}
p[-1] = 0;
free (argv);
return ret;
}
void
send_msg (char *rcpts, const char *msg_text)
{
int i, j, k;
pid_t child_pid, grand_child_pid;
struct wordsplit ws;
int p[2];
size_t size;
ws.ws_offs = mailer_argc;
ws.ws_delim = ",";
if (wordsplit (rcpts, &ws,
WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_DELIM | WRDSF_DOOFFS))
{
logmsg (LOG_ERR,
_("cannot parse recipient address list (%s)"),
rcpts);
return;
}
debug (1, (_("sending notification to %s"), rcpts));
/* Copy mailer arguments */
for (i = 0; i < mailer_argc; i++)
ws.ws_wordv[i] = mailer_argv[i];
/* j - number of the recipient;
i - index of the ws_wordv being converted;
k - index of the ws_wordv to store the converted value to.
Normally i == k, unless there are some invalid ws_wordv's,
in which case i > k.
*/
for (j = 0, k = i; j < ws.ws_wordc; j++, i++)
{
char *arg = ws.ws_wordv[i];
size_t len;
while (*arg && c_isblank (*arg))
arg++;
len = strlen (arg);
if (len > 0)
{
while (len > 0 && c_isblank (arg[len - 1]))
len--;
}
if (len == 0)
continue;
if (arg[0] == '<' && arg[len-1] == '>')
{
arg++;
len -= 2;
}
if (len == 0)
continue;
memmove (ws.ws_wordv[k], arg, len);
ws.ws_wordv[k][len] = 0;
k++;
}
ws.ws_wordv[k] = NULL;
/* Fork a child: */
child_pid = fork ();
if (child_pid < 0)
{
wordsplit_free (&ws);
logmsg (LOG_ERR,
_("cannot send mail: fork failed: %s"), strerror (errno));
return;
}
if (child_pid)
{
char *cmd = wordsplit_string (&ws);
wordsplit_free (&ws);
debug (1, (_("started mailer: %s, pid=%lu"),
cmd, (unsigned long) child_pid));
register_command (mailer_program, cmd, child_pid);
return;
}
/* Child process */
/* ============= */
signal_setup (SIG_DFL);
setsid ();
if (pipe (p))
{
logmsg (LOG_ERR, _("cannot send mail: pipe failed: %s"),
strerror (errno));
wordsplit_free (&ws);
exit (EX_OSERR);
}
grand_child_pid = fork ();
if (grand_child_pid < 0)
{
logmsg (LOG_ERR,
_("cannot send mail: fork failed: %s"), strerror (errno));
return;
}
if (grand_child_pid == 0)
{
/* Grand-child */
/* =========== */
if (p[0] != 0 && p[1] != 0)
close (0);
close (p[1]);
dup2 (p[0], 0);
execv (mailer_program, ws.ws_wordv);
exit (127);
}
/* Child again */
close (p[0]);
size = strlen (msg_text);
while (size)
{
ssize_t rc = write (p[1], msg_text, size);
if (rc <= 0)
{
logmsg (LOG_ERR, _("cannot write to pipe: %s"),
rc == 0 ? "EOF" : strerror (errno));
break;
}
size -= rc;
msg_text += rc;
}
close (p[1]);
propagate_child_exit (grand_child_pid);
}
static const char default_termination_message[] =
"From: <>\n"
"X-Agent: ${canonical-program-name} (${package} ${version})\n"
"Subject: Component ${component} ${termination} ${retcode}.\n"
"\n";
static void
notify (const char *tag, int status, struct action *act)
{
struct metadef mdef[] =
{
{ "canonical-program-name", "pies" },
{ "package", PACKAGE },
{ "version", PACKAGE_VERSION },
#define COMPONENT_IDX 3
{ "component", NULL },
#define TERMINATION_IDX 4
{ "termination", NULL },
#define RETCODE_IDX 5
{ "retcode", NULL },
#define PROGRAM_NAME_IDX 6
{ "program-name", NULL },
#define INSTANCE_IDX 7
{ "instance", NULL },
{ NULL }
};
char *msg_text = NULL;
char buf[INT_BUFSIZE_BOUND (uintmax_t)];
mdef[COMPONENT_IDX].value = (char*) tag;
mdef[INSTANCE_IDX].value = (char*) tag;
if (WIFEXITED (status))
{
/* TRANSLATORS: The subject of this statement is 'component' */
mdef[TERMINATION_IDX].value = (char*) _("exited with code");
mdef[RETCODE_IDX].value = umaxtostr (WEXITSTATUS (status), buf);
}
else if (WIFSIGNALED (status))
{
/* TRANSLATORS: The subject of this statement is 'component' */
mdef[TERMINATION_IDX].value = (char*) _("terminated on signal");
mdef[RETCODE_IDX].value = umaxtostr (WTERMSIG (status), buf);
}
else
{
mdef[TERMINATION_IDX].value = "UNKNOWN";
mdef[RETCODE_IDX].value = "UNKNOWN";
}
mdef[PROGRAM_NAME_IDX].value = (char*) program_name;
msg_text = meta_expand_string (act->message ?
act->message : default_termination_message,
mdef, NULL);
send_msg (act->addr, msg_text);
free (msg_text);
}
static int
status_matches_p (struct action *act, unsigned status)
{
int i;
if (act->nstat == 0)
return 1;
for (i = 0; i < act->nstat; i++)
if (act->status[i] == status)
return 1;
return 0;
}
static void
run_command (struct action *act, struct prog *prog, unsigned retcode,
pid_t child_pid)
{
pid_t pid;
char *argv[4];
char buf[INT_BUFSIZE_BOUND (uintmax_t)];
/* FIXME: optionally set output redirectors for this command? */
pid = fork ();
if (pid == (pid_t) -1)
{
logmsg (LOG_ERR, "fork: %s", strerror (errno));
return;
}
if (pid == 0)
{
debug (1, (_("executing %s"), act->command));
/* Child */
setsid ();
setenv ("PIES_VERSION", PACKAGE_VERSION, 1);
setenv ("PIES_COMPONENT", prog->tag, 1);
setenv ("PIES_PID", umaxtostr (child_pid, buf), 1);
if (retcode & STATUS_SIG_BIT)
setenv ("PIES_SIGNAL", umaxtostr (STATUS_CODE (retcode), buf), 1);
else
setenv ("PIES_STATUS", umaxtostr (STATUS_CODE (retcode), buf), 1);
close_fds (NULL);
argv[0] = "/bin/sh";
argv[1] = "-c";
argv[2] = act->command;
argv[3] = NULL;
execv (argv[0], argv);
exit (127);
}
/* Master */
debug (1, (_("started command: %s, pid=%lu"),
act->command, (unsigned long) pid));
register_command ((char*) _("[action]"), xstrdup (act->command), pid);
}
static void
react (struct prog *prog, int status, pid_t pid)
{
unsigned retcode;
struct action *act = prog->v.p.comp->act_head;
if (!act)
act = default_component.act_head;
if (WIFEXITED (status))
{
retcode = WEXITSTATUS (status);
debug (1, (_("%s: terminated with code %d"), prog->tag, retcode));
}
else if (WIFSIGNALED (status))
{
retcode = WTERMSIG (status);
debug (1, (_("%s: terminated on signal %d"), prog->tag, retcode));
retcode |= STATUS_SIG_BIT;
}
else
{
debug (1, (_("%s: unrecognized termination status"), prog->tag));
/* Enforce default action: */
act = NULL;
}
for (; act; act = act->next)
{
if (status_matches_p (act, retcode))
{
if (act->command)
{
run_command (act, prog, retcode, pid);
}
switch (act->act)
{
case action_restart:
if (prog->v.p.comp->mode != pies_comp_inetd)
prog_start (prog);
break;
case action_disable:
logmsg (LOG_NOTICE, _("disabling component %s"), prog->tag);
prog->v.p.status = status_disabled;
/* FIXME:
if (prog->v.p.comp->mode == pies_comp_inetd)
disable_socket (prog->v.p.socket);
*/
}
if (act->addr)
notify (prog->tag, status, act);
break;
}
}
if (!act && prog->v.p.comp->mode != pies_comp_inetd)
/* Default action: */
prog_start (prog);
}
void
progman_cleanup (int expect_term)
{
pid_t pid;
int status;
while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
{
struct prog *prog = prog_lookup_by_pid (pid);
if (!prog)
{
print_status (_("subprocess"), pid, status, expect_term);
continue;
}
prog->pid = 0;
switch (prog->type)
{
case TYPE_COMPONENT:
print_status (prog->tag, pid, status, expect_term);
if (prog->v.p.comp->mode == pies_comp_inetd)
{
struct prog *listener = prog->v.p.listener;
listener->v.p.num_instances--;
if (prog->v.p.cclass)
{
prog->v.p.cclass->count--;
if (debug_level > 1)
conn_class_report (LOG_DEBUG, prog->v.p.cclass);
if (prog->v.p.cclass->count == 0)
{
conn_class_remove (prog->v.p.cclass);
prog->v.p.cclass = NULL;
}
}
prog_stop_redirectors (prog);
destroy_prog (&prog);
react (listener, status, pid);
if (listener->v.p.comp->flags & CF_WAIT)
enable_socket (listener->v.p.socket);
}
else if (prog->v.p.comp->mode >= pies_mark_sysvinit)
{
sysvinit_acct (SYSV_ACCT_PROC_STOP, "", prog->tag, pid, "");
prog->v.p.status = status_finished;
}
else
{
if (is_sysvinit (prog->v.p.comp))
sysvinit_acct (SYSV_ACCT_PROC_STOP, "", prog->tag, pid, "");
prog->v.p.status = status_enabled;
prog_stop_dependents (prog);
if (!expect_term)
react (prog, status, pid);
}
break;
case TYPE_REDIRECTOR:
/* It was a redirector of an already finished process. */
print_status (prog->tag, pid, status,
expect_term ||
(prog->v.r.master &&
prog->v.r.master->v.p.status == status_stopping));
debug (1, (_("removing redirector %s, pid=%lu"),
prog->tag, (unsigned long)pid));
destroy_prog (&prog);
break;
case TYPE_COMMAND:
print_status (prog->tag, pid, status, expect_term);
destroy_prog (&prog);
break;
}
}
if (!expect_term)
/* This will also recompute alarm settings, if necessary */
progman_wake_sleeping (0);
}
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_COMPONENT (prog) && strcmp (prog->tag, name) == 0)
{
switch (prog->v.p.status)
{
case status_enabled:
case status_listener:
prog_stop (prog, SIGTERM);
break;
case status_disabled:
if (!(prog->v.p.comp->flags & CF_DISABLED))
prog->v.p.status = status_enabled;
break;
case status_sleeping:
prog->v.p.failcount = 0;
break;
default:
logmsg (LOG_INFO,
_("stopping component `%s': component not started"),
name);
}
}
}
void
progman_dump_stats (const char *filename)
{
FILE *fp;
struct prog *prog;
char *tmpfile = NULL;
asprintf (&tmpfile, "%s.%lu", filename, (unsigned long) getpid ());
if (!tmpfile)
{
logmsg (LOG_ERR, "%s", strerror (ENOMEM));
return;
}
logmsg (LOG_INFO, _("dumping statistics to `%s'"), tmpfile);
fp = fopen (tmpfile, "w");
if (!fp)
{
logmsg (LOG_ERR, _("cannot open file `%s' for writing: %s"),
tmpfile, strerror (errno));
return;
}
for (prog = proghead; prog; prog = prog->next)
{
int i;
char fbuf[5];
int fidx = 0;
fprintf (fp, "%-16s ", prog->tag);
switch (prog->type)
{
case TYPE_COMPONENT:
switch (prog->v.p.comp->mode)
{
case pies_comp_exec:
fbuf[fidx++] = 'C';
break;
case pies_comp_once:
fbuf[fidx++] = 'c';
break;
case pies_comp_accept:
fbuf[fidx++] = 'A';
break;
case pies_comp_inetd:
fbuf[fidx++] = 'I';
break;
case pies_comp_pass_fd:
fbuf[fidx++] = 'P';
}
switch (prog->v.p.status)
{
case status_enabled:
fbuf[fidx++] = (prog->pid != 0) ? 'R' : ' ';
break;
case status_disabled:
fbuf[fidx++] = 'D';
break;
case status_listener:
fbuf[fidx++] = 'L';
break;
case status_sleeping:
fbuf[fidx++] = 's';
break;
case status_stopping:
fbuf[fidx++] = 'S';
break;
case status_finished:
fbuf[fidx++] = 'f';
}
break;
case TYPE_REDIRECTOR:
fbuf[fidx++] = 'R';
break;
case TYPE_COMMAND:
fbuf[fidx++] = 'E';
}
fbuf[fidx++] = 0;
fprintf (fp, "%-8.8s ", fbuf);
switch (prog->type)
{
case TYPE_COMPONENT:
if (prog->pid)
fprintf (fp, "%10lu ", (unsigned long) prog->pid);
else if (prog->v.p.status == status_listener
&& prog->v.p.comp->socket_url)
fprintf (fp, "%-10s ", prog->v.p.comp->socket_url->string);
else
fprintf (fp, "%-10s ", "N/A");
if (prog->v.p.status == status_sleeping)
{
time_t t = prog->v.p.timestamp + SLEEPTIME;
fprintftime (fp, "%H:%M:%S", localtime (&t), 0, 0);
}
for (i = 0; i < prog->v.p.comp->argc; i++)
fprintf (fp, " %s", quotearg (prog->v.p.comp->argv[i]));
break;
case TYPE_REDIRECTOR:
fprintf (fp, "%10lu ", (unsigned long) prog->pid);
break;
case TYPE_COMMAND:
fprintf (fp, "%s", prog->v.c.command);
}
fputc ('\n', fp);
}
fclose (fp);
unlink (filename);
if (rename (tmpfile, filename))
{
logmsg (LOG_ERR, _("cannot rename %s to %s: %s"), tmpfile, filename,
strerror (errno));
unlink (tmpfile);
}
free (tmpfile);
}
/* EOF */