--- /dev/null
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "master-instance.h"
+#include "doveadm.h"
+#include "doveadm-print.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+extern struct doveadm_cmd doveadm_cmd_instance[];
+
+static void instance_cmd_help(doveadm_command_t *cmd) ATTR_NORETURN;
+
+static bool pid_file_read(const char *path)
+{
+ char buf[32];
+ int fd;
+ ssize_t ret;
+ pid_t pid;
+ bool found = FALSE;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ if (errno != ENOENT)
+ i_error("open(%s) failed: %m", path);
+ return FALSE;
+ }
+
+ ret = read(fd, buf, sizeof(buf));
+ if (ret < 0)
+ i_error("read(%s) failed: %m", path);
+ else if (ret > 0 && buf[ret-1] == '\n') {
+ buf[ret-1] = '\0';
+ if (str_to_pid(buf, &pid) == 0) {
+ found = !(pid == getpid() ||
+ (kill(pid, 0) < 0 && errno == ESRCH));
+ }
+ }
+ (void)close(fd);
+ return found;
+}
+
+static void cmd_instance_list(int argc ATTR_UNUSED, char *argv[] ATTR_UNUSED)
+{
+ struct master_instance_list *list;
+ struct master_instance_list_iter *iter;
+ const struct master_instance *inst;
+ const char *pidfile_path;
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ doveadm_print_header("path", "path", DOVEADM_PRINT_HEADER_FLAG_EXPAND);
+ doveadm_print_header_simple("name");
+ doveadm_print_header_simple("running");
+
+ list = master_instance_list_init(MASTER_INSTANCE_PATH);
+ iter = master_instance_list_iterate_init(list);
+ while ((inst = master_instance_iterate_list_next(iter)) != NULL) {
+ doveadm_print(inst->base_dir);
+ doveadm_print(inst->name);
+ pidfile_path = t_strconcat(inst->base_dir, "/master.pid", NULL);
+ if (pid_file_read(pidfile_path))
+ doveadm_print("yes");
+ else
+ doveadm_print("no");
+ }
+ master_instance_iterate_list_deinit(&iter);
+ master_instance_list_deinit(&list);
+}
+
+static void cmd_instance_remove(int argc, char *argv[])
+{
+ struct master_instance_list *list;
+ int ret;
+
+ if (argc != 2)
+ instance_cmd_help(cmd_instance_remove);
+
+ list = master_instance_list_init(MASTER_INSTANCE_PATH);
+ if ((ret = master_instance_list_remove(list, argv[1])) < 0)
+ i_error("Failed to remove instance");
+ else if (ret == 0)
+ i_error("Instance already didn't exist");
+ master_instance_list_deinit(&list);
+}
+
+struct doveadm_cmd doveadm_cmd_instance[] = {
+ { cmd_instance_list, "instance list", "" },
+ { cmd_instance_remove, "instance remove", "<base dir>" }
+};
+
+static void instance_cmd_help(doveadm_command_t *cmd)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(doveadm_cmd_instance); i++) {
+ if (doveadm_cmd_instance[i].cmd == cmd)
+ help(&doveadm_cmd_instance[i]);
+ }
+ i_unreached();
+}
+
+void doveadm_register_instance_commands(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(doveadm_cmd_instance); i++)
+ doveadm_register_cmd(&doveadm_cmd_instance[i]);
+}
--- /dev/null
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "ostream.h"
+#include "file-dotlock.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-instance.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+struct master_instance_list {
+ pool_t pool;
+ const char *path;
+
+ ARRAY_DEFINE(instances, struct master_instance);
+};
+
+struct master_instance_list_iter {
+ struct master_instance_list *list;
+ unsigned int idx;
+};
+
+static const struct dotlock_settings dotlock_set = {
+ .timeout = 1,
+ .stale_timeout = 60
+};
+
+struct master_instance_list *master_instance_list_init(const char *path)
+{
+ struct master_instance_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("master instances", 256);
+ list = p_new(pool, struct master_instance_list, 1);
+ list->pool = pool;
+ list->path = p_strdup(pool, path);
+ p_array_init(&list->instances, pool, 8);
+ return list;
+}
+
+void master_instance_list_deinit(struct master_instance_list **_list)
+{
+ struct master_instance_list *list = *_list;
+
+ *_list = NULL;
+ pool_unref(&list->pool);
+}
+
+static void master_instance_list_drop_stale(struct master_instance_list *list)
+{
+ const struct master_instance *instances;
+ unsigned int i, count;
+ time_t stale_timestamp = time(NULL) - MASTER_INSTANCE_AUTO_STALE_SECS;
+
+ instances = array_get(&list->instances, &count);
+ for (i = 0; i < count; ) {
+ if (instances[i].name[0] == '\0' &&
+ instances[i].last_used < stale_timestamp) {
+ array_delete(&list->instances, i, 1);
+ instances = array_get(&list->instances, &count);
+ } else {
+ i++;
+ }
+ }
+}
+
+static int
+master_instance_list_add_line(struct master_instance_list *list,
+ const char *line)
+{
+ struct master_instance *inst;
+ const char *const *args;
+ time_t last_used;
+
+ /* <last used> <name> <base dir> */
+ args = t_strsplit_tabescaped(line);
+ if (str_array_length(args) != 3)
+ return -1;
+ if (str_to_time(args[0], &last_used) < 0)
+ return -1;
+
+ inst = array_append_space(&list->instances);
+ inst->last_used = last_used;
+ inst->name = p_strdup(list->pool, args[1]);
+ inst->base_dir = p_strdup(list->pool, args[2]);
+ return 0;
+}
+
+static int master_instance_list_refresh(struct master_instance_list *list)
+{
+ struct istream *input;
+ const char *line;
+ int fd, ret = 0;
+
+ array_clear(&list->instances);
+
+ fd = open(list->path, O_RDONLY);
+ if (fd == -1) {
+ if (errno == ENOENT)
+ return 0;
+
+ i_error("open(%s) failed: %m", list->path);
+ return -1;
+ }
+ input = i_stream_create_fd(fd, PATH_MAX, TRUE);
+ while ((line = i_stream_read_next_line(input)) != NULL) T_BEGIN {
+ if (master_instance_list_add_line(list, line) < 0)
+ i_error("Invalid line in %s: %s", list->path, line);
+ } T_END;
+ if (input->stream_errno != 0) {
+ i_error("read(%s) failed: %m", line);
+ ret = -1;
+ }
+ i_stream_destroy(&input);
+ return ret;
+}
+
+static int
+master_instance_list_write(struct master_instance_list *list,
+ int fd, const char *path)
+{
+ struct ostream *output;
+ const struct master_instance *inst;
+ string_t *str = t_str_new(128);
+
+ output = o_stream_create_fd(fd, 0, FALSE);
+ o_stream_cork(output);
+ array_foreach(&list->instances, inst) {
+ str_truncate(str, 0);
+ str_printfa(str, "%ld\t", (long)inst->last_used);
+ str_tabescape_write(str, inst->name);
+ str_append_c(str, '\t');
+ str_tabescape_write(str, inst->base_dir);
+ str_append_c(str, '\n');
+ (void)o_stream_send(output, str_data(str), str_len(str));
+ }
+ o_stream_uncork(output);
+ (void)o_stream_flush(output);
+ if (output->last_failed_errno != 0) {
+ errno = output->last_failed_errno;
+ i_error("write(%s) failed: %m", path);
+ return -1;
+ }
+ return 0;
+}
+
+static int master_instance_write_init(struct master_instance_list *list,
+ struct dotlock **dotlock_r)
+{
+ int fd;
+
+ *dotlock_r = NULL;
+
+ fd = file_dotlock_open_mode(&dotlock_set, list->path, 0, 0644,
+ (uid_t)-1, (gid_t)-1, dotlock_r);
+ if (fd == -1) {
+ i_error("file_dotlock_open(%s) failed: %m", list->path);
+ return -1;
+ }
+ if (master_instance_list_refresh(list) < 0) {
+ (void)file_dotlock_delete(dotlock_r);
+ return -1;
+ }
+ return fd;
+}
+
+static int master_instance_write_finish(struct master_instance_list *list,
+ int fd, struct dotlock **dotlock)
+{
+ const char *lock_path = file_dotlock_get_lock_path(*dotlock);
+ int ret;
+
+ master_instance_list_drop_stale(list);
+
+ T_BEGIN {
+ ret = master_instance_list_write(list, fd, lock_path);
+ } T_END;
+ if (ret < 0) {
+ (void)file_dotlock_delete(dotlock);
+ return -1;
+ }
+ if (fdatasync(fd) < 0) {
+ i_error("fdatasync(%s) failed: %m", lock_path);
+ (void)file_dotlock_delete(dotlock);
+ return -1;
+ }
+ return file_dotlock_replace(dotlock, 0);
+}
+
+static struct master_instance *
+master_instance_find(struct master_instance_list *list,
+ const char *base_dir)
+{
+ struct master_instance *inst;
+
+ array_foreach_modifiable(&list->instances, inst) {
+ if (strcmp(inst->base_dir, base_dir) == 0)
+ return inst;
+ }
+ return NULL;
+}
+
+int master_instance_list_update(struct master_instance_list *list,
+ const char *base_dir)
+{
+ struct master_instance *inst;
+ struct dotlock *dotlock;
+ int fd;
+
+ if ((fd = master_instance_write_init(list, &dotlock)) == -1)
+ return -1;
+
+ inst = master_instance_find(list, base_dir);
+ if (inst == NULL) {
+ inst = array_append_space(&list->instances);
+ inst->name = "";
+ inst->base_dir = p_strdup(list->pool, base_dir);
+ }
+ inst->last_used = time(NULL);
+
+ return master_instance_write_finish(list, fd, &dotlock);
+}
+
+int master_instance_list_set_name(struct master_instance_list *list,
+ const char *base_dir, const char *name)
+{
+ const struct master_instance *orig_inst;
+ struct master_instance *inst;
+ struct dotlock *dotlock;
+ int fd;
+
+ i_assert(*name != '\0');
+
+ if ((fd = master_instance_write_init(list, &dotlock)) == -1)
+ return -1;
+
+ orig_inst = master_instance_list_find_by_name(list, name);
+ if (orig_inst != NULL &&
+ strcmp(orig_inst->base_dir, base_dir) != 0) {
+ /* name already used */
+ (void)file_dotlock_delete(&dotlock);
+ return 0;
+ }
+
+ inst = master_instance_find(list, base_dir);
+ if (inst == NULL) {
+ inst = array_append_space(&list->instances);
+ inst->base_dir = p_strdup(list->pool, base_dir);
+ }
+ inst->name = p_strdup(list->pool, name);
+ inst->last_used = time(NULL);
+
+ return master_instance_write_finish(list, fd, &dotlock) < 0 ? -1 : 1;
+}
+
+int master_instance_list_remove(struct master_instance_list *list,
+ const char *base_dir)
+{
+ struct dotlock *dotlock;
+ const struct master_instance *instances;
+ unsigned int i, count;
+ int fd;
+
+ if ((fd = master_instance_write_init(list, &dotlock)) == -1)
+ return -1;
+
+ instances = array_get(&list->instances, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(instances[i].base_dir, base_dir) == 0) {
+ array_delete(&list->instances, i, 1);
+ break;
+ }
+ }
+
+ if (i == count) {
+ (void)file_dotlock_delete(&dotlock);
+ return 0;
+ }
+ return master_instance_write_finish(list, fd, &dotlock) < 0 ? -1 : 1;
+}
+
+const struct master_instance *
+master_instance_list_find_by_name(struct master_instance_list *list,
+ const char *name)
+{
+ const struct master_instance *inst;
+
+ i_assert(*name != '\0');
+
+ if (array_count(&list->instances) == 0)
+ (void)master_instance_list_refresh(list);
+
+ array_foreach(&list->instances, inst) {
+ if (strcmp(inst->name, name) == 0)
+ return inst;
+ }
+ return NULL;
+}
+
+struct master_instance_list_iter *
+master_instance_list_iterate_init(struct master_instance_list *list)
+{
+ struct master_instance_list_iter *iter;
+
+ iter = i_new(struct master_instance_list_iter, 1);
+ iter->list = list;
+ (void)master_instance_list_refresh(list);
+ master_instance_list_drop_stale(list);
+ return iter;
+}
+
+const struct master_instance *
+master_instance_iterate_list_next(struct master_instance_list_iter *iter)
+{
+ if (iter->idx == array_count(&iter->list->instances))
+ return NULL;
+ return array_idx(&iter->list->instances, iter->idx++);
+}
+
+void master_instance_iterate_list_deinit(struct master_instance_list_iter **_iter)
+{
+ struct master_instance_list_iter *iter = *_iter;
+
+ *_iter = NULL;
+
+ i_free(iter);
+}
#include "execv-const.h"
#include "mountpoint-list.h"
#include "restrict-process-size.h"
+#include "master-instance.h"
#include "master-service.h"
#include "master-service-settings.h"
#include "askpass.h"
struct service_list *services;
static char *pidfile_path;
+static struct master_instance_list *instances;
+static struct timeout *to_instance;
static failure_callback_t *orig_fatal_callback;
static failure_callback_t *orig_error_callback;
mountpoint_list_deinit(&mountpoints);
}
+static void instance_update_now(struct master_instance_list *list)
+{
+ int ret;
+
+ ret = master_instance_list_set_name(list, services->set->base_dir,
+ services->set->instance_name);
+ if (ret == 0) {
+ /* duplicate instance names. allow without warning.. */
+ master_instance_list_update(list, services->set->base_dir);
+ }
+
+ if (to_instance != NULL)
+ timeout_remove(&to_instance);
+ to_instance = timeout_add((3600*12 + rand()%(60*30)) * 1000,
+ instance_update_now, list);
+}
+
+static void instance_update(void)
+{
+ instances = master_instance_list_init(MASTER_INSTANCE_PATH);
+ instance_update_now(instances);
+}
+
static void
sig_settings_reload(const siginfo_t *si ATTR_UNUSED,
void *context ATTR_UNUSED)
create_pid_file(pidfile_path);
create_config_symlink(set);
mountpoints_update(set);
+ instance_update();
services_monitor_start(services);
}
static void main_deinit(void)
{
+ instance_update_now(instances);
+ timeout_remove(&to_instance);
+ master_instance_list_deinit(&instances);
+
/* kill services and wait for them to die before unlinking pid file */
global_dead_pipe_close();
services_destroy(services, TRUE);