Mercurial > dovecot > core-2.2
view src/master/service.c @ 10582:615eef3139c2 HEAD
Updated copyright notices to include year 2010.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 25 Jan 2010 01:19:08 +0200 |
parents | 802b9ebe79e6 |
children | 476de3a45cf8 |
line wrap: on
line source
/* Copyright (c) 2005-2010 Dovecot authors, see the included COPYING file */ #include "common.h" #include "ioloop.h" #include "array.h" #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 "service-monitor.h" #include <unistd.h> #include <signal.h> #define SERVICE_DIE_TIMEOUT_MSECS (1000*60) #define SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS 2 struct hash_table *service_pids; void service_error(struct service *service, const char *format, ...) { va_list args; va_start(args, format); i_error("service(%s): %s", service->set->name, t_strdup_vprintf(format, args)); va_end(args); } static struct service_listener * service_create_file_listener(struct service *service, enum service_listener_type type, const struct file_listener_settings *set, const char **error_r) { struct service_listener *l; gid_t gid; l = p_new(service->list->pool, struct service_listener, 1); l->service = service; l->type = type; l->fd = -1; l->set.fileset.set = set; if (get_uidgid(set->user, &l->set.fileset.uid, &gid, error_r) < 0) return NULL; if (get_gid(set->group, &l->set.fileset.gid, error_r) < 0) return NULL; return l; } static int resolve_ip(const char *address, struct ip_addr *ip_r, const char **error_r) { struct ip_addr *ip_list; unsigned int ips_count; int ret; if (address == NULL || strcmp(address, "*") == 0) { /* IPv4 any */ net_get_ip_any4(ip_r); return 0; } if (strcmp(address, "::") == 0) { /* IPv6 any */ net_get_ip_any6(ip_r); return 0; } /* Return the first IP if there happens to be multiple. */ ret = net_gethostbyname(address, &ip_list, &ips_count); if (ret != 0) { *error_r = t_strdup_printf("Can't resolve address %s: %s", address, net_gethosterror(ret)); return -1; } if (ips_count < 1) { *error_r = t_strdup_printf("No IPs for address: %s", address); return -1; } if (ips_count > 1) { *error_r = t_strdup_printf("Multiple IPs for address: %s", address); return -1; } *ip_r = ip_list[0]; return 0; } static struct service_listener * service_create_one_inet_listener(struct service *service, const struct inet_listener_settings *set, const char *address, const char **error_r) { struct service_listener *l; l = p_new(service->list->pool, struct service_listener, 1); l->service = service; l->type = SERVICE_LISTENER_INET; l->fd = -1; l->set.inetset.set = set; l->inet_address = p_strdup(service->list->pool, address); if (resolve_ip(address, &l->set.inetset.ip, error_r) < 0) return NULL; if (set->port == 0) { *error_r = "Port not given"; return NULL; } if (set->port > 65535) { *error_r = t_strdup_printf("Invalid port: %u", set->port); return NULL; } return l; } static int service_create_inet_listeners(struct service *service, const struct inet_listener_settings *set, const char **error_r) { static struct service_listener *l; const char *const *tmp, *addresses; bool ssl_disabled = strcmp(service->set->master_set->ssl, "no") == 0; if (*set->address != '\0') addresses = set->address; else { /* use the default listen address */ addresses = service->set->master_set->listen; } tmp = t_strsplit_spaces(addresses, ", "); for (; *tmp != NULL; tmp++) { if (set->ssl && ssl_disabled) continue; l = service_create_one_inet_listener(service, set, *tmp, error_r); if (l == NULL) return -1; array_append(&service->listeners, &l, 1); } return 0; } static struct service * service_create(pool_t pool, const struct service_settings *set, struct service_list *service_list, const char **error_r) { struct file_listener_settings *const *unix_listeners; struct file_listener_settings *const *fifo_listeners; struct inet_listener_settings *const *inet_listeners; struct service *service; struct service_listener *l; const char *const *tmp; string_t *str; unsigned int i, unix_count, fifo_count, inet_count; service = p_new(pool, struct service, 1); service->list = service_list; service->set = set; service->client_limit = set->client_limit != 0 ? set->client_limit : set->master_set->default_client_limit; if (set->service_count > 0 && service->client_limit < set->service_count) service->client_limit = set->service_count; service->vsz_limit = set->vsz_limit != (uoff_t)-1 ? set->vsz_limit : set->master_set->default_vsz_limit; service->type = service->set->parsed_type; if (set->process_limit == 0) { /* unlimited */ service->process_limit = INT_MAX; } else if (set->process_limit == -1U) { /* use default */ service->process_limit = set->master_set->default_process_limit; } else { service->process_limit = set->process_limit; } if (set->executable == NULL) { *error_r = "executable not given"; return NULL; } /* default gid to user's primary group */ if (get_uidgid(set->user, &service->uid, &service->gid, error_r) < 0) return NULL; if (*set->group != '\0') { if (get_gid(set->group, &service->gid, error_r) < 0) return NULL; } if (get_gid(set->privileged_group, &service->privileged_gid, error_r) < 0) return NULL; if (*set->extra_groups != '\0') { str = t_str_new(64); tmp = t_strsplit(set->extra_groups, ","); for (; *tmp != NULL; tmp++) { gid_t gid; if (get_gid(*tmp, &gid, error_r) < 0) return NULL; if (str_len(str) > 0) str_append_c(str, ','); str_append(str, dec2str(gid)); } service->extra_gids = p_strdup(pool, str_c(str)); } if (*set->executable == '/') service->executable = set->executable; else { service->executable = p_strconcat(pool, set->master_set->libexec_dir, "/", set->executable, NULL); } if (access(t_strcut(service->executable, ' '), X_OK) < 0) { *error_r = t_strdup_printf("access(%s) failed: %m", t_strcut(service->executable, ' ')); return NULL; } /* set these later, so if something fails we don't have to worry about closing them */ service->log_fd[0] = -1; service->log_fd[1] = -1; service->status_fd[0] = -1; service->status_fd[1] = -1; service->log_process_internal_fd = -1; service->login_notify_fd = -1; if (service->type == SERVICE_TYPE_ANVIL) { service->status_fd[0] = service_anvil_global->status_fd[0]; service->status_fd[1] = service_anvil_global->status_fd[1]; } if (array_is_created(&set->unix_listeners)) unix_listeners = array_get(&set->unix_listeners, &unix_count); else { unix_listeners = NULL; unix_count = 0; } if (array_is_created(&set->fifo_listeners)) fifo_listeners = array_get(&set->unix_listeners, &fifo_count); else { fifo_listeners = NULL; fifo_count = 0; } if (array_is_created(&set->inet_listeners)) inet_listeners = array_get(&set->inet_listeners, &inet_count); else { inet_listeners = NULL; inet_count = 0; } if (unix_count == 0 && service->type == SERVICE_TYPE_CONFIG) { *error_r = "Service must have unix listeners"; return NULL; } p_array_init(&service->listeners, pool, unix_count + fifo_count + inet_count); for (i = 0; i < unix_count; i++) { l = service_create_file_listener(service, SERVICE_LISTENER_UNIX, unix_listeners[i], error_r); if (l == NULL) return NULL; array_append(&service->listeners, &l, 1); } for (i = 0; i < fifo_count; i++) { l = service_create_file_listener(service, SERVICE_LISTENER_UNIX, fifo_listeners[i], error_r); if (l == NULL) return NULL; array_append(&service->listeners, &l, 1); } for (i = 0; i < inet_count; i++) { if (service_create_inet_listeners(service, inet_listeners[i], error_r) < 0) return NULL; service->have_inet_listeners = TRUE; } return service; } static unsigned int pid_hash(const void *p) { const pid_t *pid = p; return (unsigned int)*pid; } static int pid_hash_cmp(const void *p1, const void *p2) { const pid_t *pid1 = p1, *pid2 = p2; return *pid1 < *pid2 ? -1 : *pid1 > *pid2 ? 1 : 0; } struct service * service_lookup(struct service_list *service_list, const char *name) { struct service *const *services; array_foreach(&service_list->services, services) { struct service *service = *services; if (strcmp(service->set->name, name) == 0) return service; } return NULL; } struct service * service_lookup_type(struct service_list *service_list, enum service_type type) { struct service *const *services; array_foreach(&service_list->services, services) { struct service *service = *services; if (service->type == type) return service; } return NULL; } static bool service_want(struct service_settings *set) { char *const *proto; if (*set->protocol == '\0') return TRUE; for (proto = set->master_set->protocols_split; *proto != NULL; proto++) { if (strcmp(*proto, set->protocol) == 0) return TRUE; } return FALSE; } 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; struct service_settings *const *service_settings; pool_t pool; const char *error; unsigned int i, count; 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; service_settings = array_get(&set->services, &count); p_array_init(&service_list->services, pool, count); for (i = 0; i < count; i++) { if (!service_want(service_settings[i])) continue; service = service_create(pool, service_settings[i], service_list, &error); if (service == NULL) { *error_r = t_strdup_printf("service(%s) %s", service_settings[i]->name, error); return -1; } switch (service->type) { case SERVICE_TYPE_LOG: if (service_list->log != NULL) { *error_r = "Multiple log services specified"; return -1; } service_list->log = service; break; case SERVICE_TYPE_CONFIG: if (service_list->config != NULL) { *error_r = "Multiple config services specified"; return -1; } service_list->config = service; break; case SERVICE_TYPE_ANVIL: if (service_list->anvil != NULL) { *error_r = "Multiple anvil services specified"; return -1; } service_list->anvil = service; break; default: break; } array_append(&service_list->services, &service, 1); } if (service_list->log == NULL) { *error_r = "log service not specified"; return -1; } if (service_list->config == NULL) { *error_r = "config process not specified"; return -1; } *services_r = service_list; return 0; } void service_signal(struct service *service, int signo) { struct service_process *process = service->processes; for (; process != NULL; process = process->next) { i_assert(process->service == service); if (!SERVICE_PROCESS_IS_INITIALIZED(process) && signo != SIGKILL) { /* too early to signal it */ continue; } if (kill(process->pid, signo) < 0 && errno != ESRCH) { service_error(service, "kill(%s, %d) failed: %m", dec2str(process->pid), signo); } } } static void service_login_notify_send(struct service *service) { service->last_login_notify_time = ioloop_time; if (service->to_login_notify != NULL) timeout_remove(&service->to_login_notify); service_signal(service, SIGUSR1); } static void service_login_notify_timeout(struct service *service) { service_login_notify_send(service); } void service_login_notify(struct service *service, bool all_processes_full) { enum master_login_state state; int diff; if (service->last_login_full_notify == all_processes_full || service->login_notify_fd == -1) return; /* change the state always immediately. it's cheap. */ service->last_login_full_notify = all_processes_full; state = all_processes_full ? MASTER_LOGIN_STATE_FULL : MASTER_LOGIN_STATE_NONFULL; if (lseek(service->login_notify_fd, state, SEEK_SET) < 0) service_error(service, "lseek(notify fd) failed: %m"); /* but don't send signal to processes too often */ diff = ioloop_time - service->last_login_notify_time; if (diff < SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS) { if (service->to_login_notify != NULL) return; diff = (SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS - diff) * 1000; service->to_login_notify = timeout_add(diff, service_login_notify_timeout, service); } else { service_login_notify_send(service); } } static void services_kill_timeout(struct service_list *service_list) { struct service *const *services, *log_service; bool sigterm_log; int sig; if (!service_list->sigterm_sent || !service_list->sigterm_sent_to_log) sig = SIGTERM; else sig = SIGKILL; sigterm_log = service_list->sigterm_sent; service_list->sigterm_sent = TRUE; i_warning("Processes aren't dying after reload, sending %s.", sig == SIGTERM ? "SIGTERM" : "SIGKILL"); log_service = NULL; array_foreach(&service_list->services, services) { struct service *service = *services; if (service->type == SERVICE_TYPE_LOG) log_service = service; else service_signal(service, sig); } /* kill log service later so it could still have a chance of logging something */ if (log_service != NULL && sigterm_log) { service_signal(log_service, sig); service_list->sigterm_sent_to_log = TRUE; } } void services_destroy(struct service_list *service_list) { /* make sure we log if child processes died unexpectedly */ services_monitor_reap_children(); services_monitor_stop(service_list); if (service_list->refcount > 1 && service_list->service_set->shutdown_clients) { service_list->to_kill = timeout_add(SERVICE_DIE_TIMEOUT_MSECS, services_kill_timeout, service_list); } service_list->destroyed = TRUE; 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_monitor_listen_start(service); } void service_throttle(struct service *service, unsigned int secs) { if (service->to_throttle != NULL) return; service_monitor_listen_stop(service); service->to_throttle = timeout_add(secs * 1000, service_throttle_timeout, service); } void services_throttle_time_sensitives(struct service_list *list, unsigned int secs) { struct service *const *services; array_foreach(&list->services, services) { struct service *service = *services; if (service->type == SERVICE_TYPE_UNKNOWN) service_throttle(service, 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); }