From: Sharvil Nanavati Date: Mon, 28 Jul 2014 04:12:20 +0000 (-0700) Subject: Allow iterating over sections in a config file and writing config to disk. X-Git-Tag: android-x86-7.1-r1~1277^2~380 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=36c1c09593a58717c78497cd55c4ed1dcf52183e;p=android-x86%2Fsystem-bt.git Allow iterating over sections in a config file and writing config to disk. These two additional operations will allow us to replace the existing XML-based config files and switch to a single file format. --- diff --git a/osi/include/config.h b/osi/include/config.h index 5e45003a5..2694ca67b 100644 --- a/osi/include/config.h +++ b/osi/include/config.h @@ -14,6 +14,7 @@ // 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 @@ -21,8 +22,8 @@ // 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 @@ -76,3 +77,46 @@ void config_set_bool(config_t *config, const char *section, const char *key, boo // 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); diff --git a/osi/src/config.c b/osi/src/config.c index f38c7f0c6..5bf85130e 100644 --- a/osi/src/config.c +++ b/osi/src/config.c @@ -24,6 +24,9 @@ struct config_t { 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); @@ -37,25 +40,36 @@ static entry_t *entry_find(const config_t *config, const char *section, const ch 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) { @@ -162,6 +176,82 @@ void config_set_string(config_t *config, const char *section, const char *key, c 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; diff --git a/osi/test/config_test.cpp b/osi/test/config_test.cpp index 1ce199dce..5d6073af2 100644 --- a/osi/test/config_test.cpp +++ b/osi/test/config_test.cpp @@ -111,3 +111,70 @@ TEST_F(ConfigTest, config_get_int_default) { 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); +}