X-Git-Url: https://git.sur5r.net/?p=i3%2Fi3;a=blobdiff_plain;f=src%2Fconfig_parser.c;h=9f972fed8f80b4510931cac6edb52cef431c832a;hp=b197eb223e981186982391d6d6607a1cff728857;hb=HEAD;hpb=a8757625c32e58676a3aa99c1859734b7ac09c8d diff --git a/src/config_parser.c b/src/config_parser.c index b197eb22..9f972fed 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; @@ -170,7 +171,7 @@ static long get_long(const char *identifier) { static void clear_stack(void) { for (int c = 0; c < 10; c++) { - if (stack[c].type == STACK_STR && stack[c].val.str != NULL) + if (stack[c].type == STACK_STR) free(stack[c].val.str); stack[c].identifier = NULL; stack[c].val.str = NULL; @@ -234,7 +235,7 @@ static void next_state(const cmdp_token *token) { * */ static const char *start_of_line(const char *walk, const char *beginning) { - while (*walk != '\n' && *walk != '\r' && walk >= beginning) { + while (walk >= beginning && *walk != '\n' && *walk != '\r') { walk--; } @@ -742,7 +743,7 @@ static char *migrate_config(char *input, off_t size) { /* read the script’s output */ int conv_size = 65535; - char *converted = smalloc(conv_size); + char *converted = scalloc(conv_size, 1); int read_bytes = 0, ret; do { if (read_bytes == conv_size) { @@ -763,6 +764,7 @@ static char *migrate_config(char *input, off_t size) { wait(&status); if (!WIFEXITED(status)) { fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n"); + FREE(converted); return NULL; } @@ -777,6 +779,7 @@ static char *migrate_config(char *input, off_t size) { fprintf(stderr, "# i3 config file (v4)\n"); /* TODO: nag the user with a message to include a hint for i3 in their config file */ } + FREE(converted); return NULL; } @@ -811,18 +814,80 @@ void start_config_error_nagbar(const char *configpath, bool has_errors) { 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)); @@ -835,6 +900,15 @@ 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); + if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) { + die("Could not fread: %s\n", strerror(errno)); + } + rewind(fstr); + + bool invalid_sets = false; + while (!feof(fstr)) { if (!continuation) continuation = buffer; @@ -848,8 +922,10 @@ bool parse_file(const char *f, bool use_nagbar) { } /* sscanf implicitly strips whitespace. */ - const bool skip_line = (sscanf(buffer, "%511s %511[^\n]", key, value) < 1 || strlen(key) < 3); + value[0] = '\0'; + 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) { @@ -867,50 +943,68 @@ bool parse_file(const char *f, bool use_nagbar) { continue; } - if (strcasecmp(key, "set") == 0) { - if (value[0] != '$') { + if (strcasecmp(key, "set") == 0 && *value != '\0') { + char v_key[512]; + char v_value[4096] = {'\0'}; + + if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) { + ELOG("Failed to parse variable specification '%s', skipping it.\n", value); + invalid_sets = true; + continue; + } + + if (v_key[0] != '$') { ELOG("Malformed variable assignment, name has to start with $\n"); + invalid_sets = true; 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] = {'\0'}; + char v_key[512]; + char fallback[4096] = {'\0'}; + + /* 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); + invalid_sets = true; continue; } - if (!(v_value = strstr(value, " "))) - v_value = strstr(value, "\t"); - - *(v_value++) = '\0'; - while (*v_value == '\t' || *v_value == ' ') - v_value++; - - struct Variable *new = scalloc(1, sizeof(struct Variable)); - struct Variable *test = NULL, *loc = NULL; - new->key = sstrdup(v_key); - new->value = sstrdup(v_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 (v_key[0] != '$') { + ELOG("Malformed variable assignment, name has to start with $\n"); + invalid_sets = true; + continue; } - if (loc == NULL) { - SLIST_INSERT_HEAD(&variables, new, variables); - } else { - SLIST_INSERT_AFTER(loc, new, variables); + 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); } - 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; @@ -924,7 +1018,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; @@ -935,7 +1029,7 @@ bool parse_file(const char *f, bool use_nagbar) { /* Then, allocate a new buffer and copy the file over to the new one, * but replace occurrences of our variables */ char *walk = buf, *destwalk; - char *new = smalloc(stbuf.st_size + extra_bytes + 1); + char *new = scalloc(stbuf.st_size + extra_bytes + 1, 1); destwalk = new; while (walk < (buf + stbuf.st_size)) { /* Find the next variable */ @@ -971,7 +1065,7 @@ bool parse_file(const char *f, bool use_nagbar) { int version = detect_version(buf); if (version == 3) { /* We need to convert this v3 configuration */ - char *converted = migrate_config(new, stbuf.st_size); + char *converted = migrate_config(new, strlen(new)); if (converted != NULL) { ELOG("\n"); ELOG("****************************************************************\n"); @@ -1004,12 +1098,12 @@ bool parse_file(const char *f, bool use_nagbar) { check_for_duplicate_bindings(context); reorder_bindings(); - if (use_nagbar && (context->has_errors || context->has_warnings)) { + if (use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) { ELOG("FYI: You are using i3 version %s\n", i3_version); if (version == 3) ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - start_config_error_nagbar(f, context->has_errors); + start_config_error_nagbar(f, context->has_errors || invalid_sets); } bool has_errors = context->has_errors;