add support for including config files within config files.

you can include another file by using a path relative to the primary
config location or an absolute path.

the syntax is:
	include "file";

this is meant to provide a reasonble work around for issue #6, so that
people can store and share their configuration file but exclude sensitive
information.
This commit is contained in:
Steven Jackson 2015-09-19 18:00:23 +01:00
commit a23ef4dc9c
6 changed files with 181 additions and 13 deletions

View file

@ -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) if (node->key != NULL && g_ascii_strcasecmp(node->key, key) == 0)
return node; 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; return NULL;

View file

@ -2,20 +2,26 @@
#define __ICONFIG_H #define __ICONFIG_H
enum { enum {
NODE_TYPE_KEY, NODE_TYPE_KEY,
NODE_TYPE_VALUE, NODE_TYPE_VALUE,
NODE_TYPE_BLOCK, NODE_TYPE_INCLUDE,
NODE_TYPE_LIST, NODE_TYPE_BLOCK,
NODE_TYPE_COMMENT NODE_TYPE_LIST,
NODE_TYPE_COMMENT,
NODE_TYPE_COUNT
}; };
#define has_node_value(a) \ #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) \ #define is_node_list(a) \
((a)->type == NODE_TYPE_BLOCK || (a)->type == NODE_TYPE_LIST) ((a)->type == NODE_TYPE_BLOCK || (a)->type == NODE_TYPE_LIST)
typedef struct _CONFIG_NODE CONFIG_NODE; typedef struct _CONFIG_NODE CONFIG_NODE;
typedef struct _CONFIG_REC CONFIG_REC; typedef struct _CONFIG_REC CONFIG_REC;
typedef struct _CONFIG_INCLUDE CONFIG_INCLUDE;
struct _CONFIG_NODE { struct _CONFIG_NODE {
int type; int type;
@ -23,6 +29,13 @@ struct _CONFIG_NODE {
void *value; 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; } /* a = { x=y; y=z; }
node1: type = NODE_TYPE_BLOCK, key = "a", value = (GSList *) nodes 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 also in comments so they won't be forgotten when the config file is
written. 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 { 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; char *fname;
int create_mode; 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; char *last_error;
CONFIG_NODE *mainnode; 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_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_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); 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. */ /* Remove one node from block/list. */
void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node); void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node);

View file

@ -2,5 +2,5 @@
#include "iconfig.h" #include "iconfig.h"
/* private */ /* private */
int config_error(CONFIG_REC *rec, const char *msg); int config_error(CONFIG_REC *rec, const char *msg, ...);

View file

@ -43,10 +43,16 @@ static unsigned int g_istr_hash(gconstpointer v)
return h /* % M */; 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); 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; return -1;
} }
@ -133,6 +139,7 @@ static GTokenType config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node)
GTokenType last_char; GTokenType last_char;
int print_warning; int print_warning;
char *key; char *key;
int key_is_include = FALSE;
g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR); g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR);
g_return_val_if_fail(node != 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)) { (rec->scanner->token == G_TOKEN_STRING)) {
key = g_strdup(rec->scanner->value.v_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); config_parse_get_token(rec->scanner, node);
} }
switch (rec->scanner->token) { switch (rec->scanner->token) {
case G_TOKEN_STRING: 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 */ /* value */
config_node_set_str(rec, node, key, rec->scanner->value.v_string); config_node_set_str(rec, node, key, rec->scanner->value.v_string);
g_free_not_null(key); g_free_not_null(key);
@ -332,6 +350,8 @@ void config_close(CONFIG_REC *rec)
g_hash_table_destroy(rec->cache_nodes); g_hash_table_destroy(rec->cache_nodes);
g_free_not_null(rec->last_error); g_free_not_null(rec->last_error);
g_free_not_null(rec->fname); g_free_not_null(rec->fname);
if (rec->includes)
g_hash_table_destroy(rec->includes);
g_free(rec); g_free(rec);
} }

View file

@ -20,6 +20,15 @@
#include "module.h" #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) static void cache_remove(CONFIG_REC *rec, CONFIG_NODE *node)
{ {
char *path; char *path;
@ -39,7 +48,7 @@ void config_node_remove(CONFIG_REC *rec, CONFIG_NODE *parent, CONFIG_NODE *node)
if (parent == NULL) if (parent == NULL)
parent = rec->mainnode; parent = rec->mainnode;
rec->modifycounter++; config_rec_increase_modifycounter(rec);
cache_remove(rec, node); cache_remove(rec, node);
parent->value = g_slist_remove(parent->value, 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: case NODE_TYPE_COMMENT:
g_free_not_null(node->value); g_free_not_null(node->value);
break; 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_BLOCK:
case NODE_TYPE_LIST: case NODE_TYPE_LIST:
while (node->value != NULL) 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); 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) void config_node_set_str(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, const char *value)
{ {
CONFIG_NODE *node; 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); 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) void config_node_set_int(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int value)

View file

@ -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) if (config_write_word(rec, node->value, TRUE) == -1)
return -1; return -1;
break; 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: case NODE_TYPE_BLOCK:
/* key = { */ /* key = { */
if (node->key != NULL) { if (node->key != NULL) {
@ -217,6 +229,11 @@ static int config_node_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
/* "value, " */ /* "value, " */
len = 2 + strlen(node->value); len = 2 + strlen(node->value);
break; 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_BLOCK:
case NODE_TYPE_LIST: case NODE_TYPE_LIST:
/* "{ list }; " */ /* "{ list }; " */