X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fconfig_parser.c;h=e72923d6658905eee9521d7518fc896f66a923ae;hb=a6d8ed9b1ac6efa507d65b752758522bcfcc5213;hp=ea00412d78b2d2533c5dd0b87486aaa3da7e9f4e;hpb=840ce51bfddb83857aef872238b93ff441ced212;p=i3%2Fi3 diff --git a/src/config_parser.c b/src/config_parser.c index ea00412d..e72923d6 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1,5 +1,3 @@ -#undef I3__FILE__ -#define I3__FILE__ "config_parser.c" /* * vim:ts=4:sw=4:expandtab * @@ -25,6 +23,8 @@ * nearest token. * */ +#include "all.h" + #include #include #include @@ -35,13 +35,14 @@ #include #include #include - -#include "all.h" +#include // Macros to make the YAJL API a bit easier to use. #define y(x, ...) yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__) #define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str)) +xcb_xrm_database_t *database = NULL; + #ifndef TEST_PARSER pid_t config_error_nagbar_pid = -1; static struct context *context; @@ -122,7 +123,7 @@ static void push_string(const char *identifier, const char *str) { /* When we arrive here, the stack is full. This should not happen and * means there’s either a bug in this parser or the specification * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + fprintf(stderr, "BUG: config_parser stack full. This means either a bug " "in the code, or a new command which contains more than " "10 identified tokens.\n"); exit(1); @@ -142,7 +143,7 @@ static void push_long(const char *identifier, long num) { /* When we arrive here, the stack is full. This should not happen and * means there’s either a bug in this parser or the specification * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + fprintf(stderr, "BUG: config_parser stack full. This means either a bug " "in the code, or a new command which contains more than " "10 identified tokens.\n"); exit(1); @@ -178,53 +179,6 @@ static void clear_stack(void) { } } -// TODO: remove this if it turns out we don’t need it for testing. -#if 0 -/******************************************************************************* - * A dynamically growing linked list which holds the criteria for the current - * command. - ******************************************************************************/ - -typedef struct criterion { - char *type; - char *value; - - TAILQ_ENTRY(criterion) criteria; -} criterion; - -static TAILQ_HEAD(criteria_head, criterion) criteria = - TAILQ_HEAD_INITIALIZER(criteria); - -/* - * Stores the given type/value in the list of criteria. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void push_criterion(void *unused_criteria, const char *type, - const char *value) { - struct criterion *criterion = smalloc(sizeof(struct criterion)); - criterion->type = sstrdup(type); - criterion->value = sstrdup(value); - TAILQ_INSERT_TAIL(&criteria, criterion, criteria); -} - -/* - * Clears the criteria linked list. - * Accepts a pointer as first argument, since it is 'call'ed by the parser. - * - */ -static void clear_criteria(void *unused_criteria) { - struct criterion *criterion; - while (!TAILQ_EMPTY(&criteria)) { - criterion = TAILQ_FIRST(&criteria); - free(criterion->type); - free(criterion->value); - TAILQ_REMOVE(&criteria, criterion, criteria); - free(criterion); - } -} -#endif - /******************************************************************************* * The parser itself. ******************************************************************************/ @@ -830,18 +784,108 @@ static char *migrate_config(char *input, off_t size) { return converted; } +/** + * Launch nagbar to indicate errors in the configuration file. + */ +void start_config_error_nagbar(const char *configpath, bool has_errors) { + char *editaction, *pageraction; + sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", configpath); + sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-f", + (config.font.pattern ? config.font.pattern : "fixed"), + "-t", + (has_errors ? "error" : "warning"), + "-m", + (has_errors ? "You have an error in your i3 config file!" : "Your config is outdated. Please fix the warnings to make sure everything works."), + "-b", + "edit config", + editaction, + (errorfilename ? "-b" : NULL), + (has_errors ? "show errors" : "show warnings"), + pageraction, + NULL}; + + start_nagbar(&config_error_nagbar_pid, argv); + free(editaction); + free(pageraction); +} + +/* + * Inserts or updates a variable assignment depending on whether it already exists. + * + */ +static void upsert_variable(struct variables_head *variables, char *key, char *value) { + struct Variable *current; + SLIST_FOREACH(current, variables, variables) { + if (strcmp(current->key, key) != 0) { + continue; + } + + DLOG("Updated variable: %s = %s -> %s\n", key, current->value, value); + FREE(current->value); + current->value = sstrdup(value); + return; + } + + DLOG("Defined new variable: %s = %s\n", key, value); + struct Variable *new = scalloc(1, sizeof(struct Variable)); + struct Variable *test = NULL, *loc = NULL; + new->key = sstrdup(key); + new->value = sstrdup(value); + /* ensure that the correct variable is matched in case of one being + * the prefix of another */ + SLIST_FOREACH(test, variables, variables) { + if (strlen(new->key) >= strlen(test->key)) + break; + loc = test; + } + + if (loc == NULL) { + SLIST_INSERT_HEAD(variables, new, variables); + } else { + SLIST_INSERT_AFTER(loc, new, variables); + } +} + +static char *get_resource(char *name) { + if (conn == NULL) { + return NULL; + } + + /* Load the resource database lazily. */ + if (database == NULL) { + database = xcb_xrm_database_from_default(conn); + + if (database == NULL) { + ELOG("Failed to open the resource database.\n"); + + /* Load an empty database so we don't keep trying to load the + * default database over and over again. */ + database = xcb_xrm_database_from_string(""); + + return NULL; + } + } + + char *resource; + xcb_xrm_resource_get_string(database, name, NULL, &resource); + return resource; +} + /* * Parses the given file by first replacing the variables, then calling * parse_config and possibly launching i3-nagbar. * */ bool parse_file(const char *f, bool use_nagbar) { - SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); + struct variables_head variables = SLIST_HEAD_INITIALIZER(&variables); int fd; struct stat stbuf; char *buf; FILE *fstr; - char buffer[4096], key[512], value[512], *continuation = NULL; + char buffer[4096], key[512], value[4096], *continuation = NULL; if ((fd = open(f, O_RDONLY)) == -1) die("Could not open configuration file: %s\n", strerror(errno)); @@ -854,6 +898,11 @@ bool parse_file(const char *f, bool use_nagbar) { if ((fstr = fdopen(fd, "r")) == NULL) die("Could not fdopen: %s\n", strerror(errno)); + FREE(current_config); + current_config = scalloc(stbuf.st_size + 1, 1); + fread(current_config, 1, stbuf.st_size, fstr); + rewind(fstr); + while (!feof(fstr)) { if (!continuation) continuation = buffer; @@ -862,51 +911,89 @@ bool parse_file(const char *f, bool use_nagbar) { break; die("Could not read configuration file\n"); } - if (buffer[strlen(buffer) - 1] != '\n') { + if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) { ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer)); } + + /* sscanf implicitly strips whitespace. */ + const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3); + const bool comment = (key[0] == '#'); + value[4095] = '\n'; + continuation = strstr(buffer, "\\\n"); if (continuation) { - continue; + if (!comment) { + continue; + } + DLOG("line continuation in comment is ignored: \"%.*s\"\n", (int)strlen(buffer) - 1, buffer); + continuation = NULL; } strncpy(buf + strlen(buf), buffer, strlen(buffer) + 1); - /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ - if (sscanf(buffer, "%511s %511[^\n]", key, value) < 1 || - key[0] == '#' || strlen(key) < 3) + /* Skip comments and empty lines. */ + if (skip_line || comment) { continue; + } if (strcasecmp(key, "set") == 0) { - if (value[0] != '$') { + char v_key[512]; + char v_value[4096]; + + if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) { + ELOG("Failed to parse variable specification '%s', skipping it.\n", value); + continue; + } + + if (v_key[0] != '$') { ELOG("Malformed variable assignment, name has to start with $\n"); continue; } - /* get key/value for this variable */ - char *v_key = value, *v_value; - if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) { - ELOG("Malformed variable assignment, need a value\n"); + upsert_variable(&variables, v_key, v_value); + continue; + } else if (strcasecmp(key, "set_from_resource") == 0) { + char res_name[512]; + char v_key[512]; + char fallback[4096]; + + /* Ensure that this string is terminated. For example, a user might + * want a variable to be empty if the resource can't be found and + * uses + * set_from_resource $foo i3wm.foo + * Without explicitly terminating the string first, sscanf() will + * leave it uninitialized, causing garbage in the config.*/ + fallback[0] = '\0'; + + if (sscanf(value, "%511s %511s %4095[^\n]", v_key, res_name, fallback) < 1) { + ELOG("Failed to parse resource specification '%s', skipping it.\n", value); continue; } - if (!(v_value = strstr(value, " "))) - v_value = strstr(value, "\t"); + if (v_key[0] != '$') { + ELOG("Malformed variable assignment, name has to start with $\n"); + continue; + } - *(v_value++) = '\0'; - while (*v_value == '\t' || *v_value == ' ') - v_value++; + char *res_value = get_resource(res_name); + if (res_value == NULL) { + DLOG("Could not get resource '%s', using fallback '%s'.\n", res_name, fallback); + res_value = sstrdup(fallback); + } - struct Variable *new = scalloc(1, sizeof(struct Variable)); - new->key = sstrdup(v_key); - new->value = sstrdup(v_value); - SLIST_INSERT_HEAD(&variables, new, variables); - DLOG("Got new variable %s = %s\n", v_key, v_value); + upsert_variable(&variables, v_key, res_value); + FREE(res_value); continue; } } fclose(fstr); + if (database != NULL) { + xcb_xrm_database_free(database); + /* Explicitly set the database to NULL again in case the config gets reloaded. */ + database = NULL; + } + /* For every custom variable, see how often it occurs in the file and * how much extra bytes it requires when replaced. */ struct Variable *current, *nearest; @@ -920,7 +1007,7 @@ bool parse_file(const char *f, bool use_nagbar) { char *next; for (next = bufcopy; next < (bufcopy + stbuf.st_size) && - (next = strcasestr(next, current->key)) != NULL; + (next = strcasestr(next, current->key)) != NULL; next += strlen(current->key)) { *next = '_'; extra_bytes += extra; @@ -929,7 +1016,7 @@ bool parse_file(const char *f, bool use_nagbar) { FREE(bufcopy); /* Then, allocate a new buffer and copy the file over to the new one, - * but replace occurences of our variables */ + * but replace occurrences of our variables */ char *walk = buf, *destwalk; char *new = smalloc(stbuf.st_size + extra_bytes + 1); destwalk = new; @@ -1005,29 +1092,7 @@ bool parse_file(const char *f, bool use_nagbar) { if (version == 3) ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - char *editaction, - *pageraction; - sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f); - sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); - char *argv[] = { - NULL, /* will be replaced by the executable path */ - "-f", - (config.font.pattern ? config.font.pattern : "fixed"), - "-t", - (context->has_errors ? "error" : "warning"), - "-m", - (context->has_errors ? "You have an error in your i3 config file!" : "Your config is outdated. Please fix the warnings to make sure everything works."), - "-b", - "edit config", - editaction, - (errorfilename ? "-b" : NULL), - (context->has_errors ? "show errors" : "show warnings"), - pageraction, - NULL}; - - start_nagbar(&config_error_nagbar_pid, argv); - free(editaction); - free(pageraction); + start_config_error_nagbar(f, context->has_errors); } bool has_errors = context->has_errors;