// not exist. In other words, |config_has_section| will return false for
// empty sections.
// - Duplicate keys in a section will overwrite previous values.
+// - All strings are case sensitive.
#include <stdbool.h>
// a section.
#define CONFIG_DEFAULT_SECTION "Global"
-struct config_t;
typedef struct config_t config_t;
+typedef struct config_section_node_t config_section_node_t;
// Loads the specified file and returns a handle to the config file. If there
// was a problem loading the file or allocating memory, this function returns
// not already exist, this function creates them. |config|, |section|, |key|, and
// |value| must not be NULL.
void config_set_string(config_t *config, const char *section, const char *key, const char *value);
+
+// Removes |section| from the |config| (and, as a result, all keys in the section).
+// Returns true if |section| was found and removed from |config|, false otherwise.
+// Neither |config| nor |section| may be NULL.
+bool config_remove_section(config_t *config, const char *section);
+
+// Removes one specific |key| residing in |section| of the |config|. Returns true
+// if the section and key were found and the key was removed, false otherwise.
+// None of |config|, |section|, or |key| may be NULL.
+bool config_remove_key(config_t *config, const char *section, const char *key);
+
+// Returns an iterator to the first section in the config file. If there are no
+// sections, the iterator will equal the return value of |config_section_end|.
+// The returned pointer must be treated as an opaque handle and must not be freed.
+// The iterator is invalidated on any config mutating operation. |config| may not
+// be NULL.
+const config_section_node_t *config_section_begin(const config_t *config);
+
+// Returns an iterator to one past the last section in the config file. It does not
+// represent a valid section, but can be used to determine if all sections have been
+// iterated over. The returned pointer must be treated as an opaque handle and must
+// not be freed and must not be iterated on (must not call |config_section_next| on
+// it). |config| may not be NULL.
+const config_section_node_t *config_section_end(const config_t *config);
+
+// Moves |iter| to the next section. If there are no more sections, |iter| will
+// equal the value of |config_section_end|. |iter| may not be NULL and must be
+// a pointer returned by either |config_section_begin| or |config_section_next|.
+const config_section_node_t *config_section_next(const config_section_node_t *iter);
+
+// Returns the name of the section referred to by |iter|. The returned pointer is
+// owned by the config module and must not be freed by the caller. The pointer will
+// remain valid until |config_free| is called. |iter| may not be NULL and must not
+// equal the value returned by |config_section_end|.
+const char *config_section_name(const config_section_node_t *iter);
+
+// Saves |config| to a file given by |filename|. Note that this could be a destructive
+// operation: if |filename| already exists, it will be overwritten. The config
+// module does not preserve comments or formatting so if a config file was opened
+// with |config_new| and subsequently overwritten with |config_save|, all comments
+// and special formatting in the original file will be lost. Neither |config| nor
+// |filename| may be NULL.
+bool config_save(const config_t *config, const char *filename);
list_t *sections;
};
+// Empty definition; this type is aliased to list_node_t.
+struct config_section_iter_t {};
+
static void config_parse(FILE *fp, config_t *config);
static section_t *section_new(const char *name);
config_t *config_new(const char *filename) {
assert(filename != NULL);
- FILE *fp = fopen(filename, "rt");
- if (!fp) {
- ALOGE("%s unable to open file '%s': %s", __func__, filename, strerror(errno));
- return NULL;
- }
-
config_t *config = calloc(1, sizeof(config_t));
if (!config) {
ALOGE("%s unable to allocate memory for config_t.", __func__);
- fclose(fp);
- return NULL;
+ goto error;
}
config->sections = list_new(section_free);
- config_parse(fp, config);
+ if (!config->sections) {
+ ALOGE("%s unable to allocate list for sections.", __func__);
+ goto error;
+ }
+ FILE *fp = fopen(filename, "rt");
+ if (!fp) {
+ ALOGE("%s unable to open file '%s': %s", __func__, filename, strerror(errno));
+ goto error;
+ }
+ config_parse(fp, config);
fclose(fp);
-
return config;
+
+error:;
+ if (config)
+ list_free(config->sections);
+ free(config);
+
+ if (fp)
+ fclose(fp);
+
+ return NULL;
}
void config_free(config_t *config) {
list_append(sec->entries, entry);
}
+bool config_remove_section(config_t *config, const char *section) {
+ assert(config != NULL);
+ assert(section != NULL);
+
+ section_t *sec = section_find(config, section);
+ if (!sec)
+ return false;
+
+ return list_remove(config->sections, sec);
+}
+
+bool config_remove_key(config_t *config, const char *section, const char *key) {
+ assert(config != NULL);
+ assert(section != NULL);
+ assert(key != NULL);
+
+ section_t *sec = section_find(config, section);
+ entry_t *entry = entry_find(config, section, key);
+ if (!sec || !entry)
+ return false;
+
+ return list_remove(sec->entries, entry);
+}
+
+const config_section_node_t *config_section_begin(const config_t *config) {
+ assert(config != NULL);
+ return (const config_section_node_t *)list_begin(config->sections);
+}
+
+const config_section_node_t *config_section_end(const config_t *config) {
+ assert(config != NULL);
+ return (const config_section_node_t *)list_end(config->sections);
+}
+
+const config_section_node_t *config_section_next(const config_section_node_t *node) {
+ assert(node != NULL);
+ return (const config_section_node_t *)list_next((const list_node_t *)node);
+}
+
+const char *config_section_name(const config_section_node_t *node) {
+ assert(node != NULL);
+ const list_node_t *lnode = (const list_node_t *)node;
+ const section_t *section = (const section_t *)list_node(lnode);
+ return section->name;
+}
+
+bool config_save(const config_t *config, const char *filename) {
+ assert(config != NULL);
+ assert(filename != NULL);
+ assert(*filename != '\0');
+
+ FILE *fp = fopen(filename, "wt");
+ if (!fp) {
+ ALOGE("%s unable to write file '%s': %s", __func__, filename, strerror(errno));
+ return false;
+ }
+
+ for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) {
+ const section_t *section = (const section_t *)list_node(node);
+ fprintf(fp, "[%s]\n", section->name);
+
+ for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) {
+ const entry_t *entry = (const entry_t *)list_node(enode);
+ fprintf(fp, "%s = %s\n", entry->key, entry->value);
+ }
+
+ // Only add a separating newline if there are more sections.
+ if (list_next(node) != list_end(config->sections))
+ fputc('\n', fp);
+ }
+
+ fflush(fp);
+ fclose(fp);
+ return true;
+}
+
static char *trim(char *str) {
while (isspace(*str))
++str;
EXPECT_EQ(config_get_int(config, "DID", "primaryRecord", 123), 123);
config_free(config);
}
+
+TEST_F(ConfigTest, config_remove_section) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_TRUE(config_remove_section(config, "DID"));
+ EXPECT_FALSE(config_has_section(config, "DID"));
+ EXPECT_FALSE(config_has_key(config, "DID", "productId"));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_remove_section_missing) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_FALSE(config_remove_section(config, "not a section"));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_remove_key) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_EQ(config_get_int(config, "DID", "productId", 999), 0x1200);
+ EXPECT_TRUE(config_remove_key(config, "DID", "productId"));
+ EXPECT_FALSE(config_has_key(config, "DID", "productId"));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_remove_key_missing) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_EQ(config_get_int(config, "DID", "productId", 999), 0x1200);
+ EXPECT_TRUE(config_remove_key(config, "DID", "productId"));
+ EXPECT_EQ(config_get_int(config, "DID", "productId", 999), 999);
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_section_begin) {
+ config_t *config = config_new(CONFIG_FILE);
+ const config_section_node_t *section = config_section_begin(config);
+ EXPECT_TRUE(section != NULL);
+ const char *section_name = config_section_name(section);
+ EXPECT_TRUE(section != NULL);
+ EXPECT_TRUE(!strcmp(section_name, CONFIG_DEFAULT_SECTION));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_section_next) {
+ config_t *config = config_new(CONFIG_FILE);
+ const config_section_node_t *section = config_section_begin(config);
+ EXPECT_TRUE(section != NULL);
+ section = config_section_next(section);
+ EXPECT_TRUE(section != NULL);
+ const char *section_name = config_section_name(section);
+ EXPECT_TRUE(section != NULL);
+ EXPECT_TRUE(!strcmp(section_name, "DID"));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_section_end) {
+ config_t *config = config_new(CONFIG_FILE);
+ const config_section_node_t * section = config_section_begin(config);
+ section = config_section_next(section);
+ section = config_section_next(section);
+ EXPECT_EQ(section, config_section_end(config));
+ config_free(config);
+}
+
+TEST_F(ConfigTest, config_save_basic) {
+ config_t *config = config_new(CONFIG_FILE);
+ EXPECT_TRUE(config_save(config, CONFIG_FILE));
+ config_free(config);
+}