#include "askpass.h"
#include "capabilities.h"
#include "service.h"
+#include "service-anvil.h"
#include "service-listen.h"
#include "service-monitor.h"
#include "service-process.h"
const struct master_settings *set;
void **sets;
struct service_list *new_services;
+ struct service *service;
const char *error;
i_warning("SIGHUP received - reloading configuration");
/* switch to new configuration. */
services_monitor_stop(services);
- (void)services_listen_using(new_services, services);
+ if (services_listen_using(new_services, services) < 0) {
+ services_monitor_start(services);
+ return;
+ }
+
+ /* anvil never dies. it just gets moved to the new services list */
+ service = service_lookup_type(services, SERVICE_TYPE_ANVIL);
+ if (service != NULL) {
+ while (service->processes != NULL)
+ service_process_destroy(service->processes);
+ }
services_destroy(services);
services = new_services;
i_free(pidfile_path);
services_destroy(services);
+ service_anvil_global_deinit();
service_pids_deinit();
}
/* create service structures from settings. if there are any errors in
service configuration we'll catch it here. */
service_pids_init();
+ service_anvil_global_init();
if (services_create(set, child_process_env, &services, &error) < 0)
i_fatal("%s", error);
#define ANVIL_HANDSHAKE "VERSION\tanvil\t1\t0\n"
+struct service_anvil_global *service_anvil_global;
+
static void
-service_list_anvil_discard_input_stop(struct service_list *service_list)
+service_list_anvil_discard_input_stop(struct service_anvil_global *anvil)
{
- if (service_list->anvil_io_blocking != NULL) {
- io_remove(&service_list->anvil_io_blocking);
- io_remove(&service_list->anvil_io_nonblocking);
+ if (anvil->io_blocking != NULL) {
+ io_remove(&anvil->io_blocking);
+ io_remove(&anvil->io_nonblocking);
}
}
-static void
-anvil_input_fd_discard(struct service_list *service_list, int fd)
+static void anvil_input_fd_discard(struct service_anvil_global *anvil, int fd)
{
char buf[1024];
ssize_t ret;
ret = read(fd, buf, sizeof(buf));
if (ret <= 0) {
i_error("read(anvil fd) failed: %m");
- service_list_anvil_discard_input_stop(service_list);
+ service_list_anvil_discard_input_stop(anvil);
}
}
-static void anvil_input_blocking_discard(struct service_list *service_list)
+static void anvil_input_blocking_discard(struct service_anvil_global *anvil)
{
- anvil_input_fd_discard(service_list,
- service_list->blocking_anvil_fd[0]);
+ anvil_input_fd_discard(anvil, anvil->blocking_fd[0]);
}
-static void anvil_input_nonblocking_discard(struct service_list *service_list)
+static void anvil_input_nonblocking_discard(struct service_anvil_global *anvil)
{
- anvil_input_fd_discard(service_list,
- service_list->nonblocking_anvil_fd[0]);
+ anvil_input_fd_discard(anvil, anvil->nonblocking_fd[0]);
}
-static void service_list_anvil_discard_input(struct service_list *service_list)
+static void service_list_anvil_discard_input(struct service_anvil_global *anvil)
{
- service_list->anvil_io_blocking =
- io_add(service_list->blocking_anvil_fd[0], IO_READ,
- anvil_input_blocking_discard, service_list);
- service_list->anvil_io_nonblocking =
- io_add(service_list->nonblocking_anvil_fd[0], IO_READ,
- anvil_input_nonblocking_discard, service_list);
+ if (anvil->io_blocking != NULL)
+ return;
+
+ anvil->io_blocking = io_add(anvil->blocking_fd[0], IO_READ,
+ anvil_input_blocking_discard, anvil);
+ anvil->io_nonblocking = io_add(anvil->nonblocking_fd[0], IO_READ,
+ anvil_input_nonblocking_discard, anvil);
}
static int anvil_send_handshake(int fd, const char **error_r)
return 0;
}
-int service_list_init_anvil(struct service_list *service_list,
- const char **error_r)
+void service_anvil_monitor_start(struct service_list *service_list)
{
- if (pipe(service_list->blocking_anvil_fd) < 0) {
- *error_r = t_strdup_printf("pipe() failed: %m");
- return -1;
- }
- if (pipe(service_list->nonblocking_anvil_fd) < 0) {
- (void)close(service_list->blocking_anvil_fd[0]);
- (void)close(service_list->blocking_anvil_fd[1]);
- *error_r = t_strdup_printf("pipe() failed: %m");
- return -1;
+ struct service *service;
+
+ if (service_anvil_global->process_count == 0)
+ service_list_anvil_discard_input(service_anvil_global);
+ else {
+ service = service_lookup_type(service_list, SERVICE_TYPE_ANVIL);
+ service_process_create(service);
}
- fd_set_nonblock(service_list->nonblocking_anvil_fd[1], TRUE);
+}
- fd_close_on_exec(service_list->blocking_anvil_fd[0], TRUE);
- fd_close_on_exec(service_list->blocking_anvil_fd[1], TRUE);
- fd_close_on_exec(service_list->nonblocking_anvil_fd[0], TRUE);
- fd_close_on_exec(service_list->nonblocking_anvil_fd[1], TRUE);
+void service_anvil_process_created(struct service_process *process)
+{
+ struct service_anvil_global *anvil = service_anvil_global;
+ const char *error;
- i_assert(service_list->anvil_kills == NULL);
- service_list->anvil_kills =
- service_process_notify_init(service_list->nonblocking_anvil_fd[1],
- service_process_write_anvil_kill);
- return 0;
+ service_anvil_global->pid = process->pid;
+ service_anvil_global->uid = process->uid;
+ service_anvil_global->process_count++;
+ service_list_anvil_discard_input_stop(anvil);
+
+ if (anvil_send_handshake(anvil->blocking_fd[1], &error) < 0 ||
+ anvil_send_handshake(anvil->nonblocking_fd[1], &error) < 0)
+ service_error(process->service, "%s", error);
}
-void services_anvil_init(struct service_list *service_list)
+void service_anvil_process_destroyed(struct service_process *process)
{
- /* this can't be in _init_anvil() because we can't do io_add()s
- before forking with kqueue. */
- service_list_anvil_discard_input(service_list);
+ i_assert(service_anvil_global->process_count > 0);
+ if (--service_anvil_global->process_count == 0)
+ service_list_anvil_discard_input(service_anvil_global);
+
+ if (service_anvil_global->pid == process->pid)
+ service_anvil_global->pid = 0;
}
-void service_list_deinit_anvil(struct service_list *service_list)
+void service_anvil_global_init(void)
{
- service_list_anvil_discard_input_stop(service_list);
- service_process_notify_deinit(&service_list->anvil_kills);
- if (close(service_list->blocking_anvil_fd[0]) < 0)
- i_error("close(anvil) failed: %m");
- if (close(service_list->blocking_anvil_fd[1]) < 0)
- i_error("close(anvil) failed: %m");
- if (close(service_list->nonblocking_anvil_fd[0]) < 0)
- i_error("close(anvil) failed: %m");
- if (close(service_list->nonblocking_anvil_fd[1]) < 0)
- i_error("close(anvil) failed: %m");
- service_list->blocking_anvil_fd[0] = -1;
+ struct service_anvil_global *anvil;
+
+ anvil = i_new(struct service_anvil_global, 1);
+ if (pipe(anvil->status_fd) < 0)
+ i_fatal("pipe() failed: %m");
+ if (pipe(anvil->blocking_fd) < 0)
+ i_fatal("pipe() failed: %m");
+ if (pipe(anvil->nonblocking_fd) < 0)
+ i_fatal("pipe() failed: %m");
+ fd_set_nonblock(anvil->status_fd[0], TRUE);
+ fd_set_nonblock(anvil->status_fd[1], TRUE);
+ fd_set_nonblock(anvil->nonblocking_fd[1], TRUE);
+
+ fd_close_on_exec(anvil->status_fd[0], TRUE);
+ fd_close_on_exec(anvil->status_fd[1], TRUE);
+ fd_close_on_exec(anvil->blocking_fd[0], TRUE);
+ fd_close_on_exec(anvil->blocking_fd[1], TRUE);
+ fd_close_on_exec(anvil->nonblocking_fd[0], TRUE);
+ fd_close_on_exec(anvil->nonblocking_fd[1], TRUE);
+
+ anvil->kills =
+ service_process_notify_init(anvil->nonblocking_fd[1],
+ service_process_write_anvil_kill);
+ service_anvil_global = anvil;
}
-void service_anvil_process_created(struct service *service)
+void service_anvil_global_deinit(void)
{
- struct service_list *list = service->list;
- const char *error;
-
- service_list_anvil_discard_input_stop(service->list);
+ struct service_anvil_global *anvil = service_anvil_global;
- if (anvil_send_handshake(list->blocking_anvil_fd[1], &error) < 0 ||
- anvil_send_handshake(list->nonblocking_anvil_fd[1], &error) < 0)
- service_error(service, "%s", error);
-}
+ service_list_anvil_discard_input_stop(anvil);
+ service_process_notify_deinit(&anvil->kills);
+ if (close(anvil->blocking_fd[0]) < 0)
+ i_error("close(anvil) failed: %m");
+ if (close(anvil->blocking_fd[1]) < 0)
+ i_error("close(anvil) failed: %m");
+ if (close(anvil->nonblocking_fd[0]) < 0)
+ i_error("close(anvil) failed: %m");
+ if (close(anvil->nonblocking_fd[1]) < 0)
+ i_error("close(anvil) failed: %m");
+ if (close(anvil->status_fd[0]) < 0)
+ i_error("close(anvil) failed: %m");
+ if (close(anvil->status_fd[1]) < 0)
+ i_error("close(anvil) failed: %m");
+ i_free(anvil);
-void service_anvil_process_destroyed(struct service *service)
-{
- if (service->process_count == 0 &&
- service->list->anvil_io_blocking == NULL &&
- service->list->blocking_anvil_fd[0] != -1)
- service_list_anvil_discard_input(service->list);
+ service_anvil_global = NULL;
}
#ifndef SERVICE_ANVIL_H
#define SERVICE_ANVIL_H
-int service_list_init_anvil(struct service_list *service_list,
- const char **error_r);
-void service_list_deinit_anvil(struct service_list *service_list);
-void services_anvil_init(struct service_list *service_list);
+struct service_anvil_global {
+ pid_t pid;
+ unsigned int uid;
-void service_anvil_process_created(struct service *service);
-void service_anvil_process_destroyed(struct service *service);
+ int status_fd[2];
+ /* passed to child processes */
+ int blocking_fd[2];
+ /* used by master process to notify about dying processes */
+ int nonblocking_fd[2];
+
+ struct service_process_notify *kills;
+ struct io *io_blocking, *io_nonblocking;
+
+ unsigned int process_count;
+};
+
+extern struct service_anvil_global *service_anvil_global;
+
+void service_anvil_monitor_start(struct service_list *service_list);
+
+void service_anvil_process_created(struct service_process *process);
+void service_anvil_process_destroyed(struct service_process *process);
+
+void service_anvil_global_init(void);
+void service_anvil_global_deinit(void);
#endif
int services_listen_using(struct service_list *new_service_list,
struct service_list *old_service_list)
{
- struct service *const *services;
+ struct service *const *services, *old_service, *new_service;
ARRAY_DEFINE(new_listeners_arr, struct service_listener *);
ARRAY_DEFINE(old_listeners_arr, struct service_listener *);
struct service_listener *const *new_listeners, *const *old_listeners;
unsigned int i, j, count, new_count, old_count;
+ /* rescue anvil's UNIX socket listener */
+ new_service = service_lookup_type(new_service_list, SERVICE_TYPE_ANVIL);
+ old_service = service_lookup_type(old_service_list, SERVICE_TYPE_ANVIL);
+ if (old_service != NULL && new_service != NULL) {
+ new_listeners = array_get(&new_service->listeners, &new_count);
+ old_listeners = array_get(&old_service->listeners, &old_count);
+ for (i = 0; i < old_count && i < new_count; i++) {
+ if (new_listeners[i]->type != old_listeners[i]->type)
+ break;
+ }
+ if (i != new_count && i != old_count) {
+ i_error("Can't change anvil's listeners on the fly");
+ return -1;
+ }
+ for (i = 0; i < new_count; i++) {
+ new_listeners[i]->fd = old_listeners[i]->fd;
+ old_listeners[i]->fd = -1;
+ }
+ }
+
/* first create an arrays of all listeners to make things easier */
t_array_init(&new_listeners_arr, 64);
services = array_get(&new_service_list->services, &count);
/* close what's left */
for (j = 0; j < old_count; j++) {
- if (old_listeners[j]->fd != -1) {
- if (close(old_listeners[j]->fd) < 0)
- i_error("close(listener) failed: %m");
- }
+ if (old_listeners[j]->fd == -1)
+ continue;
+
+ if (close(old_listeners[j]->fd) < 0)
+ i_error("close(listener) failed: %m");
switch (old_listeners[j]->type) {
case SERVICE_LISTENER_UNIX:
case SERVICE_LISTENER_FIFO: {
struct service *const *services;
unsigned int i, count;
- services_anvil_init(service_list);
services_log_init(service_list);
+ service_anvil_monitor_start(service_list);
services = array_get(&service_list->services, &count);
for (i = 0; i < count; i++) {
fd_close_on_exec(services[i]->status_fd[0], TRUE);
net_set_nonblock(services[i]->status_fd[1], TRUE);
fd_close_on_exec(services[i]->status_fd[1], TRUE);
-
+ }
+ if (services[i]->io_status == NULL) {
services[i]->io_status =
io_add(services[i]->status_fd[0], IO_READ,
service_status_input, services[i]);
if (service->io_status != NULL)
io_remove(&service->io_status);
- if (service->status_fd[0] != -1) {
+ if (service->status_fd[0] != -1 &&
+ service->type != SERVICE_TYPE_ANVIL) {
for (i = 0; i < 2; i++) {
if (close(service->status_fd[i]) < 0) {
service_error(service,
if (process->total_count == 0)
service_monitor_throttle(service);
- if (service->list->anvil_kills != NULL)
- service_process_notify_add(service->list->anvil_kills, process);
+ service_process_notify_add(service_anvil_global->kills, process);
}
void services_monitor_reap_children(void)
service_process_failure(process, status);
}
service_destroyed = service->list->destroyed;
+ if (service->type == SERVICE_TYPE_ANVIL)
+ service_anvil_process_destroyed(process);
service_process_destroy(process);
if (!service_destroyed) {
case SERVICE_TYPE_ANVIL:
/* nonblocking anvil fd must be the first one. anvil treats it
as the master's fd */
- dup2_append(&dups, service->list->nonblocking_anvil_fd[0],
+ dup2_append(&dups, service_anvil_global->nonblocking_fd[0],
MASTER_LISTEN_FD_FIRST + n++);
- dup2_append(&dups, service->list->blocking_anvil_fd[0],
+ dup2_append(&dups, service_anvil_global->blocking_fd[0],
MASTER_LISTEN_FD_FIRST + n++);
socket_listener_count += 2;
break;
dup2_append(&dups, service->login_notify_fd,
MASTER_LOGIN_NOTIFY_FD);
}
- dup2_append(&dups, service->list->blocking_anvil_fd[1],
+ dup2_append(&dups, service_anvil_global->blocking_fd[1],
MASTER_ANVIL_FD);
dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
struct service_process *process;
unsigned int uid = ++uid_counter;
pid_t pid;
+ bool process_forked;
if (service->to_throttle != NULL) {
/* throttling service, don't create new processes */
return NULL;
}
- pid = fork();
+ if (service->type == SERVICE_TYPE_ANVIL &&
+ service_anvil_global->pid != 0) {
+ pid = service_anvil_global->pid;
+ uid = service_anvil_global->uid;
+ process_forked = FALSE;
+ } else {
+ pid = fork();
+ process_forked = TRUE;
+ }
+
if (pid < 0) {
service_error(service, "fork() failed: %m");
return NULL;
process_exec(service->executable, NULL);
}
- switch (service->type) {
- case SERVICE_TYPE_ANVIL:
- service_anvil_process_created(service);
- /* fall through */
- default:
- process = i_new(struct service_process, 1);
- process->service = service;
- break;
- }
-
- DLLIST_PREPEND(&service->processes, process);
+ process = i_new(struct service_process, 1);
+ process->service = service;
process->refcount = 1;
process->pid = pid;
process->uid = uid;
- process->to_status =
- timeout_add(SERVICE_FIRST_STATUS_TIMEOUT_SECS * 1000,
- service_process_status_timeout, process);
+ if (process_forked) {
+ process->to_status =
+ timeout_add(SERVICE_FIRST_STATUS_TIMEOUT_SECS * 1000,
+ service_process_status_timeout, process);
+ }
process->available_count = service->client_limit;
service->process_count++;
service->process_avail++;
+ DLLIST_PREPEND(&service->processes, process);
service_list_ref(service->list);
hash_table_insert(service_pids, &process->pid, process);
+
+ if (service->type == SERVICE_TYPE_ANVIL && process_forked)
+ service_anvil_process_created(process);
return process;
}
timeout_remove(&process->to_status);
if (process->to_idle != NULL)
timeout_remove(&process->to_idle);
-
- switch (process->service->type) {
- case SERVICE_TYPE_ANVIL:
- service_anvil_process_destroyed(service);
- break;
- default:
- break;
- }
-
if (service->list->log_byes != NULL)
service_process_notify_add(service->list->log_byes, process);
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 {
return NULL;
}
+struct service *
+service_lookup_type(struct service_list *service_list, enum service_type type)
+{
+ struct service *const *services;
+ unsigned int i, count;
+
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++) {
+ if (services[i]->type == type)
+ return services[i];
+ }
+ return NULL;
+}
+
static bool service_want(struct service_settings *set)
{
char *const *proto;
return -1;
}
- if (service_list_init_anvil(service_list, error_r) < 0)
- return -1;
-
*services_r = service_list;
return 0;
}
services_monitor_reap_children();
services_monitor_stop(service_list);
- service_list_deinit_anvil(service_list);
if (service_list->refcount > 1 &&
service_list->service_set->shutdown_clients) {
int master_log_fd[2];
struct service_process_notify *log_byes;
- /* passed to child processes */
- int blocking_anvil_fd[2];
- /* used by master process to notify about dying processes */
- int nonblocking_anvil_fd[2];
- struct service_process_notify *anvil_kills;
- struct io *anvil_io_blocking, *anvil_io_nonblocking;
-
ARRAY_DEFINE(services, struct service *);
unsigned int destroyed:1;
void services_throttle_time_sensitives(struct service_list *list,
unsigned int secs);
-/* Find a service by name. */
+/* Find service by name. */
struct service *
service_lookup(struct service_list *service_list, const char *name);
+/* Find service by type */
+struct service *
+service_lookup_type(struct service_list *service_list, enum service_type type);
void service_error(struct service *service, const char *format, ...)
ATTR_FORMAT(2, 3);