diff options
Diffstat (limited to 'src/progman.c')
-rw-r--r-- | src/progman.c | 1741 |
1 files changed, 1741 insertions, 0 deletions
diff --git a/src/progman.c b/src/progman.c new file mode 100644 index 0000000..9383aae --- /dev/null +++ b/src/progman.c @@ -0,0 +1,1741 @@ +/* This file is part of Mailfromd. + Copyright (C) 2007, 2008, 2009 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 <http://www.gnu.org/licenses/>. */ + +#include "pies.h" + +#define TYPE_COMPONENT 0 +#define TYPE_RETR 1 + +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 */ + }; + +struct prog +{ + struct prog *next, *prev; + int type; + pid_t pid; /* PID */ + char *tag; /* Entry tag (for diagnostics purposes) */ + int idx; /* Numeric identifier */ + char **prereq; + int facility; + union + { + struct + { + struct component *comp; + int argc; /* Number of elements in comp->argv */ + int socket; + struct prog *redir[2]; /* Pointers to redirectors */ + time_t timestamp; /* Time of last startup */ + size_t count; /* Number of failed starts since timestamp */ + enum prog_status status; /* Current component status */ + } p; + + struct + { + struct prog *master; + } r; + } v; +}; + +#define IS_PROG(p) ((p)->type == TYPE_COMPONENT) + +static int numprog; +static struct prog *proghead, *progtail; +static pies_depmap_t depmap; +static int recompute_alarm; + +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_PROG (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 component * +progman_lookup_component (const char *tag) +{ + struct prog *prog; + for (prog = proghead; prog; prog = prog->next) + if (IS_PROG (prog) && strcmp (prog->tag, tag) == 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 (prog->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) +{ + unlink_prog (*pp); + free (*pp); + *pp = NULL; +} + +static char * +redir_tag (struct prog *master, int type) +{ + static char *redirstr[2] = { "stdout", "stderr" }; + char *str = NULL; + if (type < MU_ARRAY_SIZE(redirstr)) + asprintf (&str, "%s/%s", master->tag, redirstr[type]); + else + asprintf (&str, "%s/%d", master->tag, type); + if (!str) + xalloc_die (); + 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_RETR; + pstr = (char *) (pp + 1); + pp->tag = pstr; + strcpy (pp->tag, tag); + pstr += strlen (pp->tag) + 1; + free (tag); + pp->v.r.master = master; + pp->idx = -1; /* Retranslators are not indexed */ + 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] == NULL) + { + pp = register_redir (type, master); + master->v.p.redir[type] = pp; + } + else + { + pp = master->v.p.redir[type]; + prog_stop (pp, SIGKILL); /* Just in case */ + } + pp->pid = pid; +} + +static struct prog * +register_prog0 (struct component *comp, int index) +{ + struct prog *newp; + int i; + + newp = xzalloc (sizeof (*newp)); + newp->type = TYPE_COMPONENT; + newp->tag = comp->tag; + newp->pid = 0; + newp->idx = index; + newp->facility = comp->facility; + newp->v.p.comp = comp; + for (i = 0; comp->argv[i]; i++) + ; + newp->v.p.argc = i; + newp->v.p.socket = -1; + if (comp->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 +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) + { + mu_list_count (comp->prereq, &depc); + if (depc == 1) + { + char *item; + mu_list_get (comp->prereq, 0, (void**)&item); + 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) + mu_list_destroy (&comp->prereq); + } + } + + 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 + { + mu_iterator_t itr = NULL; + mu_list_get_iterator (comp->prereq, &itr); + for (mu_iterator_first (itr), depc = 0; + !mu_iterator_is_done (itr); mu_iterator_next (itr), depc++) + { + char *str; + mu_iterator_current (itr, (void**)&str); + prog->prereq[depc] = str; + } + mu_iterator_destroy (&itr); + } + } + prog->prereq[depc] = NULL; +} + +void +register_prog (struct component *comp) +{ + register_prog0 (comp, numprog++); +} + +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) + { + mu_error (_("cannot open output file %s: %s"), + master->v.p.comp->redir[stream].v.file, + mu_strerror (errno)); + return -1; + } + /* Fix file ownership */ + pw = getpwnam (master->v.p.comp->privs.user); + if (pw) + chown (master->v.p.comp->redir[stream].v.file, pw->pw_uid, pw->pw_gid); + return fd; +} + +int +open_redirector (struct prog *master, int stream) +{ + int p[2]; + FILE *fp; + char *buf = NULL; + size_t size = 0; + pid_t pid; + int i, prio; + char *tag; + + 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); + + for (i = getmaxfd (); i >= 0; i--) + { + if (i != p[0]) + close (i); + } + + log_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: + mu_diag_output (MU_DIAG_CRIT, + _("cannot run redirector `%s': fork failed: %s"), + master->tag, mu_strerror (errno)); + return -1; + + default: + update_redir (stream, master, pid); + close (p[0]); + return p[1]; + } +} + +extern char **environ; + +static char * +find_env (const char *name, int val) +{ + 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 char ** +env_setup (char **env) +{ + char **old_env = environ; + char **new_env; + int count, i, n; + + if (!env) + return old_env; + + if (strcmp (env[0], "-") == 0) + { + old_env = NULL; + env++; + } + + /* Count new environment size */ + count = 0; + if (old_env) + for (i = 0; old_env[i]; i++) + count++; + + for (i = 0; env[i]; i++) + count++; + + /* Allocate the 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 (env, old_env[i])) + new_env[n++] = old_env[i]; + } + + for (i = 0; env[i]; i++) + { + char *p; + + if (env[i][0] == '-') + { + /* Skip unset directives. */ + continue; + } + if ((p = strchr (env[i], '='))) + { + if (p == env[i]) + continue; /* Ignore erroneous entry */ + if (p[-1] == '+') + new_env[n++] = env_concat (env[i], p - env[i] - 1, + find_env(env[i], 1), p + 1); + else if (p[1] == '+') + new_env[n++] = env_concat (env[i], p - env[i], + p + 2, find_env(env[i], 1)); + else + new_env[n++] = env[i]; + } + else + { + p = find_env (env[i], 0); + if (p) + new_env[n++] = p; + } + } + new_env[n] = NULL; + return new_env; +} + +static void +prog_start (struct prog *prog) +{ + int i; + pid_t pid; + time_t now; + int redir[2]; + + if (prog->pid > 0 || !IS_PROG (prog)) + return; + + /* 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; + + 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) + { + mu_error (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); + prog->v.p.timestamp = now; + prog->v.p.status = status_sleeping; + recompute_alarm = 1; + return; + } + + switch (prog->v.p.comp->mode) + { + case pies_comp_exec: + break; + + case pies_comp_pass_fd: + MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, _("unlinking %s\n"), + prog->v.p.comp->pass_fd_socket); + if (unlink (prog->v.p.comp->pass_fd_socket) && errno != ENOENT) + { + mu_error (_("cannot unlink %s: %s"), prog->v.p.comp->pass_fd_socket, + mu_strerror (errno)); + return; + } + /* fall through */ + + case pies_comp_accept: + prog->v.p.socket = create_socket (prog->v.p.comp->socket_url, + prog->v.p.comp->privs.user, + prog->v.p.comp->umask); + if (prog->v.p.socket == -1) + { + prog->v.p.status = status_disabled; + return; + } + if (listen (prog->v.p.socket, 8)) + { + mu_error ("listen: %s", mu_strerror (errno)); + close (prog->v.p.socket); + prog->v.p.socket = -1; + prog->v.p.status = status_disabled; + return; + } + break; + + case pies_comp_inetd: + /* Wait until an incoming connection is requested */ + if (prog->v.p.socket == -1) + return; + } + + MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, _("starting %s\n"), prog->tag); + + if (prog->v.p.comp->rmfile) + { + MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, _("unlinking %s\n"), + prog->v.p.comp->rmfile); + if (unlink (prog->v.p.comp->rmfile) && errno != ENOENT) + mu_error (_("%s: cannot remove file `%s': %s"), + prog->tag, prog->v.p.comp->rmfile, mu_strerror (errno)); + } + + 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); + if (prog->v.p.comp->dir) + { + MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, _("chdir %s\n"), + prog->v.p.comp->dir); + if (chdir (prog->v.p.comp->dir)) + mu_error (_("%s: cannot change to directory %s: %s"), + prog->tag, prog->v.p.comp->dir, mu_strerror (errno)); + } + + environ = env_setup (prog->v.p.comp->env); + if (mu_debug_check_level (pies_debug, MU_DEBUG_TRACE4)) + { + int i; + for (i = 0; environ[i]; i++) + __MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE4, "%s ", environ[i]); + mu_debug_printf (pies_debug, MU_DEBUG_TRACE4, "\n"); + } + mf_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 (mu_debug_check_level (pies_debug, MU_DEBUG_TRACE1)) + { + char *cmdline; + if (mu_argcv_string (prog->v.p.argc, prog->v.p.comp->argv, + &cmdline) == 0) + { + __MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, + "executing %s\n", cmdline); + free (cmdline); + } + } + + switch (prog->v.p.comp->mode) + { + case pies_comp_pass_fd: + case pies_comp_exec: + if (redir[RETR_OUT] == -1) + { + close (1); + open ("/dev/null", O_WRONLY); + } + else if (redir[RETR_OUT] != 1) + { + dup2 (redir[RETR_OUT], 1); + } + break; + + case pies_comp_accept: + case pies_comp_inetd: + dup2 (prog->v.p.socket, 0); + dup2 (prog->v.p.socket, 1); + close (prog->v.p.socket); + prog->v.p.socket = -1; + break; + } + + if (redir[RETR_ERR] == -1) + { + close (2); + open ("/dev/null", O_WRONLY); + } + else if (redir[RETR_ERR] != 1) + { + dup2 (redir[RETR_ERR], 2); + } + + /* Close unneded descripitors */ + for (i = getmaxfd (); i > 2; i--) + { + if (prog->v.p.comp->mode == pies_comp_pass_fd + && i == prog->v.p.socket) + continue; + close (i); + } + + execvp (prog->v.p.comp->program ? + prog->v.p.comp->program : prog->v.p.comp->argv[0], + prog->v.p.comp->argv); + openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility); + syslog (LOG_CRIT, _("cannot start `%s': %s"), prog->tag, + mu_strerror (errno)); + _exit (EX_SOFTWARE); + + case -1: + mu_diag_output (MU_DIAG_CRIT, + _("cannot run `%s': fork failed: %s"), + prog->tag, mu_strerror (errno)); + break; + + default: + if (prog->v.p.comp->mode == pies_comp_pass_fd) + { + sleep(1); + pass_fd (prog->v.p.comp->pass_fd_socket, prog->v.p.socket); + /* FIXME: Error code */; + } + if (prog->v.p.comp->mode != pies_comp_exec) + close (prog->v.p.socket); + prog->pid = pid; + prog->v.p.status = status_enabled; + } +} + +int +pies_check_acl (mu_acl_t acl, struct sockaddr *s, int salen) +{ + mu_acl_result_t res; + int rc; + + if (!acl) + return 0; + + rc = mu_acl_check_sockaddr (acl, s, salen, &res); + if (rc) + { + char *p = mu_sockaddr_to_astr (s, salen); + mu_error (_("access from %s blocked: cannot check ACLs: %s"), + p, mu_strerror (rc)); + free (p); + return 1; + } + + switch (res) + { + case mu_acl_result_undefined: + { + char *p = mu_sockaddr_to_astr (s, salen); + mu_diag_output (MU_DIAG_INFO, + _("%s: undefined ACL result; access allowed"), + p); + free (p); + } + break; + + case mu_acl_result_accept: + break; + + case mu_acl_result_deny: + { + char *p = mu_sockaddr_to_astr (s, salen); + mu_error (_("Access from %s blocked."), p); + free (p); + return 1; + } + } + return 0; +} + +int +progman_accept (int socket) +{ + int fd; + struct prog *p; + union + { + struct sockaddr s_in; + struct sockaddr s_un; + } addr; + socklen_t addrlen = sizeof addr; + + fd = accept (socket, (struct sockaddr*) &addr, &addrlen); + if (fd == -1) + { + mu_error (_("accept failed: %s"), mu_strerror (errno)); + return 1; + } + + p = prog_lookup_by_socket (socket); + if (!p) + { + mu_error (_("INTERNAL ERROR: no matching prog for fd %d"), socket); + close (fd); + return 1; + } + + if (mu_debug_check_level (pies_debug, MU_DEBUG_TRACE1)) + { + char *s = mu_sockaddr_to_astr ((struct sockaddr *)&addr, addrlen); + __MU_DEBUG2 (pies_debug, MU_DEBUG_TRACE1, "%s wants %s\n", s, p->tag); + free (s); + } + + if (pies_check_acl (p->v.p.comp->acl, (struct sockaddr *)&addr, addrlen) + || pies_check_acl (pies_acl, (struct sockaddr *)&addr, addrlen)) + { + close (fd); + return 1; + } + + p = register_prog0 (p->v.p.comp, -1); + p->v.p.socket = fd; + prog_start (p); + close (fd); + p->v.p.socket = -1; + return 0; +} + + +void +component_fixup_depend (struct component *comp) +{ + mu_iterator_t itr; + + if (comp->depend == NULL) + return; + + mu_list_get_iterator (comp->depend, &itr); + for (mu_iterator_first (itr); + !mu_iterator_is_done (itr); mu_iterator_next (itr)) + { + char *tag; + struct component *tgt; + + mu_iterator_current (itr, (void**)&tag); + tgt = progman_lookup_component (tag); + if (!tgt) + { + mu_error (_("component %s declares dependency target %s, " + "which is not declared"), + comp->tag, tag); + continue; + } + if (!tgt->prereq) + { + int rc = mu_list_create (&tgt->prereq); + if (rc) + { + mu_error (_("cannot create list: %s"), mu_strerror (rc)); + continue; + } + } + /* FIXME: memory allocation */ + mu_list_append (tgt->prereq, xstrdup (comp->tag)); + } + mu_list_destroy (&comp->depend); +} + +void +fixup_prerequisites () +{ + struct prog *prog; + + for (prog = proghead; prog; prog = prog->next) + if (IS_PROG (prog)) + component_fixup_depend (prog->v.p.comp); +} + +void +rebuild_prerequisites () +{ + struct prog *prog; + for (prog = proghead; prog; prog = prog->next) + if (IS_PROG (prog)) + prog_rebuild_prerequisites (prog); +} + +void +print_dep (struct prog *prog) +{ + pies_depmap_pos_t pos; + unsigned n; + + mu_diag_printf (MU_DIAG_NOTICE, "%s -> ", prog->tag); + for (n = depmap_first (depmap, depmap_col, prog->idx, &pos); + n != (unsigned)-1; + n = depmap_next (depmap, pos)) + { + struct prog *dp = prog_lookup_by_idx (n); + mu_diag_printf (MU_DIAG_NOTICE, "%s -> ", dp->tag); + } + mu_diag_printf (MU_DIAG_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; + unsigned i, j; + + printf ("%s:\n", _("Dependency map")); + printf (" "); + for (i = 0; i < numprog; i++) + printf (" %2d", i); + printf ("\n"); + for (i = 0; i < numprog; i++) + { + printf ("%2d ", i); + for (j = 0; j < numprog; j++) + printf (" %c ", depmap_isset (depmap, i, j) ? 'X' : ' '); + printf ("\n"); + } + printf ("\n%s:\n", _("Legend")); + for (i = 0; i < numprog; i++) + { + prog = prog_lookup_by_idx (i); + if (prog) + printf ("%2d: %s\n", i, prog->tag); + } +} + +int +progman_build_depmap () +{ + int rc = 0; + unsigned i; + struct prog *prog; + pies_depmap_t dp; + + fixup_prerequisites (); + rebuild_prerequisites (); + depmap = depmap_alloc (numprog); + 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; + mu_error (_("component %s depends on %s, " + "which is not declared"), + prog->tag, prog->prereq[i]); + rc++; + } + else + depmap_set (depmap, prog->idx, dep->idx); + } + } + dp = depmap_copy (depmap); + depmap_tc (dp); + for (i = 0; i < numprog; i++) + if (depmap_isset (dp, i, i)) + { + prog = prog_lookup_by_idx (i); + mu_error (_("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_PROG (prog)) + { + struct component *comp = prog->v.p.comp; + if (comp->mode == pies_comp_inetd) + { + int fd = create_socket (comp->socket_url, + comp->privs.user, comp->umask); + if (fd == -1) + prog->v.p.status = status_disabled; + else if (register_listener (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; + MU_DEBUG (pies_debug, MU_DEBUG_TRACE2, "Recomputing alarm settings\n"); + for (prog = proghead; prog; prog = prog->next) + if (IS_PROG (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; + } + } + MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE2, "alarm=%lu\n", + (unsigned long)alarm_time); + if (alarm_time) + alarm (alarm_time); +} + + + +void +progman_start () +{ + struct prog *prog; + + recompute_alarm = 0; + MU_DEBUG (pies_debug, MU_DEBUG_TRACE1, "Starting components\n"); + for (prog = proghead; prog; prog = prog->next) + if (IS_PROG (prog) + && ((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) + mu_error (_("INTERNAL ERROR: attempting to kill unexisting process %s"), + prog->tag); + else + kill (prog->pid, SIGKILL); + } + else + recompute_alarm = 1; +} + +void +progman_wake_sleeping () +{ + struct prog *prog; + time_t now = time (NULL); + + MU_DEBUG (pies_debug, MU_DEBUG_TRACE1, + "Managing sleeping/stopping components\n"); + + for (prog = proghead; prog; prog = prog->next) + if (IS_PROG (prog)) + { + switch (prog->v.p.status) + { + case status_sleeping: + if (now - prog->v.p.timestamp >= SLEEPTIME) + { + prog->v.p.status = status_enabled; + prog->v.p.count = 0; + prog->v.p.timestamp = 0; + prog_start (prog); + } + 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; + unsigned settle_timeout = 0; + + if (!prog->prereq) + return 0; /* Ok to startup */ + MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, "Starting prerequisites of %s\n", + prog->tag); + ret = 0; + for (i = 0; prog->prereq[i]; i++) + { + struct prog *dp = prog_lookup_by_tag (prog->prereq[i]); + if (!IS_PROG (dp)) /* Skip redirectors */ + continue; + if (prog->v.p.comp->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; + } + prog_start (prog_lookup_by_tag (prog->prereq[i])); + if (!(dp->v.p.status == status_enabled && dp->pid)) + ret = 1; + else + settle_timeout = prog->v.p.comp->settle_timeout; + } + if (ret == 0 && settle_timeout) + sleep (settle_timeout); + 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; + unsigned n; + int warned = 0; + + prog_stop_redirectors (prog); + for (n = depmap_first (depmap, depmap_row, prog->idx, &pos); + n != (unsigned)-1; + n = depmap_next (depmap, pos)) + { + struct prog *dp = prog_lookup_by_idx (n); + if (!dp) /* should not happen */ + continue; + if (!warned && dp->pid) + { + MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, + "Stopping dependencies of %s\n", + 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; + } + } + MU_DEBUG2 (pies_debug, MU_DEBUG_TRACE1, + "Stopping %s (%lu)\n", + prog->tag, (unsigned long) prog->pid); + kill (prog->pid, sig); +} + +static void +prog_stop_all (int sig) +{ + struct prog *prog; + + MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, + "Stopping all components (signal %d)\n", sig); + for (prog = progtail; prog; prog = prog->prev) + if (IS_PROG (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 (char *tag, pid_t pid, int status, int expect_term) +{ + if (WIFEXITED (status)) + { + if (WEXITSTATUS (status) == 0) + MU_DEBUG2 (pies_debug, MU_DEBUG_TRACE1, + _("%s (%lu) exited successfully\n"), + tag, (unsigned long) pid); + else + mu_d |