/* This file is part of Pies.
Copyright (C) 2008, 2009 Sergey Poznyakoff
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.
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 Pies. If not, see . */
#include "pies.h"
#include
#include
#include "meta1lex.h"
char *conffile = SYSCONFDIR "/pies.conf";
int preprocess_only; /* Preprocess config, do nothing more */
int lint_mode; /* Test configuration syntax and exit */
int log_to_stderr; /* Use stderr for logging */
int log_facility = LOG_USER;
char *log_tag;
struct pies_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;
pies_acl_t pies_acl;
limits_record_t pies_limits;
int force_option;
/* Logging */
void
log_setup (int want_stderr)
{
if (!want_stderr)
openlog (log_tag, LOG_PID, log_facility);
}
static int
stderr_closed_p ()
{
int fd = dup (0);
if (fd < 0)
return 1;
close (fd);
return fd <= 2;
}
int
assert_grecs_value_type (grecs_locus_t * locus,
const grecs_value_t * value, int type)
{
if (!value || value->type != type)
{
grecs_error (locus, 0, _("expected %s, but found %s"),
grecs_data_type_string (type),
grecs_data_type_string (value->type));
return 1;
}
return 0;
}
int
assert_scalar_stmt (grecs_locus_t * locus, enum grecs_callback_command cmd)
{
if (cmd != grecs_callback_set_value)
{
grecs_error (locus, 0, _("Unexpected block statement"));
return 1;
}
return 0;
}
static int
_cb_action (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
enum return_action *pact = varptr;
static struct tokendef actab[] = {
{"disable", action_disable},
{"restart", action_restart},
{NULL}
};
int res;
if (assert_scalar_stmt (locus, cmd)
|| assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
return 1;
if (strtotok (actab, value->v.string, &res))
{
grecs_error (locus, 0, _("unknown action code: %s"), value->v.string);
return 1;
}
*pact = res;
return 0;
}
struct grecs_keyword return_code_keywords[] = {
{"action",
/* TRANSLATORS: disable and restart are keywords, do not translate them. */
N_("arg: {disable | restart}"),
N_("Specifies action to take when a component finishes with this "
"return code."),
grecs_type_string, NULL, offsetof (struct component, act_temp.act),
_cb_action,
},
{"notify",
N_("arg: emails"),
N_("Notify this address when then component terminates."),
grecs_type_string, NULL, offsetof (struct component, act_temp.addr)
},
{"message",
NULL,
N_("Notification message text (with headers)."),
grecs_type_string, NULL, offsetof (struct component, act_temp.message),
NULL},
{"exec",
NULL,
N_("Execute this command."),
grecs_type_string, NULL,
offsetof (struct component, act_temp.command),
NULL,
},
{NULL}
};
#define S(s) { #s, s }
static struct tokendef ex_tokendef[] = {
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 tokendef sig_tokendef[] = {
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,
grecs_locus_t * locus,
grecs_value_t * val, int argc,
const char *(*getarg) (grecs_value_t *, int, grecs_locus_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, locus), "*") == 0))
allflag = 1;
else
{
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)
{
grecs_error (locus, 0, _("%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)
{
grecs_error (locus, 0, _("%s: not a number"), p);
continue;
}
}
else if (strtotok_ci (sig_tokendef, arg, &n))
{
grecs_error (locus, 0, _("%s: not a signal code"), arg);
continue;
}
n |= STATUS_SIG_BIT;
}
else if (strtotok_ci (ex_tokendef, arg, &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;
}
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 (grecs_value_t * val, int num, grecs_locus_t * locus)
{
if (num != 0)
return NULL;
return val->v.string;
}
const char *
_get_array_arg (grecs_value_t * val, int num, grecs_locus_t * locus)
{
if (num < val->v.arg.c)
{
if (assert_grecs_value_type (locus, val, GRECS_TYPE_STRING) == 0)
return val->v.arg.v[num].v.string;
}
return NULL;
}
const char *
_get_list_arg (grecs_value_t * val, int num, grecs_locus_t * locus)
{
grecs_value_t *elt = (grecs_value_t *) gl_list_get_at (val->v.list, num);
if (!elt)
{
grecs_error (locus, 0, _("cannot get list item"));
}
else if (assert_grecs_value_type (locus, elt, GRECS_TYPE_STRING) == 0)
return elt->v.string;
return NULL;
}
static int
return_code_section_parser (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr,
grecs_value_t * value, void *cb_data)
{
struct component *comp = varptr;
size_t count;
struct action *act;
switch (cmd)
{
case grecs_callback_section_begin:
if (!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 = gl_list_size (value->v.list);
act = create_action (comp, locus, value, count, _get_list_arg);
}
*(struct component **) cb_data = comp;
if (!act)
return 1;
memset (&comp->act_temp, 0, sizeof (comp->act_temp));
break;
case grecs_callback_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;
case grecs_callback_set_value:
grecs_error (locus, 0, _("invalid use of block statement"));
}
return 0;
}
static char **
config_array_to_argv (grecs_value_t *val, grecs_locus_t *locus)
{
int i, j;
int argc;
char **argv;
argc = val->v.arg.c;
argv = xcalloc (argc + 1, sizeof (argv[0]));
for (i = j = 0; i < argc; i++)
{
if (assert_grecs_value_type (locus, &val->v.arg.v[i], GRECS_TYPE_STRING)
== 0)
argv[j++] = xstrdup (val->v.arg.v[i].v.string);
}
argv[j] = NULL;
return argv;
}
static int
_cb_command (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
int argc;
char **argv, ***pargv = varptr;
struct wordsplit ws;
switch (value->type)
{
case GRECS_TYPE_STRING:
if (wordsplit (value->v.string, &ws, WRDSF_DEFFLAGS))
{
grecs_error (locus, 0, "wordsplit: %s", strerror (errno));
return 1;
}
argc = ws.ws_wordc;
argv = ws.ws_wordv;
ws.ws_wordv = NULL;
break;
case GRECS_TYPE_ARRAY:
argv = config_array_to_argv (value, locus);
break;
case GRECS_TYPE_LIST:
grecs_error (locus, 0, _("unexpected list"));
return 1;
}
*pargv = argv;
return 0;
}
static int
_cb_umask (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
mode_t *pmode = varptr;
char *p;
unsigned long n;
if (assert_scalar_stmt (locus, cmd)
|| assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
return 1;
n = strtoul (value->v.string, &p, 8);
if (*p)
{
grecs_error (locus, 0, _("invalid octal number"));
return 1;
}
*pmode = n;
return 0;
}
static int
_cb_env (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
int argc;
char **argv;
char ***penv = varptr;
struct wordsplit ws;
switch (value->type)
{
case GRECS_TYPE_STRING:
if (wordsplit (value->v.string, &ws, WRDSF_DEFFLAGS))
{
grecs_error (locus, 0, "wordsplit: %s", strerror (errno));
return 1;
}
argc = ws.ws_wordc;
argv = ws.ws_wordv;
ws.ws_wordv = NULL;
break;
case GRECS_TYPE_ARRAY:
argv = config_array_to_argv (value, locus);
break;
case GRECS_TYPE_LIST:
grecs_error (locus, 0, _("unexpected list"));
return 1;
}
*penv = argv;
return 0;
}
int
string_to_syslog_priority (const char *key, int *pres)
{
static struct tokendef tokdef_prio[] = {
{"EMERG", LOG_EMERG},
{"ALERT", LOG_ALERT},
{"CRIT", LOG_CRIT},
{"ERR", LOG_ERR},
{"WARNING", LOG_WARNING},
{"NOTICE", LOG_NOTICE},
{"INFO", LOG_INFO},
{"DEBUG", LOG_DEBUG},
{NULL}
};
return strtotok_ci (tokdef_prio, key, pres);
}
int
string_to_syslog_facility (const char *key, int *pres)
{
static struct tokendef tokdef_fac[] = {
{"auth", LOG_AUTH},
{"authpriv", LOG_AUTHPRIV},
{"cron", LOG_CRON},
{"daemon", LOG_DAEMON},
{"ftp", LOG_FTP},
{"kern", LOG_KERN},
{"lpr", LOG_LPR},
{"mail", LOG_MAIL},
{"news", LOG_NEWS},
{"syslog", LOG_SYSLOG},
{"user", LOG_USER},
{"uucp", LOG_UUCP},
{"local0", LOG_LOCAL0},
{"local1", LOG_LOCAL1},
{"local2", LOG_LOCAL2},
{"local3", LOG_LOCAL3},
{"local4", LOG_LOCAL4},
{"local5", LOG_LOCAL5},
{"local6", LOG_LOCAL6},
{"local7", LOG_LOCAL7},
{NULL}
};
return strtotok_ci (tokdef_fac, key, pres);
}
static int
cb_syslog_facility (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
const char *str;
if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
return 1;
str = value->v.string;
if (c_isdigit (str[0]))
{
char *p;
int n = strtoul (str, &p, 10);
if (*p)
grecs_error (locus, 0,
_("expected facility number or symbolic name"));
else
*(int *) varptr = n;
}
else if (string_to_syslog_facility (str, varptr))
grecs_error (locus, 0, _("unknown syslog facility `%s'"), str);
return 0;
}
static int
_cb_redir (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
struct redirector *rp = varptr;
static struct tokendef redirtab[] = {
{"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;
break;
}
rp->type = redir_syslog;
if (string_to_syslog_priority (value->v.string, &rp->v.prio))
{
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"),
value->v.arg.v[0].v.string);
else
{
if (res != redir_null)
{
if (value->v.arg.c != 2)
{
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 = xstrdup (value->v.arg.v[1].v.string);
break;
}
}
rp->type = res;
}
break;
default:
grecs_error (locus, 0, _("unexpected list"));
}
return 0;
}
static int
_cb_url (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
struct pies_url *url;
if (assert_scalar_stmt (locus, cmd)
|| assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
return 1;
if (pies_url_create (&url, value->v.string))
{
grecs_error (locus, 0, _("%s: cannot create URL: %s"),
value->v.string, strerror (errno));
return 0;
}
*(struct pies_url **) varptr = url;
return 0;
}
static struct tokendef 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 (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
int res;
if (assert_scalar_stmt (locus, cmd)
|| assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
return 1;
if (strtotok (modetab, value->v.string, &res))
grecs_error (locus, 0, _("%s: unrecognised mode"), value->v.string);
else
*(enum pies_comp_mode *) varptr = res;
return 0;
}
static int
_cb_limits (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
limits_record_t *plrec = varptr;
char *p;
if (assert_scalar_stmt (locus, cmd)
|| assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
return 1;
if (parse_limits (plrec, (char *) value->v.string, &p))
grecs_error (locus, 0, _("invalid limit string (near %s)"), p);
return 0;
}
struct grecs_keyword component_keywords[] = {
{"mode",
/* TRANSLATORS: The words between '{' and '}' are keywords, do not
translate them. */
N_
("mode: {exec | wait | accept | inetd | nostartaccept | pass-fd | pass}"),
N_("Component execution mode."),
grecs_type_string, NULL, offsetof (struct component, mode),
_cb_mode,
},
{"program",
NULL,
N_("Full name of the program."),
grecs_type_string, NULL,
offsetof (struct component, program),
NULL,
},
{"command",
NULL,
N_("Command line."),
grecs_type_string, NULL, offsetof (struct component, argv),
_cb_command,
},
{"prerequisites",
N_("list"),
N_("List of prerequisites."),
grecs_type_string | GRECS_LIST, NULL, offsetof (struct component, prereq),
NULL,
},
{"dependents",
N_("list"),
N_("List of components for which this one is a prerequisite."),
grecs_type_string | GRECS_LIST, NULL, offsetof (struct component, depend),
NULL,
},
{"disable",
NULL,
N_("Disable this entry."),
grecs_type_bool, NULL, offsetof (struct component, disabled),
NULL,
},
{"settle-timeout",
NULL,
N_("Time to wait before starting this component."),
grecs_type_uint, NULL,
offsetof (struct component, settle_timeout),
NULL,
},
{"precious",
NULL,
N_("Mark this entry as precious."),
grecs_type_bool, NULL,
offsetof (struct component, precious),
NULL,
},
{"socket",
N_("url: string"),
N_("Listen on the given url."),
grecs_type_string, NULL,
offsetof (struct component, socket_url),
_cb_url,
},
{"pass-fd-socket",
N_("name"),
N_("Pass fd through this socket."),
grecs_type_string, NULL,
offsetof (struct component, pass_fd_socket),
NULL,
},
{"acl",
N_("name: string"),
N_("Define ACL."),
grecs_type_section, NULL, 0,
acl_section_parser, NULL, acl_keywords},
{"remove-file",
N_("file"),
N_("Remove file before starting the component."),
grecs_type_string, NULL, offsetof (struct component, rmfile),
NULL,
},
{"facility",
N_("arg"),
N_("Override default syslog facility for this component."),
grecs_type_string, NULL, offsetof (struct component, facility),
cb_syslog_facility,
},
{"stdout",
/* TRANSLATORS: file and syslog are keywords. Do not translate them. */
N_("type: {file | syslog}> "),
NULL, /* FIXME: Docstring? */
grecs_type_section, NULL, 0,
return_code_section_parser, NULL, return_code_keywords},
{NULL}
};
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;
}
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, grecs_locus_t * locus)
{
int header = 0;
int i;
#define COMPERR(fmt, arg) \
do \
{ \
if (!header) \
{ \
grecs_error (locus, 0, _("in component %s:"), comp->tag); \
header = 1; \
} \
grecs_error (locus, 0, 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
}
struct component *
component_create (const char *name)
{
struct component *comp = progman_lookup_component (name);
if (!comp)
{
comp = xzalloc (sizeof (*comp));
comp->facility = log_facility;
comp->redir[RETR_OUT].type = comp->redir[RETR_ERR].type = redir_null;
comp->tag = xstrdup (name);
}
return comp;
}
void
component_finish (struct component *comp, grecs_locus_t *locus)
{
if (component_verify (comp, locus) == 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);
}
}
static int
component_section_parser (enum grecs_callback_command cmd,
grecs_locus_t * locus,
void *varptr, grecs_value_t * value, void *cb_data)
{
struct component *comp;
void **section_data = cb_data;
switch (cmd)
{
case grecs_callback_section_begin:
if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
return 1;
comp = component_create (value->v.string);
*section_data = comp;
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;
}
/* syslog */
static struct grecs_keyword syslog_kw[] = {
{"facility",
N_("name"),
N_("Set syslog facility. Arg is one of the following: user, daemon, "
"auth, authpriv, mail, cron, local0 through local7 (case-insensitive), "
"or a facility number."),
grecs_type_string, &log_facility, 0, cb_syslog_facility},
{"tag", N_("string"), N_("Tag syslog messages with this string"),
grecs_type_string, &log_tag},
# if 0
{"print-priority", N_("arg"), N_("Prefix each message with its priority"),
grecs_type_bool, &syslog_include_prio},
#endif
{NULL},
};
struct component default_component;
static int
_cm_include_meta1 (enum grecs_callback_command cmd,
grecs_locus_t *locus,
void *varptr, grecs_value_t *value, void *cb_data)
{
if (assert_grecs_value_type (locus, value, GRECS_TYPE_STRING))
return 1;
meta1_config_parse (value->v.string);
return 0;
}
struct grecs_keyword pies_keywords[] = {
/* FIXME */
{"component",
N_(""),
NULL, /* FIXME: Docstring */
grecs_type_section, NULL, 0,
component_section_parser, NULL, component_keywords},
{"syslog",
NULL,
N_("Configure syslog logging"),
grecs_type_section, NULL, 0, NULL, NULL, syslog_kw},
{"debug",
NULL,
N_("Set debug verbosity level."),
grecs_type_uint, &debug_level, 0, NULL},
{"source-info",
NULL,
N_("Show source info with debugging messages."),
grecs_type_bool, &source_info_option, 0, NULL},
{"pidfile",
NULL,
N_("Write PID to this file."),
grecs_type_string, &pidfile, 0,
NULL,
},
{"control-file",
NULL,
N_("Set location of the control file."),
grecs_type_string, &ctlfile, 0,
NULL,
},
{"stat-file",
NULL,
N_("Set location of the statistics output file."),
grecs_type_string, &statfile, 0,
NULL,
},
{"user",
NULL,
N_("Run with this user privileges."),
grecs_type_string, &pies_privs.user, 0,
NULL,
},
{"group",
NULL,
N_("Retain supplementary group."),
grecs_type_string | GRECS_LIST, /*FIXME*/ &pies_privs.groups, 0,
NULL,
},
{"allgroups",
NULL,
N_("Retain all supplementary groups of which user is a member."),
grecs_type_bool, &pies_privs.allgroups, 0,
NULL,
},
{"umask",
N_("arg: number"),
N_("Force this umask."),
grecs_type_string, &pies_umask, 0,
_cb_umask,
},
{"limits",
NULL,
N_("Set global system limits."),
grecs_type_string, &pies_limits, 0, _cb_limits,
},
{"shutdown-timeout",
"n",
N_("Wait seconds for all components to shut down."),
grecs_type_uint, &shutdown_timeout, 0,
NULL,
},
{"return-code",
N_(""),
NULL, /* FIXME: Docstring? */
grecs_type_section, &default_component, 0,
return_code_section_parser, NULL, return_code_keywords},
{"acl",
N_("name: string"),
N_("Define ACL."),
grecs_type_section, NULL, 0,
acl_section_parser, NULL, acl_keywords},
{"include-meta1",
N_("file: string"),
N_("Include components from the specified MeTA1 configuration file."),
grecs_type_string, NULL, 0,
_cm_include_meta1,
},
{"meta1-queue-dir",
NULL,
N_("Set name of MeTA1 queue directory (default /var/spool/meta1)."),
grecs_type_string, &meta1_queue_dir, 0,
NULL,
},
{NULL}
};
void
config_init ()
{
grecs_set_keywords (pies_keywords);
grecs_include_path_setup (DEFAULT_VERSION_INCLUDE_DIR,
DEFAULT_INCLUDE_DIR, NULL);
grecs_preprocessor = DEFAULT_PREPROCESSOR;
grecs_log_to_stderr = log_to_stderr;
}
void
config_help ()
{
static char docstring[] =
N_("Configuration file structure for pies.\n"
"For more information, use `info pies configuration'.");
grecs_format_docstring (stdout, docstring, 0);
grecs_format_statement_array (stdout, pies_keywords, 1, 0);
}
const char *program_version = "pies (" PACKAGE_STRING ")";
const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">";
static char doc[] = N_("pies -- process invocation and execution supervisor");
static char args_doc[] = "";
enum
{
OPT_FOREGROUND = 256,
OPT_SYSLOG,
OPT_STDERR,
OPT_DUMP_PREREQ,
OPT_DUMP_DEPMAP,
OPT_FORCE,
OPT_CONFIG_HELP,
OPT_SOURCE_INFO
};
#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"),},
{"debug", 'x', N_("LEVEL"), 0,
N_("set debug verbosity level"), GRP + 1},
{"source-info", OPT_SOURCE_INFO, NULL, 0,
N_("show source info with debugging messages"), GRP + 1},
{"force", OPT_FORCE, NULL, 0,
N_("force startup even if another instance may be running"), GRP + 1},
{"lint", 't', NULL, 0,
N_("parse configuration file and exit"), GRP + 1},
{NULL, 'E', NULL, 0,
N_("preprocess config and exit"), GRP + 1},
{"config-file", 'c', N_("FILE"), 0,
N_("use FILE instead of the default configuration"), GRP + 1},
{"config-help", OPT_CONFIG_HELP, NULL, 0,
N_("show configuration file summary"), 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)
{
switch (key)
{
case 't':
lint_mode = 1;
break;
case 'E':
preprocess_only = 1;
break;
case 'c':
conffile = arg;
break;
case OPT_CONFIG_HELP:
config_help ();
exit (0);
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':
debug_level = strtoul (arg, NULL, 0);
break;
case OPT_SOURCE_INFO:
source_info_option = 1;
break;
case OPT_FORCE:
force_option = 1;
break;
case ARGP_KEY_INIT:
break;
case ARGP_KEY_FINI:
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;
logmsg (LOG_NOTICE, "received signal %d", sig);
break;
case SIGHUP:
logmsg (LOG_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)
logmsg (LOG_ERR,
_("cannot open pid file `%s': %s"),
pidfile, strerror (errno));
return -1;
}
while ((c = fgetc (fp)) != EOF)
{
if (isdigit (c))
n = n * 10 + c - '0';
else if (c == '\n')
break;
else
{
logmsg (LOG_ERR,
_("unexpected character %#03o in pidfile `%s'"),
c, pidfile);
return -1;
}
}
fclose (fp);
if (n && kill (n, 0))
{
logmsg (LOG_ERR,
_("cannot signal master process %lu: %s"),
(unsigned long) n, 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 <= 0)
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;
logmsg (LOG_INFO, _("stopping components"));
fp = fopen (ctlfile, "r");
if (!fp)
{
logmsg (LOG_ERR, _("cannot open control file `%s': %s"),
ctlfile, strerror (errno));
return;
}
if (unlink (ctlfile))
{
logmsg (LOG_ERR, _("cannot unlink control file `%s': %s"),
ctlfile, 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)
{
logmsg (LOG_ERR, _("cannot open control file `%s': %s"),
ctlfile, 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)
{
logmsg (LOG_CRIT, _("pies is not running"));
return 1;
}
logmsg (LOG_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)
logmsg (LOG_ERR, _("cannot unlink statfile `%s': %s"),
statfile, strerror (errno));
switch (pies_check_status (&pid))
{
case pies_status_ctr:
logmsg (LOG_INFO, _("pies is not running"));
break;
case pies_status_stale:
logmsg (LOG_INFO,
_("pies is not running, but a pidfile "
"is found (pid %lu)"), (unsigned long) pid);
return 1; /* FIXME: hm? */
case pies_status_noresp:
logmsg (LOG_INFO,
_("pies seems to run with pid %lu, but is not responding"),
(unsigned long) pid);
if (unlink (statfile))
logmsg (LOG_ERR, _("cannot unlink statfile `%s': %s"),
statfile, strerror (errno));
return 2;
case pies_status_running:
fp = fopen (statfile, "r");
if (!fp)
logmsg (LOG_ERR, _("cannot open statfile `%s': %s"),
statfile, strerror (errno));
else
{
char c;
if (unlink (statfile))
logmsg (LOG_ERR, _("cannot unlink statfile `%s': %s"),
statfile, 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)
{
logmsg (LOG_CRIT, _("pies is not running"));
return EX_USAGE;
}
logmsg (LOG_INFO, _("stopping pies at PID %lu"), (unsigned long) pid);
return kill (pid, SIGTERM) ? EX_SOFTWARE : 0;
}
/* Pidfile */
/* Check whether pidfile NAME exists and if so, whether its PID is still
active. Exit if it is. */
void
check_pidfile (char *name)
{
unsigned long pid;
FILE *fp = fopen (name, "r");
if (!fp)
{
if (errno == ENOENT)
return;
logmsg (LOG_ERR, _("cannot open pidfile `%s': %s"),
name, strerror (errno));
exit (EX_TEMPFAIL);
}
if (fscanf (fp, "%lu", &pid) != 1)
{
logmsg (LOG_ERR, ("cannot get pid from pidfile `%s'"), name);
}
else
{
if (kill (pid, 0) == 0)
{
logmsg (LOG_ERR,
_
("%s appears to run with pid %lu. If it does not, remove `%s' and retry."),
program_name, pid, name);
exit (EX_USAGE);
}
}
fclose (fp);
if (unlink (pidfile))
{
logmsg (LOG_ERR, _("cannot unlink pidfile `%s': %s"),
name, strerror (errno));
exit (EX_USAGE);
}
}
void
create_pidfile (char *name)
{
FILE *fp = fopen (name, "w");
if (!fp)
{
logmsg (LOG_ERR, _("cannot create pidfile `%s': %s"),
name, strerror (errno));
exit (EX_TEMPFAIL);
}
fprintf (fp, "%lu", (unsigned long) getpid ());
fclose (fp);
}
void
remove_pidfile (char *name)
{
if (unlink (name))
logmsg (LOG_ERR, _("cannot unlink pidfile `%s': %s"),
name, strerror (errno));
}
const char version_etc_copyright[] =
/* Do *not* mark this string for translation. %s is a copyright
symbol suitable for this locale */
"Copyright %s 2009 Sergey Poznyakoff";
static void
version (FILE *stream, struct argp_state *state)
{
fprintf (stream, "%s (%s) %s\n", "pies", PACKAGE, PACKAGE_VERSION);
/* TRANSLATORS: Translate "(C)" to the copyright symbol
(C-in-a-circle), if this symbol is available in the user's
locale. Otherwise, do not translate "(C)"; leave it as-is. */
fprintf (stream, version_etc_copyright, _("(C)"));
fputs (_("\
\n\
License GPLv3+: GNU GPL version 3 or later \n\
This is free software: you are free to change and redistribute it.\n\
There is NO WARRANTY, to the extent permitted by law.\n\
\n\
"),
stream);
/* TRANSLATORS: %s denotes an author name. */
fprintf (stream, _("Written by %s.\n"), "Sergey Poznyakoff");
}
int
main (int argc, char **argv)
{
int index;
pid_t pid;
extern char **environ;
set_program_name (argv[0]);
#ifdef ENABLE_NLS
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
bindtextdomain ("mailfromd", LOCALEDIR);
textdomain (PACKAGE);
#endif
mf_proctitle_init (argc, argv, environ);
/* Set default logging */
log_setup (!stderr_closed_p ());
config_init ();
argp_program_version_hook = version;
if (argp_parse (&argp, argc, argv, 0, &index, NULL))
exit (EX_USAGE);
if (preprocess_only)
exit (grecs_preproc_run (conffile, grecs_preprocessor) ? EX_CONFIG : 0);
if (grecs_parse (conffile))
exit (EX_CONFIG);
if (lint_mode)
{
progman_build_depmap ();
exit (0);
}
/* Re-setup logging: it might have been reset in the config file */
log_setup (log_to_stderr);
if (argc != index && command != 'R')
{
logmsg (LOG_ERR, "extra command line arguments");
exit (EX_CONFIG);
}
progman_build_depmap ();
switch (command)
{
case OPT_RESTART:
pies_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:
pies_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)
{
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);
}
logmsg (LOG_INFO, _("%s starting"), program_version);
if (!foreground)
check_pidfile (pidfile);
if (!foreground && daemon (0, 0) == -1)
{
logmsg (LOG_ERR, _("cannot become a daemon: %s"), strerror (errno));
exit (EX_SOFTWARE);
}
create_pidfile (pidfile);
if (argv[0][0] != '/')
logmsg (LOG_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);
remove_pidfile (pidfile);
signal_setup (SIG_DFL);
execv (argv[0], argv);
}
logmsg (LOG_INFO, _("%s terminated"), program_version);
exit (EX_OK);
}
void
xalloc_die ()
{
logmsg (LOG_CRIT, _("not enough memory"));
abort ();
}
/* EOF */