]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
config, lib-settings: Add support for binary config file cache
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Sun, 9 Mar 2025 09:09:07 +0000 (11:09 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Fri, 14 Mar 2025 10:29:43 +0000 (10:29 +0000)
This is mainly intended for optimizing our CI performance. It shouldn't
be used in production.

src/config/config-dump-full.c
src/config/config-parser-private.h
src/config/config-parser.c
src/config/config-parser.h
src/config/config-request.h
src/config/doveconf.c
src/lib-master/master-service-settings.c
src/lib-master/master-service-settings.h
src/lib-master/test-master-service-settings.c
src/lib-settings/settings.c
src/lib-settings/settings.h

index ff75f067682371716e416af1821d509cab0be682..23cf56b7449e87d6170c323ca3daa6057f38131e 100644 (file)
@@ -8,6 +8,7 @@
 #include "safe-mkstemp.h"
 #include "ostream.h"
 #include "settings.h"
+#include "master-service-settings.h"
 #include "config-parser.h"
 #include "config-request.h"
 #include "config-filter.h"
    "DOVECOT-CONFIG\t1.0\n"
    <64bit big-endian: settings full size>
 
+   <32bit big-endian: number of paths used for caching>
+   Repeat for "number of paths":
+     <NUL-terminated string: path>
+     <64bit big-endian: inode>
+     <64bit big-endian: size>
+     <32bit big-endian: mtime UNIX timestamp>
+     <32bit big-endian: mtime nsecs>
+     <32bit big-endian: ctime UNIX timestamp>
+     <32bit big-endian: ctime nsecs>
+
    <32bit big-endian: event filter strings count>
    Repeat for "event filter strings count":
      <NUL-terminated string: event filter string>
@@ -116,6 +127,54 @@ static int output_blob_size(struct ostream *output, uoff_t blob_size_offset)
        return 0;
 }
 
+static const char *config_get_cache_dir(enum config_dump_flags flags)
+{
+       if ((flags & CONFIG_DUMP_FLAG_WRITE_BINARY_CACHE) == 0)
+               return NULL;
+       return getenv("DOVECOT_CONFIG_CACHE");
+}
+
+static void
+config_dump_full_write_cache_paths(struct ostream *output,
+                                  struct config_parsed *config,
+                                  enum config_dump_flags flags,
+                                  const char **cache_path_r)
+{
+       const struct config_path *path;
+       const char *cache_dir = config_get_cache_dir(flags);
+       uint32_t num32 = 0;
+
+       if (cache_dir == NULL) {
+               /* no config caching - nobody cares about the paths */
+               o_stream_nsend(output, &num32, sizeof(num32));
+               *cache_path_r = NULL;
+               return;
+       }
+
+       num32 = cpu32_to_be(array_count(config_parsed_get_paths(config)));
+       o_stream_nsend(output, &num32, sizeof(num32));
+       array_foreach(config_parsed_get_paths(config), path) {
+               o_stream_nsend(output, path->path, strlen(path->path) + 1);
+               uint64_t num64 = cpu32_to_be(path->st.st_ino);
+               o_stream_nsend(output, &num64, sizeof(num64));
+               num64 = cpu32_to_be(path->st.st_size);
+               o_stream_nsend(output, &num64, sizeof(num64));
+               num32 = cpu32_to_be(path->st.st_mtime);
+               o_stream_nsend(output, &num32, sizeof(num32));
+               num32 = cpu32_to_be(ST_MTIME_NSEC(path->st));
+               o_stream_nsend(output, &num32, sizeof(num32));
+               num32 = cpu32_to_be(path->st.st_ctime);
+               o_stream_nsend(output, &num32, sizeof(num32));
+               num32 = cpu32_to_be(ST_CTIME_NSEC(path->st));
+               o_stream_nsend(output, &num32, sizeof(num32));
+       }
+
+       const struct config_path *main_path =
+               array_front(config_parsed_get_paths(config));
+       *cache_path_r = master_service_get_binary_config_cache_path(cache_dir,
+                                                                   main_path->path);
+}
+
 static void
 config_dump_full_append_filter_query(string_t *str,
                                     const struct config_filter *filter,
@@ -674,7 +733,8 @@ int config_dump_full(struct config_parsed *config,
                        str_free(&dump_ctx.delayed_output);
                        return -1;
                }
-               if (dest == CONFIG_DUMP_FULL_DEST_TEMPDIR)
+               if (dest == CONFIG_DUMP_FULL_DEST_TEMPDIR &&
+                   config_get_cache_dir(flags) == NULL)
                        i_unlink(str_c(path));
                dump_ctx.output = o_stream_create_fd_autoclose(&fd, IO_BLOCK_SIZE);
                o_stream_set_name(dump_ctx.output, str_c(path));
@@ -697,6 +757,11 @@ int config_dump_full(struct config_parsed *config,
                settings_full_size_offset = output->offset;
                o_stream_nsend(output, &blob_size, sizeof(blob_size));
 
+               const char *cache_path;
+               config_dump_full_write_cache_paths(output, config, flags,
+                                                  &cache_path);
+               if (cache_path != NULL)
+                       final_path = cache_path;
                config_dump_full_write_filters(output, config);
        }
 
index 122c79d55234899752e4b4323df2c55cdb0803f3..7b3ab118ab7b0e50714eb854eeb9e06e225b9080 100644 (file)
@@ -78,6 +78,7 @@ struct config_parser_context {
        pool_t pool;
        const char *path;
 
+       ARRAY_TYPE(config_path) seen_paths;
        HASH_TABLE(const char *, struct config_parser_key *) all_keys;
        ARRAY(struct config_filter_parser *) all_filter_parsers;
        HASH_TABLE(struct config_filter *,
index ba6cb0bea55ecf18ddfaf9fdd3861f5fcecffa75..4c2d8b7fd57bfcadaaab778ef19cadeb5ea5dd4c 100644 (file)
@@ -57,6 +57,7 @@ struct config_parsed {
        const char *dovecot_config_version;
        struct config_filter_parser *const *filter_parsers;
        struct config_module_parser *module_parsers;
+       ARRAY_TYPE(config_path) seen_paths;
        ARRAY_TYPE(const_string) errors;
        HASH_TABLE(const char *, const struct setting_define *) key_hash;
        HASH_TABLE(const char *, struct config_include_group_filters *) include_groups;
@@ -113,6 +114,29 @@ void config_parser_set_change_counter(struct config_parser_context *ctx,
        ctx->change_counter = change_counter;
 }
 
+static void
+config_parser_add_seen_file(struct config_parser_context *ctx,
+                           const struct stat *st, const char *path)
+{
+       struct config_path *seen_path = array_append_space(&ctx->seen_paths);
+       seen_path->path = p_strdup(ctx->pool, path);
+       seen_path->st = *st;
+}
+
+static int
+config_parser_add_seen_file_fd(struct config_parser_context *ctx,
+                              int fd, const char *path, const char **error_r)
+{
+       struct stat st;
+
+       if (fstat(fd, &st) < 0) {
+               *error_r = t_strdup_printf("fstat(%s) failed: %m", path);
+               return -1;
+       }
+       config_parser_add_seen_file(ctx, &st, path);
+       return 0;
+}
+
 static struct config_section_stack *
 config_parser_add_filter_array(struct config_parser_context *ctx,
                               const char *filter_key, const char *name)
@@ -669,6 +693,7 @@ static int config_apply_file(struct config_parser_context *ctx,
                             const struct config_line *line,
                             const char *path, const char **output_r)
 {
+       struct stat st;
        const char *full_path, *error;
 
        if (path[0] == '\0') {
@@ -686,13 +711,15 @@ static int config_apply_file(struct config_parser_context *ctx,
        /* preserve original relative path in doveconf output */
        if (full_path != path && ctx->expand_values)
                path = full_path;
-       if (settings_parse_read_file(full_path, path, ctx->pool, NULL,
+       if (settings_parse_read_file(full_path, path, ctx->pool, &st,
                                     output_r, &error) < 0) {
                ctx->error = p_strdup(ctx->pool, error);
                if (config_apply_error(ctx, line->key) < 0)
                        return -1;
                /* delayed error */
                *output_r = "";
+       } else {
+               config_parser_add_seen_file(ctx, &st, full_path);
        }
        return 0;
 }
@@ -1684,8 +1711,8 @@ config_all_parsers_check(struct config_parser_context *ctx,
 }
 
 static int
-str_append_file(string_t *str, const char *key, const char *path,
-               const char **error_r)
+str_append_file(struct config_parser_context *ctx, string_t *str,
+               const char *key, const char *path, const char **error_r)
 {
        unsigned char buf[1024];
        int fd;
@@ -1699,6 +1726,8 @@ str_append_file(string_t *str, const char *key, const char *path,
                                           key, path);
                return -1;
        }
+       if (config_parser_add_seen_file_fd(ctx, fd, path, error_r) < 0)
+               return -1;
        while ((ret = read(fd, buf, sizeof(buf))) > 0)
                str_append_data(str, buf, ret);
        if (ret < 0) {
@@ -1732,6 +1761,8 @@ static int settings_add_include(struct config_parser_context *ctx, const char *p
                                           path);
                return -1;
        }
+       if (config_parser_add_seen_file_fd(ctx, fd, path, error_r) < 0)
+               return -1;
 
        new_input = p_new(ctx->pool, struct input_stack, 1);
        new_input->prev = ctx->cur_input;
@@ -2343,6 +2374,7 @@ config_parse_finish(struct config_parser_context *ctx,
        pool_ref(new_config->pool);
        new_config->dovecot_config_version = ctx->dovecot_config_version;
        p_array_init(&new_config->errors, ctx->pool, 1);
+       new_config->seen_paths = ctx->seen_paths;
 
        array_sort(&ctx->all_filter_parsers, config_parser_filter_cmp);
        array_append_zero(&ctx->all_filter_parsers);
@@ -2498,7 +2530,7 @@ static int config_write_value(struct config_parser_context *ctx,
                                                line->key) :
                                line->key;
                        path = fix_relative_path(line->value, ctx->cur_input);
-                       if (str_append_file(ctx->value, key_with_path, path,
+                       if (str_append_file(ctx, ctx->value, key_with_path, path,
                                            &error) < 0) {
                                /* file reading failed */
                                ctx->error = p_strdup(ctx->pool, error);
@@ -2904,6 +2936,11 @@ int config_parse_file(const char *path, enum config_parse_flags flags,
        ctx.delay_errors = (flags & CONFIG_PARSE_FLAG_DELAY_ERRORS) != 0;
        ctx.ignore_unknown = (flags & CONFIG_PARSE_FLAG_IGNORE_UNKNOWN) != 0;
        hash_table_create(&ctx.all_keys, ctx.pool, 500, str_hash, strcmp);
+       p_array_init(&ctx.seen_paths, ctx.pool, 8);
+       if (fd != -1) {
+               if (config_parser_add_seen_file_fd(&ctx, fd, path, error_r) < 0)
+                       return -1;
+       }
 
        for (count = 0; all_infos[count] != NULL; count++) ;
        config_parser_set_change_counter(&ctx, CONFIG_PARSER_CHANGE_DEFAULTS);
@@ -3274,6 +3311,12 @@ bool config_parsed_get_includes(struct config_parsed *config,
        return array_count(groups) > 0;
 }
 
+const ARRAY_TYPE(config_path) *
+config_parsed_get_paths(struct config_parsed *config)
+{
+       return &config->seen_paths;
+}
+
 void config_parsed_free(struct config_parsed **_config)
 {
        struct config_parsed *config = *_config;
index 949aac100b5b175554f580e6a3b3a00172f689d7..ddac6eb9374c13734e5cc890000a42e4a1887773 100644 (file)
@@ -3,6 +3,8 @@
 
 #include "config-filter.h"
 
+#include <sys/stat.h>
+
 #define CONFIG_MODULE_DIR MODULEDIR"/settings"
 
 /* change_counter used for default settings created internally */
@@ -66,6 +68,12 @@ struct config_module_parser {
 };
 ARRAY_DEFINE_TYPE(config_module_parsers, struct config_module_parser *);
 
+struct config_path {
+       const char *path;
+       struct stat st;
+};
+ARRAY_DEFINE_TYPE(config_path, struct config_path);
+
 extern struct module *modules;
 
 int config_parse_net(const char *value, struct ip_addr *ip_r,
@@ -116,7 +124,9 @@ bool config_parsed_get_includes(struct config_parsed *config,
                                const struct config_filter_parser *filter,
                                unsigned int parser_idx,
                                ARRAY_TYPE(config_include_group) *groups);
-
+/* Get all paths used for generating the config */
+const ARRAY_TYPE(config_path) *
+config_parsed_get_paths(struct config_parsed *config);
 void config_parsed_free(struct config_parsed **config);
 
 void config_parse_load_modules(bool dump_config_import);
index 1b5c83612b0ac6114c56fc1c0e59101295de7139..8d67a0de56d28d095cd918197a2895f21beb55bb 100644 (file)
@@ -24,6 +24,8 @@ enum config_dump_scope {
 };
 
 enum config_dump_flags {
+       /* Write config binary cache if it's enabled. */
+       CONFIG_DUMP_FLAG_WRITE_BINARY_CACHE     = 0x01,
        CONFIG_DUMP_FLAG_DEDUPLICATE_KEYS       = 0x08,
 };
 
index 17dafbf00688d44802235416bf2259f1c4e6e819..0f0fcad7c88e53be045333fded0e4c3d2a59c5cf 100644 (file)
@@ -1251,7 +1251,8 @@ int main(int argc, char *argv[])
        if (dump_full && exec_args != NULL) {
                int temp_fd = config_dump_full(config,
                                               CONFIG_DUMP_FULL_DEST_TEMPDIR,
-                                              0, &import_environment);
+                                              CONFIG_DUMP_FLAG_WRITE_BINARY_CACHE,
+                                              &import_environment);
                if (getenv(DOVECOT_PRESERVE_ENVS_ENV) != NULL) {
                        /* Standalone binary is getting its configuration via
                           doveconf. Clean the environment before calling it.
index 399d753ad75aee2a828612ee9265d70d57a023fc..b48bf28797d011c2a04a39b3a128d556ea2a953d 100644 (file)
@@ -7,6 +7,9 @@
 #include "fdpass.h"
 #include "write-full.h"
 #include "str.h"
+#include "sha2.h"
+#include "hex-binary.h"
+#include "restrict-access.h"
 #include "syslog-util.h"
 #include "eacces-error.h"
 #include "env-util.h"
@@ -582,3 +585,27 @@ master_service_get_import_environment_keyvals(struct master_service *service)
        }
        return str_c(keyvals);
 }
+
+const char *
+master_service_get_binary_config_cache_path(const char *cache_dir,
+                                           const char *main_path)
+{
+       struct sha256_ctx hash;
+       sha256_init(&hash);
+       sha256_loop(&hash, main_path, strlen(main_path) + 1);
+       uid_t euid = geteuid();
+       sha256_loop(&hash, &euid, sizeof(euid));
+       gid_t egid = getegid();
+       sha256_loop(&hash, &egid, sizeof(egid));
+       unsigned int groups_count;
+       const gid_t *groups = restrict_get_groups_list(&groups_count);
+       sha256_loop(&hash, groups, sizeof(*groups) * groups_count);
+       unsigned char digest[SHA256_RESULTLEN];
+       sha256_result(&hash, digest);
+
+       string_t *cache_path = t_str_new(128);
+       str_append(cache_path, cache_dir);
+       str_append(cache_path, "/binary.conf.");
+       binary_to_hex_append(cache_path, digest, sizeof(digest));
+       return str_c(cache_path);
+}
index d37870030fe436c4cc241f73fc9b02f57d6109a3..e993138e8a6db241aa88af23d381dde13a825b2b 100644 (file)
@@ -95,4 +95,8 @@ master_service_get_service_settings(struct master_service *service);
 const char *
 master_service_get_import_environment_keyvals(struct master_service *service);
 
+const char *
+master_service_get_binary_config_cache_path(const char *cache_dir,
+                                           const char *main_path);
+
 #endif
index 287b28e4cff7e33074465bccc73a0f4d1a9a18ab..51a7f6183ecfc788a9b3f5440fa37bd633003659 100644 (file)
@@ -29,22 +29,31 @@ static const struct {
               "\x00\x00\x00\x00\x00\x00\x00\x01"), // full size
          "Full size mismatch" },
 
-       /* event filter count is truncated */
+       /* cache path count is truncated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
               "\x00\x00\x00\x00\x00\x00\x00\x04" // full size
+              "\x00\x00\x00"), // cache path count
+         "Full size mismatch" },
+
+       /* event filter count is truncated */
+       { DATA("DOVECOT-CONFIG\t1.0\n"
+              "\x00\x00\x00\x00\x00\x00\x00\x08" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00"), // event filter count
          "Full size mismatch" },
 
        /* event filter strings are truncated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x04" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x08" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x10\x00"), // event filter count
          "'filter string' points outside area" },
 
        /* full file size is 7 bytes, which makes the first block size
           truncated, since it needs 8 bytes */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x0D" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x11" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -52,7 +61,8 @@ static const struct {
          "Area too small when reading size of 'block size'" },
        /* first block size is 0, which is too small */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x0E" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x12" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -60,7 +70,8 @@ static const struct {
          "'block name' points outside area" },
        /* first block size is 1, but full file size is too small */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x0E" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x12" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -68,7 +79,8 @@ static const struct {
          "'block size' points outside are" },
        /* block name is not NUL-terminated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x10" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x14" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -79,7 +91,8 @@ static const struct {
 
        /* settings count is truncated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x13" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x17" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -90,7 +103,8 @@ static const struct {
 
        /* settings keys are truncated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x14" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x18" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -101,7 +115,8 @@ static const struct {
 
        /* filter count is truncated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x19" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x1D" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -114,7 +129,8 @@ static const struct {
 
        /* filter settings size is truncated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x21" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x25" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -128,7 +144,8 @@ static const struct {
 
        /* filter settings is truncated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x22" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x26" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -141,7 +158,8 @@ static const struct {
          "'filter settings size' points outside area" },
        /* filter error is missing */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x2F" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x33" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -157,7 +175,8 @@ static const struct {
          "'filter error string' points outside area" },
        /* filter error is not NUL-terminated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x3D" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x41" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -174,7 +193,8 @@ static const struct {
          "'filter error string' points outside area" },
        /* include group count is truncated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x40" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x44" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -192,7 +212,8 @@ static const struct {
          "Area too small when reading uint of 'include group count'" },
        /* include group count is too large */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x41" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x45" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -210,7 +231,8 @@ static const struct {
          "'group label string' points outside area" },
        /* group label not NUL-terminated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x42" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x46" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -229,7 +251,8 @@ static const struct {
          "'group label string' points outside area" },
        /* group name not NUL-terminated */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x44" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x48" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
@@ -249,7 +272,8 @@ static const struct {
          "'group name string' points outside area" },
        /* invalid filter string */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x36" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x3A" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "F\x00" // event filter[0]
               "F\x00" // override event filter[0]
@@ -268,7 +292,8 @@ static const struct {
 
        /* Duplicate block name */
        { DATA("DOVECOT-CONFIG\t1.0\n"
-              "\x00\x00\x00\x00\x00\x00\x00\x3E" // full size
+              "\x00\x00\x00\x00\x00\x00\x00\x42" // full size
+              "\x00\x00\x00\x00" // cache path count
               "\x00\x00\x00\x01" // event filter count
               "\x00" // event filter[0]
               "\x00" // override event filter[0]
index ec60f7ad0864973e80ef57af179c63da56c3c717..09a69cfd12af8cdf5f0c60166ff6ddb44c52bd93 100644 (file)
@@ -14,6 +14,7 @@
 #include "var-expand.h"
 
 #include <ctype.h>
+#include <sys/stat.h>
 
 enum set_seen_type {
        /* Setting has not been changed */
@@ -210,6 +211,24 @@ static void settings_override_free(struct settings_override *override)
        event_unref(&override->filter_event);
 }
 
+static int
+settings_block_read_uint64(struct settings_mmap *mmap,
+                          size_t *offset, size_t end_offset,
+                          const char *name, uint64_t *num_r,
+                          const char **error_r)
+{
+       if (*offset + sizeof(*num_r) > end_offset) {
+               *error_r = t_strdup_printf(
+                       "Area too small when reading uint of '%s' "
+                       "(offset=%zu, end_offset=%zu, file_size=%zu)", name,
+                       *offset, end_offset, mmap->mmap_size);
+               return -1;
+       }
+       *num_r = be64_to_cpu_unaligned(CONST_PTR_OFFSET(mmap->mmap_base, *offset));
+       *offset += sizeof(*num_r);
+       return 0;
+}
+
 static int
 settings_block_read_uint32(struct settings_mmap *mmap,
                           size_t *offset, size_t end_offset,
@@ -364,6 +383,70 @@ settings_event_filter_name_find(struct event_filter *filter,
        return settings_event_filter_node_name_find(node, filter_name, FALSE);
 }
 
+static int
+settings_read_config_paths(struct settings_mmap *mmap,
+                          enum settings_read_flags flags,
+                          size_t *offset, const char **error_r)
+{
+       uint32_t count;
+       if (settings_block_read_uint32(mmap, offset, mmap->mmap_size,
+                                      "config paths count", &count,
+                                      error_r) < 0)
+               return -1;
+
+       for (uint32_t i = 0; i < count; i++) {
+               const char *config_path;
+               if (settings_block_read_str(mmap, offset, mmap->mmap_size,
+                                           "config path", &config_path,
+                                           error_r) < 0)
+                       return -1;
+               uint64_t inode, size;
+               if (settings_block_read_uint64(mmap, offset, mmap->mmap_size,
+                                              "config path size",
+                                              &size, error_r) < 0)
+                       return -1;
+               if (settings_block_read_uint64(mmap, offset, mmap->mmap_size,
+                                              "config path inode",
+                                              &inode, error_r) < 0)
+                       return -1;
+               uint32_t mtime_sec, mtime_nsec, ctime_sec, ctime_nsec;
+               if (settings_block_read_uint32(mmap, offset, mmap->mmap_size,
+                                              "config path mtime sec",
+                                              &mtime_sec, error_r) < 0)
+                       return -1;
+               if (settings_block_read_uint32(mmap, offset, mmap->mmap_size,
+                                              "config path mtime nsec",
+                                              &mtime_nsec, error_r) < 0)
+                       return -1;
+
+               if (settings_block_read_uint32(mmap, offset, mmap->mmap_size,
+                                              "config path ctime sec",
+                                              &ctime_sec, error_r) < 0)
+                       return -1;
+               if (settings_block_read_uint32(mmap, offset, mmap->mmap_size,
+                                              "config path ctime nsec",
+                                              &ctime_nsec, error_r) < 0)
+                       return -1;
+
+               if ((flags & SETTINGS_READ_CHECK_CACHE_TIMESTAMPS) == 0)
+                       continue;
+
+               struct stat st;
+               if (stat(config_path, &st) < 0) {
+                       if (errno == ENOENT || ENOACCESS(errno))
+                               return 0;
+                       *error_r = t_strdup_printf("stat(%s) failed: %m",
+                                                  config_path);
+                       return -1;
+               }
+               if (st.st_ino != inode || (uoff_t)st.st_size != size ||
+                   st.st_mtime != mtime_sec || ST_MTIME_NSEC(st) != mtime_nsec ||
+                   st.st_ctime != ctime_sec || ST_CTIME_NSEC(st) != ctime_nsec)
+                       return 0;
+       }
+       return 1;
+}
+
 static int
 settings_read_filters(struct settings_mmap *mmap, const char *service_name,
                      enum settings_read_flags flags, size_t *offset,
@@ -626,6 +709,9 @@ settings_mmap_parse(struct settings_mmap *mmap, const char *service_name,
        }
 
        size_t offset = full_size_offset + sizeof(settings_full_size);
+       int ret;
+       if ((ret = settings_read_config_paths(mmap, flags, &offset, error_r)) <= 0)
+               return ret;
        if (settings_read_filters(mmap, service_name, flags, &offset,
                                  &protocols, error_r) < 0)
                return -1;
@@ -641,6 +727,8 @@ settings_mmap_parse(struct settings_mmap *mmap, const char *service_name,
        } else {
                *specific_protocols_r = NULL;
        }
+       if ((flags & SETTINGS_READ_CHECK_CACHE_TIMESTAMPS) != 0)
+               return 1;
        return 0;
 }
 
@@ -1271,8 +1359,12 @@ int settings_read(struct settings_root *root, int fd, const char *path,
        root->mmap = mmap;
        hash_table_create(&mmap->blocks, mmap->pool, 0, str_hash, strcmp);
 
-       return settings_mmap_parse(root->mmap, service_name, flags,
-                                  specific_protocols_r, error_r);
+       int ret = settings_mmap_parse(root->mmap, service_name, flags,
+                                     specific_protocols_r, error_r);
+       if (ret < 0 ||
+           (ret == 0 && (flags & SETTINGS_READ_CHECK_CACHE_TIMESTAMPS) != 0))
+               settings_mmap_unref(&root->mmap);
+       return ret;
 }
 
 bool settings_has_mmap(struct settings_root *root)
index 10adac7557ff148a2746dabafcaffcd54d5ce0fe..90e0af05caa5ad0af5f2cac61de84fa5f463344d 100644 (file)
@@ -35,6 +35,11 @@ enum settings_override_type {
 enum settings_read_flags {
        /* Don't drop filters that contain a mismatching protocol */
        SETTINGS_READ_NO_PROTOCOL_FILTER = BIT(0),
+       /* Check that all the paths referenced by the binary config still have
+          the same mtime and ctime. If not, fail the config reading.
+          Changes settings_read() return value to return 1 on success, or 0
+          if timestamps were obsolete. */
+       SETTINGS_READ_CHECK_CACHE_TIMESTAMPS = BIT(1),
 };
 
 enum settings_get_flags {