i3
config_parser.c
Go to the documentation of this file.
1/*
2 * vim:ts=4:sw=4:expandtab
3 *
4 * i3 - an improved tiling window manager
5 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6 *
7 * config_parser.c: hand-written parser to parse configuration directives.
8 *
9 * See also src/commands_parser.c for rationale on why we use a custom parser.
10 *
11 * This parser works VERY MUCH like src/commands_parser.c, so read that first.
12 * The differences are:
13 *
14 * 1. config_parser supports the 'number' token type (in addition to 'word' and
15 * 'string'). Numbers are referred to using &num (like $str).
16 *
17 * 2. Criteria are not executed immediately, they are just stored.
18 *
19 * 3. config_parser recognizes \n and \r as 'end' token, while commands_parser
20 * ignores them.
21 *
22 * 4. config_parser skips the current line on invalid inputs and follows the
23 * nearest <error> token.
24 *
25 */
26#include "all.h"
27
28#include <fcntl.h>
29#include <stdbool.h>
30#include <stdint.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <sys/stat.h>
35#include <sys/types.h>
36#include <sys/wait.h>
37#include <unistd.h>
38#include <libgen.h>
39
40#include <xcb/xcb_xrm.h>
41
42xcb_xrm_database_t *database = NULL;
43
44#ifndef TEST_PARSER
46#endif
47
48/*******************************************************************************
49 * The data structures used for parsing. Essentially the current state and a
50 * list of tokens for that state.
51 *
52 * The GENERATED_* files are generated by generate-commands-parser.pl with the
53 * input parser-specs/configs.spec.
54 ******************************************************************************/
55
56#include "GENERATED_config_enums.h"
57
58typedef struct token {
59 char *name;
60 char *identifier;
61 /* This might be __CALL */
62 cmdp_state next_state;
63 union {
64 uint16_t call_identifier;
67
68typedef struct tokenptr {
70 int n;
72
73#include "GENERATED_config_tokens.h"
74
75/*
76 * Pushes a string (identified by 'identifier') on the stack. We simply use a
77 * single array, since the number of entries we have to store is very small.
78 *
79 */
80static void push_string(struct stack *ctx, const char *identifier, const char *str) {
81 for (int c = 0; c < 10; c++) {
82 if (ctx->stack[c].identifier != NULL &&
83 strcmp(ctx->stack[c].identifier, identifier) != 0) {
84 continue;
85 }
86 if (ctx->stack[c].identifier == NULL) {
87 /* Found a free slot, let’s store it here. */
88 ctx->stack[c].identifier = identifier;
89 ctx->stack[c].val.str = sstrdup(str);
90 ctx->stack[c].type = STACK_STR;
91 } else {
92 /* Append the value. */
93 char *prev = ctx->stack[c].val.str;
94 sasprintf(&(ctx->stack[c].val.str), "%s,%s", prev, str);
95 free(prev);
96 }
97 return;
98 }
99
100 /* When we arrive here, the stack is full. This should not happen and
101 * means there’s either a bug in this parser or the specification
102 * contains a command with more than 10 identified tokens. */
103 fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
104 "in the code, or a new command which contains more than "
105 "10 identified tokens.\n");
106 exit(EXIT_FAILURE);
107}
108
109static void push_long(struct stack *ctx, const char *identifier, long num) {
110 for (int c = 0; c < 10; c++) {
111 if (ctx->stack[c].identifier != NULL) {
112 continue;
113 }
114 /* Found a free slot, let’s store it here. */
115 ctx->stack[c].identifier = identifier;
116 ctx->stack[c].val.num = num;
117 ctx->stack[c].type = STACK_LONG;
118 return;
119 }
120
121 /* When we arrive here, the stack is full. This should not happen and
122 * means there’s either a bug in this parser or the specification
123 * contains a command with more than 10 identified tokens. */
124 fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
125 "in the code, or a new command which contains more than "
126 "10 identified tokens.\n");
127 exit(EXIT_FAILURE);
128}
129
130static const char *get_string(struct stack *ctx, const char *identifier) {
131 for (int c = 0; c < 10; c++) {
132 if (ctx->stack[c].identifier == NULL) {
133 break;
134 }
135 if (strcmp(identifier, ctx->stack[c].identifier) == 0) {
136 return ctx->stack[c].val.str;
137 }
138 }
139 return NULL;
140}
141
142static long get_long(struct stack *ctx, const char *identifier) {
143 for (int c = 0; c < 10; c++) {
144 if (ctx->stack[c].identifier == NULL) {
145 break;
146 }
147 if (strcmp(identifier, ctx->stack[c].identifier) == 0) {
148 return ctx->stack[c].val.num;
149 }
150 }
151 return 0;
152}
153
154static void clear_stack(struct stack *ctx) {
155 for (int c = 0; c < 10; c++) {
156 if (ctx->stack[c].type == STACK_STR) {
157 free(ctx->stack[c].val.str);
158 }
159 ctx->stack[c].identifier = NULL;
160 ctx->stack[c].val.str = NULL;
161 ctx->stack[c].val.num = 0;
162 }
163}
164
165/*******************************************************************************
166 * The parser itself.
167 ******************************************************************************/
168
169#include "GENERATED_config_call.h"
170
171static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
172 cmdp_state _next_state = token->next_state;
173
174 if (token->next_state == __CALL) {
176 .ctx = ctx,
177 };
178 GENERATED_call(&(ctx->current_match), ctx->stack, token->extra.call_identifier, &subcommand_output);
179 if (subcommand_output.has_errors) {
180 ctx->has_errors = true;
181 }
182 _next_state = subcommand_output.next_state;
183 clear_stack(ctx->stack);
184 }
185
186 ctx->state = _next_state;
187 if (ctx->state == INITIAL) {
188 clear_stack(ctx->stack);
189 }
190
191 /* See if we are jumping back to a state in which we were in previously
192 * (statelist contains INITIAL) and just move statelist_idx accordingly. */
193 for (int i = 0; i < ctx->statelist_idx; i++) {
194 if ((cmdp_state)(ctx->statelist[i]) != _next_state) {
195 continue;
196 }
197 ctx->statelist_idx = i + 1;
198 return;
199 }
200
201 /* Otherwise, the state is new and we add it to the list */
202 ctx->statelist[ctx->statelist_idx++] = _next_state;
203}
204
205/*
206 * Returns a pointer to the start of the line (one byte after the previous \r,
207 * \n) or the start of the input, if this is the first line.
208 *
209 */
210static const char *start_of_line(const char *walk, const char *beginning) {
211 while (walk >= beginning && *walk != '\n' && *walk != '\r') {
212 walk--;
213 }
214
215 return walk + 1;
216}
217
218/*
219 * Copies the line and terminates it at the next \n, if any.
220 *
221 * The caller has to free() the result.
222 *
223 */
224static char *single_line(const char *start) {
225 char *result = sstrdup(start);
226 char *end = strchr(result, '\n');
227 if (end != NULL) {
228 *end = '\0';
229 }
230 return result;
231}
232
233static void parse_config(struct parser_ctx *ctx, const char *input, struct context *context) {
234 /* Dump the entire config file into the debug log. We cannot just use
235 * DLOG("%s", input); because one log message must not exceed 4 KiB. */
236 const char *dumpwalk = input;
237 int linecnt = 1;
238 while (*dumpwalk != '\0') {
239 char *next_nl = strchr(dumpwalk, '\n');
240 if (next_nl != NULL) {
241 DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk);
242 dumpwalk = next_nl + 1;
243 } else {
244 DLOG("CONFIG(line %3d): %s\n", linecnt, dumpwalk);
245 break;
246 }
247 linecnt++;
248 }
249 ctx->state = INITIAL;
250 for (int i = 0; i < 10; i++) {
251 ctx->statelist[i] = INITIAL;
252 }
253 ctx->statelist_idx = 1;
254
255 const char *walk = input;
256 const size_t len = strlen(input);
257 int c;
258 const cmdp_token *token;
259 bool token_handled;
260 linecnt = 1;
261
262#ifndef TEST_PARSER
264 .ctx = ctx,
265 };
266 cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
267#endif
268
269 /* The "<=" operator is intentional: We also handle the terminating 0-byte
270 * explicitly by looking for an 'end' token. */
271 while ((size_t)(walk - input) <= len) {
272 /* Skip whitespace before every token, newlines are relevant since they
273 * separate configuration directives. */
274 while ((*walk == ' ' || *walk == '\t') && *walk != '\0') {
275 walk++;
276 }
277
278 cmdp_token_ptr *ptr = &(tokens[ctx->state]);
279 token_handled = false;
280 for (c = 0; c < ptr->n; c++) {
281 token = &(ptr->array[c]);
282
283 /* A literal. */
284 if (token->name[0] == '\'') {
285 if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
286 if (token->identifier != NULL) {
287 push_string(ctx->stack, token->identifier, token->name + 1);
288 }
289 walk += strlen(token->name) - 1;
291 token_handled = true;
292 break;
293 }
294 continue;
295 }
296
297 if (strcmp(token->name, "number") == 0) {
298 /* Handle numbers. We only accept decimal numbers for now. */
299 char *end = NULL;
300 errno = 0;
301 long int num = strtol(walk, &end, 10);
302 if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
303 (errno != 0 && num == 0)) {
304 continue;
305 }
306
307 /* No valid numbers found */
308 if (end == walk) {
309 continue;
310 }
311
312 if (token->identifier != NULL) {
313 push_long(ctx->stack, token->identifier, num);
314 }
315
316 /* Set walk to the first non-number character */
317 walk = end;
319 token_handled = true;
320 break;
321 }
322
323 if (strcmp(token->name, "string") == 0 ||
324 strcmp(token->name, "word") == 0) {
325 const char *beginning = walk;
326 /* Handle quoted strings (or words). */
327 if (*walk == '"') {
328 beginning++;
329 walk++;
330 while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\')) {
331 walk++;
332 }
333 } else {
334 if (token->name[0] == 's') {
335 while (*walk != '\0' && *walk != '\r' && *walk != '\n') {
336 walk++;
337 }
338 } else {
339 /* For a word, the delimiters are white space (' ' or
340 * '\t'), closing square bracket (]), comma (,) and
341 * semicolon (;). */
342 while (*walk != ' ' && *walk != '\t' &&
343 *walk != ']' && *walk != ',' &&
344 *walk != ';' && *walk != '\r' &&
345 *walk != '\n' && *walk != '\0') {
346 walk++;
347 }
348 }
349 }
350 if (walk != beginning) {
351 char *str = scalloc(walk - beginning + 1, 1);
352 /* We copy manually to handle escaping of characters. */
353 int inpos, outpos;
354 for (inpos = 0, outpos = 0;
355 inpos < (walk - beginning);
356 inpos++, outpos++) {
357 /* We only handle escaped double quotes to not break
358 * backwards compatibility with people using \w in
359 * regular expressions etc. */
360 if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') {
361 inpos++;
362 }
363 str[outpos] = beginning[inpos];
364 }
365 if (token->identifier) {
366 push_string(ctx->stack, token->identifier, str);
367 }
368 free(str);
369 /* If we are at the end of a quoted string, skip the ending
370 * double quote. */
371 if (*walk == '"') {
372 walk++;
373 }
375 token_handled = true;
376 break;
377 }
378 }
379
380 if (strcmp(token->name, "line") == 0) {
381 while (*walk != '\0' && *walk != '\n' && *walk != '\r') {
382 walk++;
383 }
385 token_handled = true;
386 linecnt++;
387 walk++;
388 break;
389 }
390
391 if (strcmp(token->name, "end") == 0) {
392 if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
394 token_handled = true;
395 /* To make sure we start with an appropriate matching
396 * datastructure for commands which do *not* specify any
397 * criteria, we re-initialize the criteria system after
398 * every command. */
399#ifndef TEST_PARSER
400 cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
401#endif
402 linecnt++;
403 walk++;
404 break;
405 }
406 }
407 }
408
409 if (!token_handled) {
410 /* Figure out how much memory we will need to fill in the names of
411 * all tokens afterwards. */
412 int tokenlen = 0;
413 for (c = 0; c < ptr->n; c++) {
414 tokenlen += strlen(ptr->array[c].name) + strlen("'', ");
415 }
416
417 /* Build up a decent error message. We include the problem, the
418 * full input, and underline the position where the parser
419 * currently is. */
420 char *errormessage;
421 char *possible_tokens = smalloc(tokenlen + 1);
422 char *tokenwalk = possible_tokens;
423 for (c = 0; c < ptr->n; c++) {
424 token = &(ptr->array[c]);
425 if (token->name[0] == '\'') {
426 /* A literal is copied to the error message enclosed with
427 * single quotes. */
428 *tokenwalk++ = '\'';
429 strcpy(tokenwalk, token->name + 1);
430 tokenwalk += strlen(token->name + 1);
431 *tokenwalk++ = '\'';
432 } else {
433 /* Skip error tokens in error messages, they are used
434 * internally only and might confuse users. */
435 if (strcmp(token->name, "error") == 0) {
436 continue;
437 }
438 /* Any other token is copied to the error message enclosed
439 * with angle brackets. */
440 *tokenwalk++ = '<';
441 strcpy(tokenwalk, token->name);
442 tokenwalk += strlen(token->name);
443 *tokenwalk++ = '>';
444 }
445 if (c < (ptr->n - 1)) {
446 *tokenwalk++ = ',';
447 *tokenwalk++ = ' ';
448 }
449 }
450 *tokenwalk = '\0';
451 sasprintf(&errormessage, "Expected one of these tokens: %s",
452 possible_tokens);
453 free(possible_tokens);
454
455 /* Go back to the beginning of the line */
456 const char *error_line = start_of_line(walk, input);
457
458 /* Contains the same amount of characters as 'input' has, but with
459 * the unparsable part highlighted using ^ characters. */
460 char *position = scalloc(strlen(error_line) + 1, 1);
461 const char *copywalk;
462 for (copywalk = error_line;
463 *copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0';
464 copywalk++) {
465 position[(copywalk - error_line)] = (copywalk >= walk ? '^' : (*copywalk == '\t' ? '\t' : ' '));
466 }
467 position[(copywalk - error_line)] = '\0';
468
469 ELOG("CONFIG: %s\n", errormessage);
470 ELOG("CONFIG: (in file %s)\n", context->filename);
471 char *error_copy = single_line(error_line);
472
473 /* Print context lines *before* the error, if any. */
474 if (linecnt > 1) {
475 const char *context_p1_start = start_of_line(error_line - 2, input);
476 char *context_p1_line = single_line(context_p1_start);
477 if (linecnt > 2) {
478 const char *context_p2_start = start_of_line(context_p1_start - 2, input);
479 char *context_p2_line = single_line(context_p2_start);
480 ELOG("CONFIG: Line %3d: %s\n", linecnt - 2, context_p2_line);
481 free(context_p2_line);
482 }
483 ELOG("CONFIG: Line %3d: %s\n", linecnt - 1, context_p1_line);
484 free(context_p1_line);
485 }
486 ELOG("CONFIG: Line %3d: %s\n", linecnt, error_copy);
487 ELOG("CONFIG: %s\n", position);
488 free(error_copy);
489 /* Print context lines *after* the error, if any. */
490 for (int i = 0; i < 2; i++) {
491 char *error_line_end = strchr(error_line, '\n');
492 if (error_line_end != NULL && *(error_line_end + 1) != '\0') {
493 error_line = error_line_end + 1;
494 error_copy = single_line(error_line);
495 ELOG("CONFIG: Line %3d: %s\n", linecnt + i + 1, error_copy);
496 free(error_copy);
497 }
498 }
499
500 context->has_errors = true;
501
502 /* Skip the rest of this line, but continue parsing. */
503 while ((size_t)(walk - input) <= len && *walk != '\n') {
504 walk++;
505 }
506
507 free(position);
508 free(errormessage);
509 clear_stack(ctx->stack);
510
511 /* To figure out in which state to go (e.g. MODE or INITIAL),
512 * we find the nearest state which contains an <error> token
513 * and follow that one. */
514 bool error_token_found = false;
515 for (int i = ctx->statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
516 cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]);
517 for (int j = 0; j < errptr->n; j++) {
518 if (strcmp(errptr->array[j].name, "error") != 0) {
519 continue;
520 }
521 next_state(&(errptr->array[j]), ctx);
522 error_token_found = true;
523 break;
524 }
525 }
526
527 assert(error_token_found);
528 }
529 }
530}
531
532/*******************************************************************************
533 * Code for building the stand-alone binary test.commands_parser which is used
534 * by t/187-commands-parser.t.
535 ******************************************************************************/
536
537#ifdef TEST_PARSER
538
539/*
540 * Logs the given message to stdout while prefixing the current time to it,
541 * but only if debug logging was activated.
542 * This is to be called by DLOG() which includes filename/linenumber
543 *
544 */
545void debuglog(char *fmt, ...) {
546 va_list args;
547
548 va_start(args, fmt);
549 fprintf(stdout, "# ");
550 vfprintf(stdout, fmt, args);
551 va_end(args);
552}
553
554void errorlog(char *fmt, ...) {
555 va_list args;
556
557 va_start(args, fmt);
558 vfprintf(stderr, fmt, args);
559 va_end(args);
560}
561
562static int criteria_next_state;
563
564void cfg_criteria_init(I3_CFG, int _state) {
565 criteria_next_state = _state;
566}
567
568void cfg_criteria_add(I3_CFG, const char *ctype, const char *cvalue) {
569}
570
571void cfg_criteria_pop_state(I3_CFG) {
572 result->next_state = criteria_next_state;
573}
574
575int main(int argc, char *argv[]) {
576 if (argc < 2) {
577 fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
578 return 1;
579 }
580 struct stack stack;
581 memset(&stack, '\0', sizeof(struct stack));
582 struct parser_ctx ctx = {
583 .use_nagbar = false,
584 .stack = &stack,
585 };
586 SLIST_INIT(&(ctx.variables));
587 struct context context;
588 context.filename = "<stdin>";
589 parse_config(&ctx, argv[1], &context);
590}
591
592#else
593
597void start_config_error_nagbar(const char *configpath, bool has_errors) {
598 char *editaction, *pageraction;
599 sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", configpath);
600 sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
601 char *argv[] = {
602 NULL, /* will be replaced by the executable path */
603 "-f",
604 (config.font.pattern ? config.font.pattern : "fixed"),
605 "-t",
606 (has_errors ? "error" : "warning"),
607 "-m",
608 (has_errors ? "You have an error in your i3 config file!" : "Your config is outdated. Please fix the warnings to make sure everything works."),
609 "-b",
610 "edit config",
611 editaction,
612 (errorfilename ? "-b" : NULL),
613 (has_errors ? "show errors" : "show warnings"),
614 pageraction,
615 NULL};
616
618 free(editaction);
619 free(pageraction);
620}
621
622/*
623 * Inserts or updates a variable assignment depending on whether it already exists.
624 *
625 */
626static void upsert_variable(struct variables_head *variables, char *key, char *value) {
627 struct Variable *current;
628 SLIST_FOREACH (current, variables, variables) {
629 if (strcmp(current->key, key) != 0) {
630 continue;
631 }
632
633 DLOG("Updated variable: %s = %s -> %s\n", key, current->value, value);
634 FREE(current->value);
635 current->value = sstrdup(value);
636 return;
637 }
638
639 DLOG("Defined new variable: %s = %s\n", key, value);
640 struct Variable *new = scalloc(1, sizeof(struct Variable));
641 struct Variable *test = NULL, *loc = NULL;
642 new->key = sstrdup(key);
643 new->value = sstrdup(value);
644 /* ensure that the correct variable is matched in case of one being
645 * the prefix of another */
646 SLIST_FOREACH (test, variables, variables) {
647 if (strlen(new->key) >= strlen(test->key)) {
648 break;
649 }
650 loc = test;
651 }
652
653 if (loc == NULL) {
654 SLIST_INSERT_HEAD(variables, new, variables);
655 } else {
656 SLIST_INSERT_AFTER(loc, new, variables);
657 }
658}
659
660static char *get_resource(char *name) {
661 if (conn == NULL) {
662 return NULL;
663 }
664
665 /* Load the resource database lazily. */
666 if (database == NULL) {
667 database = xcb_xrm_database_from_default(conn);
668
669 if (database == NULL) {
670 ELOG("Failed to open the resource database.\n");
671
672 /* Load an empty database so we don't keep trying to load the
673 * default database over and over again. */
674 database = xcb_xrm_database_from_string("");
675
676 return NULL;
677 }
678 }
679
680 char *resource;
681 xcb_xrm_resource_get_string(database, name, NULL, &resource);
682 return resource;
683}
684
685/*
686 * Releases the memory of all variables in ctx.
687 *
688 */
690 struct Variable *current;
691 while (!SLIST_EMPTY(&(ctx->variables))) {
692 current = SLIST_FIRST(&(ctx->variables));
693 FREE(current->key);
694 FREE(current->value);
695 SLIST_REMOVE_HEAD(&(ctx->variables), variables);
696 FREE(current);
697 }
698}
699
700/*
701 * Parses the given file by first replacing the variables, then calling
702 * parse_config and possibly launching i3-nagbar.
703 *
704 */
705parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) {
706 int fd;
707 struct stat stbuf;
708 char *buf;
709 FILE *fstr;
710 char buffer[4096], key[512], value[4096], *continuation = NULL;
711
712 char *old_dir = getcwd(NULL, 0);
713 char *dir = NULL;
714 /* dirname(3) might modify the buffer, so make a copy: */
715 char *dirbuf = sstrdup(f);
716 if ((dir = dirname(dirbuf)) != NULL) {
717 LOG("Changing working directory to config file directory %s\n", dir);
718 if (chdir(dir) == -1) {
719 ELOG("chdir(%s) failed: %s\n", dir, strerror(errno));
720 return PARSE_FILE_FAILED;
721 }
722 }
723 free(dirbuf);
724
725 if ((fd = open(f, O_RDONLY)) == -1) {
726 return PARSE_FILE_FAILED;
727 }
728
729 if (fstat(fd, &stbuf) == -1) {
730 return PARSE_FILE_FAILED;
731 }
732
733 buf = scalloc(stbuf.st_size + 1, 1);
734
735 if ((fstr = fdopen(fd, "r")) == NULL) {
736 return PARSE_FILE_FAILED;
737 }
738
739 included_file->raw_contents = scalloc(stbuf.st_size + 1, 1);
740 if ((ssize_t)fread(included_file->raw_contents, 1, stbuf.st_size, fstr) != stbuf.st_size) {
741 return PARSE_FILE_FAILED;
742 }
743 rewind(fstr);
744
745 bool invalid_sets = false;
746
747 while (!feof(fstr)) {
748 if (!continuation) {
749 continuation = buffer;
750 }
751 if (fgets(continuation, sizeof(buffer) - (continuation - buffer), fstr) == NULL) {
752 if (feof(fstr)) {
753 break;
754 }
755 return PARSE_FILE_FAILED;
756 }
757 if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) {
758 ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer));
759 }
760
761 /* sscanf implicitly strips whitespace. */
762 value[0] = '\0';
763 const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3);
764 const bool comment = (key[0] == '#');
765 value[4095] = '\n';
766
767 continuation = strstr(buffer, "\\\n");
768 if (continuation) {
769 if (!comment) {
770 continue;
771 }
772 DLOG("line continuation in comment is ignored: \"%.*s\"\n", (int)strlen(buffer) - 1, buffer);
773 continuation = NULL;
774 }
775
776 strcpy(buf + strlen(buf), buffer);
777
778 /* Skip comments and empty lines. */
779 if (skip_line || comment) {
780 continue;
781 }
782
783 if (strcasecmp(key, "set") == 0 && *value != '\0') {
784 char v_key[512];
785 char v_value[4096] = {'\0'};
786
787 if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) {
788 ELOG("Failed to parse variable specification '%s', skipping it.\n", value);
789 invalid_sets = true;
790 continue;
791 }
792
793 if (v_key[0] != '$') {
794 ELOG("Malformed variable assignment, name has to start with $\n");
795 invalid_sets = true;
796 continue;
797 }
798
799 upsert_variable(&(ctx->variables), v_key, v_value);
800 continue;
801 } else if (strcasecmp(key, "set_from_resource") == 0) {
802 char res_name[512] = {'\0'};
803 char v_key[512];
804 char fallback[4096] = {'\0'};
805
806 /* Ensure that this string is terminated. For example, a user might
807 * want a variable to be empty if the resource can't be found and
808 * uses
809 * set_from_resource $foo i3wm.foo
810 * Without explicitly terminating the string first, sscanf() will
811 * leave it uninitialized, causing garbage in the config.*/
812 fallback[0] = '\0';
813
814 if (sscanf(value, "%511s %511s %4095[^\n]", v_key, res_name, fallback) < 1) {
815 ELOG("Failed to parse resource specification '%s', skipping it.\n", value);
816 invalid_sets = true;
817 continue;
818 }
819
820 if (v_key[0] != '$') {
821 ELOG("Malformed variable assignment, name has to start with $\n");
822 invalid_sets = true;
823 continue;
824 }
825
826 char *res_value = get_resource(res_name);
827 if (res_value == NULL) {
828 DLOG("Could not get resource '%s', using fallback '%s'.\n", res_name, fallback);
829 res_value = sstrdup(fallback);
830 }
831
832 upsert_variable(&(ctx->variables), v_key, res_value);
833 FREE(res_value);
834 continue;
835 }
836 }
837 fclose(fstr);
838
839 if (database != NULL) {
840 xcb_xrm_database_free(database);
841 /* Explicitly set the database to NULL again in case the config gets reloaded. */
842 database = NULL;
843 }
844
845 /* For every custom variable, see how often it occurs in the file and
846 * how much extra bytes it requires when replaced. */
847 struct Variable *current, *nearest;
848 int extra_bytes = 0;
849 /* We need to copy the buffer because we need to invalidate the
850 * variables (otherwise we will count them twice, which is bad when
851 * 'extra' is negative) */
852 char *bufcopy = sstrdup(buf);
853 SLIST_FOREACH (current, &(ctx->variables), variables) {
854 int extra = (strlen(current->value) - strlen(current->key));
855 char *next;
856 for (next = bufcopy;
857 next < (bufcopy + stbuf.st_size) &&
858 (next = strcasestr(next, current->key)) != NULL;) {
859 /* We need to invalidate variables completely (otherwise we may count
860 * the same variable more than once, thus causing buffer overflow or
861 * allocation failure) with spaces (variable names cannot contain spaces) */
862 char *end = next + strlen(current->key);
863 while (next < end) {
864 *next++ = ' ';
865 }
866 extra_bytes += extra;
867 }
868 }
869 FREE(bufcopy);
870
871 /* Then, allocate a new buffer and copy the file over to the new one,
872 * but replace occurrences of our variables */
873 char *walk = buf, *destwalk;
874 char *new = scalloc(stbuf.st_size + extra_bytes + 1, 1);
875 destwalk = new;
876 while (walk < (buf + stbuf.st_size)) {
877 /* Find the next variable */
878 SLIST_FOREACH (current, &(ctx->variables), variables) {
879 current->next_match = strcasestr(walk, current->key);
880 }
881 nearest = NULL;
882 int distance = stbuf.st_size;
883 SLIST_FOREACH (current, &(ctx->variables), variables) {
884 if (current->next_match == NULL) {
885 continue;
886 }
887 if ((current->next_match - walk) < distance) {
888 distance = (current->next_match - walk);
889 nearest = current;
890 }
891 }
892 if (nearest == NULL) {
893 /* If there are no more variables, we just copy the rest */
894 strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
895 destwalk += (buf + stbuf.st_size) - walk;
896 *destwalk = '\0';
897 break;
898 } else {
899 /* Copy until the next variable, then copy its value */
900 strncpy(destwalk, walk, distance);
901 strcpy(destwalk + distance, nearest->value);
902 walk += distance + strlen(nearest->key);
903 destwalk += distance + strlen(nearest->value);
904 }
905 }
906
907 included_file->variable_replaced_contents = sstrdup(new);
908
909 struct context *context = scalloc(1, sizeof(struct context));
910 context->filename = f;
911 parse_config(ctx, new, context);
912 if (ctx->has_errors) {
913 context->has_errors = true;
914 }
915
917
918 if (ctx->use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
919 ELOG("FYI: You are using i3 version %s\n", i3_version);
920 start_config_error_nagbar(f, context->has_errors || invalid_sets);
921 }
922
923 const bool has_errors = context->has_errors;
924
926 free(context);
927 free(new);
928 free(buf);
929
930 if (chdir(old_dir) == -1) {
931 ELOG("chdir(%s) failed: %s\n", old_dir, strerror(errno));
932 return PARSE_FILE_FAILED;
933 }
934 free(old_dir);
935 if (has_errors) {
937 }
938 return PARSE_FILE_SUCCESS;
939}
940
941#endif
void check_for_duplicate_bindings(struct context *context)
Checks for duplicate key bindings (the same keycode or keysym is configured more than once).
Definition bindings.c:784
static struct stack stack
static struct CommandResultIR subcommand_output
Config config
Definition config.c:19
static int criteria_next_state
struct tokenptr cmdp_token_ptr
static void next_state(const cmdp_token *token, struct parser_ctx *ctx)
pid_t config_error_nagbar_pid
static const char * start_of_line(const char *walk, const char *beginning)
static char * single_line(const char *start)
static void push_string(struct stack *ctx, const char *identifier, const char *str)
static void clear_stack(struct stack *ctx)
struct token cmdp_token
static char * get_resource(char *name)
static void parse_config(struct parser_ctx *ctx, const char *input, struct context *context)
xcb_xrm_database_t * database
parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file)
Parses the given file by first replacing the variables, then calling parse_config and launching i3-na...
static void upsert_variable(struct variables_head *variables, char *key, char *value)
static long get_long(struct stack *ctx, const char *identifier)
static const char * get_string(struct stack *ctx, const char *identifier)
static void push_long(struct stack *ctx, const char *identifier, long num)
void free_variables(struct parser_ctx *ctx)
Releases the memory of all variables in ctx.
void start_config_error_nagbar(const char *configpath, bool has_errors)
Launch nagbar to indicate errors in the configuration file.
void start_nagbar(pid_t *nagbar_pid, char *argv[])
Starts an i3-nagbar instance with the given parameters.
Definition util.c:360
const char * i3_version
Git commit identifier, from version.c.
Definition version.c:13
static xcb_cursor_context_t * ctx
Definition xcursor.c:19
void errorlog(char *fmt,...)
Definition log.c:325
void debuglog(char *fmt,...)
Definition log.c:348
char * errorfilename
Definition log.c:38
int main(int argc, char *argv[])
Definition main.c:279
xcb_connection_t * conn
XCB connection and root screen.
Definition main.c:54
#define I3_CFG
The beginning of the prototype for every cfg_ function.
parse_file_result_t
@ PARSE_FILE_CONFIG_ERRORS
@ PARSE_FILE_SUCCESS
@ PARSE_FILE_FAILED
#define DLOG(fmt,...)
Definition libi3.h:105
#define LOG(fmt,...)
Definition libi3.h:95
char * sstrdup(const char *str)
Safe-wrapper around strdup which exits if malloc returns NULL (meaning that there is no more memory a...
#define ELOG(fmt,...)
Definition libi3.h:100
void * scalloc(size_t num, size_t size)
Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there is no more memory a...
int sasprintf(char **strp, const char *fmt,...)
Safe-wrapper around asprintf which exits if it returns -1 (meaning that there is no more memory avail...
void * smalloc(size_t size)
Safe-wrapper around malloc which exits if malloc returns NULL (meaning that there is no more memory a...
#define SLIST_FOREACH(var, head, field)
Definition queue.h:114
#define SLIST_INIT(head)
Definition queue.h:127
#define SLIST_INSERT_HEAD(head, elm, field)
Definition queue.h:138
#define SLIST_EMPTY(head)
Definition queue.h:111
#define SLIST_FIRST(head)
Definition queue.h:109
#define SLIST_REMOVE_HEAD(head, field)
Definition queue.h:149
#define SLIST_INSERT_AFTER(slistelm, elm, field)
Definition queue.h:132
#define FREE(pointer)
Definition util.h:47
char * name
char * identifier
union token::@0 extra
cmdp_state next_state
uint16_t call_identifier
cmdp_token * array
An intermediate representation of the result of a parse_config call.
Used during the config file lexing/parsing to keep the state of the lexer in order to provide useful ...
char * line_copy
bool has_warnings
bool has_errors
const char * filename
Holds a user-assigned variable for parsing the configuration file.
char * key
char * next_match
char * value
List entry struct for an included file.
char * variable_replaced_contents
char * raw_contents
i3Font font
char * pattern
The pattern/name used to load the font.
Definition libi3.h:71