summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-12 14:44:41 -0400
committerpommicket <pommicket@gmail.com>2025-09-12 14:45:26 -0400
commit45393c79ab36bb64b933b44983a1cd8dffc0ebac (patch)
tree3efcdfd61a60ce02d08f4ff5d5ca0b445c1964a4
parent81c1cecf40b0733446fb0d945505155d87bf74c6 (diff)
Implement pom_conf_next_key, pom_conf_next_unread_key
-rw-r--r--examples/read_conf.c11
-rw-r--r--pom.c87
-rw-r--r--pom.h33
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);
}
diff --git a/pom.c b/pom.c
index b795f3b..6aed7e8 100644
--- a/pom.c
+++ b/pom.c
@@ -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
diff --git a/pom.h b/pom.h
index 75fefd1..7af38c8 100644
--- a/pom.h
+++ b/pom.h
@@ -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`.