}
static int
-master_service_read_config(struct master_service *service,
+master_service_read_config(struct master_service *service, const char *path,
const struct master_service_settings_input *input,
const char **error_r)
{
- const char *path;
struct stat st;
int fd, ret;
- path = master_service_get_config_path(service);
if (service->config_fd != -1) {
fd = service->config_fd;
service->config_fd = -1;
const struct setting_parser_info *tmp_root;
struct setting_parser_context *parser;
struct istream *istream;
- const char *error, *env, *const *keys;
+ const char *path, *error, *env, *const *keys;
void **sets;
unsigned int i;
int ret, fd = -1;
if (getenv("DOVECONF_ENV") == NULL && !service->default_settings) {
- fd = master_service_read_config(service, input, error_r);
+ path = input->config_path != NULL ? input->config_path :
+ master_service_get_config_path(service);
+ fd = master_service_read_config(service, path, input, error_r);
if (fd == -1)
return -1;
}
if (service->set_pool != NULL) {
- settings_parser_deinit(&service->set_parser);
+ if (service->set_parser != NULL)
+ settings_parser_deinit(&service->set_parser);
p_clear(service->set_pool);
} else {
service->set_pool =
return master_service_settings_read(service, &input, error_r);
}
+pool_t master_service_settings_detach(struct master_service *service)
+{
+ pool_t pool = service->set_pool;
+
+ settings_parser_deinit(&service->set_parser);
+ service->set_pool = NULL;
+ return pool;
+}
+
const struct master_service_settings *
master_service_settings_get(struct master_service *service)
{
struct master_service_settings_input {
const struct setting_parser_info **roots;
const struct dynamic_settings_parser *dyn_parsers;
+ const char *config_path;
bool preserve_home;
const char *module;
int master_service_settings_read_simple(struct master_service *service,
const struct setting_parser_info **roots,
const char **error_r);
+/* destroy settings parser and clear service's set_pool, so that
+ master_service_settings_read*() can be called without freeing memory used
+ by existing settings structures. */
+pool_t master_service_settings_detach(struct master_service *service);
+
const struct master_service_settings *
master_service_settings_get(struct master_service *service);
void **master_service_settings_get_others(struct master_service *service);
#define DOVECOT_CONFIG_BIN_PATH BINDIR"/doveconf"
+#define MASTER_SERVICE_NAME "master"
#define FATAL_FILENAME "master-fatal.lastlog"
#define MASTER_PID_FILE_NAME "master.pid"
#define SERVICE_TIME_MOVED_BACKWARDS_MAX_THROTTLE_SECS (60*3)
static char *pidfile_path;
static struct service_list *services;
static fatal_failure_callback_t *orig_fatal_callback;
-
static const char *child_process_env[3]; /* @UNSAFE */
+static const struct setting_parser_info *set_roots[] = {
+ &master_setting_parser_info,
+ NULL
+};
+
void process_exec(const char *cmd, const char *extra_args[])
{
const char *executable, *p, **argv;
sig_settings_reload(const siginfo_t *si ATTR_UNUSED,
void *context ATTR_UNUSED)
{
- struct master_settings *new_set;
+ struct master_service_settings_input input;
+ const struct master_settings *set;
+ void **sets;
struct service_list *new_services;
const char *error;
- pool_t pool;
/* see if hostname changed */
hostpid_init();
-#if 0 // FIXME
- /* FIXME: this loses process structures for existing processes.
- figure out something. */
- new_set = master_settings_read(pool, config_binary, config_path);
- new_services = new_set == NULL ? NULL :
- services_create(new_set, child_process_env, &error);
-#endif
- if (new_services == NULL) {
+ memset(&input, 0, sizeof(input));
+ input.roots = set_roots;
+ input.module = MASTER_SERVICE_NAME;
+ input.config_path = services_get_config_socket_path(services);
+ if (master_service_settings_read(master_service, &input, &error) < 0) {
+ i_error("Error reading configuration: %s", error);
+ return;
+ }
+ sets = master_service_settings_get_others(master_service);
+ set = sets[0];
+
+ if (services_create(set, child_process_env,
+ &new_services, &error) < 0) {
/* new configuration is invalid, keep the old */
i_error("Config reload failed: %s", error);
return;
}
+ new_services->config->config_file_path =
+ p_strdup(new_services->pool,
+ services->config->config_file_path);
/* switch to new configuration. */
(void)services_listen_using(new_services, services);
services_destroy(services);
- services = new_services;
+ services = new_services;
services_monitor_start(services);
}
static void
sig_reap_children(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
{
- services_monitor_reap_children(services);
+ services_monitor_reap_children();
}
static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED)
i_free(pidfile_path);
services_destroy(services);
+ service_pids_deinit();
}
static const char *get_full_config_path(struct service_list *list)
int main(int argc, char *argv[])
{
- static const struct setting_parser_info *set_roots[] = {
- &master_setting_parser_info,
- NULL
- };
struct master_settings *set;
unsigned int child_process_env_idx = 0;
const char *getopt_str, *error, *env_tz, *doveconf_arg = NULL;
else
child_process_env[child_process_env_idx++] = "GDB=1";
#endif
- master_service = master_service_init("master",
+ master_service = master_service_init(MASTER_SERVICE_NAME,
MASTER_SERVICE_FLAG_STANDALONE |
MASTER_SERVICE_FLAG_DONT_LOG_TO_STDERR,
argc, argv);
/* create service structures from settings. if there are any errors in
service configuration we'll catch it here. */
- services = services_create(set, child_process_env, &error);
- if (services == NULL)
+ service_pids_init();
+ if (services_create(set, child_process_env, &services, &error) < 0)
i_fatal("%s", error);
services->config->config_file_path = get_full_config_path(services);
extern struct setting_parser_info master_setting_parser_info;
-struct master_settings *
-master_settings_read(pool_t pool, const char *config_binary,
- const char *config_path);
bool master_settings_do_fixes(const struct master_settings *set);
#endif
fd_close_on_exec(client_fd, TRUE);
/* we have a request. check its validity. */
- auth_process = hash_table_lookup(service->list->pids, &req.auth_pid);
+ auth_process = hash_table_lookup(service_pids, &req.auth_pid);
if (auth_process == NULL) {
service_error(service, "authentication request for unknown "
"auth server PID %s", dec2str(req.auth_pid));
break;
}
- process = hash_table_lookup(service->list->pids, &status.pid);
+ process = hash_table_lookup(service_pids, &status.pid);
if (process == NULL) {
/* we've probably wait()ed it away already. ignore */
return;
service_process_notify_add(service->list->anvil_kills, process);
}
-void services_monitor_reap_children(struct service_list *service_list)
+void services_monitor_reap_children(void)
{
struct service_process *process;
struct service *service;
int status;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
- process = hash_table_lookup(service_list->pids, &pid);
+ process = hash_table_lookup(service_pids, &pid);
if (process == NULL) {
i_error("waitpid() returned unknown PID %s",
dec2str(pid));
void services_monitor_stop(struct service_list *service_list);
/* Call after SIGCHLD has been detected */
-void services_monitor_reap_children(struct service_list *service_list);
+void services_monitor_reap_children(void);
void service_monitor_stop(struct service *service);
void service_monitor_listen_start(struct service *service);
static void
service_process_setup_environment(struct service *service, unsigned int uid)
{
- const struct master_service_settings *set;
- struct service_listener *const *listeners;
+ const struct master_service_settings *set = service->list->service_set;
const char *const *p;
- unsigned int limit, count;
+ unsigned int limit;
/* remove all environment, and put back what we need */
env_clean();
case SERVICE_TYPE_LOG:
/* give the log's configuration directly, so it won't depend
on config process */
- set = master_service_settings_get(master_service);
env_put("DOVECONF_ENV=1");
env_put(t_strconcat("LOG_PATH=", set->log_path, NULL));
env_put(t_strconcat("INFO_LOG_PATH=", set->info_log_path, NULL));
env_put(t_strconcat("SYSLOG_FACILITY=", set->syslog_facility, NULL));
break;
default:
- listeners = array_get(&service->list->config->listeners,
- &count);
- i_assert(count > 0);
env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
- listeners[0]->set.fileset.set->path, NULL));
+ services_get_config_socket_path(service->list), NULL));
break;
}
service->process_count++;
service->process_avail++;
- hash_table_insert(service->list->pids, &process->pid, process);
+ service_list_ref(service->list);
+ hash_table_insert(service_pids, &process->pid, process);
return process;
}
void service_process_destroy(struct service_process *process)
{
struct service *service = process->service;
+ struct service_list *service_list = service->list;
- hash_table_remove(service->list->pids, &process->pid);
+ hash_table_remove(service_pids, &process->pid);
if (process->available_count > 0)
service->process_avail--;
process->destroyed = TRUE;
service_process_unref(process);
+ service_list_unref(service_list);
}
void service_process_ref(struct service_process *process)
#include "aqueue.h"
#include "hash.h"
#include "str.h"
+#include "master-service.h"
+#include "master-service-settings.h"
#include "service.h"
#include "service-anvil.h"
#include "service-process.h"
#include <unistd.h>
#include <signal.h>
+#define SERVICE_DIE_TIMEOUT_MSECS (1000*10)
+
+struct hash_table *service_pids;
+
void service_error(struct service *service, const char *format, ...)
{
va_list args;
return NULL;
}
-struct service_list *
-services_create(const struct master_settings *set,
- const char *const *child_process_env, const char **error_r)
+int services_create(const struct master_settings *set,
+ const char *const *child_process_env,
+ struct service_list **services_r, const char **error_r)
{
struct service_list *service_list;
struct service *service, *const *services;
pool = pool_alloconly_create("services pool", 4096);
service_list = p_new(pool, struct service_list, 1);
+ service_list->refcount = 1;
service_list->pool = pool;
+ service_list->service_set = master_service_settings_get(master_service);
+ service_list->set_pool = master_service_settings_detach(master_service);
service_list->child_process_env = child_process_env;
service_list->master_log_fd[0] = -1;
service_list->master_log_fd[1] = -1;
if (service == NULL) {
*error_r = t_strdup_printf("service(%s) %s",
service_settings[i]->name, error);
- return NULL;
+ return -1;
}
switch (service->type) {
case SERVICE_TYPE_LOG:
if (service_list->log != NULL) {
*error_r = "Multiple log services specified";
- return NULL;
+ return -1;
}
service_list->log = service;
break;
case SERVICE_TYPE_CONFIG:
if (service_list->config != NULL) {
*error_r = "Multiple config services specified";
- return NULL;
+ return -1;
}
service_list->config = service;
break;
*error_r = t_strdup_printf(
"auth_dest_service doesn't exist: %s",
dest_service);
- return NULL;
+ return -1;
}
}
}
if (service_list->log == NULL) {
*error_r = "log service not specified";
- return NULL;
+ return -1;
}
if (service_list->config == NULL) {
*error_r = "config process not specified";
- return NULL;
+ return -1;
}
if (service_list_init_anvil(service_list, error_r) < 0)
- return NULL;
+ return -1;
- service_list->pids = hash_table_create(default_pool, pool, 0,
- pid_hash, pid_hash_cmp);
- return service_list;
+ *services_r = service_list;
+ return 0;
}
void service_signal(struct service *service, int signo)
struct hash_iterate_context *iter;
void *key, *value;
- iter = hash_table_iterate_init(service->list->pids);
+ iter = hash_table_iterate_init(service_pids);
while (hash_table_iterate(iter, &key, &value)) {
struct service_process *process = value;
hash_table_iterate_deinit(&iter);
}
-void services_destroy(struct service_list *service_list)
+static void services_kill_timeout(struct service_list *service_list)
{
- struct hash_iterate_context *iter;
- void *key, *value;
+ struct service *const *services;
+ unsigned int i, count;
+ int sig;
+
+ if (!service_list->sigterm_sent)
+ sig = SIGTERM;
+ else
+ sig = SIGKILL;
+ service_list->sigterm_sent = TRUE;
+
+ i_warning("Processes aren't dying after reload, sending %s.",
+ sig == SIGTERM ? "SIGTERM" : "SIGKILL");
+
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++)
+ service_signal(services[i], sig);
+}
+void services_destroy(struct service_list *service_list)
+{
/* make sure we log if child processes died unexpectedly */
- services_monitor_reap_children(service_list);
+ services_monitor_reap_children();
services_monitor_stop(service_list);
+ service_list_deinit_anvil(service_list);
- /* free all child process information */
- iter = hash_table_iterate_init(service_list->pids);
- while (hash_table_iterate(iter, &key, &value))
- service_process_destroy(value);
- hash_table_iterate_deinit(&iter);
+ if (service_list->refcount > 1) {
+ service_list->to_kill =
+ timeout_add(SERVICE_DIE_TIMEOUT_MSECS,
+ services_kill_timeout, service_list);
+ }
- service_list_deinit_anvil(service_list);
- hash_table_destroy(&service_list->pids);
+ service_list_unref(service_list);
+}
+
+void service_list_ref(struct service_list *service_list)
+{
+ i_assert(service_list->refcount > 0);
+ service_list->refcount++;
+}
+
+void service_list_unref(struct service_list *service_list)
+{
+ i_assert(service_list->refcount > 0);
+ if (--service_list->refcount > 0)
+ return;
+
+ if (service_list->to_kill != NULL)
+ timeout_remove(&service_list->to_kill);
+ pool_unref(&service_list->set_pool);
pool_unref(&service_list->pool);
}
+const char *services_get_config_socket_path(struct service_list *service_list)
+{
+ struct service_listener *const *listeners;
+ unsigned int count;
+
+ listeners = array_get(&service_list->config->listeners, &count);
+ i_assert(count > 0);
+ return listeners[0]->set.fileset.set->path;
+}
+
static void service_throttle_timeout(struct service *service)
{
timeout_remove(&service->to_throttle);
service_throttle(services[i], secs);
}
}
+
+void service_pids_init(void)
+{
+ service_pids = hash_table_create(default_pool, default_pool, 0,
+ pid_hash, pid_hash_cmp);
+}
+
+void service_pids_deinit(void)
+{
+ struct hash_iterate_context *iter;
+ void *key, *value;
+
+ /* free all child process information */
+ iter = hash_table_iterate_init(service_pids);
+ while (hash_table_iterate(iter, &key, &value))
+ service_process_destroy(value);
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&service_pids);
+}
struct service_list {
pool_t pool;
+ pool_t set_pool;
+ int refcount;
+ struct timeout *to_kill;
+
+ const struct master_service_settings *service_set;
struct service *config;
struct service *log;
- struct hash_table *pids;
const char *const *child_process_env;
/* nonblocking log fds usd by master */
struct service_process_notify *anvil_kills;
ARRAY_DEFINE(services, struct service *);
+
+ unsigned int sigterm_sent:1;
};
+extern struct hash_table *service_pids;
+
/* Create all services from settings */
-struct service_list *
-services_create(const struct master_settings *set,
- const char *const *child_process_env, const char **error_r);
+int services_create(const struct master_settings *set,
+ const char *const *child_process_env,
+ struct service_list **services_r, const char **error_r);
/* Destroy services */
void services_destroy(struct service_list *service_list);
+void service_list_ref(struct service_list *service_list);
+void service_list_unref(struct service_list *service_list);
+
+/* Return path to configuration process socket. */
+const char *services_get_config_socket_path(struct service_list *service_list);
+
/* Send a signal to all processes in a given service */
void service_signal(struct service *service, int signo);
void service_error(struct service *service, const char *format, ...)
ATTR_FORMAT(2, 3);
+void service_pids_init(void);
+void service_pids_deinit(void);
+
#endif