master-service.c revision d2c41d6587f973d3b215e035288a07619bc22c2a
/* Copyright (c) 2005-2017 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "lib-signals.h"
#include "ioloop.h"
#include "path-util.h"
#include "array.h"
#include "strescape.h"
#include "env-util.h"
#include "home-expand.h"
#include "process-title.h"
#include "restrict-access.h"
#include "fd-close-on-exec.h"
#include "settings-parser.h"
#include "syslog-util.h"
#include "master-instance.h"
#include "master-login.h"
#include "master-service-ssl.h"
#include "master-service-private.h"
#include "master-service-settings.h"
#include <unistd.h>
#include <syslog.h>
#define MASTER_CONFIG_FILE_ENV "CONFIG_FILE"
/* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number */
#define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION"
/* when we're full of connections, how often to check if login state has
changed. we normally notice it immediately because of a signal, so this is
just a fallback against race conditions. */
#define MASTER_SERVICE_STATE_CHECK_MSECS 1000
/* If die callback hasn't managed to stop the service for this many seconds,
force it. */
struct master_service *master_service;
static void
const char *master_service_getopt_string(void)
{
return "c:i:ko:OL";
}
{
/* SIGINT comes either from master process or from keyboard. we don't
want to log it in either case.*/
i_warning("Killed with signal %d (by pid=%s uid=%s code=%s)",
/* never die when idling */
return;
/* SIGINT came from master. die only if we're not handling
any clients currently. */
return;
!service->idle_die_callback()) {
/* we don't want to die - send a notification to master
so it doesn't think we're ignoring it completely. */
return;
}
}
}
static void
{
}
{
i_fatal("Dovecot version mismatch: "
"(if you don't care, set version_ignore=yes)",
}
}
{
unsigned int i;
const char *value;
bool have_ssl_sockets = FALSE;
if (service->socket_count == 0)
return;
for (i = 0; i < service->socket_count; i++) {
l->fd = MASTER_LISTEN_FD_FIRST + i;
const char *const *settings =
settings++;
}
}
settings++;
}
}
}
}
struct master_service *
{
struct master_service *service;
unsigned int count;
const char *value;
#ifdef DEBUG
(flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
count = 0;
}
#endif
if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
/* make sure we can dump core, at least until
privileges are dropped. (i'm not really sure why this
is needed, because doing the same just before exec
doesn't help, and exec shouldn't affect this with
non-setuid/gid binaries..) */
}
/* NOTE: we start rooted, so keep the code minimal until
restrict_access_by_env() is called */
lib_init();
/* Set a logging prefix temporarily. This will be ignored once the log
is properly initialized */
/* make sure all the data stack allocations during init will be freed
before we get to ioloop. the corresponding t_pop() is in
master_service_init_finish(). */
if ((flags & MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME) == 0)
/* ignore these signals as early as possible */
/* keep getopt_str first in case it contains "+" */
else
if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
} else {
}
/* listener configuration */
i_fatal("Invalid SOCKET_COUNT environment");
T_BEGIN {
} T_END;
/* set up some kind of logging until we know exactly how and where
we want to log */
else
if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
/* initialize master_status structure */
/* set the default limit */
count == 0)
/* seve the process limit */
count > 0)
count > 0)
/* set the default service count */
count > 0)
/* set the idle kill timeout */
} else {
}
if ((flags & MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN) != 0) {
/* since we're going to keep the config socket open anyway,
open it now so we can read settings even after privileges
are dropped. */
}
return service;
}
{
int c;
service->getopt_str)) > 0) {
break;
}
return c;
}
bool master_getopt_str_is_valid(const char *str)
{
unsigned int i, j;
/* make sure there are no duplicates. there are few enough characters
that this should be fast enough. */
for (i = 0; str[i] != '\0'; i++) {
continue;
return FALSE;
}
}
return TRUE;
}
const char *prefix)
{
return;
}
/* logging via log service */
return;
}
return;
}
/* error logging goes to file or stderr */
}
/* something gets logged to syslog */
int facility;
&facility))
/* set error handlers back to file */
}
}
if (*path != '\0')
}
if (*path != '\0')
}
}
bool set)
{
}
void (*callback)(void))
{
}
bool (*callback)(void))
{
}
{
struct master_instance_list *list;
const struct master_instance *inst;
const char *instance_path, *path;
/* note that we don't have any settings yet. we're just finding out
which dovecot.conf we even want to read! so we must use the
hardcoded state_dir path. */
const char *error;
}
}
{
const char *path;
switch (opt) {
case 'c':
break;
case 'i':
break;
case 'k':
break;
case 'o':
break;
case 'O':
break;
case 'L':
break;
default:
return FALSE;
}
return TRUE;
}
{
else {
service);
service->die_callback();
}
}
}
{
/* status fd is a write-only pipe, so if we're here it means the
master wants us to die (or died itself). don't die until all
service connections are finished. */
/* the log fd may also be closed already, don't die when trying to
log later */
}
{
/* set default signal handlers */
}
i_fatal("Must be started by dovecot master process");
/* start listening errors for status fd, it means master died */
}
if (service->want_ssl_settings &&
/* we already have a connection to be served */
}
/* close data stack frame opened by master_service_init() */
i_panic("Leaked t_pop() call");
}
}
void master_service_env_clean(void)
{
env_clean();
else T_BEGIN {
} T_END;
}
unsigned int client_limit)
{
unsigned int used;
}
{
return service->total_available_count;
}
{
return service->process_limit;
}
{
return service->process_min_avail;
}
{
return service->idle_kill_secs;
}
unsigned int count)
{
unsigned int used;
}
}
{
return service->service_count_left;
}
{
return service->socket_count;
}
int listen_fd)
{
unsigned int i;
i = listen_fd - MASTER_LISTEN_FD_FIRST;
}
void (*callback)(void))
{
}
{
return service->config_path;
}
{
return service->version_string;
}
{
}
{
}
{
}
{
unsigned int current_count;
return;
/* make sure we stop after servicing current connections */
if (current_count == 0)
else {
/* notify master that we're not accepting any more
connections */
}
}
{
}
{
}
{
return;
if (ret < 0) {
/* anvil process was probably recreated, don't bother
logging an error about losing connection to it */
return;
}
i_error("write(anvil) failed: %m");
} else if (ret == 0)
i_error("write(anvil) failed: EOF");
else {
}
}
{
}
{
/* more concurrent clients can still be added */
return TRUE;
}
/* after handling this client, the whole process will stop. */
return FALSE;
}
/* overflow callback is set. it's possible that the current
existing client may be replaced by a new client, which needs
the listener to try to accept new connections. */
return TRUE;
}
/* the listener isn't needed until the current client is disconnected */
return FALSE;
}
struct master_service_connection *conn)
{
i_error("close(service connection) failed: %m");
/* reading FIFOs stays open forever, don't count them
as real clients */
}
if (!master_service_want_listener(service)) {
/* we're not going to accept any more connections after
this. go ahead and close the connection early. don't
do this before calling callback, because it may want
to access the listen_fd (e.g. to check socket
permissions). */
}
}
}
struct master_service_connection *conn)
{
}
{
}
{
/* we can listen again */
} else {
}
if (service->service_count_left == 0) {
/* we've finished handling all clients, and
a) master has closed the connection
b) there are no listeners (std-client?) */
} else {
}
}
enum master_login_state state)
{
switch (state) {
return;
/* some processes should now be able to handle new connections,
although we can't. but there may be race conditions, so
make sure that we'll check again soon if the state has
changed to "full" without our knowledge. */
service);
return;
case MASTER_LOGIN_STATE_FULL:
/* make sure we're listening for more connections */
return;
}
}
{
int ret;
if (ret < 0)
i_error("lseek(login notify fd) failed: %m");
else
}
{
i_error("close(master config fd) failed: %m");
}
}
{
unsigned int i;
}
/* run atexit callbacks before destroying ioloop */
for (i = 0; i < service->socket_count; i++)
lib_deinit();
}
static void master_service_listen(struct master_service_listener *l)
{
struct master_service_connection conn;
/* we are full. stop listening for now, unless overflow
callback destroys one of the existing connections */
if (service->call_avail_overflow &&
return;
}
}
int orig_errno = errno;
return;
/* it's not a socket. should be a fifo. */
/* BSDI fails accept(fifo) with EINVAL. */
} else {
errno = orig_errno;
i_error("net_accept() failed: %m");
/* try again later after one of the existing
connections has died */
return;
}
/* use the "listener" as the connection fd and stop the
listener. */
l->fd = -1;
}
if (l->haproxy)
else
}
{
unsigned int i;
return;
for (i = 0; i < service->socket_count; i++) {
}
}
}
{
unsigned int i;
for (i = 0; i < service->socket_count; i++) {
}
}
{
unsigned int i;
for (i = 0; i < service->socket_count; i++) {
}
}
{
unsigned int i;
/* close via listeners. some fds might be pipes that are
currently handled as clients. we don't want to close them. */
for (i = 0; i < service->socket_count; i++) {
i_error("close(listener %d) failed: %m",
}
}
}
}
{
return TRUE;
if (!service->initial_status_sent)
return TRUE;
return FALSE;
}
static void
{
sizeof(service->master_status));
/* success */
/* delayed important update sent successfully */
}
} else if (ret >= 0) {
/* shouldn't happen? */
/* failure */
i_error("write(master_status_fd) failed: %m");
} else if (important_update) {
/* reader is busy, but it's important to get this notification
through. send it when possible. */
}
}
}
{
bool important_update;
used_count));
} T_END;
/* a) closed, b) updating to same state */
return;
}
!important_update) {
/* don't spam master */
else {
service);
}
return;
}
}
unsigned major_version)
{
unsigned int minor_version;
}
unsigned major_version,
unsigned int *minor_version_r)
{
bool ret;
return FALSE;
line += 8;
return FALSE;
T_BEGIN {
if (p == NULL)
else {
}
} T_END;
return ret;
}