unsigned int max_lines_per_sec;
/* Add this previs to each logged line */
+#define MASTER_LOG_PREFIX_NAME "MASTER"
unsigned int prefix_len;
/* unsigned char prefix[]; */
};
int argc, char *argv[])
{
struct master_service *service;
+ const char *str;
i_assert(name != NULL);
+#ifdef DEBUG
+ if (getenv("GDB") == NULL) {
+ int count;
+
+ str = getenv("SOCKET_COUNT");
+ count = str == NULL ? 0 : atoi(str);
+ fd_debug_verify_leaks(MASTER_LISTEN_FD_FIRST + count, 1024);
+ }
+#endif
+
/* NOTE: we start rooted, so keep the code minimal until
restrict_access_by_env() is called */
lib_init();
} else {
service->version_string = PACKAGE_VERSION;
}
+ str = getenv("SOCKET_COUNT");
+ if (str != NULL)
+ service->socket_count = atoi(str);
/* set up some kind of logging until we know exactly how and where
we want to log */
/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
#include "common.h"
+#include "array.h"
#include "ioloop.h"
#include "llist.h"
#include "hash.h"
#include "master-service.h"
#include "log-connection.h"
+#include <stdio.h>
+#include <stdlib.h>
#include <unistd.h>
#define FATAL_QUEUE_TIMEOUT_MSECS 500
char *prefix;
struct hash_table *clients;
+ unsigned int master:1;
unsigned int handshaked:1;
};
static struct log_connection *log_connections = NULL;
+static ARRAY_DEFINE(logs_by_fd, struct log_connection *);
static struct log_client *log_client_get(struct log_connection *log, pid_t pid)
{
(void)net_addr2ip(failure->text + 3, &client->ip);
}
-static void log_remove_pid(struct log_connection *log, pid_t pid)
-{
- struct log_client *client;
-
- client = hash_table_lookup(log->clients, POINTER_CAST(pid));
- if (client != NULL) {
- hash_table_remove(log->clients, POINTER_CAST(pid));
- i_free(client);
- }
-}
-
static void log_parse_option(struct log_connection *log,
const struct failure_line *failure)
{
if (strncmp(failure->text, "ip=", 3) == 0)
log_parse_ip(log, failure);
- else if (strcmp(failure->text, "bye") == 0)
- log_remove_pid(log, failure->pid);
}
-static bool
-log_handle_seen_fatal(struct log_connection *log, const char **_text)
+static void log_parse_master_line(const char *line)
{
- const char *text = *_text;
+ struct log_connection *const *logs, *log;
struct log_client *client;
- pid_t pid = 0;
-
- while (*text >= '0' && *text <= '9') {
- pid = pid*10 + (*text - '0');
- text++;
+ const char *p, *p2;
+ unsigned int count;
+ int service_fd;
+ long pid;
+
+ p = strchr(line, ' ');
+ if (p == NULL || (p2 = strchr(++p, ' ')) == NULL) {
+ i_error("Received invalid input from master: %s", line);
+ return;
}
- if (*text != ' ' || pid == 0)
- return FALSE;
- *_text = text;
+ service_fd = atoi(t_strcut(line, ' '));
+ pid = strtol(t_strcut(p, ' '), NULL, 10);
+ logs = array_get(&logs_by_fd, &count);
+ if (service_fd >= (int)count || logs[service_fd] == NULL) {
+ i_error("Received master input for invalid service_fd %d: %s",
+ service_fd, line);
+ return;
+ }
+ log = logs[service_fd];
client = hash_table_lookup(log->clients, POINTER_CAST(pid));
- if (client != NULL && client->fatal_logged) {
- log_remove_pid(log, pid);
- return TRUE;
+ line = p2 + 1;
+
+ if (strcmp(line, "BYE") == 0) {
+ if (client == NULL) {
+ /* we haven't seen anything important from this client.
+ it's not an error. */
+ return;
+ }
+ hash_table_remove(log->clients, POINTER_CAST(pid));
+ i_free(client);
+ } else if (strncmp(line, "DEFAULT-FATAL ", 14) == 0) {
+ /* If the client has logged a fatal/panic, don't log this
+ message. */
+ if (client == NULL || !client->fatal_logged)
+ i_error("%s", line + 14);
+ else {
+ hash_table_remove(log->clients, POINTER_CAST(pid));
+ i_free(client);
+ }
+ } else {
+ i_error("Received unknown command from master: %s", line);
}
- return FALSE;
}
static void log_it(struct log_connection *log, const char *line)
struct failure_line failure;
struct log_client *client;
+ if (log->master) {
+ log_parse_master_line(line);
+ return;
+ }
+
i_failure_parse_line(line, &failure);
switch (failure.log_type) {
case LOG_TYPE_FATAL:
client = log_client_get(log, failure.pid);
client->fatal_logged = TRUE;
break;
- case LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL:
- /* Special case for master connection. If the following PID
- has logged a fatal/panic, don't log this message. */
- failure.log_type = LOG_TYPE_ERROR;
- if (failure.pid != master_pid) {
- i_error("Non-master process %s "
- "sent LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL",
- dec2str(failure.pid));
- break;
- }
-
- if (log_handle_seen_fatal(log, &failure.text))
- return;
- break;
case LOG_TYPE_OPTION:
log_parse_option(log, &failure);
return;
handshake.prefix_len);
*data += sizeof(handshake) + handshake.prefix_len;
}
+ if (strcmp(log->prefix, MASTER_LOG_PREFIX_NAME) == 0) {
+ if (log->fd != MASTER_LISTEN_FD_FIRST) {
+ i_error("Received master prefix in handshake "
+ "from non-master fd %d", log->fd);
+ return FALSE;
+ }
+ log->master = TRUE;
+ }
log->handshaked = TRUE;
return TRUE;
}
line = data;
if (!log->handshaked)
- log_connection_handshake(log, &line, ret);
+ (void)log_connection_handshake(log, &line, ret);
p = line;
while ((p = strchr(line, '\n')) != NULL) {
log->io = io_add(fd, IO_READ, log_connection_input, log);
log->clients = hash_table_create(default_pool, default_pool, 0,
NULL, NULL);
+ array_idx_set(&logs_by_fd, fd, &log);
DLLIST_PREPEND(&log_connections, log);
log_connection_input(log);
struct hash_iterate_context *iter;
void *key, *value;
+ array_idx_clear(&logs_by_fd, log->fd);
+
DLLIST_REMOVE(&log_connections, log);
iter = hash_table_iterate_init(log->clients);
if (log->io != NULL)
io_remove(&log->io);
+ if (close(log->fd) < 0)
+ i_error("close(log connection fd) failed: %m");
i_free(log->prefix);
i_free(log);
master_service_client_connection_destroyed(service);
}
+void log_connections_init(void)
+{
+ i_array_init(&logs_by_fd, 64);
+}
+
void log_connections_deinit(void)
{
/* normally we don't exit until all log connections are gone,
but we could get here when we're being killed by a signal */
while (log_connections != NULL)
log_connection_destroy(log_connections);
+ array_free(&logs_by_fd);
}
struct log_connection *log_connection_create(int fd);
void log_connection_destroy(struct log_connection *log);
+void log_connections_init(void);
void log_connections_deinit(void);
#endif
lib_signals_set_handler(SIGUSR1, TRUE, sig_reopen_logs, NULL);
master_pid = getppid();
+ log_connections_init();
}
static void main_deinit(void)
#include "common.h"
#include "array.h"
+#include "aqueue.h"
#include "hash.h"
#include "ioloop.h"
#include "fd-close-on-exec.h"
+#include "fd-set-nonblock.h"
#include "service.h"
#include "service-process.h"
#include "service-log.h"
#include <unistd.h>
-int services_log_init(struct service_list *service_list)
+static int service_log_fds_init(const char *log_prefix, int log_fd[2],
+ buffer_t *handshake_buf)
{
struct log_service_handshake handshake;
- struct service *const *services;
- unsigned int i, count;
- buffer_t *handshake_buf;
- ssize_t ret = 0;
+ ssize_t ret;
+
+ i_assert(log_fd[0] == -1);
+
+ if (pipe(log_fd) < 0) {
+ i_error("pipe() failed: %m");
+ return -1;
+ }
+ fd_close_on_exec(log_fd[0], TRUE);
+ fd_close_on_exec(log_fd[1], TRUE);
memset(&handshake, 0, sizeof(handshake));
handshake.log_magic = MASTER_LOG_MAGIC;
+ handshake.prefix_len = strlen(log_prefix);
+
+ buffer_set_used_size(handshake_buf, 0);
+ buffer_append(handshake_buf, &handshake, sizeof(handshake));
+ buffer_append(handshake_buf, log_prefix, strlen(log_prefix));
+
+ ret = write(log_fd[1], handshake_buf->data, handshake_buf->used);
+ if (ret < 0) {
+ i_error("write(log handshake) failed: %m");
+ return -1;
+ }
+ if ((size_t)ret != handshake_buf->used) {
+ i_error("write(log handshake) didn't write everything");
+ return -1;
+ }
+ return 0;
+}
+
+int services_log_init(struct service_list *service_list)
+{
+ struct service *const *services;
+ unsigned int i, count, n;
+ const char *log_prefix;
+ buffer_t *handshake_buf;
+ ssize_t ret = 0;
handshake_buf = buffer_create_dynamic(default_pool, 256);
services = array_get(&service_list->services, &count);
+
+ if (service_log_fds_init(MASTER_LOG_PREFIX_NAME,
+ service_list->master_log_fd,
+ handshake_buf) < 0)
+ ret = -1;
+ else
+ fd_set_nonblock(service_list->master_log_fd[1], TRUE);
+
+ n = 1;
for (i = 0; i < count; i++) {
if (services[i]->type == SERVICE_TYPE_LOG)
continue;
- i_assert(services[i]->log_fd[0] == -1);
- if (pipe(services[i]->log_fd) < 0) {
- i_error("pipe() failed: %m");
- ret = -1;
- break;
- }
- fd_close_on_exec(services[i]->log_fd[0], TRUE);
- fd_close_on_exec(services[i]->log_fd[1], TRUE);
-
- handshake.prefix_len = strlen(services[i]->set->name) + 2;
-
- buffer_set_used_size(handshake_buf, 0);
- buffer_append(handshake_buf, &handshake, sizeof(handshake));
- buffer_append(handshake_buf, services[i]->set->name,
- strlen(services[i]->set->name));
- buffer_append(handshake_buf, ": ", 2);
-
- ret = write(services[i]->log_fd[1],
- handshake_buf->data, handshake_buf->used);
- if (ret < 0) {
- i_error("write(log handshake) failed: %m");
- break;
- }
- if ((size_t)ret != handshake_buf->used) {
- i_error("write(log handshake) didn't write everything");
+ log_prefix = t_strconcat(services[i]->set->name, ": ", NULL);
+ if (service_log_fds_init(log_prefix,
+ services[i]->log_fd,
+ handshake_buf) < 0) {
ret = -1;
break;
}
+ services[i]->log_process_internal_fd =
+ MASTER_LISTEN_FD_FIRST + n++;
}
+
buffer_free(&handshake_buf);
if (ret < 0) {
services_log_deinit(service_list);
return 0;
}
-static void service_remove_log_io_writes(struct service *service)
+void services_log_clear_byes(struct service_list *service_list)
{
- struct hash_iterate_context *iter;
- void *key, *value;
+ struct service_process *const *processes, *process;
+ unsigned int i, count;
- iter = hash_table_iterate_init(service->list->pids);
- while (hash_table_iterate(iter, &key, &value)) {
- struct service_process *process = value;
+ if (service_list->io_log_write == NULL)
+ return;
- if (process->io_log_write != NULL)
- io_remove(&process->io_log_write);
+ processes = array_idx_modifiable(&service_list->bye_arr, 0);
+ count = aqueue_count(service_list->bye_queue);
+ for (i = 0; i < count; i++) {
+ process = processes[aqueue_idx(service_list->bye_queue, i)];
+ service_process_unref(process);
}
- hash_table_iterate_deinit(&iter);
+ aqueue_clear(service_list->bye_queue);
+ array_clear(&service_list->bye_arr);
+
+ io_remove(&service_list->io_log_write);
}
void services_log_deinit(struct service_list *service_list)
}
services[i]->log_fd[0] = -1;
services[i]->log_fd[1] = -1;
- service_remove_log_io_writes(services[i]);
+ services[i]->log_process_internal_fd = -1;
}
}
+ services_log_clear_byes(service_list);
+ if (service_list->master_log_fd[0] != -1) {
+ if (close(service_list->master_log_fd[0]) < 0)
+ i_error("close(master log fd) failed: %m");
+ if (close(service_list->master_log_fd[1]) < 0)
+ i_error("close(master log fd) failed: %m");
+ service_list->master_log_fd[0] = -1;
+ service_list->master_log_fd[1] = -1;
+ }
}
void services_log_dup2(ARRAY_TYPE(dup2) *dups,
unsigned int first_fd, unsigned int *fd_count)
{
struct service *const *services;
- unsigned int i, n, count;
+ unsigned int i, n = 0, count;
+
+ /* master log fd is always the first one */
+ dup2_append(dups, service_list->master_log_fd[0], first_fd);
+ n++; *fd_count += 1;
services = array_get(&service_list->services, &count);
- for (i = n = 0; i < count; i++) {
- if (services[i]->log_fd[1] != -1) {
- dup2_append(dups, services[i]->log_fd[0], first_fd + n);
- n++; *fd_count += 1;
- }
+ for (i = 0; i < count; i++) {
+ if (services[i]->log_fd[1] == -1)
+ continue;
+
+ i_assert((int)(first_fd + n) == services[i]->log_process_internal_fd);
+ dup2_append(dups, services[i]->log_fd[0], first_fd + n);
+ n++; *fd_count += 1;
}
}
int services_log_init(struct service_list *service_list);
void services_log_deinit(struct service_list *service_list);
+void services_log_clear_byes(struct service_list *service_list);
void services_log_dup2(ARRAY_TYPE(dup2) *dups,
struct service_list *service_list,
unsigned int first_fd, unsigned int *fd_count);
#include "common.h"
#include "array.h"
+#include "aqueue.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include <signal.h>
#include <sys/wait.h>
-static const char **
+static void
service_dup_fds(struct service *service, int auth_fd, int std_fd)
{
struct service_listener *const *listeners;
ARRAY_TYPE(dup2) dups;
- unsigned int i, count, n, socket_listener_count;
+ unsigned int i, count, n = 0, socket_listener_count;
/* stdin/stdout is already redirected to /dev/null. Other master fds
should have been opened with fd_close_on_exec() so we don't have to
socket_listener_count = 0;
listeners = array_get(&service->listeners, &count);
- t_array_init(&dups, count + 4);
- for (i = n = 0; i < count; i++) {
+ t_array_init(&dups, count + 10);
+
+ if (service->type == SERVICE_TYPE_LOG) {
+ i_assert(n == 0);
+ services_log_dup2(&dups, service->list, MASTER_LISTEN_FD_FIRST,
+ &socket_listener_count);
+ n += socket_listener_count;
+ }
+
+ for (i = 0; i < count; i++) {
if (listeners[i]->fd == -1)
continue;
dup2_append(&dups, auth_fd, MASTER_AUTH_FD);
env_put(t_strdup_printf("MASTER_AUTH_FD=%d", MASTER_AUTH_FD));
break;
- case SERVICE_TYPE_LOG:
- services_log_dup2(&dups, service->list,
- MASTER_LISTEN_FD_FIRST + n,
- &socket_listener_count);
- /* fall through */
default:
i_assert(auth_fd == -1);
dup2_append(&dups, null_fd, MASTER_AUTH_FD);
if (dup2_array(&dups) < 0)
service_error(service, "dup2s failed");
-#ifdef DEBUG
env_put(t_strdup_printf("SOCKET_COUNT=%d", socket_listener_count));
-#endif
-
- if (socket_listener_count == 1)
- return NULL;
- else {
- const char **args = t_new(const char *, 3);
- args[0] = "-s";
- args[1] = dec2str(socket_listener_count);
- return args;
- }
}
static int validate_uid_gid(struct master_settings *set, uid_t uid, gid_t gid,
}
if (pid == 0) {
/* child */
- const char **args;
-
if (fd[0] != -1)
(void)close(fd[0]);
service_process_setup_environment(service, uid);
base64_encode(data, data_size, str);
env_put(str_c(str));
}
- args = service_dup_fds(service, fd[1], std_fd);
+ service_dup_fds(service, fd[1], std_fd);
drop_privileges(service, auth_args);
- process_exec(service->executable, args);
+ process_exec(service->executable, NULL);
}
switch (service->type) {
return process;
}
-static void service_process_log_bye(struct service_process *process)
+static int service_process_write_bye(struct service_process *process)
{
const char *data;
+ data = t_strdup_printf("%d %s BYE\n",
+ process->service->log_process_internal_fd,
+ dec2str(process->pid));
+ if (write(process->service->list->master_log_fd[1],
+ data, strlen(data)) < 0) {
+ if (errno != EAGAIN)
+ i_error("write(log process) failed: %m");
+ return -1;
+ }
+ return 0;
+}
+
+static void service_list_log_flush_byes(struct service_list *service_list)
+{
+ struct service_process *const *processes, *process;
+
+ while (aqueue_count(service_list->bye_queue) > 0) {
+ processes = array_idx_modifiable(&service_list->bye_arr, 0);
+ process = processes[aqueue_idx(service_list->bye_queue, 0)];
+
+ if (service_process_write_bye(process) < 0) {
+ if (errno != EAGAIN)
+ services_log_clear_byes(service_list);
+ return;
+ }
+ service_process_unref(process);
+ aqueue_delete_tail(service_list->bye_queue);
+ }
+ io_remove(&service_list->io_log_write);
+}
+
+static void service_process_log_bye(struct service_process *process)
+{
+ struct service_list *service_list = process->service->list;
+
if (process->service->log_fd[1] == -1) {
/* stopping all services */
return;
}
- data = t_strdup_printf("\001%c%s bye\n",
- LOG_TYPE_OPTION+1, dec2str(process->pid));
- if (write(process->service->log_fd[1], data, strlen(data)) < 0) {
+ if (service_process_write_bye(process) < 0) {
if (errno != EAGAIN)
- i_error("write(log process) failed: %m");
- else {
- process->io_log_write =
- io_add(process->service->log_fd[1], IO_WRITE,
- service_process_log_bye, process);
- service_process_ref(process);
- }
- } else {
- if (process->io_log_write != NULL) {
- io_remove(&process->io_log_write);
- service_process_unref(process);
+ return;
+
+ if (service_list->io_log_write == NULL) {
+ service_list->io_log_write =
+ io_add(service_list->master_log_fd[1], IO_WRITE,
+ service_list_log_flush_byes,
+ service_list);
}
+ aqueue_append(service_list->bye_queue, &process);
+ service_process_ref(process);
}
}
if (--process->refcount > 0)
return TRUE;
- i_assert(process->io_log_write == NULL);
i_assert(process->destroyed);
i_free(process);
/* log it via the log process in charge of handling
this process's logging */
- data = t_strdup_printf("\001%c%s %s %s\n",
- type+1, my_pid, dec2str(process->pid), str);
- if (write(process->service->log_fd[1], data, strlen(data)) < 0) {
+ data = t_strdup_printf("%d %s DEFAULT-FATAL %s\n",
+ process->service->log_process_internal_fd,
+ dec2str(process->pid), str);
+ if (write(process->service->list->master_log_fd[1],
+ data, strlen(data)) < 0) {
i_error("write(log process) failed: %m");
- i_log_type(type, "%s", str);
+ i_error("%s", str);
}
}
/* kill the process if it doesn't send initial status notification */
struct timeout *to_status;
- /* we're waiting to be able to send "bye" to log process */
- struct io *io_log_write;
unsigned int destroyed:1;
};
#include "common.h"
#include "array.h"
+#include "aqueue.h"
#include "hash.h"
#include "str.h"
#include "service.h"
service->log_fd[1] = -1;
service->status_fd[0] = -1;
service->status_fd[1] = -1;
+ service->log_process_internal_fd = -1;
if (array_is_created(&set->unix_listeners))
unix_listeners = array_get(&set->unix_listeners, &unix_count);
service_list = p_new(pool, struct service_list, 1);
service_list->pool = pool;
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);
service_list->pids = hash_table_create(default_pool, pool, 0,
pid_hash, pid_hash_cmp);
+ p_array_init(&service_list->bye_arr, pool, 64);
+ service_list->bye_queue = aqueue_init(&service_list->bye_arr.arr);
return service_list;
}
hash_table_iterate_deinit(&iter);
hash_table_destroy(&service_list->pids);
+ aqueue_deinit(&service_list->bye_queue);
pool_unref(&service_list->pool);
}
/* max number of processes allowed */
unsigned int process_limit;
- /* log process pipe file descriptors */
+ /* log process pipe file descriptors. */
int log_fd[2];
+ /* fd that log process sees log_fd[0] as. can be used to identify
+ service name when sending commands via master_log_fd. */
+ int log_process_internal_fd;
/* status report pipe file descriptors */
int status_fd[2];
struct hash_table *pids;
const char *const *child_process_env;
+ /* nonblocking log fds usd by master */
+ int master_log_fd[2];
+ /* we're waiting to be able to send "bye" to log process */
+ struct io *io_log_write;
+ /* List of processes who are waiting for the "bye" */
+ struct aqueue *bye_queue;
+ ARRAY_DEFINE(bye_arr, struct service_process *);
+
ARRAY_DEFINE(services, struct service *);
};