/* This file is part of GNU Pies. Copyright (C) 2007-2019 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 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; } int progman_foreach (int (*filter) (struct prog *, void *data), void *data) { struct prog *prog; int rc = 0; for (prog = proghead; prog; ) { struct prog *next = prog->next; if ((rc = filter (prog, data)) != 0) break; prog = next; } return rc; } 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 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->active && 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 *prog, struct prog *ref) { if (!ref) { prog->prev = NULL; prog->next = proghead; if (proghead) proghead->prev = prog; else progtail = prog; proghead = prog; } else { struct prog *x; prog->prev = ref; prog->next = ref->next; if ((x = ref->next)) x->prev = prog; else progtail = prog; ref->next = prog; } } 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 && p->v.p.socket != -1) deregister_socket (p->v.p.socket); /* FIXME: Remove also all dependent progs (esp. tcpmux) */ if (p->v.p.redir[RETR_OUT]) p->v.p.redir[RETR_OUT]->v.r.master = NULL; if (p->v.p.redir[RETR_ERR]) p->v.p.redir[RETR_ERR]->v.r.master = NULL; break; case TYPE_REDIRECTOR: { struct prog *master = p->v.r.master; component_ref_decr (p->v.r.comp); if (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);*/ 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; pp->v.r.comp = master->v.p.comp; component_ref_incr (pp->v.r.comp); link_prog (pp, NULL); 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; } /* Given the component COMP find the element of the program list after which to link in the new prog associated with that COMP. The progs in the resulting list must be arranged exactly in the same order as the corresponding components. Points to note: 1. If comp->prog is already present, follow progs in its next chain until the last one that points to the same COMP is found. 2. Otherwise, if that component is not the first one, step back to the previous component and do the same for its comp->prog chain. 3. Return the prog found. */ static struct prog * find_prog_ref (struct component *comp) { struct prog *prog; if (!comp->prog) { comp = comp->prev; if (!comp) return NULL; /* FIXME: Skip redirectors? */ } if (comp->prog) { for (prog = comp->prog; prog->next && IS_COMPONENT (prog->next) && prog->next->v.p.comp == comp; prog = prog->next) ; } else prog = NULL; return prog; } static struct prog * register_prog0 (struct component *comp) { struct prog *newp; newp = grecs_zalloc (sizeof (*newp)); newp->type = TYPE_COMPONENT; newp->pid = 0; newp->v.p.comp = comp; newp->v.p.socket = -1; if (comp->mode == pies_comp_inetd) newp->v.p.status = status_listener; else newp->v.p.status = status_stopped; if ((comp->flags & CF_DISABLED) || comp->mode == pies_comp_ondemand || comp->mode == pies_comp_shutdown) newp->active = 0; else newp->active = 1; switch (comp->mode) { case pies_comp_exec: case pies_comp_startup: case pies_comp_shutdown: break; default: comp->redir[RETR_OUT].type = redir_null; } link_prog (newp, find_prog_ref (comp)); 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, progtail); } static inline int progman_startup_phase (void) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) { if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_startup && prog->v.p.status == status_running) return 1; } return 0; } int progman_waiting_p (void) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) { if (IS_COMPONENT (prog) && prog->v.p.status == status_running && (prog->wait || prog->v.p.comp->mode == pies_comp_startup)) { debug (3, ("%s: waiting for %s (%lu)", __FUNCTION__, prog_tag (prog), (unsigned long) prog->pid)); return 1; } } return 0; } 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)); } /* Position to the end of file */ if (lseek (fd, 0, SEEK_END) == -1) { if (errno != ESPIPE) logmsg (LOG_ERR, "lseek(%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; 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->v.p.comp->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); } #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_var[] = { ENV_PROTO, ENV_SOCKTYPE, ENV_LOCALIP, ENV_LOCALPORT, ENV_LOCALHOST, ENV_REMOTEIP, ENV_REMOTEPORT, ENV_REMOTEHOST, NULL }; static inline void debug_environ (struct prog *prog, int l) { if (debug_level >= l) { int i; char **env = environ_ptr (prog->v.p.env); logmsg_printf (LOG_DEBUG, "environment: "); for (i = 0; env[i]; i++) logmsg_printf (LOG_DEBUG, "%s ", env[i]); logmsg_printf (LOG_DEBUG, "\n"); } } /* 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); environ_set (prog->v.p.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"; environ_set (prog->v.p.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) environ_set (prog->v.p.env, ENV_LOCALIP, p); p = umaxtostr (ntohs (sa_server.s_in.sin_port), buf); environ_set (prog->v.p.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 environ_set (prog->v.p.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) environ_set (prog->v.p.env, ENV_REMOTEIP, p); p = umaxtostr (ntohs (sa_client->s_in.sin_port), buf); environ_set (prog->v.p.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 environ_set (prog->v.p.env, ENV_REMOTEHOST, host->h_name); } } /* FIXME: $REMOTEINFO ? */ debug_environ (prog, 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; pies_schedule_children (PIES_CHLD_RESCHEDULE_ALARM); 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_stopped; prog->active = 0; 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_stopped; prog->active = 0; 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)); } prog->v.p.env = environ_create (environ); if (prog->v.p.comp->flags & CF_SOCKENV) { size_t i; for (i = 0; sockenv_var[i]; i++) environ_unset (prog->v.p.env, sockenv_var[i], NULL); } envop_exec (prog->v.p.comp->envop, prog->v.p.env); if (init_process) { size_t i; for (i = 0; sysvinit_environ_hint[i]; i++) environ_add (prog->v.p.env, sysvinit_environ_hint[i]); } debug_environ (prog, 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) { environ = environ_ptr (prog->v.p.env); 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->active) { logmsg (LOG_NOTICE, "disabling sysvinit component %s", prog_tag (prog)); prog->v.p.status = status_stopped; prog->active = 0; } 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; 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) { logfuncall ("unlink", prog->v.p.comp->pass_fd_socket, 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_running; 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) { logfuncall ("accept", NULL, 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); } static int prog_create_socket (struct prog *prog, void *data) { if (IS_COMPONENT (prog) && prog->v.p.status == status_listener) { struct component *comp = prog->v.p.comp; if (!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) destroy_prog (&prog); else if (register_program_socket (comp->socket_type, fd, prog)) { close (fd); destroy_prog (&prog); } else prog->v.p.socket = fd; } } return 0; } void progman_create_sockets (void) { progman_foreach (prog_create_socket, NULL); } void progman_recompute_alarm (void) { struct prog *prog; time_t now = time (NULL); time_t alarm_time = 0, x; 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 program_init_startup (void) { struct prog *prog; for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_startup) { debug (1, ("running startup components")); break; } for (; prog; prog = prog->next) if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_startup) { prog_start (prog); } } void progman_start (void) { struct prog *prog; if (progman_waiting_p ()) /* Noting to do if there are processes left in the previous runlevel (in sysv-init mode) or startup components running. */ return; debug (1, ("starting components")); for (prog = proghead; prog; prog = prog->next) if (IS_COMPONENT (prog)) { switch (prog->v.p.status) { case status_stopped: case status_sleeping: prog_start (prog); break; case status_running: case status_stopping: case status_finished: break; case status_listener: if (!prog->active) disable_socket (prog->v.p.socket); else enable_socket (prog->v.p.socket); } } } 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 nonexistent process %s"), prog_tag (prog)); else if (prog->v.p.comp->flags & CF_SIGGROUP) kill (-prog->pid, SIGKILL); else kill (prog->pid, SIGKILL); } else pies_schedule_children (PIES_CHLD_RESCHEDULE_ALARM); } void progman_wake_sleeping (int onalrm) { struct prog *prog; time_t now = time (NULL); debug (1, (_("checking for components to start"))); for (prog = proghead; prog; prog = prog->next) { if (IS_ACTIVE_COMPONENT (prog) && prog->wait) { /* The following works on the assumption that prog->wait is set when enabling the component and gets cleared right after it has finished. */ if (prog->v.p.status != status_running) prog_start (prog); return; } } for (prog = proghead; prog; prog = prog->next) switch (prog->v.p.status) { case status_sleeping: if (IS_ACTIVE_COMPONENT (prog)) { 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_running; 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) pies_schedule_children (PIES_CHLD_RESCHEDULE_ALARM); } break; case status_stopping: check_stopping (prog, now); break; case status_stopped: if (IS_ACTIVE_COMPONENT (prog)) prog_start (prog); break; default: break; } } static int prog_start_prerequisites (struct prog *prog) { int ret = 0; pies_depmap_pos_t pos; struct component *comp; int warned = 0; if (!prog->active) 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; } if (!prog->active) { prog->active = 0; return 1; } p = comp->prog; switch (p->v.p.status) { case status_running: continue; case status_stopped: break; 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_running)//FIXME 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_running) { prog->v.p.status = status_stopping; prog->v.p.timestamp = time (NULL); pies_schedule_children (PIES_CHLD_RESCHEDULE_ALARM); } } 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 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) { logfuncall ("waitpid", NULL, 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_NAME, "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, master_pid; char *argv[4]; char buf[INT_BUFSIZE_BOUND (uintmax_t)]; master_pid = getpid (); /* 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_MASTER_PID", umaxtostr (master_pid, buf), 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->active = 0; /* 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); prog->stop = 0; 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); 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); } destroy_prog (&prog); } else { if (prog->v.p.comp->mode >= pies_mark_sysvinit && prog->v.p.comp->mode != pies_comp_ondemand) { sysvinit_acct (SYSV_ACCT_PROC_STOP, "", prog_tag (prog), pid, ""); prog->v.p.status = status_finished; if (prog->wait) { pies_schedule_children (PIES_CHLD_WAKEUP); prog->wait = 0; } } else if (prog->v.p.comp->mode == pies_comp_startup) { prog->v.p.status = status_finished; if (!progman_startup_phase ()) pies_schedule_children (PIES_CHLD_WAKEUP); } else { if (is_sysvinit (prog->v.p.comp)) sysvinit_acct (SYSV_ACCT_PROC_STOP, "", prog_tag (prog), pid, ""); prog->v.p.status = status_stopped; 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 **progptr) { struct prog *prog = *progptr; if (prog && IS_COMPONENT (prog)) { switch (prog->v.p.status) { case status_running: logmsg (LOG_INFO, _("stopping component %s"), prog_tag (prog)); prog_stop (prog, SIGTERM); break; case status_listener: prog_deactivate_listener (prog); /* fall through */ case status_stopped: prog->stop = 0; if (!component_is_active (prog->v.p.comp)) destroy_prog (progptr); break; case status_sleeping: if (!component_is_active (prog->v.p.comp)) destroy_prog (progptr); else if (prog->stop) prog->stop = 0; else { logmsg (LOG_INFO, _("waking up component %s"), prog_tag (prog)); prog->v.p.failcount = 0; } break; case status_stopping: break; case status_finished: prog->stop = 0; if (!component_is_active (prog->v.p.comp)) destroy_prog (progptr); else logmsg (LOG_INFO, _("stopping component %s: component not started"), prog_tag (prog)); } } } void prog_deactivate_listener (struct prog *prog) { logmsg (LOG_INFO, _("deactivating listener %s"), prog_tag (prog)); if (prog->v.p.socket != -1) { deregister_socket (prog->v.p.socket); prog->v.p.socket = -1; } } int prog_activate_listener (struct prog *prog) { struct component *comp = prog->v.p.comp; logmsg (LOG_INFO, _("activating listener %s"), prog_tag (prog)); 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) return -1; else if (register_program_socket (comp->socket_type, fd, prog)) { close (fd); return -1; } else prog->v.p.socket = fd; } return 0; } /* Starting at PROG, find first program component marked for termination. */ static struct prog * prog_to_stop (struct prog *prog) { for (; prog; prog = prog->next) if (IS_COMPONENT (prog) && prog->stop) break; return prog; } #define DIAG_CON \ (init_process ? (DIAG_TO_STDERR|DIAG_REOPEN_LOG) : diag_output) /* Stop all program components marked for termination. Wait at most 2*shutdown_timeout seconds. */ void progman_gc (void) { time_t start; struct prog *prog, *next; /* Find first marked prog */ prog = prog_to_stop (proghead); if (!prog) return; /* First round: */ /* Gracefully stop it and all marked programs that follow */ diagmsg (DIAG_CON, LOG_INFO, "Sending processes the TERM signal"); start = time (NULL); do { next = prog->next; progman_stop_component (&prog); } while ((prog = prog_to_stop (next))); /* Wait for them to terminate */ while (1) { progman_cleanup (1); prog = prog_to_stop (proghead); if (!prog) return; if (time (NULL) - start < shutdown_timeout) sleep (1); else break; } /* Second round: */ /* Kill the remaining programs with SIGKILL */ diagmsg (DIAG_CON, LOG_INFO, "Sending processes the KILL signal"); start = time (NULL); do { next = prog->next; prog_stop (prog, SIGKILL); } while ((prog = prog_to_stop (next))); /* Wait for them to terminate */ while (1) { progman_cleanup (1); prog = prog_to_stop (proghead); if (!prog) return; if (time (NULL) - start < shutdown_timeout) sleep (1); else break; } /* FIXME: Report remaining programs */ } static int start_shutdown (struct prog *prog, void *data) { if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_shutdown) { int *p = data; if (!*p) { diagmsg (DIAG_CON, LOG_INFO, "Starting shutdown components"); *p = 1; } prog->active = 1; prog_start (prog); prog->stop = 1; } return 0; } void program_shutdown (void) { time_t start = time (NULL); int sd = 0; /* Activate shutdown components. */ progman_foreach (start_shutdown, &sd); while (prog_to_stop (proghead)) { progman_cleanup (1); if (time (NULL) - start < shutdown_timeout) sleep (1); else { progman_gc (); break; } } } static int mark_for_stopping (struct prog *prog, void *data) { if (IS_COMPONENT (prog)) prog->stop = 1; return 0; } void progman_stop (void) { progman_foreach (mark_for_stopping, NULL); progman_gc (); program_shutdown (); } /* EOF */