]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
master: Support reloading configuration.
authorTimo Sirainen <tss@iki.fi>
Fri, 14 Aug 2009 22:14:21 +0000 (18:14 -0400)
committerTimo Sirainen <tss@iki.fi>
Fri, 14 Aug 2009 22:14:21 +0000 (18:14 -0400)
--HG--
branch : HEAD

src/lib-master/master-service-settings.c
src/lib-master/master-service-settings.h
src/master/main.c
src/master/master-settings.h
src/master/service-auth-source.c
src/master/service-monitor.c
src/master/service-monitor.h
src/master/service-process.c
src/master/service.c
src/master/service.h

index d65817b0670efb9d9fc0bf402129cf28e5be2524..71541ee16995e3093dd33e733173a33ab10600b4 100644 (file)
@@ -97,15 +97,13 @@ master_service_exec_config(struct master_service *service, bool preserve_home)
 }
 
 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;
@@ -184,19 +182,22 @@ int master_service_settings_read(struct master_service *service,
        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 =
@@ -289,6 +290,15 @@ int master_service_settings_read_simple(struct master_service *service,
        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)
 {
index 2c9c6853ea9835fc218cfa43976ab1628dc6f750..2bafb0f609fe3f573fb0753b0ca52c30e99aa07f 100644 (file)
@@ -18,6 +18,7 @@ struct master_service_settings {
 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;
@@ -34,6 +35,11 @@ int master_service_settings_read(struct master_service *service,
 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);
index 699c76ffbc1c972f68a6e0723ea3042dbeae0a7c..a4cb5a5407d88fadec79d82c93da21a6be4c92b6 100644 (file)
@@ -27,6 +27,7 @@
 
 #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)
@@ -41,9 +42,13 @@ int null_fd;
 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;
@@ -304,32 +309,41 @@ static void
 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);
 }
 
@@ -342,7 +356,7 @@ sig_log_reopen(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
 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)
@@ -404,6 +418,7 @@ static void main_deinit(void)
        i_free(pidfile_path);
 
        services_destroy(services);
+       service_pids_deinit();
 }
 
 static const char *get_full_config_path(struct service_list *list)
@@ -571,10 +586,6 @@ static void print_build_options(void)
 
 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;
@@ -589,7 +600,7 @@ int main(int argc, char *argv[])
        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);
@@ -721,8 +732,8 @@ int main(int argc, char *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);
index 8ee35532753647027b3147b8f94580a9ba55b9e4..cd8e5c7ba7c2e4d674bb53979b8dd43d2bd16afa 100644 (file)
@@ -61,9 +61,6 @@ struct master_settings {
 
 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
index 28798f89ba52d3870301b4696d83a295b25bf5ee..9d325540edef0b329f61d8913fb3df68743e990f 100644 (file)
@@ -204,7 +204,7 @@ service_process_auth_source_input(struct service_process_auth_source *process)
        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));
index 6b7dd5d47948620e5ff84fd2026277b485795885..c7bd74c10fd7cc5892a8bce9a8661d809e424766 100644 (file)
@@ -44,7 +44,7 @@ static void service_status_input(struct service *service)
                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;
@@ -239,7 +239,7 @@ static void service_process_failure(struct service_process *process, int status)
                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;
@@ -247,7 +247,7 @@ void services_monitor_reap_children(struct service_list *service_list)
        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));
index 85ffb7dc98b2a2d861e56c5d39215d01497b7483..8e7b5508867520927e6a1cf9accdb7d7aea87f88 100644 (file)
@@ -8,7 +8,7 @@ void services_monitor_start(struct service_list *service_list);
 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);
index 8d2c88cab2c458f33dac393556ab7194cc6f06dc..0232c4bc2c0eb41070cf955d768856d4c109a1ad 100644 (file)
@@ -367,10 +367,9 @@ static void drop_privileges(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();
@@ -385,7 +384,6 @@ service_process_setup_environment(struct service *service, unsigned int uid)
        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));
@@ -393,11 +391,8 @@ service_process_setup_environment(struct service *service, unsigned int uid)
                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;
        }
 
@@ -534,15 +529,17 @@ service_process_create(struct service *service, const char *const *auth_args,
        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--;
@@ -568,6 +565,7 @@ void service_process_destroy(struct service_process *process)
 
        process->destroyed = TRUE;
        service_process_unref(process);
+       service_list_unref(service_list);
 }
 
 void service_process_ref(struct service_process *process)
index 9bf1110993eb0313145d1d0761b788a7a62db307..1dbe1a4b433378f2ea288e7269b240a5801d76fb 100644 (file)
@@ -6,6 +6,8 @@
 #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;
@@ -311,9 +317,9 @@ service_lookup(struct service_list *service_list, const char *name)
        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;
@@ -325,7 +331,10 @@ services_create(const struct master_settings *set,
        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;
@@ -339,21 +348,21 @@ services_create(const struct master_settings *set,
                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;
@@ -376,27 +385,26 @@ services_create(const struct master_settings *set,
                                *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)
@@ -404,7 +412,7 @@ 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;
 
@@ -419,27 +427,71 @@ void service_signal(struct service *service, int signo)
        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);
@@ -468,3 +520,22 @@ void services_throttle_time_sensitives(struct service_list *list,
                        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);
+}
index a1a5a36ec3ceb93e4e16fd76dd4a3d08c3134448..f9eba36c9b783fbdc6470ad650dd3bf9e9257964 100644 (file)
@@ -94,10 +94,14 @@ struct service {
 
 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 */
@@ -111,16 +115,26 @@ struct service_list {
        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);
 
@@ -133,4 +147,7 @@ void services_throttle_time_sensitives(struct service_list *list,
 void service_error(struct service *service, const char *format, ...)
        ATTR_FORMAT(2, 3);
 
+void service_pids_init(void);
+void service_pids_deinit(void);
+
 #endif