/* This file is part of GNU Pies. Copyright (C) 2007-2016 Sergey Poznyakoff GNU Pies is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Pies is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Pies. If not, see . */ #include "pies.h" #include "prog.h" static struct prog *proghead, *progtail; static int recompute_alarm; static struct grecs_symtab *conn_tab; struct prog * progman_locate (const char *name) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (strcmp (prog_tag (prog), name) == 0) break; return prog; } void progman_foreach (int (*filter) (struct prog *, void *data), void *data) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (filter (prog, data)) break; } /* FIXME: Rewrite this using progman_foreach? */ void progman_iterate_comp (int (*fun) (struct component *, void *), void *data) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog) && !(prog->v.p.comp->mode == pies_comp_inetd && prog->v.p.listener) && fun (prog->v.p.comp, data)) break; } 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; } struct prog * prog_lookup_by_tag (const char *tag) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (strcmp (prog_tag (prog), tag) == 0) break; return prog; } struct prog * prog_lookup_by_service (const char *service) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog) && prog->v.p.comp->service && strcmp (prog->v.p.comp->service, service) == 0) break; return prog; } struct component * progman_lookup_component (const char *tag) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog) && strcmp (prog_tag (prog), tag) == 0) return prog->v.p.comp; return NULL; } struct component * progman_lookup_tcpmux (const char *service, const char *master) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog) && ISCF_TCPMUX (prog->v.p.comp->flags) && !(prog->v.p.comp->flags & CF_DISABLED) && prog->v.p.comp->service && strcasecmp (prog->v.p.comp->service, service) == 0 && prog->v.p.comp->tcpmux && strcmp (prog->v.p.comp->tcpmux, master) == 0) return prog->v.p.comp; return NULL; } void prog_stop (struct prog *prog, int sig); static int prog_start_prerequisites (struct prog *prog); char const * prog_tag (struct prog const *prog) { switch (prog->type) { case TYPE_COMPONENT: return prog->v.p.comp->tag; case TYPE_REDIRECTOR: return prog->v.r.tag; case TYPE_COMMAND: return prog->v.c.tag; } abort (); } 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; unlink_prog (p); switch (p->type) { case TYPE_COMPONENT: component_ref_decr (p->v.p.comp); if (p->v.p.status == status_listener) deregister_socket (p->v.p.socket); break; case TYPE_REDIRECTOR: { struct prog *master = p->v.r.master; component_ref_decr (master->v.p.comp); 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);*/ free (p->v.r.tag); } break; case TYPE_COMMAND: free (p->v.c.tag); free (p->v.c.command); } free (p); *pp = NULL; } static char * redir_tag (struct prog *master, int type) { static char *redirstr[2] = { "stdout", "stderr" }; char *str = NULL; size_t len = 0; char const *tag = prog_tag (master); if (type < ARRAY_SIZE(redirstr)) grecs_asprintf (&str, &len, "%s/%s", tag, redirstr[type]); else grecs_asprintf (&str, &len, "%s/%d", tag, type); return str; } static struct prog * register_redir (int type, struct prog *master) { char *tag = redir_tag (master, type); struct prog *pp = grecs_zalloc (sizeof (*pp)); pp->type = TYPE_REDIRECTOR; pp->v.r.tag = tag; pp->v.r.master = master; link_prog (pp, 1); component_ref_incr (master->v.p.comp); return pp; } void update_redir (int type, struct prog *master, pid_t pid) { struct prog *pp; if (master->v.p.redir[type]) { pp = master->v.p.redir[type]; prog_stop (pp, SIGKILL); /* Just in case */ } pp = register_redir (type, master); master->v.p.redir[type] = pp; pp->pid = pid; } static struct prog * register_prog0 (struct component *comp) { struct prog *newp; newp = grecs_zalloc (sizeof (*newp)); newp->type = TYPE_COMPONENT; newp->pid = 0; newp->facility = comp->facility; newp->v.p.comp = comp; newp->v.p.socket = -1; if (comp->flags & CF_DISABLED) newp->v.p.status = status_disabled; else if (comp->mode == pies_comp_inetd) newp->v.p.status = status_listener; if (comp->mode != pies_comp_exec) comp->redir[RETR_OUT].type = redir_null; link_prog (newp, 0); component_ref_incr (comp); return newp; } void register_prog (struct component *comp) { comp->prog = register_prog0 (comp); } void register_command (char *tag, char *command, pid_t pid) { struct prog *newp = grecs_zalloc (sizeof (*newp)); newp->type = TYPE_COMMAND; newp->pid = pid; newp->v.c.tag = grecs_strdup (tag); newp->v.c.command = command; link_prog (newp, 0); } int progman_waiting_p () { struct prog *prog; for (prog = proghead; prog; prog = prog->next) { if (IS_COMPONENT (prog) && prog->v.p.wait && prog->pid > 0) { debug(1, ("%s: waiting for %s (%lu)", __FUNCTION__, prog_tag (prog), (unsigned long) prog->pid)); return 1; } } return 0; } size_t progman_running_count () { size_t size = 0; struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (prog->pid > 0) size++; return size; } RETSIGTYPE redir_exit (int sig) { _exit (0); } int redirect_to_file (struct prog *master, int stream) { struct passwd *pw; int fd = open (master->v.p.comp->redir[stream].v.file, O_RDWR|O_CREAT, 0644 & ~master->v.p.comp->umask); if (fd == -1) { logmsg (LOG_ERR, _("cannot open output file %s: %s"), master->v.p.comp->redir[stream].v.file, strerror (errno)); return -1; } /* Fix file ownership */ if (master->v.p.comp->privs.user && (pw = getpwnam (master->v.p.comp->privs.user)) != NULL) { if (chown (master->v.p.comp->redir[stream].v.file, pw->pw_uid, pw->pw_gid)) logmsg (LOG_ERR, "chown %s: %s", master->v.p.comp->redir[stream].v.file, strerror (errno)); } return fd; } static void close_fds (fd_set *fdset) { int i; for (i = FD_SETSIZE-1; i >= 0; i--) { if (fdset && FD_ISSET (i, fdset)) continue; close (i); } } void free_redirector (struct redirector *rp) { if (rp->type == redir_file) free (rp->v.file); } int open_redirector (struct prog *master, int stream) { int p[2]; FILE *fp; char *buf = NULL; size_t size = 0; pid_t pid; int prio; char *tag; fd_set fdset; if (init_process) return -1; 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; } if (pipe (p)) { logmsg (LOG_CRIT, "pipe: %s", strerror (errno)); return -1; } switch (pid = fork ()) { case 0: /* Redirector process */ tag = redir_tag (master, stream); mf_proctitle_format ("%s redirector", tag); free (tag); FD_ZERO (&fdset); FD_SET (p[0], &fdset); close_fds (&fdset); diag_setup (0); signal_setup (redir_exit); close (p[1]); fp = fdopen (p[0], "r"); if (fp == NULL) _exit (1); openlog (prog_tag (master), 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"), prog_tag (master), strerror (errno)); return -1; default: debug (1, (_("redirector for %s started, pid=%lu"), prog_tag (master), (unsigned long) pid)); update_redir (stream, master, pid); close (p[0]); return p[1]; } } static unsigned conn_class_hasher (void *data, unsigned long n_buckets) { struct conn_class *pcclass = data; unsigned char const *tag = (unsigned char const *)pcclass->tag; size_t value = 0; unsigned char ch; size_t len; while ((ch = *tag++)) value = (value * 31 + ch) % n_buckets; for (tag = (const unsigned char *)&pcclass->sa_storage, len = pcclass->sa_len; len; tag++, len--) value = (value * 31 + *tag) % n_buckets; return value; } /* Compare two strings for equality. */ static int conn_class_compare (void const *data1, void const *data2) { struct conn_class const *p1 = data1; struct conn_class const *p2 = data2; return !(p1->sa_len == p2->sa_len && memcmp (&p1->sa_storage, &p2->sa_storage, p1->sa_len) == 0 && strcmp (p1->tag, p2->tag) == 0); } static void conn_class_free (void *data) { free (data); } static int conn_class_copy(void *a, void *b) { memcpy (a, b, sizeof(struct conn_class)); memset (b, 0, sizeof(struct conn_class)); return 0; } static struct conn_class * conn_class_lookup (const char *tag, union pies_sockaddr_storage const *sa_storage_ptr, size_t sa_len) { int install = 1; struct conn_class *probe, *ret; probe = grecs_malloc (sizeof (probe[0])); probe->tag = tag; probe->sa_storage = *sa_storage_ptr; probe->sa_len = sa_len; switch (probe->sa_storage.s.sa_family) { case AF_INET: probe->sa_storage.s_in.sin_port = 0; break; case AF_UNIX: break; default: logmsg (LOG_ERR, _("unexpected address family: %d"), probe->sa_storage.s.sa_family); break; } probe->count = 0; if (!conn_tab) { conn_tab = grecs_symtab_create(sizeof (struct conn_class), conn_class_hasher, conn_class_compare, conn_class_copy, NULL, conn_class_free); if (!conn_tab) grecs_alloc_die (); } ret = grecs_symtab_lookup_or_install (conn_tab, probe, &install); if (!ret) { logmsg (LOG_CRIT, "cannot allocate conn_class: %s", strerror (errno)); abort (); } free (probe); return ret; } static void conn_class_report (int prio, struct conn_class *pcclass) { char *s = sockaddr_to_astr ((struct sockaddr *)&pcclass->sa_storage, pcclass->sa_len); logmsg (prio, _("connections in class %s/%s: %lu"), pcclass->tag, s, (unsigned long)pcclass->count); free (s); } static void conn_class_remove (struct conn_class *pcclass) { if (!conn_tab) { logmsg (LOG_CRIT, "attempt to free unexisting conn_class %p", pcclass); conn_class_report (LOG_CRIT, pcclass); abort (); } grecs_symtab_remove (conn_tab, pcclass); } extern char **environ; /* Environment */ static size_t envsize; /* Size of environ. If it is not 0, then we have allocated space for the environ. */ #define ENV_PROTO "PROTO" #define ENV_SOCKTYPE "SOCKTYPE" #define ENV_LOCALIP "LOCALIP" #define ENV_LOCALPORT "LOCALPORT" #define ENV_LOCALHOST "LOCALHOST" #define ENV_REMOTEIP "REMOTEIP" #define ENV_REMOTEPORT "REMOTEPORT" #define ENV_REMOTEHOST "REMOTEHOST" static char *sockenv_hint[] = { "-" ENV_PROTO, "-" ENV_SOCKTYPE, "-" ENV_LOCALIP, "-" ENV_LOCALPORT, "-" ENV_LOCALHOST, "-" ENV_REMOTEIP, "-" ENV_REMOTEPORT, "-" ENV_REMOTEHOST, NULL }; #define DEBUG_ENVIRON(l) \ do \ if (debug_level >= (l)) \ { \ int i; \ logmsg_printf (LOG_DEBUG, "environment: "); \ for (i = 0; environ[i]; i++) \ logmsg_printf (LOG_DEBUG, "%s ", environ[i]); \ logmsg_printf (LOG_DEBUG, "\n"); \ } \ while (0) static void add_env (const char *name, const char *value) { size_t i; size_t namelen = strlen (name); char *p; for (i = 0; environ[i]; i++) { if (!strncmp (environ[i], name, namelen) && environ[i][namelen] == '=') break; } if (environ[i] == NULL) { if (envsize == 0) { char **new_env; envsize = i + 4; new_env = grecs_calloc (envsize, sizeof new_env[0]); for (i = 0; (new_env[i] = environ[i]); i++) ; environ = new_env; } else if (i == envsize) { envsize += 4; environ = grecs_realloc (environ, envsize * sizeof environ[0]); } environ[i+1] = NULL; } if (!value) value = ""; p = grecs_malloc (namelen + 1 + strlen (value) + 1); strcpy (p, name); p[namelen] = '='; strcpy (p + namelen + 1, value); environ[i] = p; } static char * find_env (const char *name, int val) { if (environ) { int nlen = strcspn (name, "+="); int i; for (i = 0; environ[i]; i++) { size_t elen = strcspn (environ[i], "="); if (elen == nlen && memcmp (name, environ[i], nlen) == 0) return val ? environ[i] + elen + 1 : environ[i]; } } return NULL; } static int locate_unset (char **env, const char *name) { int i; int nlen = strcspn (name, "="); for (i = 0; env[i]; i++) { if (env[i][0] == '-') { size_t elen = strcspn (env[i] + 1, "="); if (elen == nlen && memcmp (name, env[i] + 1, nlen) == 0) { if (env[i][nlen + 1]) return strcmp (name + nlen, env[i] + 1 + nlen) == 0; else return 1; } } } return 0; } static char * env_concat (const char *name, size_t namelen, const char *a, const char *b) { char *res; size_t len; if (a && b) { res = grecs_malloc (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 = grecs_malloc (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++; len = strlen (b); res = grecs_malloc (namelen + 1 + len + 1); strcpy (res + namelen + 1, b); } memcpy (res, name, namelen); res[namelen] = '='; return res; } static void environ_setup (char **hint) { char **old_env = environ; char **new_env; size_t count, i, n; if (!hint) return; if (strcmp (hint[0], "-") == 0) { old_env = NULL; hint++; } /* Count new environment size */ count = 0; if (old_env) for (i = 0; old_env[i]; i++) count++; for (i = 0; hint[i]; i++) count++; /* Allocate new environment. */ new_env = grecs_calloc (count + 1, sizeof new_env[0]); /* Populate the environment. */ n = 0; if (old_env) for (i = 0; old_env[i]; i++) { if (!locate_unset (hint, old_env[i])) new_env[n++] = old_env[i]; } for (i = 0; hint[i]; i++) { char *p; if (hint[i][0] == '-') { /* Skip unset directives. */ continue; } if ((p = strchr (hint[i], '='))) { if (p == hint[i]) continue; /* Ignore erroneous entry */ if (p[-1] == '+') new_env[n++] = env_concat (hint[i], p - hint[i] - 1, find_env(hint[i], 1), p + 1); else if (p[1] == '+') new_env[n++] = env_concat (hint[i], p - hint[i], p + 2, find_env(hint[i], 1)); else new_env[n++] = hint[i]; } else { p = find_env (hint[i], 0); if (p) new_env[n++] = p; } } new_env[n] = NULL; if (envsize) free (environ); environ = new_env; envsize = count + 1; } /* Pass socket information in environment variables. */ void prog_sockenv (struct prog *prog) { char buf[INT_BUFSIZE_BOUND (uintmax_t)]; char *p; struct hostent *host; union pies_sockaddr_storage sa_server; socklen_t len = sizeof (sa_server); union pies_sockaddr_storage *sa_client = &prog->v.p.sa_storage; socklen_t cltlen = prog->v.p.sa_len; const char *proto = NULL; if (!(prog->v.p.comp->flags & CF_SOCKENV)) return; if (socket_type_to_str (prog->v.p.comp->socket_type, &proto)) proto = umaxtostr (prog->v.p.comp->socket_type, buf); add_env (ENV_SOCKTYPE, proto); if (prog->v.p.comp->socket_url) proto = prog->v.p.comp->socket_url->proto_s; else if (prog->v.p.listener) proto = prog->v.p.listener->v.p.comp->socket_url->proto_s; else if (ISCF_TCPMUX (prog->v.p.comp->flags)) proto = "TCP"; add_env (ENV_PROTO, proto); if (getsockname (prog->v.p.socket, (struct sockaddr *) &sa_server, &len) < 0) logmsg (LOG_WARNING, "getsockname(): %s", strerror (errno)); else if (sa_server.s.sa_family == AF_INET) { p = inet_ntoa (sa_server.s_in.sin_addr); if (p) add_env (ENV_LOCALIP, p); p = umaxtostr (ntohs (sa_server.s_in.sin_port), buf); add_env (ENV_LOCALPORT, p); if (prog->v.p.comp->flags & CF_RESOLVE) { if ((host = gethostbyaddr ((char *) &sa_server.s_in.sin_addr, sizeof (sa_server.s_in.sin_addr), AF_INET)) == NULL) logmsg (LOG_WARNING, "gethostbyaddr: %s", strerror (errno)); else add_env (ENV_LOCALHOST, host->h_name); } } if (cltlen > 0 && sa_client->s.sa_family == AF_INET) { p = inet_ntoa (sa_client->s_in.sin_addr); if (p) add_env (ENV_REMOTEIP, p); p = umaxtostr (ntohs (sa_client->s_in.sin_port), buf); add_env (ENV_REMOTEPORT, p); if (prog->v.p.comp->flags & CF_RESOLVE) { if ((host = gethostbyaddr ((char *) &sa_client->s_in.sin_addr, sizeof (sa_client->s_in.sin_addr), AF_INET)) == NULL) logmsg (LOG_WARNING, "gethostbyaddr: %s", strerror (errno)); else add_env (ENV_REMOTEHOST, host->h_name); } } /* FIXME: $REMOTEINFO ? */ DEBUG_ENVIRON (4); } static int check_rate (struct prog *prog, unsigned testtime, size_t max_count) { time_t now; time (&now); if (prog->v.p.timestamp + testtime > now) prog->v.p.failcount++; else { prog->v.p.failcount = 0; prog->v.p.timestamp = now; } if (prog->v.p.failcount > max_count) { prog->v.p.timestamp = now; prog->v.p.status = status_sleeping; recompute_alarm = 1; return 1; } return 0; } static int check_spawn_rate (struct prog *prog) { if (check_rate (prog, TESTTIME, MAXSPAWN)) { logmsg (LOG_NOTICE, ngettext ("%s is respawning too fast, disabled for %d minute", "%s is respawning too fast, disabled for %d minutes", SLEEPTIME / 60), prog_tag (prog), SLEEPTIME / 60); return 1; } return 0; } static int check_connection_rate (struct prog *prog) { size_t max_rate = prog->v.p.comp->max_rate ? prog->v.p.comp->max_rate : default_max_rate; if (max_rate && check_rate (prog, 60, max_rate)) { logmsg (LOG_NOTICE, ngettext ("%s is starting too often, disabled for %d minute", "%s is starting too often, disabled for %d minutes", SLEEPTIME / 60), prog_tag (prog), SLEEPTIME / 60); return 1; } return 0; } static int prog_open_socket (struct prog *prog) { prog->v.p.socket = create_socket (prog->v.p.comp->socket_url, prog->v.p.comp->socket_type, prog->v.p.comp->privs.user, prog->v.p.comp->umask); if (prog->v.p.socket == -1) { prog->v.p.status = status_disabled; return 1; } if (listen (prog->v.p.socket, 8)) { logmsg (LOG_ERR, "listen: %s", strerror (errno)); close (prog->v.p.socket); prog->v.p.socket = -1; prog->v.p.status = status_disabled; return 1; } return 0; } static void prog_start_prologue (struct prog *prog) { if (prog->v.p.comp->dir) { debug (1, (_("chdir %s"), prog->v.p.comp->dir)); if (chdir (prog->v.p.comp->dir)) logmsg (LOG_ERR, _("%s: cannot change to directory %s: %s"), prog_tag (prog), prog->v.p.comp->dir, strerror (errno)); } environ_setup (prog->v.p.comp->env ? prog->v.p.comp->env : ((prog->v.p.comp->flags & CF_SOCKENV) ? sockenv_hint : NULL)); if (init_process) environ_setup (sysvinit_environ_hint); DEBUG_ENVIRON (4); pies_priv_setup (&prog->v.p.comp->privs); if (prog->v.p.comp->umask) umask (prog->v.p.comp->umask); set_limits (prog_tag (prog), prog->v.p.comp->limits ? prog->v.p.comp->limits : pies_limits); if (debug_level >= 1) { int i; struct component *comp = prog->v.p.comp; logmsg_printf (LOG_DEBUG, "executing"); for (i = 0; i < comp->argc; i++) logmsg_printf (LOG_DEBUG, " %s", quotearg (comp->argv[i])); logmsg_printf (LOG_DEBUG, "\n"); } } static void prog_execute (struct prog *prog) { if (prog->v.p.comp->builtin) { mf_proctitle_format ("inetd %s", prog->v.p.comp->builtin->service); prog->v.p.comp->builtin->fun (0, prog->v.p.comp); _exit (0); } execvp (prog->v.p.comp->program ? prog->v.p.comp->program : prog->v.p.comp->argv[0], prog->v.p.comp->argv); openlog (log_tag, LOG_PID, prog->v.p.comp->facility); syslog (LOG_CRIT, _("cannot start `%s': %s"), prog_tag (prog), strerror (errno)); _exit (EX_SOFTWARE); } void progman_run_comp (struct component *comp, int fd, union pies_sockaddr_storage *sa, socklen_t salen) { struct prog *prog = register_prog0 (comp); prog->v.p.socket = fd; prog->v.p.sa_storage = *sa; prog->v.p.sa_len = salen; prog->v.p.cclass = conn_class_lookup (comp->tag, sa, salen); prog_start_prologue (prog); prog_sockenv (prog); prog_execute (prog); } static void prog_start (struct prog *prog) { pid_t pid; int redir[2]; fd_set fdset; if (prog->pid > 0 || !IS_COMPONENT (prog)) return; if (is_sysvinit (prog->v.p.comp)) { if (!init_process) { if (prog->v.p.status != status_disabled) { logmsg (LOG_NOTICE, "disabling sysvinit component %s", prog_tag (prog)); prog->v.p.status = status_disabled; } return; } prog->v.p.wait = is_comp_wait (prog->v.p.comp); debug (1, ("ok to start %s", prog->v.p.comp->tag)); } /* This call returns 1 in two cases: Either prog is marked as disabled, in which case there's nothing more to do, or one or more of its prerequisites are in status_stopping. In the latter case either the components in question will exit or a SIGALRM will get delivered. In both cases, it will cause further processing of `prog'. */ if (prog_start_prerequisites (prog)) return; switch (prog->v.p.comp->mode) { case pies_comp_exec: if (check_spawn_rate (prog)) return; break; case pies_comp_pass_fd: if (check_spawn_rate (prog)) return; debug (1, (_("unlinking %s"), prog->v.p.comp->pass_fd_socket)); if (unlink (prog->v.p.comp->pass_fd_socket) && errno != ENOENT) { logmsg (LOG_ERR, _("cannot unlink %s: %s"), prog->v.p.comp->pass_fd_socket, strerror (errno)); return; } if (prog_open_socket (prog)) return; break; case pies_comp_accept: if (check_spawn_rate (prog)) return; if (prog_open_socket (prog)) return; break; case pies_comp_inetd: /* Wait until an incoming connection is requested */ if (prog->v.p.socket == -1) return; default: break; } debug (1, (_("starting %s"), prog_tag (prog))); 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), prog->v.p.comp->rmfile, strerror (errno)); } if (prog->v.p.comp->builtin && prog->v.p.comp->builtin->single_process) { prog->v.p.comp->builtin->fun (prog->v.p.socket, prog->v.p.comp); return; } redir[RETR_OUT] = open_redirector (prog, RETR_OUT); redir[RETR_ERR] = open_redirector (prog, RETR_ERR); switch (pid = fork ()) { /* The child branch. */ case 0: signal_setup (SIG_DFL); setsid (); prog_start_prologue (prog); switch (prog->v.p.comp->mode) { case pies_comp_accept: case pies_comp_inetd: prog_sockenv (prog); dup2 (prog->v.p.socket, 0); dup2 (prog->v.p.socket, 1); close (prog->v.p.socket); prog->v.p.socket = -1; break; default: if (init_process) { int fd = console_open (O_RDWR|O_NOCTTY); if (fd < 0) { logmsg (LOG_CRIT, "open(%s): %s", console_device, strerror (errno)); fd = open ("/dev/null", O_RDWR); } if (fd != 0) dup2 (fd, 0); if (fd != 1) dup2 (fd, 1); if (fd != 2) dup2 (fd, 2); } else { if (prog->v.p.comp->flags & CF_NULLINPUT) { close(0); if (open("/dev/null", O_RDONLY) != 0) { logmsg (LOG_CRIT, "can't open /dev/null: %s", strerror(errno)); _exit(127); } } if (redir[RETR_OUT] == -1) { close (1); open ("/dev/null", O_WRONLY); } else if (redir[RETR_OUT] != 1) { dup2 (redir[RETR_OUT], 1); } } break; } if (!init_process) { if (redir[RETR_ERR] == -1) { if (!DIAG_OUTPUT (DIAG_TO_STDERR)) { close (2); open ("/dev/null", O_WRONLY); } } else if (redir[RETR_ERR] != 1) { dup2 (redir[RETR_ERR], 2); } } /* Close unneeded descripitors */ FD_ZERO (&fdset); FD_SET (0, &fdset); FD_SET (1, &fdset); FD_SET (2, &fdset); if (prog->v.p.comp->mode == pies_comp_pass_fd) FD_SET (prog->v.p.socket, &fdset); close_fds (&fdset); prog_execute (prog); case -1: logmsg (LOG_CRIT, _("cannot run `%s': fork failed: %s"), prog_tag (prog), strerror (errno)); break; default: if (prog->v.p.comp->mode == pies_comp_pass_fd) { pass_fd (prog->v.p.comp->pass_fd_socket, prog->v.p.socket, prog->v.p.comp->pass_fd_timeout ? prog->v.p.comp->pass_fd_timeout : DEFAULT_PASS_FD_TIMEOUT); /* FIXME: Error code */; } if (prog->v.p.comp->flags & CF_WAIT) { disable_socket (prog->v.p.socket); } else if (prog->v.p.comp->mode == pies_comp_accept || prog->v.p.comp->mode == pies_comp_inetd || prog->v.p.comp->mode == pies_comp_pass_fd) close (prog->v.p.socket); else if (is_sysvinit (prog->v.p.comp)) sysvinit_acct (SYSV_ACCT_PROC_START, "", prog_tag (prog), pid, ""); if (redir[RETR_OUT] != -1) close (redir[RETR_OUT]); if (redir[RETR_ERR]) close (redir[RETR_ERR]); prog->pid = pid; prog->v.p.status = status_enabled; debug (1, (_("%s started, pid=%lu"), prog_tag (prog), (unsigned long) pid)); } } int check_acl (pies_acl_t acl, struct sockaddr *s, socklen_t salen, pies_identity_t identity) { struct acl_input input; int rc; if (!acl) return 0; input.addr = s; input.addrlen = salen; input.identity = identity; rc = pies_acl_check (acl, &input, 1); if (rc == 0) { char *p = sockaddr_to_astr (s, salen); logmsg (LOG_ERR, _("access from %s blocked"), p); free (p); return 1; } return 0; } void fd_report (int fd, const char *msg) { size_t len; if (!msg) return; for (len = strlen (msg); len; ) { ssize_t rc = write (fd, msg, len); if (rc == -1) { logmsg (LOG_ERR, _("error writing to socket: %s"), strerror (errno)); break; } else if (rc == 0) break; len -= rc; msg += rc; } } static int _prog_accept (struct prog *p) { int fd; struct prog *pinst; union pies_sockaddr_storage addr; socklen_t addrlen = sizeof addr; struct conn_class *pcclass; fd = accept (p->v.p.socket, (struct sockaddr*) &addr, &addrlen); if (fd == -1) { logmsg (LOG_ERR, _("accept failed: %s"), strerror (errno)); return 1; } if (debug_level >= 1) { char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen); logmsg (LOG_DEBUG, _("%s wants %s"), s, prog_tag (p)); free (s); } if (check_acl (p->v.p.comp->acl, (struct sockaddr *)&addr, addrlen, NULL) || check_acl (pies_acl, (struct sockaddr *)&addr, addrlen, NULL)) { fd_report (fd, p->v.p.comp->access_denied_message); close (fd); return 1; } if (p->v.p.comp->max_instances && p->v.p.num_instances >= p->v.p.comp->max_instances) { char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen); logmsg (LOG_ERR, _("%s: access from %s denied: too many instances running"), prog_tag (p), s); free (s); fd_report (fd, p->v.p.comp->max_instances_message); close (fd); return 1; } pcclass = conn_class_lookup (prog_tag (p), &addr, addrlen); if (p->v.p.comp->max_ip_connections && pcclass->count >= p->v.p.comp->max_ip_connections) { char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen); logmsg (LOG_ERR, _("%s: access from %s denied: " "too many connections from that ip"), prog_tag (p), s); free (s); fd_report (fd, p->v.p.comp->max_ip_connections_message); close (fd); return 1; } if (check_connection_rate (p)) { disable_socket (p->v.p.socket); close (fd); return 1; } pcclass->count++; if (debug_level > 1) conn_class_report (LOG_DEBUG, pcclass); pinst = register_prog0 (p->v.p.comp); pinst->v.p.socket = fd; pinst->v.p.listener = p; pinst->v.p.sa_storage = addr; pinst->v.p.sa_len = addrlen; pinst->v.p.cclass = pcclass; prog_start (pinst); close (fd); pinst->v.p.socket = -1; p->v.p.num_instances++; return 0; } static int _prog_wait (struct prog *p) { struct prog *pinst; debug (1, (_("someone wants %s"), prog_tag (p))); if (p->v.p.comp->max_instances && p->v.p.num_instances >= p->v.p.comp->max_instances) { logmsg (LOG_ERR, _("%s: too many instances running, dropping connection"), prog_tag (p)); return 1; } pinst = register_prog0 (p->v.p.comp); pinst->v.p.socket = p->v.p.socket; pinst->v.p.listener = p; prog_start (pinst); pinst->v.p.socket = -1; p->v.p.num_instances++; return 0; } int progman_accept (int socket, void *data) { struct prog *p = data; if (p->v.p.comp->socket_type == SOCK_STREAM && !(p->v.p.comp->flags & CF_WAIT)) return _prog_accept (p); return _prog_wait (p); } void progman_create_sockets () { struct prog *prog; for (prog = proghead; prog; prog = prog->next) { if (IS_COMPONENT (prog)) { struct component *comp = prog->v.p.comp; if (comp->mode == pies_comp_inetd && !ISCF_TCPMUX (comp->flags) && prog->v.p.socket == -1) { int fd = create_socket (comp->socket_url, comp->socket_type, comp->privs.user, comp->umask); if (fd == -1) prog->v.p.status = status_disabled; else if (register_program_socket (comp->socket_type, fd, prog)) { close (fd); prog->v.p.status = status_disabled; } else prog->v.p.socket = fd; } } } } void progman_recompute_alarm () { struct prog *prog; time_t now = time (NULL); time_t alarm_time = 0, x; recompute_alarm = 0; debug (2, ("Recomputing alarm settings")); for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog)) { switch (prog->v.p.status) { case status_sleeping: x = SLEEPTIME - (now - prog->v.p.timestamp); if (alarm_time == 0 || x < alarm_time) alarm_time = x; break; case status_stopping: x = shutdown_timeout - (now - prog->v.p.timestamp); if (alarm_time == 0 || x < alarm_time) alarm_time = x; break; default: break; } } debug (2, ("alarm=%lu", (unsigned long)alarm_time)); if (alarm_time) alarm (alarm_time); } void progman_start () { struct prog *prog; if (progman_waiting_p ()) /* Noting to do if there are processes left in the previous runlevel */ return; recompute_alarm = 0; debug (1, ("starting components")); for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog)) { if (prog->v.p.comp->mode == pies_comp_inetd) { if (prog->v.p.comp->flags & CF_DISABLED) disable_socket (prog->v.p.socket); else { prog->v.p.status = status_listener; enable_socket (prog->v.p.socket); } } else if ((prog->v.p.status == status_enabled && prog->pid == 0) || prog->v.p.status == status_sleeping) prog_start (prog); } } static void check_stopping (struct prog *prog, time_t now) { if (now - prog->v.p.timestamp >= shutdown_timeout) { if (prog->pid == 0) logmsg (LOG_EMERG, _("INTERNAL ERROR: attempting to kill unexisting process %s"), prog_tag (prog)); else if (prog->v.p.comp->flags & CF_SIGGROUP) kill (-prog->pid, SIGKILL); else kill (prog->pid, SIGKILL); } else recompute_alarm = 1; } void progman_wake_sleeping (int onalrm) { struct prog *prog; time_t now = time (NULL); if (progman_waiting_p ()) /* Noting to do if there are processes left in the previous runlevel */ return; debug (1, (_("managing sleeping/stopping components"))); for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog)) { switch (prog->v.p.status) { case status_sleeping: if (now - prog->v.p.timestamp >= SLEEPTIME) { if (prog->v.p.comp->mode == pies_comp_inetd) { prog->v.p.status = status_listener; enable_socket (prog->v.p.socket); } else { prog->v.p.status = status_enabled; prog->v.p.failcount = 0; prog->v.p.timestamp = 0; prog_start (prog); } } /* If there is no alarm pending, recompute next alarm. This allows to cope with eventual clock inaccuracies. */ if (onalrm) recompute_alarm = 1; break; case status_stopping: check_stopping (prog, now); break; case status_enabled: if (prog->pid == 0) prog_start (prog); break; default: break; } } if (recompute_alarm) progman_recompute_alarm (); } static int prog_start_prerequisites (struct prog *prog) { int ret = 0; pies_depmap_pos_t pos; struct component *comp; int warned = 0; if (prog->v.p.status == status_disabled) return 1; for (comp = component_depmap_first (depmap_col, prog->v.p.comp->arridx, &pos); comp; comp = component_depmap_next (pos)) { struct prog *p; if (!comp->prog || comp->flags & CF_PRECIOUS) continue; if (!warned) { debug (1, ("starting prerequisites of %s", prog_tag (prog))); warned = 1; } p = comp->prog; switch (p->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 (p, time (NULL)); ret = 1; continue; case status_finished: continue; } prog_start (p); if (p->v.p.comp->mode == pies_comp_once || !(p->v.p.status == status_enabled && p->pid)) ret = 1; } depmap_end (pos); 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) { struct component *comp; pies_depmap_pos_t pos; int warned = 0; prog_stop_redirectors (prog); for (comp = component_depmap_first (depmap_row, prog->v.p.comp->arridx, &pos); comp; comp = component_depmap_next (pos)) { struct prog *dp = comp->prog; if (!dp) /* should not happen */ continue; if (!warned && dp->pid) { debug (1, ("stopping dependencies of %s", prog_tag (prog))); warned = 1; } prog_stop (dp, SIGTERM); } depmap_end (pos); } 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 (prog), (unsigned long) prog->pid)); if (prog->type == TYPE_COMPONENT && prog->v.p.comp->flags & CF_SIGGROUP) kill (-prog->pid, sig); else kill (prog->pid, sig); } static void prog_stop_all (int sig) { struct prog *prog; debug (1, ("stopping all components (signal %d)", sig)); for (prog = progtail; prog; prog = prog->prev) if (IS_COMPONENT (prog) && (prog->v.p.status == status_enabled || prog->v.p.status == status_stopping)) prog_stop (prog, sig); } int progman_wait_until (int (*cond) (void *), void *data) { time_t start = time (NULL); do { progman_cleanup (1); if (cond && cond (data)) return 0; sleep (1); } while (time (NULL) - start < shutdown_timeout); return 1; } static int no_children_left (void *p) { return progman_running_count () == 0; } void progman_stop () { prog_stop_all (SIGTERM); if (progman_wait_until (no_children_left, NULL)) { prog_stop_all (SIGKILL); progman_wait_until (no_children_left, NULL); } } static void print_status (const char *tag, pid_t pid, int status, int expect_term) { int prio; if (init_process) { if (debug_level <= 1) return; prio = LOG_DEBUG; } else prio = LOG_ERR; if (WIFEXITED (status)) { if (WEXITSTATUS (status) == 0) debug (1, (_("%s (%lu) exited successfully"), tag, (unsigned long) pid)); else logmsg (prio, _("%s (%lu) exited with status %d"), tag, (unsigned long) pid, WEXITSTATUS (status)); } else if (WIFSIGNALED (status)) { char const *coremsg = ""; if (expect_term && WTERMSIG (status) == SIGTERM) prio = LOG_DEBUG; #ifdef WCOREDUMP if (WCOREDUMP (status)) coremsg = _(" (core dumped)"); #endif logmsg (prio, _("%s (%lu) terminated on signal %d%s"), tag, (unsigned long) pid, WTERMSIG (status), coremsg); } else if (WIFSTOPPED (status)) logmsg (prio, _("%s (%lu) stopped on signal %d"), tag, (unsigned long) pid, WSTOPSIG (status)); else logmsg (prio, _("%s (%lu) terminated with unrecognized status: %d"), tag, (unsigned long) pid, status); } static void propagate_child_exit (pid_t) ATTRIBUTE_NORETURN; static void propagate_child_exit (pid_t pid) { int wait_status; while (waitpid (pid, &wait_status, 0) == -1) if (errno != EINTR) { logmsg (LOG_ERR, _("waitpid failed: %s"), strerror (errno)); exit (EX_OSERR); } if (WIFSIGNALED (wait_status)) { int sig = WTERMSIG (wait_status); signal (sig, SIG_DFL); raise (sig); } else if (WIFEXITED (wait_status)) exit (WEXITSTATUS (wait_status)); exit (127); } char * wordsplit_string (struct wordsplit const *ws) { char *ret, *p; size_t count = ws->ws_wordc + ws->ws_offs; char **argv = grecs_calloc (count, sizeof (argv[0])); size_t i; size_t len = 0; for (i = 0; i < count; i++) { argv[i] = quotearg_n (i, ws->ws_wordv[i]); len += strlen (argv[i]); } len += count; ret = grecs_malloc (len); for (i = 0, p = ret; i < count; i++) { strcpy (p, argv[i]); p += strlen (argv[i]); *p++ = ' '; } p[-1] = 0; free (argv); return ret; } void send_msg (char *rcpts, const char *msg_text) { int i, j, k; pid_t child_pid, grand_child_pid; struct wordsplit ws; int p[2]; size_t size; ws.ws_offs = mailer_argc; ws.ws_delim = ","; if (wordsplit (rcpts, &ws, WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_DELIM | WRDSF_DOOFFS)) { logmsg (LOG_ERR, _("cannot parse recipient address list (%s)"), rcpts); return; } debug (1, (_("sending notification to %s"), rcpts)); /* Copy mailer arguments */ for (i = 0; i < mailer_argc; i++) ws.ws_wordv[i] = mailer_argv[i]; /* j - number of the recipient; i - index of the ws_wordv being converted; k - index of the ws_wordv to store the converted value to. Normally i == k, unless there are some invalid ws_wordv's, in which case i > k. */ for (j = 0, k = i; j < ws.ws_wordc; j++, i++) { char *arg = ws.ws_wordv[i]; size_t len; while (*arg && c_isblank (*arg)) arg++; len = strlen (arg); if (len > 0) { while (len > 0 && c_isblank (arg[len - 1])) len--; } if (len == 0) continue; if (arg[0] == '<' && arg[len-1] == '>') { arg++; len -= 2; } if (len == 0) continue; memmove (ws.ws_wordv[k], arg, len); ws.ws_wordv[k][len] = 0; k++; } ws.ws_wordv[k] = NULL; /* Fork a child: */ child_pid = fork (); if (child_pid < 0) { wordsplit_free (&ws); logmsg (LOG_ERR, _("cannot send mail: fork failed: %s"), strerror (errno)); return; } if (child_pid) { char *cmd = wordsplit_string (&ws); wordsplit_free (&ws); debug (1, (_("started mailer: %s, pid=%lu"), cmd, (unsigned long) child_pid)); register_command (mailer_program, cmd, child_pid); return; } /* Child process */ /* ============= */ signal_setup (SIG_DFL); setsid (); if (pipe (p)) { logmsg (LOG_ERR, _("cannot send mail: pipe failed: %s"), strerror (errno)); wordsplit_free (&ws); exit (EX_OSERR); } grand_child_pid = fork (); if (grand_child_pid < 0) { logmsg (LOG_ERR, _("cannot send mail: fork failed: %s"), strerror (errno)); return; } if (grand_child_pid == 0) { /* Grand-child */ /* =========== */ if (p[0] != 0 && p[1] != 0) close (0); close (p[1]); dup2 (p[0], 0); execv (mailer_program, ws.ws_wordv); exit (127); } /* Child again */ close (p[0]); size = strlen (msg_text); while (size) { ssize_t rc = write (p[1], msg_text, size); if (rc <= 0) { logmsg (LOG_ERR, _("cannot write to pipe: %s"), rc == 0 ? "EOF" : strerror (errno)); break; } size -= rc; msg_text += rc; } close (p[1]); propagate_child_exit (grand_child_pid); } static const char default_termination_message[] = "From: <>\n" "X-Agent: ${canonical_program_name} (${package} ${version})\n" "Subject: Component ${component} ${termination} ${retcode}.\n" "\n"; struct notify_closure { const char *tag; int status; struct action *act; }; static int notify_get_tag (char **ret, void *data) { struct notify_closure const *clos = data; char *s = strdup (clos->tag); if (!s) return WRDSE_NOSPACE; *ret = s; return WRDSE_OK; } static int notify_get_termination (char **ret, void *data) { struct notify_closure const *clos = data; char const *msg; char *s; if (WIFEXITED (clos->status)) /* TRANSLATORS: The subject of this statement is 'component' */ msg = _("exited with code"); else if (WIFSIGNALED (clos->status)) /* TRANSLATORS: The subject of this statement is 'component' */ msg = _("terminated on signal"); else msg = "UNKNOWN"; s = strdup (msg); if (!s) return WRDSE_NOSPACE; *ret = s; return WRDSE_OK; } static int notify_get_retcode (char **ret, void *data) { struct notify_closure const *clos = data; char *s = NULL; size_t l = 0; if (WIFEXITED (clos->status)) grecs_asprintf (&s, &l, "%d", WEXITSTATUS (clos->status)); else if (WIFSIGNALED (clos->status)) grecs_asprintf (&s, &l, "%d", WTERMSIG (clos->status)); else s = strdup ("UNKNOWN"); if (!s) return WRDSE_NOSPACE; *ret = s; return WRDSE_OK; } struct notify_variable { char *name; size_t name_length; int (*get) (char **ret, void *data); }; static struct notify_variable notify_vartab[] = { #define S(s) #s, sizeof(#s)-1 { S(component), notify_get_tag }, { S(termination), notify_get_termination }, { S(retcode), notify_get_retcode }, #undef S { NULL } }; static int notify_getvar (char **ret, const char *vptr, size_t vlen, void *data) { struct notify_variable *var; for (var = notify_vartab; var->name; var++) if (var->name_length == vlen && memcmp (var->name, vptr, vlen) == 0) return var->get (ret, data); return WRDSE_UNDEF; } static void notify (const char *tag, int status, struct action *act) { const char *env[] = { #define PROGRAM_NAME_IDX 1 "program_name", NULL, #define INSTANCE_IDX 3 "instance", NULL, "canonical_program_name", "pies", "package", PACKAGE, "version", PACKAGE_VERSION, NULL }; struct wordsplit ws; struct notify_closure closure; closure.tag = tag; closure.status = status; closure.act = act; env[PROGRAM_NAME_IDX] = program_name; env[INSTANCE_IDX] = instance; ws.ws_env = env; ws.ws_getvar = notify_getvar; ws.ws_closure = &closure; if (wordsplit (act->message ? act->message : default_termination_message, &ws, WRDSF_NOSPLIT | WRDSF_NOCMD | WRDSF_QUOTE | WRDSF_ENV | WRDSF_ENV_KV | WRDSF_GETVAR | WRDSF_CLOSURE)) { logmsg (LOG_ERR, "wordsplit: %s", wordsplit_strerror (&ws)); return; } send_msg (act->addr, ws.ws_wordv[0]); wordsplit_free (&ws); } static int status_matches_p (struct action *act, unsigned status) { int i; if (act->nstat == 0) return 1; for (i = 0; i < act->nstat; i++) if (act->status[i] == status) return 1; return 0; } static void run_command (struct action *act, struct prog *prog, unsigned retcode, pid_t child_pid) { pid_t pid; char *argv[4]; char buf[INT_BUFSIZE_BOUND (uintmax_t)]; /* FIXME: optionally set output redirectors for this command? */ pid = fork (); if (pid == (pid_t) -1) { logmsg (LOG_ERR, "fork: %s", strerror (errno)); return; } if (pid == 0) { debug (1, (_("executing %s"), act->command)); /* Child */ setsid (); setenv ("PIES_VERSION", PACKAGE_VERSION, 1); setenv ("PIES_COMPONENT", prog_tag (prog), 1); setenv ("PIES_PID", umaxtostr (child_pid, buf), 1); if (retcode & STATUS_SIG_BIT) setenv ("PIES_SIGNAL", umaxtostr (STATUS_CODE (retcode), buf), 1); else setenv ("PIES_STATUS", umaxtostr (STATUS_CODE (retcode), buf), 1); close_fds (NULL); argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = act->command; argv[3] = NULL; execv (argv[0], argv); exit (127); } /* Master */ debug (1, (_("started command: %s, pid=%lu"), act->command, (unsigned long) pid)); register_command ((char*) _("[action]"), grecs_strdup (act->command), pid); } static void react (struct prog *prog, int status, pid_t pid) { unsigned retcode; struct grecs_list *list = prog->v.p.comp->act_list; if (!list) list = default_component.act_list; if (WIFEXITED (status)) { retcode = WEXITSTATUS (status); debug (1, (_("%s: terminated with code %d"), prog_tag (prog), retcode)); } else if (WIFSIGNALED (status)) { retcode = WTERMSIG (status); debug (1, (_("%s: terminated on signal %d"), prog_tag (prog), retcode)); retcode |= STATUS_SIG_BIT; } else { debug (1, (_("%s: unrecognized termination status"), prog_tag (prog))); /* Enforce default action: */ list = NULL; } if (list) { struct grecs_list_entry *ep; for (ep = list->head; ep; ep = ep->next) { struct action *act = ep->data; if (status_matches_p (act, retcode)) { if (act->command) { run_command (act, prog, retcode, pid); } switch (act->act) { case action_restart: if (prog->v.p.comp->mode != pies_comp_inetd) prog_start (prog); break; case action_disable: logmsg (LOG_NOTICE, _("disabling component %s"), prog_tag (prog)); prog->v.p.status = status_disabled; /* FIXME: if (prog->v.p.comp->mode == pies_comp_inetd) disable_socket (prog->v.p.socket); */ } if (act->addr) notify (prog_tag (prog), status, act); break; } } } if (!list && prog->v.p.comp->mode != pies_comp_inetd) /* Default action: */ prog_start (prog); } void progman_cleanup (int expect_term) { pid_t pid; int status; if (!expect_term) expect_term = progman_waiting_p (); while ((pid = waitpid (-1, &status, WNOHANG)) > 0) { struct prog *prog = prog_lookup_by_pid (pid); if (!prog) { print_status (_("unknown child"), pid, status, expect_term); continue; } prog->pid = 0; switch (prog->type) { case TYPE_COMPONENT: print_status (prog_tag (prog), pid, status, expect_term); if (prog->v.p.comp->mode == pies_comp_inetd) { struct prog *listener = prog->v.p.listener; listener->v.p.num_instances--; if (prog->v.p.cclass) { prog->v.p.cclass->count--; if (debug_level > 1) conn_class_report (LOG_DEBUG, prog->v.p.cclass); if (prog->v.p.cclass->count == 0) { conn_class_remove (prog->v.p.cclass); prog->v.p.cclass = NULL; } } prog_stop_redirectors (prog); destroy_prog (&prog); if (listener->v.p.num_instances == 0 && !component_is_active (prog->v.p.comp)) destroy_prog (&listener); else { if (!expect_term) react (listener, status, pid); if (listener->v.p.comp->flags & CF_WAIT) enable_socket (listener->v.p.socket); } } else { if (prog->v.p.comp->mode >= pies_mark_sysvinit) { sysvinit_acct (SYSV_ACCT_PROC_STOP, "", prog_tag (prog), pid, ""); prog->v.p.status = status_finished; prog->v.p.wait = 0; } else { if (is_sysvinit (prog->v.p.comp)) sysvinit_acct (SYSV_ACCT_PROC_STOP, "", prog_tag (prog), pid, ""); prog->v.p.status = (prog->v.p.comp->flags & CF_DISABLED) ? status_disabled : status_enabled; prog_stop_dependents (prog); if (!expect_term) react (prog, status, pid); } if (!component_is_active (prog->v.p.comp)) destroy_prog (&prog); } break; case TYPE_REDIRECTOR: /* It was a redirector of an already finished process. */ print_status (prog_tag (prog), pid, status, expect_term || (prog->v.r.master && prog->v.r.master->v.p.status == status_stopping)); debug (1, (_("removing redirector %s, pid=%lu"), prog_tag (prog), (unsigned long)pid)); destroy_prog (&prog); break; case TYPE_COMMAND: print_status (prog_tag (prog), pid, status, expect_term); destroy_prog (&prog); break; } } if (!expect_term) /* This will also recompute alarm settings, if necessary */ progman_wake_sleeping (0); } void progman_stop_component (struct prog *prog) { if (prog && IS_COMPONENT (prog)) { switch (prog->v.p.status) { case status_enabled: case status_listener: logmsg (LOG_INFO, _("stopping component `%s'"), prog_tag (prog)); prog_stop (prog, SIGTERM); break; case status_disabled: if (!component_is_active (prog->v.p.comp)) destroy_prog (&prog); else if (!(prog->v.p.comp->flags & CF_DISABLED)) { logmsg (LOG_INFO, _("enabling component `%s'"), prog_tag (prog)); prog->v.p.status = status_enabled; } break; case status_sleeping: if (!component_is_active (prog->v.p.comp)) destroy_prog (&prog); else { logmsg (LOG_INFO, _("waking up component `%s'"), prog_tag (prog)); prog->v.p.failcount = 0; } break; default: if (!component_is_active (prog->v.p.comp)) destroy_prog (&prog); else logmsg (LOG_INFO, _("stopping component `%s': component not started"), prog_tag (prog)); } } } void progman_stop_tag (const char *name) { struct prog *prog = progman_locate (name); progman_stop_component (prog); } /* EOF */