diff options
author | pommicket <pommicket@gmail.com> | 2025-09-12 14:44:41 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-09-12 14:45:26 -0400 |
commit | 45393c79ab36bb64b933b44983a1cd8dffc0ebac (patch) | |
tree | 3efcdfd61a60ce02d08f4ff5d5ca0b445c1964a4 | |
parent | 81c1cecf40b0733446fb0d945505155d87bf74c6 (diff) |
Implement pom_conf_next_key, pom_conf_next_unread_key
-rw-r--r-- | examples/read_conf.c | 11 | ||||
-rw-r--r-- | pom.c | 87 | ||||
-rw-r--r-- | pom.h | 33 |
3 files changed, 104 insertions, 27 deletions
diff --git a/examples/read_conf.c b/examples/read_conf.c index 966f0a2..78e4847 100644 --- a/examples/read_conf.c +++ b/examples/read_conf.c @@ -22,7 +22,16 @@ int main(int argc, char **argv) { const pom_item *item; pom_item_iter *iter = NULL; while ((item = pom_conf_next_item(conf, &iter))) { - printf("Key: %s, Value: %s\n", item->key, item->value); + printf("Key: %s, Value: %s\n", item->key, item->value); + } + pom_unread_key_iter *unread = NULL; + const char *key; + while ((key = pom_conf_next_unread_key(conf, &unread))) { + printf("Unrecognized key: %s\n", key); + } + pom_key_iter *keys = NULL; + while ((key = pom_conf_next_key(conf, &keys))) { + printf("Top-level key: %s\n", key); } pom_conf_free(conf); } @@ -46,9 +46,6 @@ struct to_free { struct pom_conf { struct main_conf *main; - // this is checked vs main->version_number - // to detect when a section is used after a merge - uint64_t version_number; size_t prefix_len; struct conf_item *items; size_t items_count; @@ -60,6 +57,17 @@ struct pom_item_iter { pom_item item; }; +struct pom_unread_key_iter { + const pom_conf *conf; + const struct conf_item *conf_item; +}; + +struct pom_key_iter { + const pom_conf *conf; + char *prev_key; + const struct conf_item *conf_item; +}; + struct conf_item { const char *key, *value, *file; uint64_t line; @@ -700,7 +708,7 @@ parser_finish(struct parser *parser) { conf_item->key = ((char*)string_data->data) + (parser_item->key - sizeof(struct to_free)); conf_item->value = ((char*)string_data->data) + (parser_item->value - sizeof(struct to_free)); #if HAVE_ATOMICS - conf_item->read = ATOMIC_VAR_INIT(false); + atomic_init(&conf_item->read, false); #else conf_item->read = false; #endif @@ -853,10 +861,6 @@ static void check_conf_(const pom_conf *conf, const char *func) { if (!conf) fatal_error("NULL configuration passed to %s", func); - if (conf->version_number != conf->main->version_number) - fatal_error("Section obtained via pom_conf_section is being used\n" - "(in %s) after pom_conf_merge was called.\n" - "This is not allowed.", func); } #define check_conf(conf) check_conf_(conf, __func__) @@ -886,6 +890,71 @@ pom_conf_next_item(const pom_conf *conf, pom_item_iter **p_iter) { return &iter->item; } +const char * +pom_conf_next_unread_key(const pom_conf *conf, pom_unread_key_iter **p_iter) { + check_conf(conf); + if (!p_iter) fatal_error("NULL iter passed to %s", __func__); + if (!*p_iter) { + *p_iter = malloc(sizeof **p_iter); + if (!*p_iter) return NULL; + (*p_iter)->conf = conf; + (*p_iter)->conf_item = conf->items; + } + pom_unread_key_iter *iter = *p_iter; + if (iter->conf != conf) + fatal_error("%s being called with inconsistent configurations for a single iterator", __func__); + for (; iter->conf_item < conf->items + conf->items_count; iter->conf_item++) { + if (!( + #if HAVE_ATOMICS + atomic_load_explicit(&iter->conf_item->read, memory_order_relaxed) + #else + iter->conf_item->read + #endif + )) { + const char *key = iter->conf_item->key + conf->prefix_len; + iter->conf_item++; + return key; + } + } + free(iter); + *p_iter = NULL; + return NULL; +} + +const char * +pom_conf_next_key(const pom_conf *conf, pom_key_iter **p_iter) { + check_conf(conf); + if (!p_iter) fatal_error("NULL iter passed to %s", __func__); + if (!*p_iter) { + *p_iter = malloc(sizeof **p_iter); + if (!*p_iter) return NULL; + (*p_iter)->conf = conf; + (*p_iter)->conf_item = conf->items; + (*p_iter)->prev_key = NULL; + } + pom_key_iter *iter = *p_iter; + if (iter->conf != conf) + fatal_error("%s being called with inconsistent configurations for a single iterator", __func__); + for (; iter->conf_item < conf->items + conf->items_count; iter->conf_item++) { + const char *key = iter->conf_item->key + conf->prefix_len; + size_t first_component_len = strcspn(key, "."); + if (!iter->prev_key || strncmp(iter->prev_key, key, first_component_len) != 0) { + // new first component + char *first_component = realloc(iter->prev_key, first_component_len + 1); + if (!first_component) return NULL; + iter->prev_key = first_component; + memcpy(first_component, key, first_component_len); + first_component[first_component_len] = 0; + iter->conf_item++; + return first_component; + } + } + free(iter->prev_key); + free(iter); + *p_iter = NULL; + return NULL; +} + static size_t conf_binary_search(const pom_conf *conf, const char *key, char nxt_char, bool *found) { size_t lo = 0; @@ -929,7 +998,7 @@ pom_conf_get(const pom_conf *conf, const char *key) { struct conf_item *item = conf_get_item(conf, key); if (item) { #if HAVE_ATOMICS - atomic_store(&item->read, true); + atomic_store_explicit(&item->read, true, memory_order_relaxed); #else item->read = true; #endif @@ -4,8 +4,7 @@ /// ## Thread-safety /// /// Of course, you should not free or \ref pom_conf_merge into -/// a configuration while another thread is using it (even through a section -/// obtained via \ref pom_conf_section). +/// a configuration while another thread is using it. /// /// Other than that, all these functions are fully thread-safe /// provided that C11 atomics are available @@ -14,9 +13,11 @@ /// (ensure there is synchronization so that all threads /// have certainly read their keys before calling it). /// -/// If C11 atomics are not available, you should not use -/// the same configuration across multiple threads (even for -/// seemingly "read-only" operations), but you can still use +/// If C11 atomics are not available, you can almost certainly still get away +/// with sharing configurations across threads. +/// (Essentially, libpom may end up writing the same value to the same address +/// from separate threads, which will likely never be an issue on any real machine.) +/// Even if you are extremely paranoid, you can still use /// distinct configurations in different threads without worry. /// \mainpage libpom doxygen documentation @@ -333,7 +334,7 @@ char ** pom_conf_get_list(const pom_conf *conf, const char *key) POM__MUST_USE_R; -/// Extract section out of POM configuration. Invalidated if \ref pom_conf_merge is called. +/// Extract section out of POM configuration. /// /// Specifically, this returns the configuration consisting of all keys /// prefixed by `section.`, with that prefix removed, and their corresponding @@ -359,8 +360,6 @@ pom_conf_section(const pom_conf *conf, const char *section); /// you should still call this function repeatedly until you get `NULL`; /// otherwise memory associated with the iterator may be leaked. /// -/// The iterator is invalidated if \ref pom_conf_merge is called. -/// /// The correct usage for this function is: /// /// ```C @@ -388,8 +387,6 @@ pom_conf_next_key(const pom_conf *conf, pom_key_iter **iter); /// you should still call this function repeatedly until you get `NULL`; /// otherwise memory associated with the iterator may be leaked. /// -/// The iterator is invalidated if \ref pom_conf_merge is called. -/// /// The returned pointer is only valid until the next call to \ref pom_conf_next_item. /// /// The correct usage for this function is: @@ -417,11 +414,15 @@ pom_conf * pom_conf_copy(const pom_conf *conf) POM__MUST_USE_R; -/// Merge keys from `other` into `conf`, preferring keys in `other`. This invalidates -/// all sections made with \ref pom_conf_section. +/// Merge keys from `other` into `conf`, preferring keys in `other`. /// -/// It also invalidates any iterators from \ref pom_conf_next_item, \ref pom_conf_next_unread_key, -/// \ref pom_conf_next_key. +/// Sections obtained from \ref pom_conf_section and iterators +/// (\ref pom_conf_next_unread_key, \ref pom_conf_next_item, \ref pom_conf_next_key) +/// will still point to the old configuration. +/// For this reason, the old configuration is kept in memory until \ref pom_conf_free +/// is called, so if you are repeatedly calling this function (unlikely, but who knows), +/// you should make a copy of `conf` after each call (with \ref pom_conf_copy), +/// then free the old value of `conf`. void pom_conf_merge(pom_conf *conf, const pom_conf *other); @@ -440,8 +441,6 @@ pom_conf_merge(pom_conf *conf, const pom_conf *other); /// you should still call this function repeatedly until you get `NULL`; /// otherwise memory associated with the iterator may be leaked. /// -/// The iterator is invalidated if \ref pom_conf_merge is called. -/// /// The correct usage for this function is: /// /// ```C @@ -452,7 +451,7 @@ pom_conf_merge(pom_conf *conf, const pom_conf *other); /// } /// ``` const char * -pom_conf_next_unread_key(pom_conf *conf, pom_unread_key_iter **iter); +pom_conf_next_unread_key(const pom_conf *conf, pom_unread_key_iter **iter); #ifndef POM_NO_STDIO /// Print `key: value` for each `key` in `conf` to `stdout`. |