/* This file is part of Pies. Copyright (C) 2007, 2008, 2009 Sergey Poznyakoff 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. 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 Pies. If not, see . */ #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) { struct prog *p = *pp; switch (p->type) { case TYPE_COMPONENT: break; case TYPE_RETR: { 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); } } 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_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) { depc = gl_list_size (comp->prereq); if (depc == 1) { const char *item = gl_list_get_at (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) { gl_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 { const void *p; gl_list_iterator_t itr = gl_list_iterator (comp->prereq); while (gl_list_iterator_next (&itr, &p, NULL)) { prog->prereq[depc++] = (char*) p; } gl_list_iterator_free (&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) { logmsg (LOG_ERR, _("cannot open output file %s: %s"), master->v.p.comp->redir[stream].v.file, 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: logmsg (LOG_CRIT, _("cannot run redirector `%s': fork failed: %s"), master->tag, 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) { 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); 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: 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; } /* 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)) { logmsg (LOG_ERR, "listen: %s", 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; } 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)); } 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) { 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 = env_setup (prog->v.p.comp->env); if (debug_level >= 4) { int i; for (i = 0; environ[i]; i++) logmsg_printf (LOG_DEBUG, "%s ", environ[i]); logmsg_printf (LOG_DEBUG, "\n"); } 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; logmsg_printf (LOG_DEBUG, "executing"); for (i = 0; i < prog->v.p.argc; i++) { /* FIXME: quote */ logmsg_printf (LOG_DEBUG, " %s", prog->v.p.comp->argv[i]); } logmsg_printf (LOG_DEBUG, "\n"); } 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 (log_tag, LOG_PID, prog->v.p.comp->facility); syslog (LOG_CRIT, _("cannot start `%s': %s"), prog->tag, strerror (errno)); _exit (EX_SOFTWARE); 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) { 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; } } static int check_acl (pies_acl_t acl, struct sockaddr *s, int 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; } 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) { logmsg (LOG_ERR, _("accept failed: %s"), strerror (errno)); return 1; } p = prog_lookup_by_socket (socket); if (!p) { logmsg (LOG_EMERG, _("INTERNAL ERROR: no matching prog for fd %d"), socket); close (fd); 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)) { 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) { const void *p; gl_list_iterator_t itr; if (comp->depend == NULL) return; itr = gl_list_iterator (comp->depend); while (gl_list_iterator_next (&itr, &p, NULL)) { const char *tag = p; 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 = gl_list_create_empty(&gl_linked_list_implementation, NULL, NULL, NULL, false); } /* FIXME: memory allocation */ gl_list_add_last (tgt->prereq, xstrdup (comp->tag)); } gl_list_free (comp->depend); comp->depend = NULL; } 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; logmsg_printf (LOG_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); 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; 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; logmsg (LOG_ERR, _("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); 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_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; debug (2, ("Recomputing alarm settings")); 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; } } 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_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) logmsg (LOG_EMERG, _("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); debug (1, ("Managing sleeping/stopping components")); 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 */ 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_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) { 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)); 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_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) 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 int wait_for_child (pid_t pid) { int rc = 127; int wait_status; while (waitpid (pid, &wait_status, 0) == -1) if (errno != EINTR) { logmsg (LOG_ERR, _("waitpid failed: %s"), strerror (errno)); return EX_OSERR; } if (WIFSIGNALED (wait_status)) { int sig = WTERMSIG (wait_status); logmsg (LOG_ERR, _("child died with signal %d"), sig); } else if (WIFEXITED (wait_status)) { rc = WEXITSTATUS (wait_status); if (rc != 0) logmsg (LOG_ERR, _("child returned status %d"), rc); } return rc; } 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); if (child_pid < 0) { logmsg (LOG_ERR, _("cannot send mail: fork failed: %s"), strerror (errno)); return; } if (child_pid) /*FIXME: SIGCHLD Handler? */ return; /* Child process */ /* ============= */ signal (SIGCHLD, SIG_DFL); signal (SIGPIPE, SIG_DFL); 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]); exit (wait_for_child (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 }, { NULL } }; char *msg_text = NULL; char buf[INT_BUFSIZE_BOUND (uintmax_t)]; mdef[COMPONENT_IDX].value = (char*) tag; if (WIFEXITED (status)) { mdef[TERMINATION_IDX].value = _("exited with code"); mdef[RETCODE_IDX].value = umaxtostr (WEXITSTATUS (status), buf); } else if (WIFSIGNALED (status)) { mdef[TERMINATION_IDX].value = _("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)]; static RETSIGTYPE (*saved_handler) (int sig); int status; saved_handler = signal (SIGPIPE, SIG_IGN); pid = fork (); if (pid == (pid_t) -1) { logmsg (LOG_ERR, "fork: %s", strerror (errno)); return; } if (pid == 0) { int i; debug (1, (_("executing %s"), 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) { logmsg (LOG_ERR, "waitpid: %s", 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) { logmsg (LOG_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); debug (1, (_("%s: terminated with code %d"), prog->tag, retcode)); } else if (WIFSIGNALED (status)) { retcode = STATUS_SIG_BIT | WTERMSIG (status); debug (1, (_("%s: terminated on signal %d"), prog->tag, retcode)); } 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: prog_start (prog); break; case action_disable: logmsg (LOG_NOTICE, _("disabling component %s"), prog->tag); 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. */ debug (1, (_("removing inetd redirector %s"), 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; logmsg (LOG_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: 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; 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]")); for (i = 0; i < prog->v.p.argc; i++) { /* FIXME: quote as appropriate */ fprintf (fp, " %s", prog->v.p.comp->argv[i]); } 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)) { logmsg (LOG_ERR, _("cannot rename %s to %s: %s"), tmpfile, filename, strerror (errno)); unlink (tmpfile); } free (tmpfile); } /* EOF */