/* 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 . */
#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_("");
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}> 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_("");
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 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)
{
mu_error (_("cannot open control file `%s': %s"),
ctlfile, mu_strerror (errno));
return;
}
if (unlink (ctlfile))
{
mu_error (_("cannot unlink control file `%s': %s"),
ctlfile, mu_strerror (errno));
fclose (fp);
return;
}
while (getline (&buf, &size, fp) > 0)
{
size_t len = strlen (buf);
if (len == 0)
continue;
if (buf[len-1] == '\n')
buf[len-1] = 0;
progman_stop_component (buf);
}
free (buf);
fclose (fp);
}
int
request_restart_components (char **argv)
{
FILE *fp;
pid_t pid = pidfile_read (1);
if (pid == -1)
return 1;
fp = fopen (ctlfile, "w");
if (!fp)
{
mu_error (_("cannot open control file `%s': %s"),
ctlfile, mu_strerror (errno));
return 1;
}
for (; *argv; argv++)
fprintf (fp, "%s\n", *argv);
fclose (fp);
kill (pid, SIGUSR1);
return 0;
}
int
pies_reload ()
{
pid_t pid = pidfile_read (1);
if (pid == -1)
{
mu_diag_output (MU_DIAG_CRIT, _("pies is not running"));
return 1;
}
mu_diag_output (MU_DIAG_INFO, _("reloading pies at PID %lu"),
(unsigned long) pid);
return kill (pid, SIGHUP) ? EX_SOFTWARE : 0;
}
int
pies_status ()
{
pid_t pid;
FILE *fp;
if (unlink (statfile) && errno != ENOENT)
mu_diag_output (MU_DIAG_ERR, _("cannot unlink statfile `%s': %s"),
statfile, mu_strerror (errno));
switch (pies_check_status (&pid))
{
case pies_status_ctr:
mu_diag_output (MU_DIAG_INFO, _("pies is not running"));
break;
case pies_status_stale:
mu_diag_output (MU_DIAG_INFO,
_("pies is not running, but a pidfile "
"is found (pid %lu)"),
(unsigned long) pid);
return 1; /* FIXME: hm? */
case pies_status_noresp:
mu_diag_output (MU_DIAG_INFO,
_("pies seems to run with pid %lu, but is not responding"),
(unsigned long) pid);
if (unlink (statfile))
mu_diag_output (MU_DIAG_ERR, _("cannot unlink statfile `%s': %s"),
statfile, mu_strerror (errno));
return 2;
case pies_status_running:
fp = fopen (statfile, "r");
if (!fp)
mu_diag_output (MU_DIAG_ERR, _("cannot open statfile `%s': %s"),
statfile, mu_strerror (errno));
else
{
char c;
if (unlink (statfile))
mu_diag_output (MU_DIAG_ERR, _("cannot unlink statfile `%s': %s"),
statfile, mu_strerror (errno));
while ((c = fgetc (fp)) != EOF)
fputc (c, stdout);
fclose (fp);
}
}
return 0;
}
int
pies_stop ()
{
pid_t pid = pidfile_read (1);
if (pid == -1)
{
mu_diag_output (MU_DIAG_CRIT, _("pies is not running"));
return EX_USAGE;
}
mu_diag_output (MU_DIAG_INFO,
_("stopping pies at PID %lu"), (unsigned long) pid);
return kill (pid, SIGTERM) ? EX_SOFTWARE : 0;
}
static int
pies_cfg_verifier ()
{
return progman_build_depmap ();
}
int
main (int argc, char **argv)
{
int rc;
int index;
pid_t pid;
extern char **environ;
extern char *program_invocation_short_name; /* FIXME */
mf_init_nls ();
mf_proctitle_init (argc, argv, environ);
if (!program_invocation_short_name)
program_invocation_short_name = argv[0];
/* Set default logging */
log_setup (!stderr_closed_p ());
mu_acl_cfg_init ();
return_code_cfg_init ();
component_cfg_init ();
mu_app_cfg_verifier = pies_cfg_verifier;
mu_argp_init (program_version, package_bugreport);
mu_register_all_mailer_formats ();
rc = mu_app_init (&argp, capa, pies_cfg_param, argc, argv, 0, &index, NULL);
if (rc)
exit (EX_CONFIG);
log_setup (log_to_stderr);
if (argc != index && command != 'R')
{
mu_error ("extra command line arguments");
exit (EX_CONFIG);
}
progman_build_depmap ();
switch (command)
{
case OPT_RESTART:
mf_priv_setup (&pies_privs);
if (pies_umask)
umask (pies_umask);
exit (request_restart_components (argv + index));
case OPT_RELOAD:
exit (pies_reload ());
case OPT_STATUS:
exit (pies_status ());
case OPT_STOP:
exit (pies_stop ());
case OPT_DUMP_PREREQ:
progman_dump_prereq ();
exit (0);
case OPT_DUMP_DEPMAP:
progman_dump_depmap ();
exit (0);
default:
mf_priv_setup (&pies_privs);
if (pies_umask)
umask (pies_umask);
}
switch (pies_check_status (&pid))
{
case pies_status_ctr:
break;
case pies_status_stale:
case pies_status_noresp:
if (!force_option)
{
mu_error (_("another pies instance may be running (pid %lu), "
"use --force to override"),
(unsigned long) pid);
exit (EX_USAGE);
}
break;
case pies_status_running:
mu_error (_("another pies instance already running (pid %lu)"),
(unsigned long) pid);
exit (EX_USAGE);
}
mu_diag_output (MU_DIAG_INFO, _("%s starting"), program_version);
if (!foreground && daemon (0, 0) == -1)
{
mu_error (_("cannot become a daemon: %s"),
mu_strerror (errno));
exit (EX_SOFTWARE);
}
rc = mu_daemon_create_pidfile (pidfile);
if (rc)
mu_error (_("cannot create PID file `%s': %s"),
pidfile, mu_strerror (rc));
if (argv[0][0] != '/')
mu_diag_output (MU_DIAG_NOTICE,
N_("not started as an absolute pathname; "
"SIGHUP will not work"));
signal_setup (sig_handler);
progman_create_sockets ();
progman_start ();
do
{
if (!children_cleanup)
pies_pause ();
switch (action)
{
case ACTION_COMPRELOAD:
stop_components ();
progman_cleanup (0);
progman_start ();
action = ACTION_CONT;
break;
case ACTION_DUMPSTATS:
progman_dump_stats (statfile);
action = ACTION_CONT;
break;
}
if (action == ACTION_CONT)
{
if (children_cleanup)
{
children_cleanup = 0;
progman_cleanup (0);
}
if (got_alarm)
{
got_alarm = 0;
progman_wake_sleeping ();
}
}
}
while (action == ACTION_CONT);
progman_stop ();
if (action == ACTION_RESTART && argv[0][0] == '/')
{
int i;
for (i = getmaxfd (); i > 0; i--)
close (i);
mu_daemon_remove_pidfile ();
signal_setup (SIG_DFL);
execv (argv[0], argv);
}
mu_diag_output (MU_DIAG_INFO, _("%s terminated"), program_version);
exit (EX_OK);
}
void
xalloc_die ()
{
mu_error ("not enough memory");
abort ();
}
/*
Local Variables:
c-file-style: "gnu"
End:
*/
/* EOF */