This allows lib-master to re-read the configuration using the same open fd.
The binary configuration is written to an unlinked file. The file's fd is
sent to config clients, so the binary config stays allocated until all
config clients have closed the fd.
#include "llist.h"
#include "istream.h"
#include "ostream.h"
+#include "ostream-unix.h"
#include "strescape.h"
#include "settings-parser.h"
#include "master-service.h"
};
static struct config_connection *config_connections = NULL;
+static int global_config_fd = -1;
static const char *const *
config_connection_next_line(struct config_connection *conn)
{
const char *import_environment;
- if (config_dump_full(conn->output, &import_environment) < 0) {
- config_connection_destroy(conn);
- return -1;
+ if (null_strcmp(*args, "reload") == 0) {
+ const char *path, *error;
+
+ path = master_service_get_config_path(master_service);
+ if (config_parse_file(path, TRUE, NULL, &error) <= 0) {
+ o_stream_nsend_str(conn->output,
+ t_strconcat("-", error, "\n", NULL));
+ return 0;
+ }
+ i_close_fd(&global_config_fd);
+ }
+
+ if (global_config_fd == -1) {
+ int fd = config_dump_full(CONFIG_DUMP_FULL_DEST_RUNDIR,
+ &import_environment);
+ if (fd == -1) {
+ o_stream_nsend_str(conn->output, "-Failed\n");
+ return 0;
+ }
+ global_config_fd = fd;
}
+ if (!o_stream_unix_write_fd(conn->output, global_config_fd))
+ i_unreached();
+
+ o_stream_nsend_str(conn->output, "+\n");
return 0;
}
conn = i_new(struct config_connection, 1);
conn->fd = fd;
conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE);
- conn->output = o_stream_create_fd(fd, SIZE_MAX);
+ conn->output = o_stream_create_unix(fd, SIZE_MAX);
o_stream_set_no_error_handling(conn->output, TRUE);
conn->io = io_add(fd, IO_READ, config_connection_input, conn);
DLLIST_PREPEND(&config_connections, conn);
{
while (config_connections != NULL)
config_connection_destroy(config_connections);
+ i_close_fd(&global_config_fd);
}
#include "lib.h"
#include "str.h"
#include "strescape.h"
+#include "safe-mkstemp.h"
#include "ostream.h"
#include "config-parser.h"
#include "config-request.h"
o_stream_nsend(output, str_data(str), str_len(str));
}
-static int
+static bool
config_dump_full_sections(struct ostream *output, unsigned int section_idx)
{
struct config_filter_parser *const *filters;
config_export_parsers(export_ctx, (*filters)->parsers);
ret = config_export_finish(&export_ctx, §ion_idx);
} T_END;
- return 0;
+ return ret == 0;
}
-int config_dump_full(struct ostream *output, const char **import_environment_r)
+int config_dump_full(enum config_dump_full_dest dest,
+ const char **import_environment_r)
{
struct config_export_context *export_ctx;
struct config_filter empty_filter;
enum config_dump_flags flags;
unsigned int section_idx = 0;
- int ret;
-
- i_zero(&empty_filter);
- o_stream_cork(output);
+ int fd = -1;
+ bool failed = FALSE;
struct dump_context dump_ctx = {
- .output = output,
.delayed_output = str_new(default_pool, 256),
};
i_zero(&empty_filter);
config_export_by_filter(export_ctx, &empty_filter);
+ string_t *path = t_str_new(128);
+ const char *final_path = NULL;
+ switch (dest) {
+ case CONFIG_DUMP_FULL_DEST_RUNDIR: {
+ const char *base_dir = config_export_get_base_dir(export_ctx);
+ final_path = t_strdup_printf("%s/dovecot.conf.binary", base_dir);
+ str_append(path, final_path);
+ str_append_c(path, '.');
+ break;
+ }
+ case CONFIG_DUMP_FULL_DEST_TEMPDIR:
+ /* create an unlinked file to /tmp */
+ str_append(path, "/tmp/doveconf.");
+ break;
+ case CONFIG_DUMP_FULL_DEST_STDOUT:
+ dump_ctx.output = o_stream_create_fd(STDOUT_FILENO, IO_BLOCK_SIZE);
+ o_stream_set_name(dump_ctx.output, "<stdout>");
+ fd = 0;
+ break;
+ }
+
+ if (dump_ctx.output == NULL) {
+ fd = safe_mkstemp(path, 0700, (uid_t)-1, (gid_t)-1);
+ if (fd == -1) {
+ i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+ (void)config_export_finish(&export_ctx, §ion_idx);
+ str_free(&dump_ctx.delayed_output);
+ return -1;
+ }
+ if (dest == CONFIG_DUMP_FULL_DEST_TEMPDIR)
+ i_unlink(str_c(path));
+ dump_ctx.output = o_stream_create_fd(fd, IO_BLOCK_SIZE);
+ o_stream_set_name(dump_ctx.output, str_c(path));
+ }
+ struct ostream *output = dump_ctx.output;
+
+ i_zero(&empty_filter);
+ o_stream_cork(output);
+
*import_environment_r =
t_strdup(config_export_get_import_environment(export_ctx));
if (config_export_finish(&export_ctx, §ion_idx) < 0)
- ret = -1;
+ failed = TRUE;
else
- ret = config_dump_full_sections(output, section_idx);
+ failed = !config_dump_full_sections(output, section_idx);
if (dump_ctx.delayed_output != NULL &&
str_len(dump_ctx.delayed_output) > 0) {
str_free(&dump_ctx.delayed_output);
o_stream_nsend_str(output, "\n");
- o_stream_uncork(output);
- return ret;
+ if (o_stream_finish(output) < 0) {
+ i_error("write(%s) failed: %s",
+ o_stream_get_name(output), o_stream_get_error(output));
+ failed = TRUE;
+ }
+
+ if (final_path != NULL && !failed) {
+ if (rename(str_c(path), final_path) < 0) {
+ i_error("rename(%s, %s) failed: %m",
+ str_c(path), final_path);
+ /* the fd is still readable, so don't return failure */
+ }
+ }
+ if (!failed && dest != CONFIG_DUMP_FULL_DEST_STDOUT &&
+ lseek(fd, 0, SEEK_SET) < 0) {
+ i_error("lseek(%s, 0) failed: %m", o_stream_get_name(output));
+ failed = TRUE;
+ }
+ if (failed) {
+ if (dest == CONFIG_DUMP_FULL_DEST_STDOUT)
+ fd = -1;
+ else
+ i_close_fd(&fd);
+ }
+ o_stream_destroy(&output);
+ return fd;
}
#ifndef CONFIG_DUMP_FULL
#define CONFIG_DUMP_FULL
-int config_dump_full(struct ostream *output, const char **import_environment_r);
+enum config_dump_full_dest {
+ CONFIG_DUMP_FULL_DEST_RUNDIR,
+ CONFIG_DUMP_FULL_DEST_TEMPDIR,
+ CONFIG_DUMP_FULL_DEST_STDOUT,
+};
+
+int config_dump_full(enum config_dump_full_dest dest,
+ const char **import_environment_r);
#endif
i_unreached();
}
+const char *config_export_get_base_dir(struct config_export_context *ctx)
+{
+ enum setting_type stype;
+ unsigned int i;
+
+ for (i = 0; ctx->parsers[i].root != NULL; i++) {
+ if (ctx->parsers[i].root == &master_service_setting_parser_info) {
+ const char *const *value =
+ settings_parse_get_value(ctx->parsers[i].parser,
+ "base_dir", &stype);
+ i_assert(value != NULL);
+ return *value;
+ }
+ }
+ i_unreached();
+}
+
static void config_export_free(struct config_export_context *ctx)
{
if (ctx->dup_parsers != NULL)
struct master_service_settings_output *output_r);
const char *
config_export_get_import_environment(struct config_export_context *ctx);
+const char *config_export_get_base_dir(struct config_export_context *ctx);
int config_export_finish(struct config_export_context **ctx,
unsigned int *section_idx);
#include "ostream.h"
#include "str.h"
#include "strescape.h"
-#include "safe-mkstemp.h"
#include "settings-parser.h"
#include "master-interface.h"
#include "master-service.h"
#include "config-request.h"
#include "dovecot-version.h"
-#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sysexits.h>
i_fatal("%s", error);
if (dump_full && exec_args == NULL) {
- struct ostream *output = o_stream_create_fd(STDOUT_FILENO, 0);
- o_stream_set_no_error_handling(output, TRUE);
- ret2 = config_dump_full(output, &import_environment);
- o_stream_destroy(&output);
+ ret2 = config_dump_full(CONFIG_DUMP_FULL_DEST_STDOUT,
+ &import_environment);
} else if (dump_full) {
- /* create an unlinked file to /tmp */
- string_t *path = t_str_new(128);
- str_append(path, "/tmp/doveconf.");
- int temp_fd = safe_mkstemp(path, 0700, (uid_t)-1, (gid_t)-1);
- if (temp_fd == -1)
- i_fatal("safe_mkstemp(%s) failed: %m", str_c(path));
- i_unlink(str_c(path));
- struct ostream *output =
- o_stream_create_fd(temp_fd, IO_BLOCK_SIZE);
- if (config_dump_full(output, &import_environment) < 0)
- i_fatal("Invalid configuration");
- if (o_stream_finish(output) < 0) {
- i_fatal("write(%s) failed: %s",
- str_c(path), o_stream_get_error(output));
- }
- o_stream_destroy(&output);
-
+ int temp_fd = config_dump_full(CONFIG_DUMP_FULL_DEST_TEMPDIR,
+ &import_environment);
if (getenv(DOVECOT_PRESERVE_ENVS_ENV) != NULL) {
/* Standalone binary is getting its configuration via
doveconf. Clean the environment before calling it.
master_service_import_environment(import_environment);
master_service_env_clean();
}
- env_put(DOVECOT_CONFIG_FD_ENV, dec2str(temp_fd));
- execvp(exec_args[0], exec_args);
- i_fatal("execvp(%s) failed: %m", exec_args[0]);
+ if (temp_fd != -1) {
+ env_put(DOVECOT_CONFIG_FD_ENV, dec2str(temp_fd));
+ execvp(exec_args[0], exec_args);
+ i_fatal("execvp(%s) failed: %m", exec_args[0]);
+ }
+ ret2 = -1;
} else if (simple_output) {
struct config_export_context *ctx;
unsigned int section_idx = 0;
#include "event-filter.h"
#include "path-util.h"
#include "istream.h"
+#include "fdpass.h"
#include "write-full.h"
#include "str.h"
#include "strescape.h"
{
struct stat st;
const char *path;
- int fd;
+ int fd = -1;
*path_r = path = input->config_path != NULL ? input->config_path :
master_service_get_config_path(service);
this fails because the socket is 0600. it's useful for
developers though. :) */
fd = net_connect_unix(DOVECOT_CONFIG_SOCKET_PATH);
- if (fd >= 0) {
+ if (fd >= 0)
*path_r = DOVECOT_CONFIG_SOCKET_PATH;
- net_set_nonblock(fd, FALSE);
- return fd;
+ else {
+ /* fallback to executing doveconf */
}
- /* fallback to executing doveconf */
}
- if (stat(path, &st) < 0) {
- *error_r = errno == EACCES ? eacces_error_get("stat", path) :
- t_strdup_printf("stat(%s) failed: %m", path);
- config_error_update_path_source(service, input, error_r);
- return -1;
- }
+ if (fd == -1) {
+ if (stat(path, &st) < 0) {
+ *error_r = errno == EACCES ?
+ eacces_error_get("stat", path) :
+ t_strdup_printf("stat(%s) failed: %m", path);
+ config_error_update_path_source(service, input, error_r);
+ return -1;
+ }
- if (!S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode)) {
- /* it's not an UNIX socket, don't even try to connect */
- fd = -1;
- errno = ENOTSOCK;
- } else {
- fd = net_connect_unix_with_retries(path, 1000);
- }
- if (fd < 0) {
- *error_r = t_strdup_printf("net_connect_unix(%s) failed: %m",
- path);
- config_exec_fallback(service, input, error_r);
- return -1;
+ if (!S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode)) {
+ /* it's not an UNIX socket, don't even try to connect */
+ fd = -1;
+ errno = ENOTSOCK;
+ } else {
+ fd = net_connect_unix_with_retries(path, 1000);
+ }
+ if (fd < 0) {
+ *error_r = t_strdup_printf(
+ "net_connect_unix(%s) failed: %m", path);
+ config_exec_fallback(service, input, error_r);
+ return -1;
+ }
}
net_set_nonblock(fd, FALSE);
- return fd;
-}
+ const char *str = !input->reload_config ?
+ CONFIG_HANDSHAKE"REQ\n" :
+ CONFIG_HANDSHAKE"REQ\treload\n";
+ alarm(CONFIG_READ_TIMEOUT_SECS);
+ int ret = write_full(fd, str, strlen(str));
+ if (ret < 0)
+ *error_r = t_strdup_printf("write_full(%s) failed: %m", path);
-static int
-config_send_request(int fd, const char *path, const char **error_r)
-{
- int ret;
+ int config_fd = -1;
+ if (ret == 0) {
+ /* read the config fd as reply */
+ char buf[1024];
+ ret = fd_read(fd, buf, sizeof(buf)-1, &config_fd);
+ if (ret < 0)
+ *error_r = t_strdup_printf("fd_read() failed: %m");
+ else if (ret > 0 && buf[0] == '+' && buf[1] == '\n')
+ ; /* success */
+ else if (ret > 0 && buf[0] == '-') {
+ buf[ret] = '\0';
+ *error_r = t_strdup_printf("Failed to read config: %s",
+ t_strcut(buf+1, '\n'));
+ i_close_fd(&config_fd);
+ } else {
+ buf[ret] = '\0';
+ *error_r = t_strdup_printf(
+ "Failed to read config: Unexpected reply '%s'",
+ t_strcut(buf, '\n'));
+ i_close_fd(&config_fd);
+ }
+ }
+ alarm(0);
+ i_close_fd(&fd);
- T_BEGIN {
- const char *str = CONFIG_HANDSHAKE"REQ\n";
- ret = write_full(fd, str, strlen(str));
- } T_END;
- if (ret < 0) {
- *error_r = t_strdup_printf("write_full(%s) failed: %m", path);
+ if (config_fd == -1) {
+ config_exec_fallback(service, input, error_r);
return -1;
}
- return 0;
+ return config_fd;
}
static int
i_zero(&input);
input.never_exec = TRUE;
fd = master_service_open_config(service, &input, &path, &error);
- if (fd != -1)
+ if (fd != -1) {
service->config_fd = fd;
+ fd_close_on_exec(service->config_fd, TRUE);
+ }
}
static void
return 0;
}
linenum++;
- if (linenum == 1 && str_begins(line, "ERROR ", &line)) {
- *error_r = t_strdup(line);
- return -1;
- }
if (str_begins(line, ":FILTER ", &filter_string)) {
struct event_filter *filter = event_filter_create();
filter_string_parse_protocol(filter_string, &protocols);
i_stream_get_error(input));
} else if (input->v_offset == 0) {
*error_r = t_strdup_printf(
- "read(%s) disconnected before receiving any data",
+ "read(%s) returned EOF before receiving any data",
i_stream_get_name(input));
} else {
*error_r = t_strdup_printf(
- "read(%s) disconnected before receiving end-of-settings line",
+ "read(%s) returned EOF before receiving end-of-settings line",
i_stream_get_name(input));
}
return -1;
const char *path = NULL, *value, *error;
unsigned int i;
int ret, fd = -1;
- bool use_environment = FALSE, retry;
+ bool use_environment = FALSE;
i_zero(output_r);
- value = getenv(DOVECOT_CONFIG_FD_ENV);
- if (value != NULL) {
+ if (service->config_fd != -1 && !input->reload_config) {
+ /* config was already read once */
+ fd = service->config_fd;
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ i_fatal("lseek(config fd) failed: %m");
+ path = "<config fd>";
+ } else if ((value = getenv(DOVECOT_CONFIG_FD_ENV)) != NULL) {
/* doveconf -F parameter already executed us back.
The configuration is in DOVECOT_CONFIG_FD. */
if (str_to_int(value, &fd) < 0 || fd < 0)
i_fatal("Invalid "DOVECOT_CONFIG_FD_ENV": %s", value);
+ path = t_strdup_printf("<"DOVECOT_CONFIG_FD_ENV" %d>", fd);
} else if (getenv("DOVECONF_ENV") != NULL) {
use_environment = (service->flags &
MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) == 0;
} else if ((service->flags & MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS) == 0) {
/* Open config via socket if possible. If it doesn't work,
execute doveconf -F. */
- retry = service->config_fd != -1;
- for (;;) {
- fd = master_service_open_config(service, input,
- &path, error_r);
- if (fd == -1) {
- if (errno == EACCES)
- output_r->permission_denied = TRUE;
- return -1;
- }
-
- if (config_send_request(fd, path, error_r) == 0)
- break;
- i_close_fd(&fd);
- if (!retry) {
- config_exec_fallback(service, input, error_r);
- return -1;
- }
- /* config process died, retry connecting */
- retry = FALSE;
+ fd = master_service_open_config(service, input, &path, error_r);
+ if (fd == -1) {
+ if (errno == EACCES)
+ output_r->permission_denied = TRUE;
+ return -1;
}
}
if (fd != -1) {
istream = i_stream_create_fd(fd, SIZE_MAX);
- alarm(CONFIG_READ_TIMEOUT_SECS);
+ i_stream_set_name(istream, path);
ret = master_service_settings_read_stream(parser, event,
istream, output_r, error_r);
- alarm(0);
i_stream_unref(&istream);
if (ret < 0) {
return -1;
}
- if ((service->flags & MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN) != 0 &&
- service->config_fd == -1 && input->config_path == NULL &&
- getenv(DOVECOT_CONFIG_FD_ENV) == NULL)
+ if (input->config_path == NULL) {
+ i_assert(service->config_fd == -1 ||
+ service->config_fd == fd);
service->config_fd = fd;
- else
+ fd_close_on_exec(service->config_fd, TRUE);
+ } else {
i_close_fd(&fd);
+ }
env_remove(DOVECOT_CONFIG_FD_ENV);
}
event_unref(&event);
bool preserve_environment;
bool preserve_user;
bool preserve_home;
+ bool reload_config;
bool never_exec;
bool always_exec;
bool use_sysexits;
input.roots = set_roots;
input.config_path = services_get_config_socket_path(services);
input.never_exec = TRUE;
+ input.reload_config = TRUE;
if (master_service_settings_read(master_service, &input,
&output, &error) < 0) {
i_error("Error reading configuration: %s", error);