/* This file is part of GNU Pies. Copyright (C) 2007-2023 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_COMMAND: return prog->v.c.tag; } abort (); } int prog_sigterm (struct prog const *prog) { return (prog && prog->type == TYPE_COMPONENT) ? prog->v.p.comp->sigterm : SIGTERM; } 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; } static inline void prog_stop_redirectors (struct prog *p) { if (p->v.p.redir[RETR_OUT] != -1) { deregister_socket (p->v.p.redir[RETR_OUT]); p->v.p.redir[RETR_OUT] = -1; } if (p->v.p.redir[RETR_ERR] != -1) { deregister_socket (p->v.p.redir[RETR_ERR]); p->v.p.redir[RETR_ERR] = -1; } } void destroy_prog (struct prog **pp) { struct prog *p = *pp; unlink_prog (p); switch (p->type) { case TYPE_COMPONENT: environ_free (p->v.p.env); if (p->v.p.comp->flags & CF_EXPANDENV) argv_free (p->v.p.argv); 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) */ prog_stop_redirectors (p); break; case TYPE_COMMAND: free (p->v.c.tag); free (p->v.c.command); } free (p); *pp = NULL; } /* 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; } 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; } 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; } struct read_buffer { struct prog *master; int stream; char text[PIES_LOG_BUF_SIZE]; size_t len; int overflow; }; static int redirect_read (int fd, void *data) { struct read_buffer *rb = data; int n; int prio = rb->master->v.p.comp->redir[rb->stream].v.prio; char const *tag = prog_tag (rb->master); pid_t pid = rb->master->pid; char *p; static char *retr_stream_name[] = { [RETR_OUT] = "stdout", [RETR_ERR] = "stderr" }; n = read (fd, rb->text + rb->len, sizeof (rb->text) - rb->len); if (n == -1) { if (errno != EINTR) { logmsg (LOG_INFO, _("%s: %s while reading %s"), tag, strerror (errno), retr_stream_name[rb->stream]); rb->master->v.p.redir[rb->stream] = -1; close (fd); deregister_socket (fd); return -1; } } else if (n == 0) { debug (3, (_("%s: EOF on %s"), tag, retr_stream_name[rb->stream])); rb->master->v.p.redir[rb->stream] = -1; close (fd); deregister_socket (fd); } else { rb->len += n; while (rb->len) { p = memchr (rb->text, '\n', rb->len); if (p) { *p++ = 0; if (rb->overflow) rb->overflow = 0; else pies_syslog_message (prio, rb->text, tag, pid); n = rb->len - (p - rb->text); if (n > 0) memmove (rb->text, p, n); rb->len = n; } else if (rb->len == sizeof (rb->text)) { rb->text[sizeof (rb->text) - 1] = 0; pies_syslog_message (prio, rb->text, tag, pid); rb->len = 0; rb->overflow = 1; } else break; } } return 0; } static void read_buffer_free (void *p) { free (p); } int redirect_to_syslog (struct prog *master, int stream, int *fd) { struct read_buffer *rb; int p[2]; if (pipe (p)) { logmsg (LOG_CRIT, "pipe: %s", strerror (errno)); return -1; } rb = grecs_zalloc (sizeof (*rb)); rb->master = master; rb->stream = stream; rb->len = 0; rb->overflow = 0; register_socket (p[0], redirect_read, NULL, NULL, rb, read_buffer_free); *fd = p[0]; return p[1]; } void free_redirector (struct redirector *rp) { if (rp->type == redir_file) free (rp->v.file); } int open_redirector (struct prog *master, int stream, int *fd) { switch (master->v.p.comp->redir[stream].type) { case redir_null: *fd = -1; break; case redir_file: *fd = -1; return redirect_to_file (master, stream); case redir_syslog: return redirect_to_syslog (master, stream, fd); } return -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 progman_ws_error (const char *fmt, ...) { va_list ap; va_start (ap, fmt); vlogmsg (LOG_ERR, fmt, ap); va_end (ap); } /* * Initialization of the environment and argc/argv members of * struct prog. For regular components this is done in the main * process, so that argv can be reported correctly via the ctl * interface. For tcpmux components, which are run from the * listener child, this is not so. That does little harm as * these components are not visible from the ctl interface anyway. */ static int prog_init (struct prog *prog) { if ((prog->v.p.env = environ_create (environ)) == NULL) { logmsg (LOG_CRIT, "environ_create: %s", strerror (errno)); return -1; } 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); } if (envop_exec (prog->v.p.comp->envop, prog->v.p.env)) { logmsg (LOG_CRIT, "environ_exec: %s", strerror (errno)); return -1; } if (SYSVINIT_ACTIVE) { size_t i; for (i = 0; sysvinit_environ_hint[i]; i++) { if (environ_add (prog->v.p.env, sysvinit_environ_hint[i])) { logmsg (LOG_CRIT, "environ_add: %s", strerror (errno)); return -1; } } } debug_environ (prog, 4); if (prog->v.p.comp->flags & CF_EXPANDENV) { struct wordsplit ws; ws.ws_env = (const char **) environ_ptr (prog->v.p.env); ws.ws_error = progman_ws_error; if (wordsplit (prog->v.p.comp->command, &ws, WRDSF_QUOTE | WRDSF_SQUEEZE_DELIMS | WRDSF_NOCMD | WRDSF_ENV | WRDSF_ERROR)) { logmsg (LOG_ERR, _("%s: can't split command line: %s"), prog_tag (prog), wordsplit_strerror (&ws)); return -1; } wordsplit_get_words (&ws, &prog->v.p.argc, &prog->v.p.argv); wordsplit_free (&ws); } else { prog->v.p.argc = prog->v.p.comp->argc; prog->v.p.argv = prog->v.p.comp->argv; } if (debug_level >= 1 && prog->v.p.argv) { int i; logmsg_printf (LOG_DEBUG, "executing"); for (i = 0; i < prog->v.p.argc; i++) logmsg_printf (LOG_DEBUG, " %s", quotearg (prog->v.p.argv[i])); logmsg_printf (LOG_DEBUG, "\n"); } 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)); } 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); } 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.argv[0], prog->v.p.argv); //FIXME: pies_syslog? syslog (LOG_CRIT, _("cannot start `%s': %s"), prog_tag (prog), strerror (errno)); _exit (EX_SOFTWARE); } /* * Run a TCPMUX component. This function is invoked from a child * process. */ 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); if (prog_init (prog)) _exit (127); prog_start_prologue (prog); prog_sockenv (prog); prog_execute (prog); } static void prog_start (struct prog *prog) { pid_t pid; int redir[2]; if (prog->pid > 0 || !IS_COMPONENT (prog)) return; if (is_sysvinit (prog->v.p.comp)) { if (!SYSVINIT_ACTIVE) { 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; } if (prog_init (prog)) return; //FIXME redir[RETR_OUT] = open_redirector (prog, RETR_OUT, &prog->v.p.redir[RETR_OUT]); redir[RETR_ERR] = open_redirector (prog, RETR_ERR, &prog->v.p.redir[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 (SYSVINIT_ACTIVE) { 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 (!SYSVINIT_ACTIVE) { 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 */ pies_close_fds ((prog->v.p.comp->mode == pies_comp_pass_fd ? prog->v.p.socket : 2) + 1); 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] != -1) 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, NULL)) { 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) 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_dependents (struct prog *prog) { struct component *comp; pies_depmap_pos_t pos; int warned = 0; 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) { if (!warned && dp->pid) { debug (1, ("stopping dependencies of %s", prog_tag (prog))); warned = 1; } prog_stop (dp, prog_sigterm (dp)); } } 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, ("sending signal %d to %s (%lu)", sig, 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 (SYSVINIT_ACTIVE) { 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) == expect_term) 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 result; if (act->cond_list == NULL) result = 1; else { struct grecs_list_entry *ep; for (ep = act->cond_list->head; ep; ep = ep->next) { struct status_cond *cp = ep->data; result = cp->status == status; if (cp->neg) { if ((status & STATUS_SIG_BIT) == (cp->status & STATUS_SIG_BIT)) { result = !result; if (!result) break; } } else if (result) break; } } return result; } 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); pies_close_fds (3); 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 flags) { pid_t pid; int status; int expect_term = (flags & CLEANUP_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 ? SIGTERM : 0); continue; } prog->pid = 0; switch (prog->type) { case TYPE_COMPONENT: print_status (prog_tag (prog), pid, status, expect_term ? prog_sigterm (prog) : 0); 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 (PIES_SYSVINIT_ENABLED && 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_redirectors (prog); prog_stop_dependents (prog); if (!expect_term && component_is_active (prog->v.p.comp)) react (prog, status, pid); } if (!component_is_active (prog->v.p.comp)) destroy_prog (&prog); } break; case TYPE_COMMAND: print_status (prog_tag (prog), pid, status, expect_term ? prog_sigterm (prog) : 0); 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, prog_sigterm (prog)); 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, NULL)) { 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 \ (SYSVINIT_ACTIVE ? (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) { struct timespec ts; struct prog *prog, *next; /* Find first marked prog */ prog = prog_to_stop (proghead); if (!prog) return; /* Gracefully stop it and all marked programs that follow */ diagmsg (DIAG_CON, LOG_INFO, "Sending processes the TERM signal"); do { next = prog->next; progman_stop_component (&prog); } while ((prog = prog_to_stop (next))); /* Wait for them to terminate */ clock_gettime (CLOCK_MONOTONIC, &ts); ts.tv_sec += shutdown_timeout; do { progman_cleanup (1); if (!prog_to_stop (proghead)) return; } while (clock_nanosleep (CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) != 0); /* Kill the remaining programs with SIGKILL */ diagmsg (DIAG_CON, LOG_INFO, "Sending processes the KILL signal"); while ((prog = prog_to_stop (next)) != NULL) prog_stop (prog, SIGKILL); } static int start_shutdown (struct prog *prog, void *data) { if (IS_COMPONENT (prog) && prog->v.p.comp->mode == pies_comp_shutdown) { unsigned *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; } static void stop_progs_at_seqno (unsigned long seqno) { struct prog *prog; size_t count = 0; struct timespec ts; int warned = 0; /* Signal all programs that have this shutdown sequence number */ for (prog = proghead; prog; prog = prog->next) { if (IS_COMPONENT (prog) && prog->v.p.status == status_running && prog->v.p.comp->shutdown_seqno == seqno) { if (!warned) { debug (1, ("terminating processes at level %lu", seqno)); warned = 1; } prog->stop = 1; prog_stop (prog, prog_sigterm (prog)); count++; } } if (!count) return; debug (1, ("%zu processes signalled at level %lu", count, seqno)); clock_gettime (CLOCK_MONOTONIC, &ts); ts.tv_sec += shutdown_timeout; /* Wait for them to terminate */ while (prog_to_stop (proghead)) { if (clock_nanosleep (CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) == 0) { debug (1, ("killing %zu processes at level %lu", count, seqno)); for (prog = proghead; prog; prog = prog->next) { if (IS_COMPONENT (prog) && prog->v.p.comp->shutdown_seqno == seqno) { prog_stop (prog, SIGKILL); } } break; } progman_cleanup (CLEANUP_EXPECT_TERM); } } void progman_stop (void) { unsigned long *ordv; size_t ordc; size_t i; int sd; /* * Stop all running components in correct order (dependents first). */ ordc = components_shutdown_sequence_numbers (&ordv); for (i = 0; i < ordc; i++) { stop_progs_at_seqno (ordv[i]); } /* Activate shutdown components. */ progman_foreach (start_shutdown, &sd); /* Wait for them to finish. */ if (sd) { struct timespec ts; clock_gettime (CLOCK_MONOTONIC, &ts); ts.tv_sec += shutdown_timeout; /* Wait for them to terminate */ while (prog_to_stop (proghead)) { if (clock_nanosleep (CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) == 0) { struct prog *prog; debug (1, ("killing shutdown processes")); for (prog = proghead; prog; prog = prog->next) { if (IS_COMPONENT (prog) && (prog->v.p.status == status_running || prog->v.p.status == status_stopping)) { prog_stop (prog, SIGKILL); } } break; } progman_cleanup (CLEANUP_EXPECT_TERM); } } } /* EOF */