diff --git a/src/lib-config/get.c b/src/lib-config/get.c index f3a6b563..34096ef7 100644 --- a/src/lib-config/get.c +++ b/src/lib-config/get.c @@ -33,6 +33,14 @@ CONFIG_NODE *config_node_find(CONFIG_NODE *parent, const char *key) if (node->key != NULL && g_ascii_strcasecmp(node->key, key) == 0) return node; + + /* the primary children of an include node should also be searched */ + if (node->type == NODE_TYPE_INCLUDE) { + CONFIG_INCLUDE *inc = node->value; + node = config_node_find(inc->rec->mainnode, key); + if (node) + return node; + } } return NULL; diff --git a/src/lib-config/iconfig.h b/src/lib-config/iconfig.h index cc2cbf79..829b11ef 100644 --- a/src/lib-config/iconfig.h +++ b/src/lib-config/iconfig.h @@ -2,20 +2,26 @@ #define __ICONFIG_H enum { - NODE_TYPE_KEY, - NODE_TYPE_VALUE, - NODE_TYPE_BLOCK, - NODE_TYPE_LIST, - NODE_TYPE_COMMENT + NODE_TYPE_KEY, + NODE_TYPE_VALUE, + NODE_TYPE_INCLUDE, + NODE_TYPE_BLOCK, + NODE_TYPE_LIST, + NODE_TYPE_COMMENT, + + NODE_TYPE_COUNT }; #define has_node_value(a) \ - ((a)->type == NODE_TYPE_KEY || (a)->type == NODE_TYPE_VALUE) + ((a)->type == NODE_TYPE_KEY || (a)->type == NODE_TYPE_VALUE || \ + (a)->type == NODE_TYPE_INCLUDE) + #define is_node_list(a) \ ((a)->type == NODE_TYPE_BLOCK || (a)->type == NODE_TYPE_LIST) typedef struct _CONFIG_NODE CONFIG_NODE; typedef struct _CONFIG_REC CONFIG_REC; +typedef struct _CONFIG_INCLUDE CONFIG_INCLUDE; struct _CONFIG_NODE { int type; @@ -23,6 +29,13 @@ struct _CONFIG_NODE { void *value; }; +struct _CONFIG_INCLUDE { + /* the user probably specified a relative path from the root config, store + it for writing back, but set the rec->fname to the absolute path */ + char *original_path; + CONFIG_REC *rec; +}; + /* a = { x=y; y=z; } node1: type = NODE_TYPE_BLOCK, key = "a", value = (GSList *) nodes @@ -44,12 +57,27 @@ struct _CONFIG_NODE { also in comments so they won't be forgotten when the config file is written. + Include nodes have key=NULL, the path value is stored in CONFIG_INCLUDE's + original_path member as it's usually converted to an absolute path. All of + the include node's modifications and caches are stored in the 'root' config + and a write of the root config causes all included configs to be written + whether a modification has been made in them or not. A list of included + file names is maintained in the root rec to prevent circular inclusions. + It could eventually be better to keep a list of GFiles and use g_file_equal + for the comparison, but right now it doesn't even cover symbolic links. + */ struct _CONFIG_REC { + /* used for updating the root config's modifycounter from included configs */ + CONFIG_REC *root_rec; + /* this list is only maintained in the root_rec, it's to prevent circular + inclusions */ + GHashTable *includes; char *fname; int create_mode; - int modifycounter; /* increase every time something is changed */ + /* use config_rec_increase_modifycounter every time something changes */ + int modifycounter; char *last_error; CONFIG_NODE *mainnode; @@ -144,6 +172,9 @@ int config_node_get_bool(CONFIG_NODE *parent, const char *key, int def); void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, const char *value); void config_node_set_int(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value); void config_node_set_bool(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value); +/* if a relative path is specified then you can access the absolute path using + node->value->fname. the key is set to the specified include path */ +void config_node_set_include(CONFIG_REC *rec, CONFIG_NODE *parent, const char *fname); /* Remove one node from block/list. */ void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node); diff --git a/src/lib-config/module.h b/src/lib-config/module.h index 22e0e7c7..ca0afe29 100644 --- a/src/lib-config/module.h +++ b/src/lib-config/module.h @@ -2,5 +2,5 @@ #include "iconfig.h" /* private */ -int config_error(CONFIG_REC *rec, const char *msg); +int config_error(CONFIG_REC *rec, const char *msg, ...); diff --git a/src/lib-config/parse.c b/src/lib-config/parse.c index c106fc46..768a6b43 100644 --- a/src/lib-config/parse.c +++ b/src/lib-config/parse.c @@ -43,10 +43,16 @@ static unsigned int g_istr_hash(gconstpointer v) return h /* % M */; } -int config_error(CONFIG_REC *rec, const char *msg) +int config_error(CONFIG_REC *rec, const char *msg, ...) { + va_list vl; + g_free_and_null(rec->last_error); - rec->last_error = g_strdup(msg); + + va_start(vl, msg); + rec->last_error = g_strdup_vprintf(msg, vl); + va_end(vl); + return -1; } @@ -133,6 +139,7 @@ static GTokenType config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node) GTokenType last_char; int print_warning; char *key; + int key_is_include = FALSE; g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR); g_return_val_if_fail(node != NULL, G_TOKEN_ERROR); @@ -147,12 +154,23 @@ static GTokenType config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node) (rec->scanner->token == G_TOKEN_STRING)) { key = g_strdup(rec->scanner->value.v_string); - config_parse_warn_missing(rec, node, '=', TRUE); + if (g_ascii_strcasecmp(key, "include") == 0) + key_is_include = TRUE; + else + config_parse_warn_missing(rec, node, '=', TRUE); + config_parse_get_token(rec->scanner, node); } switch (rec->scanner->token) { case G_TOKEN_STRING: + if (key_is_include) { + g_free_not_null(key); + config_node_set_include(rec, node, rec->scanner->value.v_string); + config_parse_warn_missing(rec, node, last_char, TRUE); + return G_TOKEN_NONE; + } + /* value */ config_node_set_str(rec, node, key, rec->scanner->value.v_string); g_free_not_null(key); @@ -332,6 +350,8 @@ void config_close(CONFIG_REC *rec) g_hash_table_destroy(rec->cache_nodes); g_free_not_null(rec->last_error); g_free_not_null(rec->fname); + if (rec->includes) + g_hash_table_destroy(rec->includes); g_free(rec); } diff --git a/src/lib-config/set.c b/src/lib-config/set.c index 7ca55871..2d6dac82 100644 --- a/src/lib-config/set.c +++ b/src/lib-config/set.c @@ -20,6 +20,15 @@ #include "module.h" +static void config_rec_increase_modifycounter(CONFIG_REC *rec) +{ + g_return_if_fail(rec != NULL); + + /* handle CONFIG_RECs which are includes of other configs */ + if (rec->root_rec) + rec->root_rec->modifycounter++; +} + static void cache_remove(CONFIG_REC *rec, CONFIG_NODE *node) { char *path; @@ -39,7 +48,7 @@ void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node) if (parent == NULL) parent = rec->mainnode; - rec->modifycounter++; + config_rec_increase_modifycounter(rec); cache_remove(rec, node); parent->value = g_slist_remove(parent->value, node); @@ -49,6 +58,16 @@ void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node) case NODE_TYPE_COMMENT: g_free_not_null(node->value); break; + case NODE_TYPE_INCLUDE: { + CONFIG_INCLUDE *inc = node->value; + if (inc) { + g_hash_table_remove(inc->rec->root_rec->includes, inc->original_path); + config_close(inc->rec); + g_free_not_null(inc->original_path); + g_free(inc); + } + break; + } case NODE_TYPE_BLOCK: case NODE_TYPE_LIST: while (node->value != NULL) @@ -87,6 +106,79 @@ void config_nodes_remove_all(CONFIG_REC *rec) config_node_remove(rec, rec->mainnode, ((GSList *) rec->mainnode->value)->data); } +void config_node_set_include(CONFIG_REC *rec, CONFIG_NODE *parent, const char *fname) +{ + CONFIG_NODE *node; + CONFIG_INCLUDE *inc; + CONFIG_REC *root_rec; + char *full_path = NULL; + + g_return_if_fail(rec != NULL); + g_return_if_fail(parent != NULL); + + if (!fname || !*fname) { + config_error(rec, "Included filename is empty.\n"); + return; + } + + /* if the parent rec has no root_rec, it is the root */ + root_rec = rec->root_rec ? rec->root_rec : rec; + if (!root_rec->includes) { + root_rec->includes = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + if (!root_rec->includes) + return; + } + + if (g_hash_table_contains(root_rec->includes, fname)) { + config_error(rec, "'%s' has already been included.\n", fname); + return; + } + + if (!g_path_is_absolute(fname)) { + gchar *dirname = g_path_get_dirname(rec->fname); + if (!dirname) { + config_error(rec, "g_path_get_dirname failed.\n"); + return; + } + full_path = g_build_filename(dirname, fname, NULL); + g_free(dirname); + } + + node = g_new0(CONFIG_NODE, 1); + if (!node) { + return; + } + node->type = NODE_TYPE_INCLUDE; + + inc = g_new0(CONFIG_INCLUDE, 1); + if (!inc) { + config_node_remove(rec, parent, node); + return; + } + node->value = inc; + inc->original_path = g_strdup(fname); + + inc->rec = config_open(full_path ? full_path : fname, 0660); + if (!inc->rec) { + config_error(rec, "Unable to open '%s': %s.\n", fname, + strerror(errno)); + config_node_remove(rec, parent, node); + return; + } + g_free_not_null(full_path); + inc->rec->root_rec = root_rec; + g_hash_table_add(root_rec->includes, g_strdup(fname)); + + if (config_parse(inc->rec) != 0) { + config_node_remove(rec, parent, node); + return; + } + + parent->value = g_slist_append(parent->value, node); + config_rec_increase_modifycounter(rec); +} + void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, const char *value) { CONFIG_NODE *node; @@ -123,7 +215,7 @@ void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, } node->value = g_strdup(value); - rec->modifycounter++; + config_rec_increase_modifycounter(rec); } void config_node_set_int(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value) diff --git a/src/lib-config/write.c b/src/lib-config/write.c index 37e51f09..87561154 100644 --- a/src/lib-config/write.c +++ b/src/lib-config/write.c @@ -149,6 +149,18 @@ static int config_write_node(CONFIG_REC *rec, CONFIG_NODE *node, int line_feeds) if (config_write_word(rec, node->value, TRUE) == -1) return -1; break; + case NODE_TYPE_INCLUDE: { + CONFIG_INCLUDE *inc = node->value; + if (config_write_str(rec, "include ") == -1) + return -1; + + if (config_write_word(rec, inc->original_path, TRUE) == -1) + return -1; + + config_write(inc->rec, NULL, -1); + + break; + } case NODE_TYPE_BLOCK: /* key = { */ if (node->key != NULL) { @@ -217,6 +229,11 @@ static int config_node_get_length(CONFIG_REC *rec, CONFIG_NODE *node) /* "value, " */ len = 2 + strlen(node->value); break; + case NODE_TYPE_INCLUDE: { + CONFIG_INCLUDE *inc = node->value; + len = strlen("include \"\"") + strlen(inc->rec->fname); + break; + } case NODE_TYPE_BLOCK: case NODE_TYPE_LIST: /* "{ list }; " */