diff --git a/src/config.c b/src/config.c index 1994baa..995d0aa 100644 --- a/src/config.c +++ b/src/config.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "ini.h" #include "keys.h" @@ -22,6 +23,90 @@ #include "error.h" #include "config.h" +#define MAX_FILE_SZ 65536 +#define MAX_LINE_LEN 256 + +static char *read_file(const char *path) +{ + const char include_prefix[] = "include "; + + static char buf[MAX_FILE_SZ]; + char line[MAX_LINE_LEN]; + int sz = 0; + + FILE *fh = fopen(path, "r"); + if (!fh) { + fprintf(stderr, "\tERROR: Failed to open %s\n", path); + return NULL; + } + + while (fgets(line, sizeof line, fh)) { + int len = strlen(line); + + if (line[len-1] != '\n') { + fprintf(stderr, "\tERROR: Maximum line length exceed (%d).\n", MAX_LINE_LEN); + goto fail; + } + + if ((len+sz) > MAX_FILE_SZ) { + fprintf(stderr, "\tERROR: Max file size (%d) exceeded.\n", MAX_FILE_SZ); + goto fail; + } + + if (strstr(line, include_prefix) == line) { + int fd; + char include_path[PATH_MAX]; + char resolved_path[PATH_MAX]; + + line[len-1] = 0; + + char *tmp = strdup(path); + char *dir = dirname(tmp); + + snprintf(include_path, + sizeof include_path, + "%s/%s", dir, line+sizeof(include_prefix)-1); + + if(!realpath(include_path, resolved_path)) { + fprintf(stderr, "\tERROR: Failed to resolve include path: %s\n", include_path); + free(tmp); + continue; + } + + if (strstr(resolved_path, dir) != resolved_path) { + fprintf(stderr, "\tERROR: Naughty path detected: %s\n", include_path); + free(tmp); + continue; + } + + free(tmp); + + fd = open(include_path, O_RDONLY); + + if (fd < 0) { + fprintf(stderr, "\tERROR: Failed to include %s\n", include_path); + perror("open"); + } else { + int n; + while ((n = read(fd, buf+sz, sizeof(buf)-sz)) > 0) + sz += n; + close(fd); + } + } else { + strcpy(buf+sz, line); + sz += len; + } + } + + fclose(fh); + return buf; + +fail: + fclose(fh); + return NULL; +} + + /* Return up to two keycodes associated with the given name. */ static int lookup_keycodes(const char *name, uint8_t *code1, uint8_t *code2) { @@ -103,11 +188,7 @@ int config_add_entry(struct config *config, const char *exp) s = dot+1; } - if (parse_kvp(s, &keyname, &descstr) < 0) { - err("Invalid key value pair."); - return -1; - } - + parse_kvp(s, &keyname, &descstr); idx = config_get_layer_index(config, layername); if (idx == -1) { @@ -216,24 +297,19 @@ static void parse_globals(const char *path, struct config *config, struct ini_se size_t i; for (i = 0; i < section->nr_entries;i++) { - char *key, *val; struct ini_entry *ent = §ion->entries[i]; - if (parse_kvp(ent->line, &key, &val)) { - fprintf(stderr, "\tERROR %s:%zd: malformed config entry\n", path, ent->lnum); - continue; - } - if (!strcmp(key, "macro_timeout")) - config->macro_timeout = atoi(val); - else if (!strcmp(key, "macro_repeat_timeout")) - config->macro_repeat_timeout = atoi(val); - else if (!strcmp(key, "layer_indicator")) - config->layer_indicator = atoi(val); + if (!strcmp(ent->key, "macro_timeout")) + config->macro_timeout = atoi(ent->val); + else if (!strcmp(ent->key, "macro_repeat_timeout")) + config->macro_repeat_timeout = atoi(ent->val); + else if (!strcmp(ent->key, "layer_indicator")) + config->layer_indicator = atoi(ent->val); else fprintf(stderr, "\tERROR %s:%zd: %s is not a valid global option.\n", path, ent->lnum, - key); + ent->key); } } @@ -241,12 +317,16 @@ int config_parse(struct config *config, const char *path) { size_t i; + char *content; struct ini *ini; struct ini_section *section; config_init(config); - if (!(ini = ini_parse_file(path, NULL))) + if (!(content = read_file(path))) + return -1; + + if (!(ini = ini_parse_string(content, NULL))) return -1; /* First pass: create all layers based on section headers. */ @@ -282,10 +362,15 @@ int config_parse(struct config *config, const char *path) char entry[MAX_EXP_LEN]; struct ini_entry *ent = §ion->entries[j]; - snprintf(entry, sizeof entry, "%s.%s", layername, ent->line); + if (!ent->val) { + fprintf(stderr, "\tERROR parsing %s:%zd: invalid key value pair.\n", path, ent->lnum); + continue; + } + + snprintf(entry, sizeof entry, "%s.%s = %s", layername, ent->key, ent->val); if (config_add_entry(config, entry) < 0) - fprintf(stderr, "\tERROR %s:%zd: %s\n", path, ent->lnum, errstr); + fprintf(stderr, "\tERROR parsing %s:%zd: %s\n", path, ent->lnum, errstr); } } diff --git a/src/ini.c b/src/ini.c index ef60388..bf29558 100644 --- a/src/ini.c +++ b/src/ini.c @@ -18,11 +18,11 @@ /* * Parse a value of the form 'key = value'. The value may contain = - * and the key may itself be = as a special case. The returned - * values are pointers within the modified input string. + * and the key may itself be = as a special case. value may be NULL + * if '=' is not present in the original string. */ -int parse_kvp(char *s, char **key, char **value) +void parse_kvp(char *s, char **key, char **value) { char *last_space = NULL; char *c = s; @@ -31,6 +31,8 @@ int parse_kvp(char *s, char **key, char **value) if (*c == '=') c++; + *key = s; + *value = NULL; while (*c) { switch (*c) { case '=': @@ -38,18 +40,11 @@ int parse_kvp(char *s, char **key, char **value) *last_space = 0; else *c = 0; - c++; - while (*c && *c == ' ') - c++; + while (*++c == ' '); - if (!*s) - return -1; - - *key = s; *value = c; - - return 0; + return; case ' ': if (!last_space) last_space = c; @@ -61,36 +56,19 @@ int parse_kvp(char *s, char **key, char **value) c++; } - - return -1; } -static void read_file(const char *path, char *buf, size_t buf_sz) -{ - struct stat st; - size_t sz; - int fd; - ssize_t n = 0, nr; - - if (stat(path, &st)) { - perror("stat"); - exit(-1); - } - - sz = st.st_size; - assert(sz < buf_sz); - - fd = open(path, O_RDONLY); - while ((nr = read(fd, buf + n, sz - n))) { - n += nr; - } - - buf[sz] = '\0'; - close(fd); -} +/* + * The result is statically allocated and should not be freed by the caller. + * The returned struct is only valid until the next invocation. The + * input string may be modified and should only be freed after the + * returned ini struct is no longer required. + */ -int parse(char *s, struct ini *ini, const char *default_section_name) +struct ini *ini_parse_string(char *s, const char *default_section_name) { + static struct ini ini; + int ln = 0; size_t n = 0; @@ -130,7 +108,7 @@ int parse(char *s, struct ini *ini, const char *default_section_name) if (line[len-1] == ']') { assert(n < MAX_SECTIONS); - section = &ini->sections[n++]; + section = &ini.sections[n++]; line[len-1] = 0; @@ -148,39 +126,22 @@ int parse(char *s, struct ini *ini, const char *default_section_name) if (!section) { if(default_section_name) { - section = &ini->sections[n++]; + section = &ini.sections[n++]; strcpy(section->name, default_section_name); section->nr_entries = 0; section->lnum = 0; } else - return -1; + return NULL; } assert(section->nr_entries < MAX_SECTION_ENTRIES); ent = §ion->entries[section->nr_entries++]; - - ent->line = line; + parse_kvp(line, &ent->key, &ent->val); ent->lnum = ln; } - ini->nr_sections = n; - return 0; -} - -/* - * The result is statically allocated and should not be freed by the caller. - * The returned ini struct is only valid until the next invocation. - */ -struct ini *ini_parse_file(const char *path, const char *default_section_name) -{ - static char buf[MAX_INI_SIZE]; - static struct ini ini; - - read_file(path, buf, sizeof buf); - if (parse(buf, &ini, default_section_name) < 0) - return NULL; - + ini.nr_sections = n; return &ini; } diff --git a/src/ini.h b/src/ini.h index b9653ba..4b71f5a 100644 --- a/src/ini.h +++ b/src/ini.h @@ -12,7 +12,9 @@ #define MAX_SECTION_ENTRIES 128 struct ini_entry { - char *line; + char *key; + char *val; + size_t lnum; // The line number in the original source file. }; @@ -32,14 +34,15 @@ struct ini { }; /* - * Reads a file of the form: + * Reads a string of the form: * * [section] * * # Comment * - * entry1 - * entry2 + * key1 = val1 + * key2 = val2 + * key3 * * [section2] * ... @@ -49,15 +52,17 @@ struct ini { * sripped of leading whitespace. If a default * section name is supplied then entries not * listed under an explicit heading will be - * returned under the named section. + * returned under the named section. If + * no value is specified, val is NULL in + * the corresponding entry. * * The returned result is statically allocated and only * valid until the next invocation. It should not be * freed. */ -struct ini *ini_parse_file(const char *path, const char *default_section_name); +struct ini *ini_parse_string(char *s, const char *default_section_name); -int parse_kvp(char *s, char **key, char **value); +void parse_kvp(char *s, char **key, char **value); #endif diff --git a/src/keyd.c b/src/keyd.c index e077895..a299886 100644 --- a/src/keyd.c +++ b/src/keyd.c @@ -599,4 +599,6 @@ int main(int argc, char *argv[]) chgid(); loop(0); } + + return 0; }