From: Timo Sirainen Date: Sun, 9 Mar 2025 09:09:07 +0000 (+0200) Subject: config, lib-settings: Add support for binary config file cache X-Git-Tag: 2.4.2~890 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0301f03cbe9123302a439756f54a713373b86c69;p=thirdparty%2Fdovecot%2Fcore.git config, lib-settings: Add support for binary config file cache This is mainly intended for optimizing our CI performance. It shouldn't be used in production. --- diff --git a/src/config/config-dump-full.c b/src/config/config-dump-full.c index ff75f06768..23cf56b744 100644 --- a/src/config/config-dump-full.c +++ b/src/config/config-dump-full.c @@ -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" @@ -25,6 +26,16 @@ "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": + + <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": @@ -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); } diff --git a/src/config/config-parser-private.h b/src/config/config-parser-private.h index 122c79d552..7b3ab118ab 100644 --- a/src/config/config-parser-private.h +++ b/src/config/config-parser-private.h @@ -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 *, diff --git a/src/config/config-parser.c b/src/config/config-parser.c index ba6cb0bea5..4c2d8b7fd5 100644 --- a/src/config/config-parser.c +++ b/src/config/config-parser.c @@ -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; diff --git a/src/config/config-parser.h b/src/config/config-parser.h index 949aac100b..ddac6eb937 100644 --- a/src/config/config-parser.h +++ b/src/config/config-parser.h @@ -3,6 +3,8 @@ #include "config-filter.h" +#include + #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); diff --git a/src/config/config-request.h b/src/config/config-request.h index 1b5c83612b..8d67a0de56 100644 --- a/src/config/config-request.h +++ b/src/config/config-request.h @@ -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, }; diff --git a/src/config/doveconf.c b/src/config/doveconf.c index 17dafbf006..0f0fcad7c8 100644 --- a/src/config/doveconf.c +++ b/src/config/doveconf.c @@ -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. diff --git a/src/lib-master/master-service-settings.c b/src/lib-master/master-service-settings.c index 399d753ad7..b48bf28797 100644 --- a/src/lib-master/master-service-settings.c +++ b/src/lib-master/master-service-settings.c @@ -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); +} diff --git a/src/lib-master/master-service-settings.h b/src/lib-master/master-service-settings.h index d37870030f..e993138e8a 100644 --- a/src/lib-master/master-service-settings.h +++ b/src/lib-master/master-service-settings.h @@ -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 diff --git a/src/lib-master/test-master-service-settings.c b/src/lib-master/test-master-service-settings.c index 287b28e4cf..51a7f6183e 100644 --- a/src/lib-master/test-master-service-settings.c +++ b/src/lib-master/test-master-service-settings.c @@ -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] diff --git a/src/lib-settings/settings.c b/src/lib-settings/settings.c index ec60f7ad08..09a69cfd12 100644 --- a/src/lib-settings/settings.c +++ b/src/lib-settings/settings.c @@ -14,6 +14,7 @@ #include "var-expand.h" #include +#include 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) diff --git a/src/lib-settings/settings.h b/src/lib-settings/settings.h index 10adac7557..90e0af05ca 100644 --- a/src/lib-settings/settings.h +++ b/src/lib-settings/settings.h @@ -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 {