diff options
Diffstat (limited to 'src/pies.c')
-rw-r--r-- | src/pies.c | 1581 |
1 files changed, 1581 insertions, 0 deletions
diff --git a/src/pies.c b/src/pies.c new file mode 100644 index 0000000..10862c2 --- /dev/null +++ b/src/pies.c @@ -0,0 +1,1581 @@ +/* This file is part of Mailfromd. + Copyright (C) 2008, 2009 Sergey Poznyakoff + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "pies.h" +#include "meta1lex.h" + +int log_to_stderr; /* Use stderr for logging */ +char *log_tag; /* override mu_log_tag */ +mu_log_level_t debug_level; +mu_debug_t pies_debug; +struct mf_privs pies_privs; +int foreground; +int command; +char *pidfile = STATEDIR "/pies.pid"; +char *ctlfile = STATEDIR "/pies.ctl"; +char *statfile = STATEDIR "/pies.stat"; +mode_t pies_umask = 0; +unsigned long shutdown_timeout = 5; +mu_acl_t pies_acl; +limits_record_t pies_limits; +int force_option; + + +/* Logging */ +void +log_setup (int want_stderr) +{ + mu_debug_t debug; + + mu_diag_get_debug (&debug); + + if (log_tag) + mu_log_tag = log_tag; + + if (!want_stderr) + { + openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility); + mu_debug_set_print (debug, mu_diag_syslog_printer, NULL); + mu_debug_default_printer = mu_debug_syslog_printer; + } + else + mu_debug_default_printer = mu_debug_stderr_printer; +} + +static int +stderr_closed_p() +{ + int fd = dup (0); + if (fd < 0) + return 1; + close (fd); + return fd <= 2; +} + + +static int +_cb_action (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + enum return_action *pact = data; + static struct mu_kwd actab[] = { + { "disable", action_disable }, + { "restart", action_restart }, + { NULL } + }; + int res; + + if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) + return 1; + if (mu_kwd_xlat_name (actab, arg->v.string, &res)) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("unknown action code: %s"), arg); + return 0; + } + *pact = res; + return 0; +} + +static int +_cb_notify_addr (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + mu_address_t *paddr = data; + mu_address_t addr; + int rc; + + if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) + return 1; + rc = mu_address_create (&addr, arg->v.string); + if (rc) + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: invalid e-mail address: %s"), + arg->v.string, mu_strerror (rc)); + if (*paddr) + { + mu_address_union (paddr, addr); + mu_address_destroy (&addr); + } + else + *paddr = addr; + return 0; +} + +struct mu_cfg_param return_code_cfg_param[] = { + { "action", mu_cfg_callback, NULL, + mu_offsetof (struct component, act_temp.act), _cb_action, + N_("Specifies action to take when a component finishes with this " + "return code."), + /* TRANSLATORS: disable and restart are keywords, do not translate them. */ + N_("arg: {disable | restart}") }, + { "notify", mu_cfg_callback, NULL, + mu_offsetof (struct component, act_temp.addr), _cb_notify_addr, + N_("Notify this address when then component terminates."), + N_("arg: email-list") }, + { "message", mu_cfg_string, NULL, + mu_offsetof (struct component, act_temp.message), NULL, + N_("Notification message text (with headers).") }, + { "exec", mu_cfg_string, NULL, + mu_offsetof (struct component, act_temp.command), NULL, + N_("Execute this command.") }, + { NULL } +}; + +#define S(s) { #s, s } +static struct mu_kwd ex_kwtab[] = { + S (EX_OK), + S (EX_USAGE), + S (EX_DATAERR), + S (EX_NOINPUT), + S (EX_NOUSER), + S (EX_NOHOST), + S (EX_UNAVAILABLE), + S (EX_SOFTWARE), + S (EX_OSERR), + S (EX_OSFILE), + S (EX_CANTCREAT), + S (EX_IOERR), + S (EX_TEMPFAIL), + S (EX_PROTOCOL), + S (EX_NOPERM), + S (EX_CONFIG), + { NULL } +}; + +static struct mu_kwd sig_kwtab[] = { + S (SIGHUP), + S (SIGINT), + S (SIGQUIT), + S (SIGILL), + S (SIGTRAP), + S (SIGABRT), + S (SIGIOT), + S (SIGBUS), + S (SIGFPE), + S (SIGKILL), + S (SIGUSR1), + S (SIGSEGV), + S (SIGUSR2), + S (SIGPIPE), + S (SIGALRM), + S (SIGTERM), +#ifdef SIGSTKFLT + S (SIGSTKFLT), +#endif + S (SIGCHLD), + S (SIGCONT), + S (SIGSTOP), + S (SIGTSTP), + S (SIGTTIN), + S (SIGTTOU), +#ifdef SIGURG + S (SIGURG), +#endif +#ifdef SIGXCPU + S (SIGXCPU), +#endif +#ifdef SIGXFSZ + S (SIGXFSZ), +#endif +#ifdef SIGVTALRM + S (SIGVTALRM), +#endif +#ifdef SIGPROF + S (SIGPROF), +#endif +#ifdef SIGWINCH + S (SIGWINCH), +#endif +#ifdef SIGPOLL + S (SIGPOLL), +#endif +#ifdef SIGIO + S (SIGIO), +#endif +#ifdef SIGPWR + S (SIGPWR), +#endif +#ifdef SIGSYS + S (SIGSYS), +#endif + { NULL } +}; +#undef S + +static struct action * +create_action(struct component *comp, + mu_debug_t debug, mu_config_value_t *val, int argc, + const char *(*getarg) (mu_config_value_t *, int, mu_debug_t)) +{ + int i; + unsigned *retv; + int retc = 0; + int allflag = 0; + struct action *act; + + retv = xcalloc (argc, sizeof *retv); + if (argc == 0 || (argc == 1 && strcmp (getarg(val, 0, debug), "*") == 0)) + allflag = 1; + else + { + for (i = 0; i < argc; i++) + { + unsigned n; + const char *arg = getarg(val, i, debug); + size_t len = strlen (arg); + + if (isdigit (arg[0])) + { + char *p; + n = strtoul (arg, &p, 0); + if (*p) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: not a number"), p); + continue; + } + } + else if (len > 3 && memcmp (arg, "SIG", 3) == 0) + { + if (arg[4] == '+') + { + char *p; + n = strtoul (arg + 4, &p, 0); + if (*p) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: not a number"), p); + continue; + } + } + else if (mu_kwd_xlat_name_ci (sig_kwtab, arg, &n)) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: not a signal code"), arg); + continue; + } + n |= STATUS_SIG_BIT; + } + else if (mu_kwd_xlat_name_ci (ex_kwtab, arg, &n)) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: not a return code"), arg); + continue; + } + + /* Alles in ordnung */ + retv[retc++] = n; + } + } + + if (retc == 0 && !allflag) + { + free (retv); + return NULL; + } + + act = xzalloc (sizeof *act); + if (!allflag) + { + act->nstat = retc; + act->status = retv; + } + if (comp->act_tail) + comp->act_tail->next = act; + else + comp->act_head = act; + comp->act_tail = act; + return act; +} + +const char * +_get_string_arg (mu_config_value_t *val, int num, mu_debug_t debug) +{ + if (num != 0) + return NULL; + return val->v.string; +} + +const char * +_get_array_arg (mu_config_value_t *val, int num, mu_debug_t debug) +{ + if (num < val->v.arg.c) + { + if (mu_cfg_assert_value_type (&val->v.arg.v[num], MU_CFG_STRING, + debug) == 0) + return val->v.arg.v[num].v.string; + } + return NULL; +} + +const char * +_get_list_arg (mu_config_value_t *val, int num, mu_debug_t debug) +{ + mu_config_value_t *elt; + int rc = mu_list_get (val->v.list, num, (void**)&elt); + if (rc) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("cannot get list item: %s"), + mu_strerror (rc)); + } + else if (mu_cfg_assert_value_type (elt, MU_CFG_STRING, debug) == 0) + return elt->v.string; + return NULL; +} + +static int +return_code_section_parser (enum mu_cfg_section_stage stage, + const mu_cfg_node_t *node, + const char *section_label, void **section_data, + void *call_data, + mu_cfg_tree_t *tree) +{ + struct component *comp = *section_data; + size_t count; + struct action *act; + + if (!node->label) + { + mu_cfg_format_error (tree->debug, MU_DEBUG_ERROR, _("missing tag")); + return 1; + } + + switch (stage) + { + case mu_cfg_section_start: + switch (node->label->type) + { + case MU_CFG_STRING: + act = create_action (comp, tree->debug, node->label, + 1, + _get_string_arg); + break; + + case MU_CFG_ARRAY: + act = create_action (comp, tree->debug, node->label, + node->label->v.arg.c, + _get_array_arg); + break; + + case MU_CFG_LIST: + mu_list_count (node->label->v.list, &count); + act = create_action (comp, tree->debug, node->label, + count, + _get_list_arg); + } + + if (!act) + return 1; + memset (&comp->act_temp, 0, sizeof (comp->act_temp)); + break; + + case mu_cfg_section_end: + act = comp->act_tail; + act->act = comp->act_temp.act; + act->addr = comp->act_temp.addr; + act->message = comp->act_temp.message; + act->command = comp->act_temp.command; + break; + } + return 0; +} + +void +return_code_cfg_init () +{ + struct mu_cfg_section *section; + + if (mu_create_canned_section ("return-code", §ion)) + exit (EX_SOFTWARE); + section->parser = return_code_section_parser; + section->label = N_("<tag: exit-code-list>"); + mu_cfg_section_add_params (section, return_code_cfg_param); +} + +static int +_cb_command (mu_debug_t debug, void *data, mu_config_value_t *val) +{ + int argc; + char **argv, ***pargv = data; + int rc; + + switch (val->type) + { + case MU_CFG_STRING: + rc = mu_argcv_get (val->v.string, "", NULL, &argc, &argv); + if (rc) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + "mu_argcv_get: %s", mu_strerror (rc)); + return 1; + } + break; + + case MU_CFG_ARRAY: + argv = config_array_to_argv (val, debug); + break; + + case MU_CFG_LIST: + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("unexpected list")); + return 1; + } + *pargv = argv; + return 0; +} + +static int +_cb_umask (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + mode_t *pmode = data; + char *p; + unsigned long n; + + if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) + return 1; + n = strtoul (arg->v.string, &p, 8); + if (*p) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("invalid octal number")); + return 1; + } + *pmode = n; + return 0; +} + +static int +_cb_env (mu_debug_t debug, void *data, mu_config_value_t *val) +{ + int rc; + int argc; + char **argv; + char ***penv = data; + + switch (val->type) + { + case MU_CFG_STRING: + rc = mu_argcv_get_np (val->v.string, strlen (val->v.string), "", + NULL, 0, &argc, &argv, NULL); + if (rc) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + "mu_argcv_get: %s", mu_strerror (rc)); + return 1; + } + break; + + case MU_CFG_ARRAY: + argv = config_array_to_argv (val, debug); + break; + + case MU_CFG_LIST: + mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("unexpected list")); + return 1; + } + + *penv = argv; + return 0; +} + +static int +_cb_facility (mu_debug_t debug, void *data, mu_config_value_t *val) +{ + if (mu_cfg_assert_value_type (val, MU_CFG_STRING, debug)) + return 1; + + if (mu_string_to_syslog_facility (val->v.string, data)) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("unknown syslog facility `%s'"), + val->v.string); + return 1; + } + return 0; +} + +static int +_cb_redir (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + struct redirector *rp = data; + static struct mu_kwd redirtab[] = { + { "null", redir_null }, + { "syslog", redir_syslog }, + { "file", redir_file }, + { NULL } + }; + int res; + + switch (arg->type) + { + case MU_CFG_STRING: + if (strcmp (arg->v.string, "null") == 0) + { + rp->type = redir_null; + break; + } + rp->type = redir_syslog; + if (mu_string_to_syslog_priority (arg->v.string, &rp->v.prio)) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("unknown syslog priority `%s'"), + arg); + return 0; + } + break; + + case MU_CFG_ARRAY: + if (mu_cfg_assert_value_type (&arg->v.arg.v[0], MU_CFG_STRING, debug)) + return 0; + if (mu_kwd_xlat_name (redirtab, arg->v.arg.v[0].v.string, &res)) + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: unrecognised redirector type"), + arg->v.arg.v[0].v.string); + else + { + if (res != redir_null) + { + if (arg->v.arg.c != 2) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("wrong number of arguments")); + return 0; + } + if (mu_cfg_assert_value_type (&arg->v.arg.v[1], MU_CFG_STRING, + debug)) + return 0; + + switch (res) + { + case redir_null: + break; + + case redir_syslog: + if (mu_string_to_syslog_priority (arg->v.arg.v[1].v.string, + &rp->v.prio)) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("unknown syslog priority `%s'"), + arg->v.arg.v[1].v.string); + return 0; + } + break; + + case redir_file: + rp->v.file = xstrdup (arg->v.arg.v[1].v.string); + break; + } + } + rp->type = res; + } + break; + + default: + mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("unexpected list")); + } + + return 0; +} + +static int +_cb_url (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + int rc; + mu_url_t url; + + if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) + return 1; + rc = mu_url_create (&url, arg->v.string); + if (rc) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: cannot create URL: %s"), + arg->v.string, + mu_strerror (rc)); + return 0; + } + rc = mu_url_parse (url); + if (rc) + { + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: cannot parse URL: %s"), + arg->v.string, + mu_strerror (rc)); + mu_url_destroy (&url); + return 0; + } + *(mu_url_t*) data = url; + return 0; +} + +static struct mu_kwd modetab[] = { + { "exec", pies_comp_exec }, + { "wait", pies_comp_exec }, + { "accept", pies_comp_accept }, + { "inetd", pies_comp_inetd }, + { "nostartaccept", pies_comp_inetd }, + { "pass-fd", pies_comp_pass_fd }, + { "pass", pies_comp_pass_fd }, + { NULL } +}; + +static int +_cb_mode (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + int res; + + if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) + return 1; + if (mu_kwd_xlat_name (modetab, arg->v.string, &res)) + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("%s: unrecognised mode"), + arg->v.string); + else + *(enum pies_comp_mode *)data = res; + return 0; +} + +static int +_cb_limits (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + limits_record_t *plrec = data; + char *p; + + if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) + return 1; + if (parse_limits (plrec, (char*) arg->v.string, &p)) + mu_cfg_format_error (debug, MU_DEBUG_ERROR, + _("invalid limit string (near %s)"), + p); + return 0; +} + +struct mu_cfg_param component_cfg_param[] = { + { "mode", mu_cfg_callback, NULL, + mu_offsetof (struct component, mode), _cb_mode, + N_("Component execution mode."), + /* TRANSLATORS: The words between '{' and '}' are keywords, do not + translate them. */ + N_("mode: {exec | wait | accept | inetd | nostartaccept | pass-fd | pass}") + }, + { "program", mu_cfg_string, NULL, + mu_offsetof (struct component, program), NULL, + N_("Full name of the program.") }, + { "command", mu_cfg_callback, NULL, + mu_offsetof (struct component, argv), _cb_command, + N_("Command line.") }, + { "prerequisites", MU_CFG_LIST_OF(mu_cfg_string), NULL, + mu_offsetof (struct component, prereq), NULL, + N_("List of prerequisites."), + N_("list") }, + { "dependents", MU_CFG_LIST_OF(mu_cfg_string), NULL, + mu_offsetof (struct component, depend), NULL, + N_("List of components for which this one is a prerequisite."), + N_("list") }, + { "disable", mu_cfg_bool, NULL, + mu_offsetof (struct component, disabled), NULL, + N_("Disable this entry.") }, + { "settle-timeout", mu_cfg_uint, NULL, + mu_offsetof (struct component, settle_timeout), NULL, + N_("Time to wait before starting this component.") }, + { "precious", mu_cfg_bool, NULL, + mu_offsetof (struct component, precious), NULL, + N_("Mark this entry as precious.") }, + { "socket", mu_cfg_callback, NULL, + mu_offsetof (struct component, socket_url), _cb_url, + N_("Listen on the given url."), + N_("url: string") }, + { "pass-fd-socket", mu_cfg_string, NULL, + mu_offsetof (struct component, pass_fd_socket), NULL, + N_("Pass fd through this socket."), + N_("name") }, + { "acl", mu_cfg_section, NULL, mu_offsetof (struct component, acl), NULL, + N_("Per-component access control list") }, + { "remove-file", mu_cfg_string, NULL, + mu_offsetof (struct component, rmfile), NULL, + N_("Remove file before starting the component."), + N_("file") }, + { "facility", mu_cfg_callback, NULL, + mu_offsetof (struct component, facility), _cb_facility, + N_("Override default syslog facility for this component."), + N_("arg") }, + { "stdout", mu_cfg_callback, NULL, + mu_offsetof (struct component, redir[RETR_OUT]), _cb_redir, + N_("Redirect program's standard output to the given file or " + "syslog priority."), + /* TRANSLATORS: file and syslog are keywords. Do not translate them. */ + N_("type: {file | syslog}> <channel: string") + }, + { "stderr", mu_cfg_callback, NULL, + mu_offsetof (struct component, redir[RETR_ERR]), _cb_redir, + N_("Redirect program's standard error to the given file or " + "syslog priority."), + /* TRANSLATORS: file and syslog are keywords. Do not translate them. */ + N_("type: {file | syslog}> <channel: string") + }, + { "user", mu_cfg_string, NULL, + mu_offsetof (struct component, privs.user), NULL, + N_("Run with this user privileges.") }, + { "group", MU_CFG_LIST_OF(mu_cfg_string), NULL, + mu_offsetof (struct component, privs.groups), NULL, + N_("Retain supplementary group.") }, + { "allgroups", mu_cfg_bool, NULL, + mu_offsetof (struct component, privs.allgroups), NULL, + N_("Retain all supplementary groups of which user is a member.") }, + { "umask", mu_cfg_callback, NULL, + mu_offsetof (struct component, umask), _cb_umask, + N_("Force this umask."), + N_("arg: number") }, + { "limits", mu_cfg_callback, NULL, + mu_offsetof (struct component, limits), _cb_limits, + N_("Set system limits") }, + { "env", mu_cfg_callback, NULL, + mu_offsetof (struct component, env), _cb_env, + N_("Set program environment. Argument is a list of assignments " + "separated by white space."), + N_("arg: list") }, + { "chdir", mu_cfg_string, NULL, + mu_offsetof (struct component, dir), NULL, + N_("Change to this directory before executing the component."), + N_("dir") }, + { "return-code", mu_cfg_section }, + { NULL } +}; + +static char * +make_full_name (const char *dir, const char *file) +{ + char *p; + size_t len = strlen (dir); + + while (len > 0 && dir[len-1] == '/') + len--; + p = xmalloc (len + 1 + strlen (file) + 1); + memcpy (p, dir, len); + p[len++] = '/'; + strcpy (p + len, file); + return p; +} + +static int +component_verify (struct component *comp, mu_debug_t debug) +{ + int header = 0; + int i; +#define COMPERR(fmt, arg) \ + do \ + { \ + if (!header) \ + { \ + mu_cfg_format_error (debug, MU_DEBUG_ERROR, \ + _("in component %s:"), comp->tag); \ + header = 1; \ + } \ + mu_cfg_format_error (debug, MU_DEBUG_ERROR, fmt, arg); \ + } \ + while (0) + + if (!comp->argv) + COMPERR ("%s", _("missing command line")); + if (comp->pass_fd_socket && comp->mode != pies_comp_pass_fd) + COMPERR ("%s", _("pass-fd-socket ignored: wrong mode")); + switch (comp->mode) + { + case pies_comp_exec: + if (comp->socket_url) + COMPERR ("%s", _("socket ignored: wrong mode")); + break; + + case pies_comp_pass_fd: + if (!comp->pass_fd_socket) + COMPERR ("%s", _("must supply pass-fd-socket in this mode")); + else if (comp->pass_fd_socket[0] != '/') + { + if (comp->dir) + { + char *p = make_full_name (comp->dir, comp->pass_fd_socket); + free (comp->pass_fd_socket); + comp->pass_fd_socket = p; + } + else + COMPERR ("%s", _("pass-fd-socket must be an absolute " + "file name or chdir must be specified")); + } + /* Fall through */ + + case pies_comp_accept: + case pies_comp_inetd: + if (!comp->socket_url) + { + COMPERR ("%s", _("socket must be specified in this mode")); + /* FIXME: Memory leak */ + return 1; + } + } + + if (comp->mode != pies_comp_exec + && comp->redir[RETR_OUT].type != redir_null) + { + COMPERR ("%s", _("stdout translation invalid in this mode")); + comp->redir[RETR_OUT].type = redir_null; + } + + for (i = RETR_OUT; i <= RETR_ERR; i++) + { + if (comp->redir[i].type == redir_file && comp->redir[i].v.file[0] != '/') + { + if (comp->dir) + { + char *p = make_full_name (comp->dir, comp->redir[i].v.file); + free (comp->redir[i].v.file); + comp->redir[i].v.file = p; + } + else + COMPERR (_("%s: must be an absolute " + "file name or chdir must be specified"), + comp->redir[i].v.file); + } + } + + return header; +#undef COMPERR +} + +static int +component_section_parser (enum mu_cfg_section_stage stage, + const mu_cfg_node_t *node, + const char *section_label, void **section_data, + void *call_data, + mu_cfg_tree_t *tree) +{ + struct component *comp; + + switch (stage) + { + case mu_cfg_section_start: + if (node->label + && mu_cfg_assert_value_type (node->label, MU_CFG_STRING, + tree->debug)) + return 1; + comp = progman_lookup_component (node->label->v.string); + if (!comp) + { + comp = xzalloc (sizeof (*comp)); + comp->facility = mu_log_facility; + comp->redir[RETR_OUT].type = comp->redir[RETR_ERR].type = redir_null; + comp->tag = node->label ? xstrdup (node->label->v.string) : NULL; + } + *section_data = comp; + break; + + case mu_cfg_section_end: + comp = *(struct component **) section_data; + if (component_verify (comp, tree->debug) == 0) + { + /* FIXME: The prog list is traversed twice for each component + statement, this is suboptimal. */ + if (progman_lookup_component (comp->tag) == NULL) + register_prog (comp); + } + } + return 0; +} + +void +component_cfg_init () +{ + struct mu_cfg_section *section; + + if (mu_create_canned_section ("component", §ion)) + exit (EX_SOFTWARE); + section->parser = component_section_parser; + section->label = N_("<tag: string>"); + mu_cfg_section_add_params (section, component_cfg_param); +} + + + +struct component default_component; + +static int +_cb_debug (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + int rc; + + if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) + return 1; + rc = mu_debug_level_from_string (arg->v.string, &debug_level, debug); + if (rc) + return 0; + if (!pies_debug) + mu_debug_create (&pies_debug, NULL); + mu_debug_set_level (pies_debug, debug_level); + return 0; +} + +static int _cm_include_meta1 (mu_debug_t, void *, mu_config_value_t *); + +struct mu_cfg_param pies_cfg_param[] = { + { "component", mu_cfg_section }, + { "debug", mu_cfg_callback, NULL, 0, _cb_debug, + N_("Set debug verbosity level.") }, + { "pidfile", mu_cfg_string, &pidfile, 0, NULL, + N_("Write PID to this file.") }, + { "control-file", mu_cfg_string, &ctlfile, 0, NULL, + N_("Set location of the control file.") }, + { "stat-file", mu_cfg_string, &statfile, 0, NULL, + N_("Set location of the statistics output file.") }, + { "user", mu_cfg_string, &pies_privs.user, 0, NULL, + N_("Run with this user privileges.") }, + { "group", MU_CFG_LIST_OF(mu_cfg_string), &pies_privs.groups, 0, NULL, + N_("Retain supplementary group.") }, + { "allgroups", mu_cfg_bool, &pies_privs.allgroups, 0, NULL, + N_("Retain all supplementary groups of which user is a member.") }, + { "umask", mu_cfg_callback, &pies_umask, 0, _cb_umask, + N_("Force this umask."), + N_("arg: number") }, + { "limits", mu_cfg_callback, &pies_limits, 0, _cb_limits, + N_("Set global system limits.") }, + { "shutdown-timeout", mu_cfg_uint, &shutdown_timeout, 0, NULL, + N_("Wait <n> seconds for all components to shut down."), + "n" }, + { "return-code", mu_cfg_section, &default_component }, + { "acl", mu_cfg_section, &pies_acl, 0, NULL, + N_("Global access control list") }, + { "include-meta1", mu_cfg_callback, NULL, 0, _cm_include_meta1, + N_("Include components from the specified MeTA1 configuration file."), + N_("file: string") }, + { "meta1-queue-dir", mu_cfg_string, &meta1_queue_dir, 0, NULL, + N_("Set name of MeTA1 queue directory (default /var/spool/meta1).") }, + { NULL } +}; + +static int +_cm_include_meta1 (mu_debug_t debug, void *data, mu_config_value_t *arg) +{ + int flags = 0; + + if (mu_cfg_assert_value_type (arg, MU_CFG_STRING, debug)) + return 1; + if (meta1_config_parse (arg->v.string) == 0) + { + if (mu_cfg_parser_verbose) + flags |= MU_PARSE_CONFIG_VERBOSE; + if (mu_cfg_parser_verbose > 1) + flags |= MU_PARSE_CONFIG_DUMP; + mu_cfg_tree_reduce (meta1_parse_tree, mu_program_name, pies_cfg_param, + flags, NULL); + } + return 0; +} + + + +const char *program_version = "pies (" PACKAGE_STRING ")"; +const char *package_bugreport = "<" PACKAGE_BUGREPORT ">"; +static char doc[] = N_("pies -- process invocation and execution supervisor"); +static char args_doc[] = ""; + +static const char *capa[] = { + "common", + "logging", + "mailer", + "debug", + NULL +}; + +enum { + OPT_FOREGROUND=256, + OPT_LOG_TAG, + OPT_SYSLOG, + OPT_STDERR, + OPT_DUMP_PREREQ, + OPT_DUMP_DEPMAP, + OPT_FORCE +}; + +#define OPT_RESTART 'R' +#define OPT_RELOAD 'r' +#define OPT_STATUS 's' +#define OPT_STOP 'S' + +static struct argp_option options[] = { +#define GRP 0 + { "foreground", OPT_FOREGROUND, 0, 0, N_("remain in foreground"), GRP+1}, + { "stderr", OPT_STDERR, NULL, 0, N_("log to stderr"), }, + { "syslog", OPT_SYSLOG, NULL, 0, N_("log to syslog"), }, + { "log-tag", OPT_LOG_TAG, N_("STRING"), 0, + N_("set the identifier used in syslog messages to STRING"), GRP+1 }, + { "debug", 'x', N_("LEVEL"), 0, + N_("set debug verbosity level"), GRP+1 }, + { "force", OPT_FORCE, NULL, 0, + N_("force startup even if another instance may be running"), GRP+1 }, +#undef GRP + +#define GRP 10 + { "restart-component", OPT_RESTART, NULL, 0, + N_("restart components named in the command line"), GRP+1 }, + { "reload", OPT_RELOAD, NULL, 0, + N_("reload the running instance of pies "), GRP+1 }, + { "hup", 0, NULL, OPTION_ALIAS }, + { "status", OPT_STATUS, NULL, 0, + N_("display info about the running instance "), GRP+1 }, + { "stop", OPT_STOP, NULL, 0, + N_("stop the running instance "), GRP+1 }, +#undef GRP + +#define GRP 20 + { "dump-prereq", OPT_DUMP_PREREQ, NULL, 0, + N_("dump prerequisite charts"), GRP+1 }, + { "dump-depmap", OPT_DUMP_DEPMAP, NULL, 0, + N_("dump dependency map"), GRP+1 }, +#undef GRP + { NULL } +}; + +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + static struct mu_argp_node_list lst; + + switch (key) + { + case OPT_FOREGROUND: + foreground = 1; + break; + + case OPT_RELOAD: + case OPT_STATUS: + case OPT_STOP: + case OPT_RESTART: + case OPT_DUMP_PREREQ: + case OPT_DUMP_DEPMAP: + log_to_stderr = 1; + command = key; + break; + + case OPT_SYSLOG: + log_to_stderr = 0; + break; + + case OPT_STDERR: + log_to_stderr = 1; + break; + + case 'x': + mu_argp_node_list_new (&lst, "debug", arg); + break; + + case OPT_FORCE: + force_option = 1; + break; + + case OPT_LOG_TAG: + log_tag = arg; + break; + + case ARGP_KEY_INIT: + mu_argp_node_list_init (&lst); + break; + + case ARGP_KEY_FINI: + mu_argp_node_list_finish (&lst, NULL, NULL); + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static struct argp argp = { + options, + parse_opt, + args_doc, + doc, + NULL, + NULL, + NULL +}; + + +#define ACTION_CONT 0 +#define ACTION_STOP 1 +#define ACTION_RESTART 2 +#define ACTION_COMPRELOAD 3 +#define ACTION_DUMPSTATS 4 + +int action = ACTION_CONT; +int children_cleanup = 0; +int got_alarm = 0; + +RETSIGTYPE +sig_handler (int sig) +{ + switch (sig) + { + case SIGCHLD: + children_cleanup = 1; + break; + + case SIGTERM: + case SIGINT: + case SIGQUIT: + action = ACTION_STOP; + mu_diag_output (MU_DIAG_NOTICE, "received signal %d", sig); + break; + + case SIGHUP: + mu_diag_output (MU_DIAG_NOTICE, "received signal %d", sig); + action = ACTION_RESTART; + break; + + case SIGALRM: + got_alarm = 1; + break; + + case SIGUSR1: + action = ACTION_COMPRELOAD; + break; + + case SIGUSR2: + action = ACTION_DUMPSTATS; + break; + } + signal (sig, sig_handler); +} + +void +signal_setup (RETSIGTYPE (*sf)(int)) +{ + signal (SIGCHLD, sf); + signal (SIGTERM, sf); + signal (SIGQUIT, sf); + signal (SIGINT, sf); + signal (SIGHUP, sf); + signal (SIGALRM, sf); + signal (SIGUSR1, sf); + signal (SIGUSR2, sf); +} + + +pid_t +pidfile_read (int must_exist) +{ + int c; + pid_t n = 0; + FILE *fp = fopen (pidfile, "r"); + if (!fp) + { + if (must_exist && errno != ENOENT) + mu_diag_output (MU_DIAG_ERR, + _("cannot open pid file `%s': %s"), + pidfile, + mu_strerror (errno)); + return -1; + } + + while ((c = fgetc (fp)) != EOF) + { + if (isdigit (c)) + n = n * 10 + c - '0'; + else if (c == '\n') + break; + else + { + mu_diag_output (MU_DIAG_ERR, + _("unexpected character %#03o in pidfile `%s'"), + c, pidfile); + return -1; + } + } + fclose (fp); + if (kill (n, 0)) + { + mu_diag_output (MU_DIAG_ERR, + _("cannot signal master process %lu: %s"), + (unsigned long) n, mu_strerror (errno)); + if (errno == EPERM) + return n; /* be on the safe side */ + return -1; + } + return n; +} + + +enum pies_status + { + pies_status_ctr, /* clear to run */ + pies_status_stale, + pies_status_noresp, + pies_status_running + }; + +enum pies_status +pies_check_status (pid_t *ppid) +{ + pid_t pid = pidfile_read (0); + int i; + int rc; + + if (pid == -1) + return pies_status_ctr; + + *ppid = pid; + + if (kill (pid, SIGUSR2)) + return pies_status_stale; + + for (i = 0; i < 4 && (rc = access (statfile, R_OK)); i++) + sleep (1); + + if (rc) + return pies_status_noresp; + return pies_status_running; +} + + +void +stop_components () +{ + FILE *fp; + size_t size = 0; + char *buf = NULL; + + mu_diag_output (MU_DIAG_INFO, _("stopping components")); + + fp = fopen (ctlfile, "r"); + if (!fp) |