-rw-r--r-- | src/comp.c | 87 | ||||
-rw-r--r-- | src/pies.c | 115 | ||||
-rw-r--r-- | src/pies.h | 38 | ||||
-rw-r--r-- | src/progman.c | 291 | ||||
-rw-r--r-- | src/socket.c | 54 | ||||
-rw-r--r-- | tests/Makefile.am | 9 | ||||
-rw-r--r-- | tests/atlocal.in | 2 | ||||
-rwxr-xr-x | tests/aux/mailer (renamed from tests/mailer) | 0 | ||||
-rwxr-xr-x | tests/aux/respawn (renamed from tests/respawn) | 0 | ||||
-rwxr-xr-x | tests/aux/retcode (renamed from tests/retcode) | 0 | ||||
-rwxr-xr-x | tests/aux/startup | 7 | ||||
-rw-r--r-- | tests/redirect.at | 2 | ||||
-rw-r--r-- | tests/respawn.at | 2 | ||||
-rw-r--r-- | tests/ret-exec.at | 4 | ||||
-rw-r--r-- | tests/ret-notify.at | 6 | ||||
-rw-r--r-- | tests/startup.at | 84 | ||||
-rw-r--r-- | tests/testsuite.at | 1 |
17 files changed, 442 insertions, 260 deletions
@@ -21,27 +21,32 @@ struct complist { struct component *head; struct component *tail; }; +/* 0 on the first load, and 1 on all subsequent reloads. Tells the + component_config_commit whether we're starting from scratch or just + updating an already loaded configuration */ +static int loaded; + static struct complist comp_list[2]; static int cur; static struct component **comp_array; static size_t comp_count; static pies_depmap_t depmap; -static int +static inline int next_index (void) { return (cur + 1) % ARRAY_SIZE (comp_list); } -static int +static inline int prev_index (void) { return (cur + ARRAY_SIZE (comp_list) - 1) % ARRAY_SIZE (comp_list); } void @@ -82,12 +87,28 @@ void component_append (struct component *comp) { component_link (comp, comp_list[comp->listidx].tail); } void +comp_array_remove (size_t i) +{ + struct component *comp = comp_array[i]; + + depmap_remove (depmap, i); + while (i < comp_count -1) + { + comp_array[i] = comp_array[i+1]; + comp_array[i]->arridx = i; + i++; + } + component_free (comp); + comp_count--; +} + +void component_unlink (struct component *comp) { struct complist *list = &comp_list[comp->listidx]; struct component *x; if ((x = comp->prev)) @@ -198,13 +219,18 @@ component_ref_incr (struct component *comp) void component_ref_decr (struct component *comp) { assert (comp->ref_count > 0); if (--comp->ref_count == 0) - component_free (comp); + { + if (component_is_active (comp)) + comp_array_remove (comp->arridx); + else + component_free (comp); + } } static int argvcmp (char **a, char **b) { size_t i; @@ -422,23 +448,12 @@ report_cyclic_dependency (pies_depmap_t dp, size_t idx) } while (i != idx); logmsg_printf (LOG_NOTICE, "%s\n", comp_array[idx]->tag); } void -comp_array_remove (size_t i) -{ - struct component *comp = comp_array[i]; - if (i < comp_count - 1) - memmove (&comp_array[i], &comp_array[i+1], - (comp_count - i - 1) * sizeof comp_array[0]); - component_free (comp); - comp_count--; -} - -void component_build_depmap (void) { size_t i; pies_depmap_t dp; free (depmap); @@ -457,13 +472,12 @@ component_build_depmap (void) { logmsg (LOG_ERR, _("component %s depends on %s, " "which is not declared"), comp->tag, tag); comp_array_remove (i); - depmap_remove (depmap, i); continue; } depmap_set (depmap, i, tgt); } if (comp->depend) @@ -494,16 +508,13 @@ component_build_depmap (void) report_cyclic_dependency (dp, i); } for (i = 0; i < comp_count;) if (comp_array[i]->flags & CF_REMOVE) - { - comp_array_remove (i); - depmap_remove (depmap, i); - } + comp_array_remove (i); else i++; free (dp); } @@ -525,28 +536,42 @@ component_config_commit (void) comp_array = NULL; } else comp_array = grecs_realloc (comp_array, i * sizeof (comp_array[0])); comp_count = i; - /* Rearrange components, registering prog entries for the new ones */ - for (comp = list->head, i = 0; comp; comp = comp->next, i++) + /* Rearrange components, registering entries for the new ones */ + for (comp = list->head, i = 0; comp; ) { - match = complist_find_match (prev, comp); - if (match) + struct component *next = comp->next; + if (loaded && comp->mode == pies_comp_startup) { - component_merge (match, comp); - component_unlink (match); - match->listidx = cur; - component_link (match, comp->prev); + /* Ignore startup components */ + component_unlink (comp); component_free (comp); - comp = match; } - comp_array[i] = comp; - comp->arridx = i; + else + { + match = complist_find_match (prev, comp); + if (match) + { + component_merge (match, comp); + component_unlink (match); + match->listidx = cur; + component_link (match, comp->prev); + component_free (comp); + comp = match; + } + comp_array[i] = comp; + comp->arridx = i; + i++; + } + comp = next; } + /* Adjust comp_count */ + comp_count = i; /* Mark orphaned progs for termination */ list = &comp_list[prev]; if (list->head) { progman_foreach (mark_prog, NULL); @@ -557,12 +582,14 @@ component_config_commit (void) component_build_depmap (); /* Register new progs */ for (comp = comp_list[cur].head; comp; comp = comp->next) if (!comp->prog) register_prog (comp); + + loaded = 1; } static int component_verify (struct component *comp, grecs_locus_t *locus) { int header = 0; @@ -129,13 +129,13 @@ config_file_add_type (enum config_syntax_type syntax, const char *name) } int config_file_remove (const char *name) { struct grecs_list_entry *ep; - + for (ep = config_list->head; ep; ep = ep->next) { struct config_file *file = ep->data; if (strcmp (file->name, name) == 0) { grecs_list_remove_entry (config_list, ep); @@ -153,13 +153,13 @@ config_file_remove_all (void) } void config_file_list_serialize (struct json_value *ar) { struct grecs_list_entry *ep; - + for (ep = config_list->head; ep; ep = ep->next) { struct config_file *file = ep->data; struct json_value *obj = json_new_object (); json_object_set (obj, "syntax", json_new_string (file->syntax->name)); json_object_set (obj, "file", json_new_string (file->name)); @@ -324,13 +324,13 @@ action_free (struct action *act) return; if (act->nstat > 0) free (act->status); free (act->addr); free (act->message); free (act->command); - + free (act); } static void free_entry_action (void *act) { @@ -356,13 +356,13 @@ create_action (struct component *comp, { for (i = 0; i < argc; i++) { unsigned n; const char *arg = getarg (val, i, locus); size_t len = strlen (arg); - + if (isdigit (arg[0])) { char *p; n = strtoul (arg, &p, 0); if (*p) { @@ -391,18 +391,18 @@ create_action (struct component *comp, } else if (strtotok_ci (ex_tokendef, arg, (int *) &n)) { grecs_error (locus, 0, _("%s: not a return code"), arg); continue; } - + /* Alles in ordnung */ retv[retc++] = n; } } - + if (retc == 0 && !allflag) { free (retv); return NULL; } @@ -469,33 +469,33 @@ return_code_section_parser (enum grecs_callback_command cmd, case grecs_callback_section_begin: if (GRECS_VALUE_EMPTY_P (value)) { grecs_error (locus, 0, _("missing tag")); return 1; } - + switch (value->type) { case GRECS_TYPE_STRING: act = create_action (comp, locus, value, 1, _get_string_arg); break; - + case GRECS_TYPE_ARRAY: act = create_action (comp, locus, value, value->v.arg.c, _get_array_arg); break; - + case GRECS_TYPE_LIST: count = grecs_list_size (value->v.list); act = create_action (comp, locus, value, count, _get_list_arg); } if (!act) return 1; *(struct action **) cb_data = act; break; - + case grecs_callback_section_end: break; case grecs_callback_set_value: grecs_error (locus, 0, _("invalid use of block statement")); } @@ -539,13 +539,13 @@ _cb_command (enum grecs_callback_command cmd, grecs_error (locus, 0, "wordsplit: %s", strerror (errno)); return 1; } wordsplit_get_words (&ws, &comp->argc, &comp->argv); wordsplit_free (&ws); break; - + case GRECS_TYPE_ARRAY: comp->argv = config_array_to_argv (value, locus, &comp->argc); break; case GRECS_TYPE_LIST: grecs_error (locus, 0, _("unexpected list")); @@ -699,13 +699,13 @@ _cb_redir (enum grecs_callback_command cmd, {"null", redir_null}, {"syslog", redir_syslog}, {"file", redir_file}, {NULL} }; int res; - + switch (value->type) { case GRECS_TYPE_STRING: if (strcmp (value->v.string, "null") == 0) { rp->type = redir_null; @@ -716,13 +716,13 @@ _cb_redir (enum grecs_callback_command cmd, { grecs_error (locus, 0, _("unknown syslog priority %s"), value->v.string); return 0; } break; - + case GRECS_TYPE_ARRAY: if (assert_grecs_value_type (locus, value->v.arg.v[0], GRECS_TYPE_STRING)) return 0; if (strtotok (redirtab, value->v.arg.v[0]->v.string, &res)) grecs_error (locus, 0, _("%s: unrecognised redirector type"), @@ -736,48 +736,48 @@ _cb_redir (enum grecs_callback_command cmd, grecs_error (locus, 0, _("wrong number of arguments")); return 0; } if (assert_grecs_value_type (locus, value->v.arg.v[1], GRECS_TYPE_STRING)) return 0; - + switch (res) { case redir_null: break; - + case redir_syslog: if (string_to_syslog_priority (value->v.arg.v[1]->v.string, &rp->v.prio)) { grecs_error (locus, 0, _("unknown syslog priority %s"), value->v.arg.v[1]->v.string); return 0; } break; - + case redir_file: rp->v.file = grecs_strdup (value->v.arg.v[1]->v.string); break; } } rp->type = res; } break; - + default: grecs_error (locus, 0, _("unexpected list")); } - + return 0; } static struct tokendef socktype_xtab[] = { - { "stream", SOCK_STREAM }, - { "dgram", SOCK_DGRAM }, + { "stream", SOCK_STREAM }, + { "dgram", SOCK_DGRAM }, { "seqpacket", SOCK_SEQPACKET }, { "raw", SOCK_RAW }, { "rdm", SOCK_RDM }, #ifdef SOCK_PACKET { "packet", SOCK_PACKET }, #endif @@ -817,18 +817,20 @@ static struct tokendef modetab[] = { {"once", pies_comp_once}, {"accept", pies_comp_accept}, {"inetd", pies_comp_inetd}, {"nostartaccept", pies_comp_inetd}, {"pass-fd", pies_comp_pass_fd}, {"pass", pies_comp_pass_fd}, + {"startup", pies_comp_startup}, + {"shutdown", pies_comp_shutdown}, {"boot", pies_comp_boot}, {"bootwait", pies_comp_boot}, {"powerfail", pies_comp_powerfail}, {"powerwait", pies_comp_powerwait}, {"powerokwait", pies_comp_powerokwait}, - {"ctrlaltdel", pies_comp_ctrlaltdel}, + {"ctrlaltdel", pies_comp_ctrlaltdel}, {"ondemand", pies_comp_ondemand}, {"sysinit", pies_comp_sysinit}, {"powerfailnow", pies_comp_powerfailnow}, {"kbrequest", pies_comp_kbrequest}, {NULL} @@ -917,17 +919,17 @@ _cb_flags (enum grecs_callback_command cmd, if (str_to_cf (value->v.string, flags)) { grecs_error (locus, 0, _("%s: unrecognised flag"), value->v.string); return 1; } break; - + case GRECS_TYPE_LIST: { struct grecs_list_entry *ep; - + for (ep = value->v.list->head; ep; ep = ep->next) { const grecs_value_t *vp = ep->data; if (assert_grecs_value_type (locus, vp, GRECS_TYPE_STRING)) return 1; if (str_to_cf (vp->v.string, flags)) @@ -936,13 +938,13 @@ _cb_flags (enum grecs_callback_command cmd, vp->v.string); return 1; } } } break; - + case GRECS_TYPE_ARRAY: grecs_error (locus, 0, _("too many arguments")); return 1; } return 0; } @@ -1225,13 +1227,13 @@ struct grecs_keyword component_keywords[] = { }; struct grecs_keyword * find_component_keyword (const char *ident) { struct grecs_keyword *kwp; - + for (kwp = component_keywords; kwp->ident; kwp++) if (strcmp (kwp->ident, ident) == 0) return kwp; return NULL; } @@ -1253,13 +1255,13 @@ component_section_parser (enum grecs_callback_command cmd, break; case grecs_callback_section_end: comp = *(struct component **) section_data; component_finish (comp, locus); break; - + case grecs_callback_set_value: grecs_error (locus, 0, _("expected block statement")); } return 0; } @@ -1531,13 +1533,13 @@ pies_config_parse (char const *name) { struct grecs_node *node; struct grecs_node *tree = grecs_parse (name); if (!tree) return 1; - + for (node = tree; node; node = node->next) { node = grecs_find_node (node, "identity-provider"); if (!node) break; pies_config_provider (node); @@ -1547,13 +1549,13 @@ pies_config_parse (char const *name) return 1; grecs_tree_free (tree); if (grecs_error_count) return 1; - + return 0; } void config_help (void) { @@ -1570,26 +1572,26 @@ int pies_read_config (void) { struct grecs_list_entry *ep; int err = 0; component_config_begin (); - + for (ep = config_list->head; ep; ep = ep->next) { struct config_file *file = ep->data; if (file->syntax->parser (file->name)) ++err; } if (init_process) err = 0; - + if (err) component_config_rollback (); - + return err; } int pies_reread_config (void) { @@ -1653,13 +1655,13 @@ sig_handler (int sig) void setsigvhan (RETSIGTYPE (*handler) (int signo), int *sigv, int sigc) { int i; struct sigaction act; - + act.sa_flags = 0; sigemptyset (&act.sa_mask); for (i = 0; i < sigc; i++) sigaddset (&act.sa_mask, sigv[i]); act.sa_handler = handler; @@ -1776,13 +1778,13 @@ pies_check_status (pid_t *ppid) int request_restart_components (size_t cc, char **cv) { char **argv; size_t i, j; - + argv = grecs_calloc (5 + 3 * cc - 1, sizeof (*argv)); argv[0] = "piesctl"; argv[1] = "--url"; argv[2] = (char*) pies_control_url (); argv[3] = "restart"; j = 4; @@ -1800,13 +1802,13 @@ request_restart_components (size_t cc, char **cv) } void list_components (void) { char *argv[5]; - + argv[0] = "piesctl"; argv[1] = "--url"; argv[2] = (char*) pies_control_url (); argv[3] = "list"; argv[4] = NULL; execvp (argv[0], argv); @@ -1939,13 +1941,13 @@ remove_pidfile (char *name) static void set_mailer_argcv (void) { int i; struct wordsplit ws; - + if (wordsplit (mailer_command_line, &ws, WRDSF_DEFFLAGS)) { logmsg (LOG_CRIT, _("cannot parse mailer command line: %s"), strerror (errno)); exit (EX_CONFIG); } @@ -2030,61 +2032,61 @@ main (int argc, char **argv) { pid_t pid; extern char **environ; struct grecs_list_entry *ep; int diag_flags; int i; - + set_program_name (argv[0]); #ifdef ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); #endif mf_proctitle_init (argc, argv, environ); grecs_print_diag_fun = pies_diag_printer; - + pies_master_argc = argc; pies_master_argv = argv; - + set_quoting_style (NULL, shell_quoting_style); init_process = getpid () == 1; for (i = 1; i < argc; i++) { if (strcmp (argv[i], "--no-init") == 0) { init_process = 0; break; } } - + /* Set default logging */ if (init_process) { log_facility = LOG_DAEMON; diag_flags = DIAG_TO_STDERR | DIAG_REOPEN_LOG; } else diag_flags = DIAG_TO_SYSLOG | (stderr_closed_p () ? 0 : DIAG_TO_STDERR); - + diag_setup (diag_flags); - + config_init (); parse_options (&argc, &argv); - + if (argc && !(command == COM_RESTART_COMPONENT || command == COM_TRACE_DEPEND || command == COM_TRACE_PREREQ)) { logmsg (LOG_ERR, "extra command line arguments"); exit (EX_USAGE); } - + if (!instance) { instance = strrchr (program_name, '/'); if (!instance) instance = (char*) program_name; else @@ -2116,39 +2118,39 @@ main (int argc, char **argv) exit (EX_CONFIG); component_config_commit (); set_state_file_names (instance); set_mailer_argcv (); - + if (lint_mode) exit (0); /* Re-setup logging: it might have been reset in the config file */ diag_setup (log_to_stderr_only ? DIAG_TO_STDERR : 0); - + if (!control.url) { char const *str = default_control_url[init_process]; if (pies_url_create (&control.url, str)) { logmsg (LOG_CRIT, _("%s: cannot create control URL: %s"), str, strerror (errno)); if (!init_process) exit (EX_OSERR); } } - + switch (command) { case COM_RESTART_COMPONENT: pies_priv_setup (&pies_privs); if (pies_umask) umask (pies_umask); exit (request_restart_components (argc, argv)); - + case COM_RELOAD: exit (request_reload ()); case COM_STATUS: exit (request_status ()); @@ -2159,17 +2161,17 @@ main (int argc, char **argv) components_dump_depmap (); exit (0); case COM_TRACE_DEPEND: components_trace (argv, depmap_row); exit (0); - - case COM_TRACE_PREREQ: + + case COM_TRACE_PREREQ: components_trace (argv, depmap_col); exit (0); - + default: pies_priv_setup (&pies_privs); if (pies_umask) umask (pies_umask); } @@ -2190,47 +2192,48 @@ main (int argc, char **argv) logmsg (LOG_ERR, _("another pies instance may be running (pid %lu), " "use --force to override"), (unsigned long) pid); exit (EX_USAGE); } break; - + case pies_status_running: logmsg (LOG_ERR, _("another pies instance already running (pid %lu)"), (unsigned long) pid); exit (EX_USAGE); } - + if (!foreground) { check_pidfile (pidfile); if (daemon (0, 0) == -1) { logfuncall ("daemon", NULL, errno); exit (EX_SOFTWARE); } diag_setup (DIAG_TO_SYSLOG); } logmsg (LOG_INFO, _("%s %s starting"), proginfo.package, proginfo.version); - + if (!init_process) { if (ctl_open ()) exit (EX_UNAVAILABLE); create_pidfile (pidfile); } - + if (pies_master_argv[0][0] != '/') logmsg (LOG_NOTICE, _("not started as an absolute pathname; " "restart will not work")); signal_setup (sig_handler); progman_create_sockets (); + program_init_startup (); progman_start (); do { if (children_op == PIES_CHLD_NONE) pies_pause (); @@ -2258,13 +2261,13 @@ main (int argc, char **argv) progman_create_sockets (); progman_start (); pies_schedule_children (PIES_CHLD_WAKEUP); action = ACTION_CONT; break; - + case ACTION_STOP: if (init_process) { debug (1, ("ignoring stop/restart")); action = ACTION_CONT; } @@ -2273,13 +2276,13 @@ main (int argc, char **argv) case ACTION_CTRLALTDEL: debug (1, ("ctrl-alt-del")); sysvinit_runlevel_setup (PIES_COMP_MASK (pies_comp_ctrlaltdel)); pies_schedule_children (PIES_CHLD_WAKEUP); action = ACTION_CONT; break; - + case ACTION_KBREQUEST: debug (1, ("kbrequest")); sysvinit_runlevel_setup (PIES_COMP_MASK (pies_comp_kbrequest)); pies_schedule_children (PIES_CHLD_WAKEUP); action = ACTION_CONT; break; @@ -101,13 +101,13 @@ struct action { size_t nstat; unsigned *status; enum return_action act; /* Action to take when the component terminates */ char *addr; /* Addresses to notify about it. */ char *message; /* Notification mail. */ - char *command; /* Execute this command */ + char *command; /* Execute this command */ }; /* user privs */ struct pies_privs { @@ -134,20 +134,27 @@ enum pies_comp_mode */ pies_comp_inetd, /* Open a socket, start a component, and pass the socket fd to the component via the UNIX domain socket. Corresponds to `start_action = pass' in MeTA1. */ pies_comp_pass_fd, - + + /* Components of this type runs once on program startup. Running other + components is delayed until the last startup component finishes. */ + pies_comp_startup, + + /* FIXME: Runs before program termination */ + pies_comp_shutdown, + /* ** Init-style components */ pies_mark_sysvinit, /* Start the process when the specified runlevel is entered and wait for its termination */ - pies_comp_wait = pies_mark_sysvinit, + pies_comp_wait = pies_mark_sysvinit, /* Execute the component once, when the specified runlevel is entered */ pies_comp_once, /* Execute the component during system boot. Ignore runlevel settings. */ pies_comp_boot, /* Execute the component during system boot and wait for it to terminate. Ignore runlevel settings. */ @@ -159,13 +166,13 @@ enum pies_comp_mode pies_comp_powerwait, /* Execute the component when the power is restored. Wait for it to terminate. */ pies_comp_powerokwait, /* Execute the process when SIGINT is delivered, i.e. someone has pressed the Ctrl+Alt+Del combination. */ - pies_comp_ctrlaltdel, + pies_comp_ctrlaltdel, /* Execute the component when a specified ondemand runlevel is called */ pies_comp_ondemand, /* Execute the component on the system boot. */ pies_comp_sysinit, /* Execute the component when running on the UPS and pies is informed that the UPS battery is almost empty. */ @@ -181,22 +188,22 @@ enum pies_comp_mode #define PIES_COMP_DEFAULT 0 #define PIES_COMP_MASK(m) (1 << ((m))) #define CF_DISABLED 0x001 /* The componenet is disabled */ #define CF_PRECIOUS 0x002 /* The component is precious (should not - be disabled) */ + be disabled) */ #define CF_WAIT 0x004 /* Wait for the component instance to - terminate. */ + terminate. */ #define CF_TCPMUX 0x008 /* A plain TCPMUX service */ #define CF_TCPMUXPLUS 0x010 /* A TCPMUX-plus service, i.e. pies - must emit a '+' response before starting - it */ + must emit a '+' response before starting + it */ #define CF_INTERNAL 0x020 /* An internal inetd service */ #define CF_SOCKENV 0x040 /* Component wants socket information in - the environment */ + the environment */ #define CF_RESOLVE 0x080 /* Resolve IP addresses */ #define CF_SIGGROUP 0x100 /* Send signals to the process group */ #define CF_NULLINPUT 0x200 /* Provide null input stream */ #define CF_REMOVE 0x400 /* Marked for removal */ @@ -207,15 +214,15 @@ struct prog; struct component { struct component *prev, *next; /* Components form doubly-linked list. */ int listidx; /* Index of the list. */ size_t arridx; /* Index of this component. */ - size_t ref_count; /* Reference count. */ + size_t ref_count; /* Reference count. */ struct prog *prog; /* Prog associated with this component. */ - + enum pies_comp_mode mode; char *tag; /* Entry tag (for diagnostics purposes) */ char *program; /* Program name */ size_t argc; /* Number of command line arguments */ char **argv; /* Program command line */ char **env; /* Program environment */ @@ -229,13 +236,13 @@ struct component struct pies_privs privs; /* UID/GIDS+groups to run as */ mode_t umask; /* Umask to install before starting */ limits_record_t limits; /* System limits */ /* For exec (init) components */ char *runlevels; - + /* For inetd components */ size_t max_rate; /* Maximum number of invocations per minute */ size_t max_ip_connections; /* Max. number of connections per IP address */ int socket_type; /* Socket type */ struct inetd_builtin *builtin; /* Builtin function */ char *service; @@ -250,15 +257,15 @@ struct component char *tcpmux; /* Master service for TCPMUX */ /* Optional error messages to be sent back on the socket: */ char *access_denied_message; char *max_instances_message; char *max_ip_connections_message; - + /* Redirectors: */ - int facility; /* Syslog facility. */ + int facility; /* Syslog facility. */ struct redirector redir[2]; /* Repeaters for stdout and stderr */ /* Actions to execute on various exit codes: */ struct grecs_list *act_list; /* ACLs for control interface */ pies_acl_t list_acl; /* List access control list */ pies_acl_t adm_acl; /* Administrative ACL (stop, start, etc.) */ @@ -331,12 +338,13 @@ void free_action (struct action *act); void pies_schedule_children (int op); int pies_read_config (void); int pies_reread_config (void); void register_prog (struct component *comp); +void program_init_startup (void); int progman_waiting_p (void); void progman_start (void); void progman_gc (void); void progman_wake_sleeping (int); void progman_stop (void); void progman_cleanup (int expect_term); @@ -591,13 +599,13 @@ struct sysvinit_request /* utmp.c */ #define SYSV_ACCT_BOOT 0 #define SYSV_ACCT_RUNLEVEL 1 #define SYSV_ACCT_PROC_START 2 -#define SYSV_ACCT_PROC_STOP 3 +#define SYSV_ACCT_PROC_STOP 3 void sysvinit_acct (int what, const char *user, const char *id, pid_t pid, const char *line); /* ctl.c */ diff --git a/src/progman.c b/src/progman.c index 1b09cd5..5bc4eb3 100644 --- a/src/progman.c +++ b/src/progman.c @@ -62,13 +62,13 @@ 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) @@ -119,13 +119,13 @@ link_prog (struct prog *prog, struct prog *ref) else { struct prog *x; prog->prev = ref; prog->next = ref->next; - + if ((x = ref->next)) x->prev = prog; else progtail = prog; ref->next = prog; @@ -148,27 +148,27 @@ unlink_prog (struct prog *pp) } 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) */ + 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) { @@ -179,13 +179,13 @@ destroy_prog (struct prog **pp) } /* 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; @@ -253,13 +253,13 @@ find_prog_ref (struct component *comp) 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; @@ -267,18 +267,18 @@ find_prog_ref (struct component *comp) ; } 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; @@ -288,13 +288,13 @@ register_prog0 (struct component *comp) newp->v.p.status = status_stopped; if ((comp->flags & CF_DISABLED) || comp->mode == pies_comp_ondemand) newp->active = 0; else newp->active = 1; - + if (comp->mode != pies_comp_exec) comp->redir[RETR_OUT].type = redir_null; link_prog (newp, find_prog_ref (comp)); component_ref_incr (comp); return newp; @@ -314,20 +314,35 @@ register_command (char *tag, char *command, pid_t pid) 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) + return 1; + } + return 0; +} + int progman_waiting_p (void) { struct prog *prog; - + for (prog = proghead; prog; prog = prog->next) { - if (IS_COMPONENT (prog) && prog->wait && prog->pid > 0) + if (IS_COMPONENT (prog) + && prog->pid > 0 + && (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; } @@ -403,48 +418,48 @@ open_redirector (struct prog *master, int stream) { 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; @@ -463,13 +478,13 @@ 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; @@ -527,13 +542,13 @@ conn_class_lookup (const char *tag, 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, @@ -722,13 +737,13 @@ var_is_unset (char **env, const char *name) static char * env_concat (const char *name, size_t namelen, const char *a, const char *b) { char *res; size_t len; - + if (a && b) { res = grecs_malloc (namelen + 1 + strlen (a) + strlen (b) + 1); strcpy (res + namelen + 1, a); strcat (res + namelen + 1, b); } @@ -750,71 +765,71 @@ env_concat (const char *name, size_t namelen, const char *a, const char *b) strcpy (res + namelen + 1, b); } memcpy (res, name, namelen); res[namelen] = '='; return res; } - + static void environ_setup (char **hint) { char **old_env = environ; char **new_env; size_t count, i, j, n; - + if (!hint) return; if (strcmp (hint[0], "-") == 0) { old_env = NULL; hint++; } - + /* Count new environment size */ count = 0; if (old_env) for (i = 0; old_env[i]; i++) count++; - + for (i = 0; hint[i]; i++) count++; /* Allocate new environment. */ new_env = grecs_calloc (count + 1, sizeof new_env[0]); - + /* Populate the environment. */ n = 0; - + if (old_env) for (i = 0; old_env[i]; i++) { if (!var_is_unset (hint, old_env[i])) new_env[n++] = old_env[i]; } for (i = 0; hint[i]; i++) { char *p; - + if (hint[i][0] == '-') { /* Skip unset directives. */ continue; } /* Find the slot for the variable. Use next available slot if there's no such variable in new_env */ if (find_env_pos (new_env, hint[i], &j, NULL)) j = n; - + if ((p = strchr (hint[i], '='))) { if (p == hint[i]) continue; /* Ignore erroneous entry */ - if (p[-1] == '+') + if (p[-1] == '+') new_env[j] = env_concat (hint[i], p - hint[i] - 1, find_env_ptr (environ, hint[i], 1), p + 1); else if (p[1] == '+') new_env[j] = env_concat (hint[i], p - hint[i], p + 2, @@ -832,13 +847,13 @@ environ_setup (char **hint) ++n; } new_env[n] = NULL; if (envsize) free (environ); - + environ = new_env; envsize = count + 1; } /* Pass socket information in environment variables. */ void @@ -849,29 +864,29 @@ prog_sockenv (struct prog *prog) struct hostent *host; union pies_sockaddr_storage sa_server; socklen_t len = sizeof (sa_server); union pies_sockaddr_storage *sa_client = &prog->v.p.sa_storage; socklen_t cltlen = prog->v.p.sa_len; const char *proto = NULL; - + if (!(prog->v.p.comp->flags & CF_SOCKENV)) return; if (socket_type_to_str (prog->v.p.comp->socket_type, &proto)) proto = umaxtostr (prog->v.p.comp->socket_type, buf); add_env (ENV_SOCKTYPE, proto); - + if (prog->v.p.comp->socket_url) proto = prog->v.p.comp->socket_url->proto_s; else if (prog->v.p.listener) proto = prog->v.p.listener->v.p.comp->socket_url->proto_s; else if (ISCF_TCPMUX (prog->v.p.comp->flags)) proto = "TCP"; add_env (ENV_PROTO, proto); - + if (getsockname (prog->v.p.socket, (struct sockaddr *) &sa_server, &len) < 0) logmsg (LOG_WARNING, "getsockname(): %s", strerror (errno)); else if (sa_server.s.sa_family == AF_INET) { p = inet_ntoa (sa_server.s_in.sin_addr); @@ -917,31 +932,31 @@ prog_sockenv (struct prog *prog) } 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) { @@ -958,13 +973,13 @@ check_spawn_rate (struct prog *prog) } 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; + ? 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), @@ -1006,33 +1021,33 @@ prog_start_prologue (struct prog *prog) { debug (1, (_("chdir %s"), prog->v.p.comp->dir)); if (chdir (prog->v.p.comp->dir)) logmsg (LOG_ERR, _("%s: cannot change to directory %s: %s"), prog_tag (prog), prog->v.p.comp->dir, strerror (errno)); } - + environ_setup (prog->v.p.comp->env ? prog->v.p.comp->env : ((prog->v.p.comp->flags & CF_SOCKENV) ? sockenv_hint : NULL)); if (init_process) environ_setup (sysvinit_environ_hint); DEBUG_ENVIRON (4); pies_priv_setup (&prog->v.p.comp->privs); if (prog->v.p.comp->umask) umask (prog->v.p.comp->umask); - + set_limits (prog_tag (prog), prog->v.p.comp->limits ? prog->v.p.comp->limits : pies_limits); - + if (debug_level >= 1) { int i; struct component *comp = prog->v.p.comp; - + logmsg_printf (LOG_DEBUG, "executing"); for (i = 0; i < comp->argc; i++) logmsg_printf (LOG_DEBUG, " %s", quotearg (comp->argv[i])); logmsg_printf (LOG_DEBUG, "\n"); } } @@ -1044,13 +1059,13 @@ prog_execute (struct prog *prog) { 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)); @@ -1074,13 +1089,13 @@ progman_run_comp (struct component *comp, int fd, 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) @@ -1120,13 +1135,13 @@ prog_start (struct prog *prog) 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; @@ -1136,15 +1151,15 @@ prog_start (struct prog *prog) 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)); @@ -1155,37 +1170,37 @@ prog_start (struct prog *prog) 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: + { + 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); + dup2 (prog->v.p.socket, 0); + dup2 (prog->v.p.socket, 1); + close (prog->v.p.socket); prog->v.p.socket = -1; - break; + break; default: if (init_process) { int fd = console_open (O_RDWR|O_NOCTTY); - if (fd < 0) + if (fd < 0) { logmsg (LOG_CRIT, "open(%s): %s", console_device, strerror (errno)); fd = open ("/dev/null", O_RDWR); } if (fd != 0) @@ -1215,14 +1230,14 @@ prog_start (struct prog *prog) } else if (redir[RETR_OUT] != 1) { dup2 (redir[RETR_OUT], 1); } } - break; - } + break; + } if (!init_process) { if (redir[RETR_ERR] == -1) { if (!DIAG_OUTPUT (DIAG_TO_STDERR)) @@ -1233,25 +1248,25 @@ prog_start (struct prog *prog) } 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; @@ -1288,20 +1303,20 @@ prog_start (struct prog *prog) 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); @@ -1312,13 +1327,13 @@ check_acl (pies_acl_t acl, struct sockaddr *s, socklen_t salen, } 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); @@ -1331,30 +1346,30 @@ fd_report (int fd, const char *msg) } 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); } @@ -1391,13 +1406,13 @@ _prog_accept (struct prog *p) 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; } @@ -1444,22 +1459,22 @@ _prog_wait (struct prog *p) 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) { @@ -1523,20 +1538,40 @@ progman_recompute_alarm (void) 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 */ + /* 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) { @@ -1562,14 +1597,14 @@ progman_start (void) static void check_stopping (struct prog *prog, time_t now) { if (now - prog->v.p.timestamp >= shutdown_timeout) { if (prog->pid == 0) - logmsg (LOG_EMERG, - _("INTERNAL ERROR: attempting to kill unexisting process %s"), + logmsg (LOG_EMERG, + _("INTERNAL ERROR: attempting to kill unexisting process %s"), prog_tag (prog)); else if (prog->v.p.comp->flags & CF_SIGGROUP) kill (-prog->pid, SIGKILL); else kill (prog->pid, SIGKILL); } @@ -1622,43 +1657,43 @@ progman_wake_sleeping (int onalrm) /* 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; @@ -1666,22 +1701,22 @@ prog_start_prerequisites (struct prog *prog) 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; @@ -1697,13 +1732,13 @@ prog_start_prerequisites (struct prog *prog) 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) { @@ -1754,13 +1789,13 @@ prog_stop (struct prog *prog, int sig) } 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 int mark_for_stopping (struct prog *prog, void *data) { if (IS_COMPONENT (prog)) prog->stop = 1; @@ -1784,13 +1819,13 @@ print_status (const char *tag, pid_t pid, int status, int expect_term) 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 @@ -1798,13 +1833,13 @@ print_status (const char *tag, pid_t pid, int status, int expect_term) 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)"); @@ -1820,13 +1855,13 @@ print_status (const char *tag, pid_t pid, int status, int expect_term) 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) @@ -1852,13 +1887,13 @@ 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; @@ -1880,26 +1915,26 @@ 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. @@ -1908,13 +1943,13 @@ send_msg (char *rcpts, const char *msg_text) 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])) @@ -1931,16 +1966,16 @@ send_msg (char *rcpts, const char *msg_text) 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; @@ -1952,25 +1987,25 @@ send_msg (char *rcpts, const char *msg_text) 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; @@ -2027,22 +2062,22 @@ 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; + 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"); @@ -2050,34 +2085,34 @@ notify_get_termination (char **ret, void *data) msg = "UNKNOWN"; s = strdup (msg); if (!s) return WRDSE_NOSPACE; *ret = s; - return WRDSE_OK; + 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; -} - + return WRDSE_OK; +} + struct notify_variable { char *name; size_t name_length; int (*get) (char **ret, void *data); }; @@ -2087,23 +2122,23 @@ static struct notify_variable notify_vartab[] = { { 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, @@ -2117,13 +2152,13 @@ notify (const char *tag, int status, struct action *act) 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; @@ -2141,13 +2176,13 @@ notify (const char *tag, int status, struct action *act) } 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; @@ -2181,13 +2216,13 @@ run_command (struct action *act, struct prog *prog, unsigned retcode, 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); @@ -2205,13 +2240,13 @@ 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)) @@ -2230,27 +2265,27 @@ react (struct prog *prog, int status, pid_t pid) 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) @@ -2260,13 +2295,13 @@ react (struct prog *prog, int status, pid_t pid) 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 @@ -2291,26 +2326,26 @@ progman_cleanup (int expect_term) 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 { @@ -2318,21 +2353,29 @@ progman_cleanup (int 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_comp_startup) + { + debug (1, (_("removing startup component %s, pid=%lu"), + prog_tag (prog), (unsigned long)pid)); + destroy_prog (&prog); + if (!progman_startup_phase ()) + pies_schedule_children (PIES_CHLD_WAKEUP); + } 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; } } @@ -2348,13 +2391,13 @@ progman_cleanup (int 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 || @@ -2368,13 +2411,13 @@ progman_cleanup (int expect_term) 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 @@ -2386,13 +2429,13 @@ progman_stop_component (struct prog **progptr) 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)) @@ -2410,13 +2453,13 @@ progman_stop_component (struct prog **progptr) 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, @@ -2438,13 +2481,13 @@ prog_deactivate_listener (struct prog *prog) } 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, @@ -2532,12 +2575,12 @@ progman_gc (void) return; if (time (NULL) - start < shutdown_timeout) sleep (1); else break; } - + /* FIXME: Report remaining programs */ - + } - + /* EOF */ diff --git a/src/socket.c b/src/socket.c index aa01543..40c7aa7 100644 --- a/src/socket.c +++ b/src/socket.c @@ -20,19 +20,22 @@ static void switch_eids (uid_t *puid, gid_t *pgid, mode_t *pumask) { uid_t ouid = geteuid (); gid_t ogid = getegid (); mode_t omask = umask (*pumask); - - if (setegid (*pgid)) - logmsg (LOG_ERR, _("cannot switch to EGID %lu: %s"), - (unsigned long) *pgid, strerror (errno)); - if (seteuid (*puid)) - logmsg (LOG_ERR, _("cannot switch to EUID %lu: %s"), - (unsigned long) *puid, strerror (errno)); + + if ((*puid && *puid != ouid) || (*pgid && *pgid != ogid)) + { + if (setegid (*pgid)) + logmsg (LOG_ERR, _("cannot switch to EGID %lu: %s"), + (unsigned long) *pgid, strerror (errno)); + if (seteuid (*puid)) + logmsg (LOG_ERR, _("cannot switch to EUID %lu: %s"), + (unsigned long) *puid, strerror (errno)); + } *puid = ouid; *pgid = ogid; *pumask = omask; } int @@ -48,20 +51,20 @@ create_socket (struct pies_url *url, int socket_type, struct sockaddr_un s_un; } addr; socklen_t socklen; uid_t uid = 0; gid_t gid = 0; int switch_back; - + if (strcmp (url->scheme, "unix") == 0 || strcmp (url->scheme, "file") == 0 || strcmp (url->scheme, "socket") == 0) { struct stat st; const char *group = NULL; - + user = url->user; if (url->argc) { size_t i; for (i = 0; i < url->argc; i++) { @@ -96,36 +99,36 @@ create_socket (struct pies_url *url, int socket_type, url->string, arg + len + 1); else umaskval = 0777 & ~n; } } } - + if (user) { struct passwd *pw = getpwnam (user); if (!pw) { logmsg (LOG_ERR, _("no such user: %s"), user); return -1; } uid = pw->pw_uid; gid = pw->pw_gid; } - + if (group) { struct group *grp = getgrnam (group); if (!grp) { logmsg (LOG_ERR, _("no such group: %s"), user); return -1; } gid = grp->gr_gid; } - + if (strlen (url->path) > sizeof addr.s_un.sun_path) { errno = EINVAL; logmsg (LOG_ERR, _("%s: UNIX socket name too long"), url->path); return -1; } @@ -156,20 +159,20 @@ create_socket (struct pies_url *url, int socket_type, } } else if (strcmp (url->scheme, "inet") == 0) { const char *host = url->host; short port = url->port; - + uid = 0; gid = 0; umaskval = 0; addr.sa.sa_family = PF_INET; socklen = sizeof (addr.s_in); - + if (!host) addr.s_in.sin_addr.s_addr = INADDR_ANY; else { struct hostent *hp = gethostbyname (host); if (!hp) @@ -182,26 +185,26 @@ create_socket (struct pies_url *url, int socket_type, switch (hp->h_addrtype) { case AF_INET: memmove (&addr.s_in.sin_addr, hp->h_addr, 4); addr.s_in.sin_port = htons (port); break; - + default: logmsg (LOG_ERR, _("%s: unsupported address family"), url->string); return -1; } } } else { logmsg (LOG_ERR, "%s: unknown scheme", url->string); return -1; } - + fd = socket (addr.sa.sa_family, socket_type, url->proto); if (fd == -1) { logfuncall ("socket", url->string, errno); return -1; } @@ -247,13 +250,13 @@ pass_fd0 (int fd, int payload) # ifndef CMSG_LEN # define CMSG_LEN(size) (sizeof(struct cmsghdr) + (size)) # endif /* ! CMSG_LEN */ # ifndef CMSG_SPACE # define CMSG_SPACE(size) (sizeof(struct cmsghdr) + (size)) # endif /* ! CMSG_SPACE */ - + char control[CMSG_SPACE (sizeof (int))]; struct cmsghdr *cmptr; msg.msg_control = (caddr_t) control; msg.msg_controllen = CMSG_LEN (sizeof (int)); @@ -287,21 +290,21 @@ pass_fd (const char *socket_name, int fd, unsigned maxtime) enum { fds_init, fds_open, fds_connected, fds_ready } state = fds_init; static char *fds_descr[] = { "init", "open", "connected", "ready" }; time_t start = time (NULL); int sockfd = -1; int res = -1; struct sockaddr_un addr; - + if (strlen (socket_name) > sizeof addr.sun_path) { logmsg (LOG_ERR, _("%s: UNIX socket name too long"), socket_name); return -1; } addr.sun_family = AF_UNIX; strcpy (addr.sun_path, socket_name); - + for (;;) { time_t now = time (NULL); if (now - start > maxtime) { @@ -359,13 +362,13 @@ pass_fd (const char *socket_name, int fd, unsigned maxtime) if (state == fds_connected) { int rc; fd_set fds; struct timeval tv; - + FD_ZERO (&fds); FD_SET (sockfd, &fds); tv.tv_usec = 0; tv.tv_sec = maxtime - (now - start); rc = select (sockfd + 1, NULL, &fds, NULL, &tv); if (rc == 0) @@ -427,13 +430,13 @@ calc_fd_max (void) for (sp = si_head; sp; sp = sp->next) if (sp->fd > fd_max) fd_max = sp->fd; } void * -register_socket (int fd, +register_socket (int fd, socket_handler_t rd, socket_handler_t wr, socket_handler_t ex, void *data) { struct sockinst *sip = grecs_malloc (sizeof *sip); @@ -451,13 +454,13 @@ register_socket (int fd, { FD_ZERO (&fdset[PIES_EVT_RD]); FD_ZERO (&fdset[PIES_EVT_WR]); FD_ZERO (&fdset[PIES_EVT_EX]); si_head = sip; } - + si_tail = sip; if (rd) FD_SET (fd, &fdset[PIES_EVT_RD]); if (wr) FD_SET (fd, &fdset[PIES_EVT_WR]); if (ex) @@ -490,13 +493,13 @@ delete_sockinst (struct sockinst *sp) FD_CLR (sp->fd, &fdset[PIES_EVT_RD]); if (sp->handler[PIES_EVT_WR]) FD_CLR (sp->fd, &fdset[PIES_EVT_WR]); if (sp->handler[PIES_EVT_EX]) FD_CLR (sp->fd, &fdset[PIES_EVT_EX]); fd_max = -1; - + if (sp->prev) sp->prev->next = sp->next; else si_head = sp->next; if (sp->next) sp->next->prev = sp->prev; @@ -569,22 +572,22 @@ pies_set_hook (int (*f) (void)) void pies_pause (void) { if (pies_pause_hook && pies_pause_hook ()) return; - + if (fd_max == -1) calc_fd_max (); while (1) { fd_set rdset = fdset[PIES_EVT_RD]; fd_set wrset = fdset[PIES_EVT_WR]; fd_set exset = fdset[PIES_EVT_EX]; - + int rc = select (fd_max + 1, &rdset, &wrset, &exset, NULL); if (rc > 0) { struct sockinst *sp; int delete = 0; ++si_iterating; @@ -634,7 +637,6 @@ pies_pause (void) if (errno != EINTR) logmsg (LOG_ERR, "select: %s", strerror (errno)); break; } } } - diff --git a/tests/Makefile.am b/tests/Makefile.am index 447104b..b2f2719 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -11,13 +11,19 @@ # 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 <http://www.gnu.org/licenses/>. -EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4 respawn retcode mailer +AUXTOOLS = \ + aux/respawn\ + aux/retcode\ + aux/mailer\ + aux/startup + +EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4 $(AUXTOOLS) DISTCLEANFILES = atconfig $(check_SCRIPTS) MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE) ## ------------ ## ## package.m4. ## ## ------------ ## @@ -43,12 +49,13 @@ TESTSUITE_AT = \ control.at\ cyclic.at\ respawn.at\ redirect.at\ ret-exec.at\ ret-notify.at\ + startup.at\ version.at TESTSUITE = $(srcdir)/testsuite M4=m4 AUTOTEST = $(AUTOM4TE) --language=autotest diff --git a/tests/atlocal.in b/tests/atlocal.in index 9069bbd..1992b80 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -1,10 +1,10 @@ # @configure_input@ -*- shell-script -*- # Configurable variable values for GNU Pies test suite. # Copyright (C) 2016-2019 Sergey Poznyakoff PATH=@abs_builddir@:@abs_top_builddir@/src:$srcdir:$PATH XFAILFILE=$abs_builddir/.badversion - +auxdir="$abs_srcdir/aux" trimws() { sed 's/[ ][ ]*$//' } diff --git a/tests/mailer b/tests/aux/mailer index f832ff5..f832ff5 100755 --- a/tests/mailer +++ b/tests/aux/mailer diff --git a/tests/respawn b/tests/aux/respawn index 11d59f6..11d59f6 100755 --- a/tests/respawn +++ b/tests/aux/respawn diff --git a/tests/retcode b/tests/aux/retcode index b827ba9..b827ba9 100755 --- a/tests/retcode +++ b/tests/aux/retcode diff --git a/tests/aux/startup b/tests/aux/startup new file mode 100755 index 0000000..b9d92a3 --- a/dev/null +++ b/tests/aux/startup @@ -0,0 +1,7 @@ +#!/bin/sh +dir=${1:?} +time=${2:?} +tag=${3:?} + +touch $dir/$tag +sleep $time diff --git a/tests/redirect.at b/tests/redirect.at index 9e53ba2..3a8cca7 100644 --- a/tests/redirect.at +++ b/tests/redirect.at @@ -21,13 +21,13 @@ PIES_XFAIL_CHECK PIES_CONTROL_INIT comp_pid_file=$PWD/comp.pid outfile=$PWD/out cat > pies.conf <<_EOT component test { mode respawn; - command "$abs_srcdir/respawn -tag respawn -append -pid $comp_pid_file"; + command "$auxdir/respawn -tag respawn -append -pid $comp_pid_file"; stdout file "$outfile"; } _EOT pies --config-file control.conf --config-file pies.conf diff --git a/tests/respawn.at b/tests/respawn.at index 8d72c40..4a8e3a7 100644 --- a/tests/respawn.at +++ b/tests/respawn.at @@ -20,13 +20,13 @@ AT_CHECK([ PIES_XFAIL_CHECK PIES_CONTROL_INIT comp_pid_file=$PWD/comp.pid cat > pies.conf <<_EOT component test { mode respawn; - command "$abs_srcdir/respawn -append -pid $comp_pid_file"; + command "$auxdir/respawn -append -pid $comp_pid_file"; } _EOT pies --config-file control.conf --config-file pies.conf n=0 diff --git a/tests/ret-exec.at b/tests/ret-exec.at index bf2c1a4..0b3d716 100644 --- a/tests/ret-exec.at +++ b/tests/ret-exec.at @@ -23,16 +23,16 @@ PIES_CONTROL_INIT comp_pid_file=$PWD/comp.pid report_file=$PWD/report cat > pies.conf <<_EOT component test { mode respawn; return-code 10 { - exec "$abs_srcdir/retcode $report_file"; + exec "$auxdir/retcode $report_file"; action disable; } - command "$abs_srcdir/respawn -sleep 2 -pid $comp_pid_file -exit 10"; + command "$auxdir/respawn -sleep 2 -pid $comp_pid_file -exit 10"; } _EOT >$report_file pies --config-file control.conf --config-file pies.conf diff --git a/tests/ret-notify.at b/tests/ret-notify.at index d1a7f39..a7768aa 100644 --- a/tests/ret-notify.at +++ b/tests/ret-notify.at @@ -19,21 +19,21 @@ AT_KEYWORDS([ret-notify]) AT_CHECK([ PIES_XFAIL_CHECK PIES_CONTROL_INIT report_file=$PWD/report cat > pies.conf <<_EOT -mailer-program "$abs_srcdir/mailer"; -mailer-command-line "$abs_srcdir/mailer $report_file"; +mailer-program "$auxdir/mailer"; +mailer-command-line "$auxdir/mailer $report_file"; component test { mode respawn; return-code 10 { notify root; action disable; } - command "$abs_srcdir/respawn -sleep 2 -exit 10"; + command "$auxdir/respawn -sleep 2 -exit 10"; } _EOT >$report_file pies --config-file control.conf --config-file pies.conf diff --git a/tests/startup.at b/tests/startup.at new file mode 100644 index 0000000..72017ce --- a/dev/null +++ b/tests/startup.at @@ -0,0 +1,84 @@ +# This file is part of GNU pies testsuite. -*- Autotest -*- +# Copyright (C) 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 <http://www.gnu.org/licenses/>. + +AT_SETUP([Startup components]) + +AT_CHECK([ +PIES_XFAIL_CHECK +PIES_CONTROL_INIT +comp_pid_file=$PWD/comp.pid + +cat > pies.conf <<_EOT +component b1 { + mode startup; + command "$auxdir/startup $PWD 1 b1"; +} + +component b2 { + mode startup; + command "$auxdir/startup $PWD 2 b2"; +} + +component test { + mode respawn; + command "$auxdir/respawn -append -pid $comp_pid_file"; +} +_EOT + +pies --config-file control.conf --config-file pies.conf + +n=0 +res= +b1= +b2= +while : +do + echo "n=$n" >> tracefile + if test -z "$b1" && test -f b1; then + res="${res}b1" + b1=1 + echo "got b1" >> tracefile + fi + if test -z "$b2" && test -f b2; then + res="${res}b2" + b2=1 + echo "got b2" >> tracefile + fi + if test -f $comp_pid_file; then + echo "got pidfile" >> tracefile + res="${res}pid" + break + fi + sleep 1 + n=$(($n + 1)) + if test $n -gt 10; then + echo >&2 "timed out" + break + fi +done + +PIES_STOP +case $res in +b1b2pid|b2b1pid) echo b1b2pid;; +*) echo $res +esac +], +[0], +[b1b2pid +]) + +AT_CLEANUP + diff --git a/tests/testsuite.at b/tests/testsuite.at index 03ddd50..2a1167d 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -62,6 +62,7 @@ AT_BANNER([Dependencies]) m4_include([cyclic.at]) AT_BANNER([Components]) m4_include([respawn.at]) m4_include([redirect.at]) m4_include([ret-exec.at]) m4_include([ret-notify.at]) +m4_include([startup.at]) |