aboutsummaryrefslogtreecommitdiff
path: root/src/progman.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/progman.c')
-rw-r--r--src/progman.c1741
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_diag_output (MU_DIAG_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 = MU_DIAG_DEBUG;
+ else
+ prio = MU_DIAG_ERR;
+
+ mu_diag_output (prio,
+ _("%s (%lu) terminated on signal %d"),
+ tag, (unsigned long) pid,
+ WTERMSIG (status));
+ }
+ else if (WIFSTOPPED (status))
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) stopped on signal %d"),
+ tag, (unsigned long) pid,
+ WSTOPSIG (status));
+#ifdef WCOREDUMP
+ else if (WCOREDUMP (status))
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) dumped core"),
+ tag, (unsigned long) pid);
+#endif
+ else
+ mu_diag_output (MU_DIAG_ERR,
+ _("%s (%lu) terminated with unrecognized status"),
+ tag, (unsigned long) pid);
+}
+
+static void
+send_msg (mu_address_t rcpt, mu_message_t msg)
+{
+ mu_mailer_t mailer;
+ int rc;
+ const char *s;
+
+ mu_address_sget_email (rcpt, 1, &s);
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1, "Sending email to %s\n", s);
+
+ rc = mu_mailer_create (&mailer, NULL);
+ if (rc)
+ {
+ const char *mailer_url;
+ mu_mailer_get_url_default (&mailer_url);
+
+ mu_error (_("Cannot create mailer `%s': %s"),
+ mailer_url, mu_strerror (rc));
+ return;
+ }
+
+ /* FIXME: mailer flags? */
+ rc = mu_mailer_open (mailer, 0);
+ if (rc)
+ {
+ const char *mailer_url;
+ mu_mailer_get_url_default (&mailer_url);
+
+ mu_mailer_destroy(&mailer);
+ mu_error (_("opening mailer `%s' failed: %s"),
+ mailer_url, mu_strerror (rc));
+ return;
+ }
+
+ rc = mu_mailer_send_message (mailer, msg, NULL, rcpt);
+ mu_mailer_destroy (&mailer);
+ if (rc)
+ mu_error (_("cannot send message: %s"), mu_strerror (rc));
+}
+
+static const char default_termination_message[] =
+"From: <>\n"
+"X-Agent: ${canonical-program-name} (${package} ${version})\n"
+"Subject: Component ${component} terminated with code ${retcode}.\n"
+"\n";
+
+static void
+vartab_define_project (mu_vartab_t vtab)
+{
+ static struct {
+ char *name;
+ char *value;
+ } ptab[] = {
+ { "canonical-program-name", "pies" },
+ { "package", PACKAGE },
+ { "version", PACKAGE_VERSION },
+ { "mu-version", MAILUTILS_VERSION },
+ { NULL }
+ };
+ int i;
+
+ for (i = 0; ptab[i].name; i++)
+ mu_vartab_define (vtab, ptab[i].name, ptab[i].value, 1);
+}
+
+
+static void
+notify (const char *tag, int status, struct action *act)
+{
+ mu_vartab_t vtab;
+ char *msg_text = NULL;
+ mu_message_t msg = NULL;
+ mu_stream_t stream = NULL;
+ int rc;
+ char buf[INT_BUFSIZE_BOUND (uintmax_t)];
+
+ mu_vartab_create (&vtab);
+ mu_vartab_define (vtab, "component", tag, 1);
+ mu_vartab_define (vtab, "retcode", umaxtostr (status, buf), 1);
+ mu_vartab_define (vtab, "program-name", mu_program_name, 1);
+ vartab_define_project (vtab);
+ rc = mu_vartab_expand (vtab,
+ act->message ?
+ act->message : default_termination_message,
+ &msg_text);
+ mu_vartab_destroy (&vtab);
+ if (rc)
+ {
+ mu_error (_("cannot expand message body: %s"), mu_strerror (rc));
+ /* FIXME: Notify anyway? */
+ return;
+ }
+
+ rc = mu_message_create (&msg, NULL);
+ if (rc)
+ {
+ mu_error (_("cannot create message: %s"), mu_strerror (rc));
+ free (msg_text);
+ }
+
+ mu_message_get_stream (msg, &stream);
+ mu_stream_write (stream, msg_text, strlen (msg_text), 0, NULL);
+ free (msg_text);
+
+ send_msg (act->addr, msg);
+ mu_message_destroy (&msg, mu_message_get_owner (msg));
+}
+
+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)];
+ static RETSIGTYPE (*saved_handler) (int sig);
+ int status;
+
+ saved_handler = signal (SIGPIPE, SIG_IGN);
+
+ pid = fork ();
+
+ if (pid == (pid_t) -1)
+ {
+ mu_error ("fork: %s", mu_strerror (errno));
+ return;
+ }
+
+ if (pid == 0)
+ {
+ int i;
+
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1,
+ _("executing %s\n"), act->command);
+ /* Child */
+ 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);
+
+ for (i = getmaxfd (); i >= 0; i--)
+ close (i);
+
+ argv[0] = "/bin/sh";
+ argv[1] = "-c";
+ argv[2] = act->command;
+ argv[3] = NULL;
+
+ execv (argv[0], argv);
+ exit (127);
+ }
+
+ /* Master */
+ while (waitpid (pid, &status, 0) == -1)
+ if (errno != EINTR)
+ {
+ mu_error ("waitpid: %s", mu_strerror (errno));
+ break;
+ }
+ signal (SIGPIPE, saved_handler);
+
+ print_status (act->command, pid, status, 1);
+}
+
+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)
+ {
+ mu_diag_output (MU_DIAG_NOTICE,
+ _("subprocess %lu finished"),
+ (unsigned long) pid);
+ continue;
+ }
+ print_status (prog->tag, prog->pid, status, expect_term);
+ prog->pid = 0;
+ if (IS_PROG (prog))
+ {
+ if (prog->v.p.comp->mode == pies_comp_inetd)
+ {
+ prog_stop_redirectors (prog);
+ destroy_prog (&prog);
+ }
+ else
+ {
+ prog->v.p.status = status_enabled;
+ prog_stop_dependents (prog);
+ if (!expect_term)
+ {
+ unsigned retcode;
+ struct action *act = prog->v.p.comp->act_head;
+
+ if (!act)
+ act = default_component.act_head;
+
+ if (WIFEXITED (status))
+ retcode = WEXITSTATUS (status);
+ else if (WIFSIGNALED (status))
+ retcode = STATUS_SIG_BIT | WTERMSIG (status);
+ else
+ {
+ /* 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:
+ prog_start (prog);
+ break;
+
+ case action_disable:
+ mu_diag_output (MU_DIAG_NOTICE,
+ _("disabling component %s: "
+ "exited with code %d"),
+ prog->tag, status);
+ prog->v.p.status = status_disabled;
+ }
+ if (act->addr)
+ notify (prog->tag, status, act);
+ break;
+ }
+ }
+
+ if (!act)
+ /* Default action: */
+ prog_start (prog);
+ }
+ }
+ }
+ else
+ {
+ /* It was a redirector of an already finished inetd process. */
+ MU_DEBUG1 (pies_debug, MU_DEBUG_TRACE1,
+ _("removing inetd redirector %s\n"), prog->tag);
+ destroy_prog (&prog);
+ }
+ }
+
+ if (!expect_term)
+ /* This will also recompute alarm settings, if necessary */
+ progman_wake_sleeping ();
+}
+
+void
+progman_stop_component (const char *name)
+{
+ struct prog *prog;
+
+ mu_diag_output (MU_DIAG_INFO, _("stopping component `%s'"), name);
+ for (prog = proghead; prog; prog = prog->next)
+ if (IS_PROG (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->disabled)
+ prog->v.p.status = status_enabled;
+ break;
+
+ default:
+ mu_diag_output (MU_DIAG_INFO,
+ _("stopping component `%s': "
+ "component not started"),
+ name);
+ }
+ }
+}
+
+void
+progman_dump_stats (const char *filename)
+{
+ FILE *fp;
+ struct prog *prog;
+ char *s;
+ int rc;
+ char *tmpfile = NULL;
+
+ asprintf (&tmpfile, "%s.%lu", filename, (unsigned long) getpid ());
+ if (!tmpfile)
+ {
+ mu_error ("%s", mu_strerror (ENOMEM));
+ return;
+ }
+ mu_diag_output (MU_DIAG_INFO, _("dumping statistics to `%s'"), tmpfile);
+ fp = fopen (tmpfile, "w");
+ if (!fp)
+ {
+ mu_error (_("cannot open file `%s' for writing: %s"),
+ tmpfile, mu_strerror (errno));
+ return;
+ }
+
+ for (prog = proghead; prog; prog = prog->next)
+ {
+ switch (prog->type)
+ {
+ case TYPE_COMPONENT:
+ fprintf (fp, "component %s ", prog->tag);
+ if (prog->pid)
+ {
+ fprintf (fp, "%lu", (unsigned long) prog->pid);
+ if (prog->v.p.status == status_stopping)
+ fprintf (fp, " (stopping)");
+ }
+ else if (prog->v.p.status == status_sleeping)
+ {
+ 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 if (prog->v.p.status == status_disabled)
+ fprintf (fp, _("[disabled]"));
+ else if (prog->v.p.status == status_listener)
+ fprintf (fp, _("[listener]"));
+ else
+ fprintf (fp, _("[not running]"));
+ if (rc = mu_argcv_string (prog->v.p.argc, prog->v.p.comp->argv, &s))
+ {
+ mu_error (_("cannot convert argument list: %s"),
+ mu_strerror (rc));
+ }
+ else
+ {
+ fprintf (fp, " %s", s);
+ free (s);
+ }
+ fputc ('\n', fp);
+ break;
+
+ case TYPE_RETR:
+ fprintf (fp, _("redirector %s %lu\n"), prog->tag,
+ (unsigned long) prog->pid);
+ }
+ }
+ fclose (fp);
+ unlink (filename);
+ if (rename (tmpfile, filename))
+ {
+ mu_error (_("cannot rename %s to %s: %s"), tmpfile, filename,
+ mu_strerror (errno));
+ unlink (tmpfile);
+ }
+ free (tmpfile);
+}
+
+/*
+ Local Variables:
+ c-file-style: "gnu"
+ End:
+*/
+/* EOF */

Return to:

Send suggestions and report system problems to the System administrator.