i3
config_parser.c
Go to the documentation of this file.
1 #undef I3__FILE__
2 #define I3__FILE__ "config_parser.c"
3 /*
4  * vim:ts=4:sw=4:expandtab
5  *
6  * i3 - an improved dynamic tiling window manager
7  * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
8  *
9  * config_parser.c: hand-written parser to parse configuration directives.
10  *
11  * See also src/commands_parser.c for rationale on why we use a custom parser.
12  *
13  * This parser works VERY MUCH like src/commands_parser.c, so read that first.
14  * The differences are:
15  *
16  * 1. config_parser supports the 'number' token type (in addition to 'word' and
17  * 'string'). Numbers are referred to using &num (like $str).
18  *
19  * 2. Criteria are not executed immediately, they are just stored.
20  *
21  * 3. config_parser recognizes \n and \r as 'end' token, while commands_parser
22  * ignores them.
23  *
24  * 4. config_parser skips the current line on invalid inputs and follows the
25  * nearest <error> token.
26  *
27  */
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <stdbool.h>
33 #include <stdint.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 
39 #include "all.h"
40 
41 // Macros to make the YAJL API a bit easier to use.
42 #define y(x, ...) yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__)
43 #define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str))
44 
45 #ifndef TEST_PARSER
47 static struct context *context;
48 #endif
49 
50 /*******************************************************************************
51  * The data structures used for parsing. Essentially the current state and a
52  * list of tokens for that state.
53  *
54  * The GENERATED_* files are generated by generate-commands-parser.pl with the
55  * input parser-specs/configs.spec.
56  ******************************************************************************/
57 
58 #include "GENERATED_config_enums.h"
59 
60 typedef struct token {
61  char *name;
62  char *identifier;
63  /* This might be __CALL */
65  union {
66  uint16_t call_identifier;
67  } extra;
68 } cmdp_token;
69 
70 typedef struct tokenptr {
72  int n;
74 
76 
77 /*******************************************************************************
78  * The (small) stack where identified literals are stored during the parsing
79  * of a single command (like $workspace).
80  ******************************************************************************/
81 
82 struct stack_entry {
83  /* Just a pointer, not dynamically allocated. */
84  const char *identifier;
85  enum {
86  STACK_STR = 0,
88  } type;
89  union {
90  char *str;
91  long num;
92  } val;
93 };
94 
95 /* 10 entries should be enough for everybody. */
96 static struct stack_entry stack[10];
97 
98 /*
99  * Pushes a string (identified by 'identifier') on the stack. We simply use a
100  * single array, since the number of entries we have to store is very small.
101  *
102  */
103 static void push_string(const char *identifier, const char *str) {
104  for (int c = 0; c < 10; c++) {
105  if (stack[c].identifier != NULL &&
106  strcmp(stack[c].identifier, identifier) != 0)
107  continue;
108  if (stack[c].identifier == NULL) {
109  /* Found a free slot, let’s store it here. */
111  stack[c].val.str = sstrdup(str);
112  stack[c].type = STACK_STR;
113  } else {
114  /* Append the value. */
115  char *prev = stack[c].val.str;
116  sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
117  free(prev);
118  }
119  return;
120  }
121 
122  /* When we arrive here, the stack is full. This should not happen and
123  * means there’s either a bug in this parser or the specification
124  * contains a command with more than 10 identified tokens. */
125  fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
126  "in the code, or a new command which contains more than "
127  "10 identified tokens.\n");
128  exit(1);
129 }
130 
131 static void push_long(const char *identifier, long num) {
132  for (int c = 0; c < 10; c++) {
133  if (stack[c].identifier != NULL)
134  continue;
135  /* Found a free slot, let’s store it here. */
137  stack[c].val.num = num;
138  stack[c].type = STACK_LONG;
139  return;
140  }
141 
142  /* When we arrive here, the stack is full. This should not happen and
143  * means there’s either a bug in this parser or the specification
144  * contains a command with more than 10 identified tokens. */
145  fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
146  "in the code, or a new command which contains more than "
147  "10 identified tokens.\n");
148  exit(1);
149 }
150 
151 static const char *get_string(const char *identifier) {
152  for (int c = 0; c < 10; c++) {
153  if (stack[c].identifier == NULL)
154  break;
155  if (strcmp(identifier, stack[c].identifier) == 0)
156  return stack[c].val.str;
157  }
158  return NULL;
159 }
160 
161 static long get_long(const char *identifier) {
162  for (int c = 0; c < 10; c++) {
163  if (stack[c].identifier == NULL)
164  break;
165  if (strcmp(identifier, stack[c].identifier) == 0)
166  return stack[c].val.num;
167  }
168  return 0;
169 }
170 
171 static void clear_stack(void) {
172  for (int c = 0; c < 10; c++) {
173  if (stack[c].type == STACK_STR && stack[c].val.str != NULL)
174  free(stack[c].val.str);
175  stack[c].identifier = NULL;
176  stack[c].val.str = NULL;
177  stack[c].val.num = 0;
178  }
179 }
180 
181 // TODO: remove this if it turns out we don’t need it for testing.
182 #if 0
183 /*******************************************************************************
184  * A dynamically growing linked list which holds the criteria for the current
185  * command.
186  ******************************************************************************/
187 
188 typedef struct criterion {
189  char *type;
190  char *value;
191 
192  TAILQ_ENTRY(criterion) criteria;
193 } criterion;
194 
195 static TAILQ_HEAD(criteria_head, criterion) criteria =
196  TAILQ_HEAD_INITIALIZER(criteria);
197 
198 /*
199  * Stores the given type/value in the list of criteria.
200  * Accepts a pointer as first argument, since it is 'call'ed by the parser.
201  *
202  */
203 static void push_criterion(void *unused_criteria, const char *type,
204  const char *value) {
205  struct criterion *criterion = malloc(sizeof(struct criterion));
206  criterion->type = strdup(type);
207  criterion->value = strdup(value);
208  TAILQ_INSERT_TAIL(&criteria, criterion, criteria);
209 }
210 
211 /*
212  * Clears the criteria linked list.
213  * Accepts a pointer as first argument, since it is 'call'ed by the parser.
214  *
215  */
216 static void clear_criteria(void *unused_criteria) {
217  struct criterion *criterion;
218  while (!TAILQ_EMPTY(&criteria)) {
219  criterion = TAILQ_FIRST(&criteria);
220  free(criterion->type);
221  free(criterion->value);
222  TAILQ_REMOVE(&criteria, criterion, criteria);
223  free(criterion);
224  }
225 }
226 #endif
227 
228 /*******************************************************************************
229  * The parser itself.
230  ******************************************************************************/
231 
236 
237 /* A list which contains the states that lead to the current state, e.g.
238  * INITIAL, WORKSPACE_LAYOUT.
239  * When jumping back to INITIAL, statelist_idx will simply be set to 1
240  * (likewise for other states, e.g. MODE or BAR).
241  * This list is used to process the nearest error token. */
242 static cmdp_state statelist[10] = {INITIAL};
243 /* NB: statelist_idx points to where the next entry will be inserted */
244 static int statelist_idx = 1;
245 
246 #include "GENERATED_config_call.h"
247 
248 static void next_state(const cmdp_token *token) {
249  cmdp_state _next_state = token->next_state;
250 
251  //printf("token = name %s identifier %s\n", token->name, token->identifier);
252  //printf("next_state = %d\n", token->next_state);
253  if (token->next_state == __CALL) {
256  _next_state = subcommand_output.next_state;
257  clear_stack();
258  }
259 
260  state = _next_state;
261  if (state == INITIAL) {
262  clear_stack();
263  }
264 
265  /* See if we are jumping back to a state in which we were in previously
266  * (statelist contains INITIAL) and just move statelist_idx accordingly. */
267  for (int i = 0; i < statelist_idx; i++) {
268  if (statelist[i] != _next_state)
269  continue;
270  statelist_idx = i + 1;
271  return;
272  }
273 
274  /* Otherwise, the state is new and we add it to the list */
275  statelist[statelist_idx++] = _next_state;
276 }
277 
278 /*
279  * Returns a pointer to the start of the line (one byte after the previous \r,
280  * \n) or the start of the input, if this is the first line.
281  *
282  */
283 static const char *start_of_line(const char *walk, const char *beginning) {
284  while (*walk != '\n' && *walk != '\r' && walk >= beginning) {
285  walk--;
286  }
287 
288  return walk + 1;
289 }
290 
291 /*
292  * Copies the line and terminates it at the next \n, if any.
293  *
294  * The caller has to free() the result.
295  *
296  */
297 static char *single_line(const char *start) {
298  char *result = sstrdup(start);
299  char *end = strchr(result, '\n');
300  if (end != NULL)
301  *end = '\0';
302  return result;
303 }
304 
305 struct ConfigResultIR *parse_config(const char *input, struct context *context) {
306  /* Dump the entire config file into the debug log. We cannot just use
307  * DLOG("%s", input); because one log message must not exceed 4 KiB. */
308  const char *dumpwalk = input;
309  int linecnt = 1;
310  while (*dumpwalk != '\0') {
311  char *next_nl = strchr(dumpwalk, '\n');
312  if (next_nl != NULL) {
313  DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk);
314  dumpwalk = next_nl + 1;
315  } else {
316  DLOG("CONFIG(line %3d): %s\n", linecnt, dumpwalk);
317  break;
318  }
319  linecnt++;
320  }
321  state = INITIAL;
322  statelist_idx = 1;
323 
324  /* A YAJL JSON generator used for formatting replies. */
325  command_output.json_gen = yajl_gen_alloc(NULL);
326 
327  y(array_open);
328 
329  const char *walk = input;
330  const size_t len = strlen(input);
331  int c;
332  const cmdp_token *token;
333  bool token_handled;
334  linecnt = 1;
335 
336 // TODO: make this testable
337 #ifndef TEST_PARSER
338  cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
339 #endif
340 
341  /* The "<=" operator is intentional: We also handle the terminating 0-byte
342  * explicitly by looking for an 'end' token. */
343  while ((size_t)(walk - input) <= len) {
344  /* Skip whitespace before every token, newlines are relevant since they
345  * separate configuration directives. */
346  while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
347  walk++;
348 
349  //printf("remaining input: %s\n", walk);
350 
351  cmdp_token_ptr *ptr = &(tokens[state]);
352  token_handled = false;
353  for (c = 0; c < ptr->n; c++) {
354  token = &(ptr->array[c]);
355 
356  /* A literal. */
357  if (token->name[0] == '\'') {
358  if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
359  if (token->identifier != NULL)
360  push_string(token->identifier, token->name + 1);
361  walk += strlen(token->name) - 1;
362  next_state(token);
363  token_handled = true;
364  break;
365  }
366  continue;
367  }
368 
369  if (strcmp(token->name, "number") == 0) {
370  /* Handle numbers. We only accept decimal numbers for now. */
371  char *end = NULL;
372  errno = 0;
373  long int num = strtol(walk, &end, 10);
374  if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
375  (errno != 0 && num == 0))
376  continue;
377 
378  /* No valid numbers found */
379  if (end == walk)
380  continue;
381 
382  if (token->identifier != NULL)
383  push_long(token->identifier, num);
384 
385  /* Set walk to the first non-number character */
386  walk = end;
387  next_state(token);
388  token_handled = true;
389  break;
390  }
391 
392  if (strcmp(token->name, "string") == 0 ||
393  strcmp(token->name, "word") == 0) {
394  const char *beginning = walk;
395  /* Handle quoted strings (or words). */
396  if (*walk == '"') {
397  beginning++;
398  walk++;
399  while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\'))
400  walk++;
401  } else {
402  if (token->name[0] == 's') {
403  while (*walk != '\0' && *walk != '\r' && *walk != '\n')
404  walk++;
405  } else {
406  /* For a word, the delimiters are white space (' ' or
407  * '\t'), closing square bracket (]), comma (,) and
408  * semicolon (;). */
409  while (*walk != ' ' && *walk != '\t' &&
410  *walk != ']' && *walk != ',' &&
411  *walk != ';' && *walk != '\r' &&
412  *walk != '\n' && *walk != '\0')
413  walk++;
414  }
415  }
416  if (walk != beginning) {
417  char *str = scalloc(walk - beginning + 1);
418  /* We copy manually to handle escaping of characters. */
419  int inpos, outpos;
420  for (inpos = 0, outpos = 0;
421  inpos < (walk - beginning);
422  inpos++, outpos++) {
423  /* We only handle escaped double quotes to not break
424  * backwards compatibility with people using \w in
425  * regular expressions etc. */
426  if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"')
427  inpos++;
428  str[outpos] = beginning[inpos];
429  }
430  if (token->identifier)
431  push_string(token->identifier, str);
432  free(str);
433  /* If we are at the end of a quoted string, skip the ending
434  * double quote. */
435  if (*walk == '"')
436  walk++;
437  next_state(token);
438  token_handled = true;
439  break;
440  }
441  }
442 
443  if (strcmp(token->name, "line") == 0) {
444  while (*walk != '\0' && *walk != '\n' && *walk != '\r')
445  walk++;
446  next_state(token);
447  token_handled = true;
448  linecnt++;
449  walk++;
450  break;
451  }
452 
453  if (strcmp(token->name, "end") == 0) {
454  //printf("checking for end: *%s*\n", walk);
455  if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
456  next_state(token);
457  token_handled = true;
458 /* To make sure we start with an appropriate matching
459  * datastructure for commands which do *not* specify any
460  * criteria, we re-initialize the criteria system after
461  * every command. */
462 // TODO: make this testable
463 #ifndef TEST_PARSER
464  cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
465 #endif
466  linecnt++;
467  walk++;
468  break;
469  }
470  }
471  }
472 
473  if (!token_handled) {
474  /* Figure out how much memory we will need to fill in the names of
475  * all tokens afterwards. */
476  int tokenlen = 0;
477  for (c = 0; c < ptr->n; c++)
478  tokenlen += strlen(ptr->array[c].name) + strlen("'', ");
479 
480  /* Build up a decent error message. We include the problem, the
481  * full input, and underline the position where the parser
482  * currently is. */
483  char *errormessage;
484  char *possible_tokens = smalloc(tokenlen + 1);
485  char *tokenwalk = possible_tokens;
486  for (c = 0; c < ptr->n; c++) {
487  token = &(ptr->array[c]);
488  if (token->name[0] == '\'') {
489  /* A literal is copied to the error message enclosed with
490  * single quotes. */
491  *tokenwalk++ = '\'';
492  strcpy(tokenwalk, token->name + 1);
493  tokenwalk += strlen(token->name + 1);
494  *tokenwalk++ = '\'';
495  } else {
496  /* Skip error tokens in error messages, they are used
497  * internally only and might confuse users. */
498  if (strcmp(token->name, "error") == 0)
499  continue;
500  /* Any other token is copied to the error message enclosed
501  * with angle brackets. */
502  *tokenwalk++ = '<';
503  strcpy(tokenwalk, token->name);
504  tokenwalk += strlen(token->name);
505  *tokenwalk++ = '>';
506  }
507  if (c < (ptr->n - 1)) {
508  *tokenwalk++ = ',';
509  *tokenwalk++ = ' ';
510  }
511  }
512  *tokenwalk = '\0';
513  sasprintf(&errormessage, "Expected one of these tokens: %s",
514  possible_tokens);
515  free(possible_tokens);
516 
517  /* Go back to the beginning of the line */
518  const char *error_line = start_of_line(walk, input);
519 
520  /* Contains the same amount of characters as 'input' has, but with
521  * the unparseable part highlighted using ^ characters. */
522  char *position = scalloc(strlen(error_line) + 1);
523  const char *copywalk;
524  for (copywalk = error_line;
525  *copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0';
526  copywalk++)
527  position[(copywalk - error_line)] = (copywalk >= walk ? '^' : (*copywalk == '\t' ? '\t' : ' '));
528  position[(copywalk - error_line)] = '\0';
529 
530  ELOG("CONFIG: %s\n", errormessage);
531  ELOG("CONFIG: (in file %s)\n", context->filename);
532  char *error_copy = single_line(error_line);
533 
534  /* Print context lines *before* the error, if any. */
535  if (linecnt > 1) {
536  const char *context_p1_start = start_of_line(error_line - 2, input);
537  char *context_p1_line = single_line(context_p1_start);
538  if (linecnt > 2) {
539  const char *context_p2_start = start_of_line(context_p1_start - 2, input);
540  char *context_p2_line = single_line(context_p2_start);
541  ELOG("CONFIG: Line %3d: %s\n", linecnt - 2, context_p2_line);
542  free(context_p2_line);
543  }
544  ELOG("CONFIG: Line %3d: %s\n", linecnt - 1, context_p1_line);
545  free(context_p1_line);
546  }
547  ELOG("CONFIG: Line %3d: %s\n", linecnt, error_copy);
548  ELOG("CONFIG: %s\n", position);
549  free(error_copy);
550  /* Print context lines *after* the error, if any. */
551  for (int i = 0; i < 2; i++) {
552  char *error_line_end = strchr(error_line, '\n');
553  if (error_line_end != NULL && *(error_line_end + 1) != '\0') {
554  error_line = error_line_end + 1;
555  error_copy = single_line(error_line);
556  ELOG("CONFIG: Line %3d: %s\n", linecnt + i + 1, error_copy);
557  free(error_copy);
558  }
559  }
560 
561  context->has_errors = true;
562 
563  /* Format this error message as a JSON reply. */
564  y(map_open);
565  ystr("success");
566  y(bool, false);
567  /* We set parse_error to true to distinguish this from other
568  * errors. i3-nagbar is spawned upon keypresses only for parser
569  * errors. */
570  ystr("parse_error");
571  y(bool, true);
572  ystr("error");
573  ystr(errormessage);
574  ystr("input");
575  ystr(input);
576  ystr("errorposition");
577  ystr(position);
578  y(map_close);
579 
580  /* Skip the rest of this line, but continue parsing. */
581  while ((size_t)(walk - input) <= len && *walk != '\n')
582  walk++;
583 
584  free(position);
585  free(errormessage);
586  clear_stack();
587 
588  /* To figure out in which state to go (e.g. MODE or INITIAL),
589  * we find the nearest state which contains an <error> token
590  * and follow that one. */
591  bool error_token_found = false;
592  for (int i = statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
593  cmdp_token_ptr *errptr = &(tokens[statelist[i]]);
594  for (int j = 0; j < errptr->n; j++) {
595  if (strcmp(errptr->array[j].name, "error") != 0)
596  continue;
597  next_state(&(errptr->array[j]));
598  error_token_found = true;
599  break;
600  }
601  }
602 
603  assert(error_token_found);
604  }
605  }
606 
607  y(array_close);
608 
609  return &command_output;
610 }
611 
612 /*******************************************************************************
613  * Code for building the stand-alone binary test.commands_parser which is used
614  * by t/187-commands-parser.t.
615  ******************************************************************************/
616 
617 #ifdef TEST_PARSER
618 
619 /*
620  * Logs the given message to stdout while prefixing the current time to it,
621  * but only if debug logging was activated.
622  * This is to be called by DLOG() which includes filename/linenumber
623  *
624  */
625 void debuglog(char *fmt, ...) {
626  va_list args;
627 
628  va_start(args, fmt);
629  fprintf(stdout, "# ");
630  vfprintf(stdout, fmt, args);
631  va_end(args);
632 }
633 
634 void errorlog(char *fmt, ...) {
635  va_list args;
636 
637  va_start(args, fmt);
638  vfprintf(stderr, fmt, args);
639  va_end(args);
640 }
641 
642 static int criteria_next_state;
643 
644 void cfg_criteria_init(I3_CFG, int _state) {
645  criteria_next_state = _state;
646 }
647 
648 void cfg_criteria_add(I3_CFG, const char *ctype, const char *cvalue) {
649 }
650 
651 void cfg_criteria_pop_state(I3_CFG) {
652  result->next_state = criteria_next_state;
653 }
654 
655 int main(int argc, char *argv[]) {
656  if (argc < 2) {
657  fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
658  return 1;
659  }
660  struct context context;
661  context.filename = "<stdin>";
662  parse_config(argv[1], &context);
663 }
664 
665 #else
666 
667 /*
668  * Goes through each line of buf (separated by \n) and checks for statements /
669  * commands which only occur in i3 v4 configuration files. If it finds any, it
670  * returns version 4, otherwise it returns version 3.
671  *
672  */
673 static int detect_version(char *buf) {
674  char *walk = buf;
675  char *line = buf;
676  while (*walk != '\0') {
677  if (*walk != '\n') {
678  walk++;
679  continue;
680  }
681 
682  /* check for some v4-only statements */
683  if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
684  strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
685  strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
686  strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
687  LOG("deciding for version 4 due to this line: %.*s\n", (int)(walk - line), line);
688  return 4;
689  }
690 
691  /* if this is a bind statement, we can check the command */
692  if (strncasecmp(line, "bind", strlen("bind")) == 0) {
693  char *bind = strchr(line, ' ');
694  if (bind == NULL)
695  goto next;
696  while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
697  bind++;
698  if (*bind == '\0')
699  goto next;
700  if ((bind = strchr(bind, ' ')) == NULL)
701  goto next;
702  while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
703  bind++;
704  if (*bind == '\0')
705  goto next;
706  if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
707  strncasecmp(bind, "floating", strlen("floating")) == 0 ||
708  strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
709  strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
710  strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
711  strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
712  strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
713  strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
714  strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
715  strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
716  strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
717  strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
718  strncasecmp(bind, "bar", strlen("bar")) == 0) {
719  LOG("deciding for version 4 due to this line: %.*s\n", (int)(walk - line), line);
720  return 4;
721  }
722  }
723 
724  next:
725  /* advance to the next line */
726  walk++;
727  line = walk;
728  }
729 
730  return 3;
731 }
732 
733 /*
734  * Calls i3-migrate-config-to-v4 to migrate a configuration file (input
735  * buffer).
736  *
737  * Returns the converted config file or NULL if there was an error (for
738  * example the script could not be found in $PATH or the i3 executable’s
739  * directory).
740  *
741  */
742 static char *migrate_config(char *input, off_t size) {
743  int writepipe[2];
744  int readpipe[2];
745 
746  if (pipe(writepipe) != 0 ||
747  pipe(readpipe) != 0) {
748  warn("migrate_config: Could not create pipes");
749  return NULL;
750  }
751 
752  pid_t pid = fork();
753  if (pid == -1) {
754  warn("Could not fork()");
755  return NULL;
756  }
757 
758  /* child */
759  if (pid == 0) {
760  /* close writing end of writepipe, connect reading side to stdin */
761  close(writepipe[1]);
762  dup2(writepipe[0], 0);
763 
764  /* close reading end of readpipe, connect writing side to stdout */
765  close(readpipe[0]);
766  dup2(readpipe[1], 1);
767 
768  static char *argv[] = {
769  NULL, /* will be replaced by the executable path */
770  NULL};
771  exec_i3_utility("i3-migrate-config-to-v4", argv);
772  }
773 
774  /* parent */
775 
776  /* close reading end of the writepipe (connected to the script’s stdin) */
777  close(writepipe[0]);
778 
779  /* write the whole config file to the pipe, the script will read everything
780  * immediately */
781  int written = 0;
782  int ret;
783  while (written < size) {
784  if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
785  warn("Could not write to pipe");
786  return NULL;
787  }
788  written += ret;
789  }
790  close(writepipe[1]);
791 
792  /* close writing end of the readpipe (connected to the script’s stdout) */
793  close(readpipe[1]);
794 
795  /* read the script’s output */
796  int conv_size = 65535;
797  char *converted = malloc(conv_size);
798  int read_bytes = 0;
799  do {
800  if (read_bytes == conv_size) {
801  conv_size += 65535;
802  converted = realloc(converted, conv_size);
803  }
804  ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
805  if (ret == -1) {
806  warn("Cannot read from pipe");
807  FREE(converted);
808  return NULL;
809  }
810  read_bytes += ret;
811  } while (ret > 0);
812 
813  /* get the returncode */
814  int status;
815  wait(&status);
816  if (!WIFEXITED(status)) {
817  fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
818  return NULL;
819  }
820 
821  int returncode = WEXITSTATUS(status);
822  if (returncode != 0) {
823  fprintf(stderr, "Migration process exit code was != 0\n");
824  if (returncode == 2) {
825  fprintf(stderr, "could not start the migration script\n");
826  /* TODO: script was not found. tell the user to fix his system or create a v4 config */
827  } else if (returncode == 1) {
828  fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
829  fprintf(stderr, "# i3 config file (v4)\n");
830  /* TODO: nag the user with a message to include a hint for i3 in his config file */
831  }
832  return NULL;
833  }
834 
835  return converted;
836 }
837 
838 /*
839  * Parses the given file by first replacing the variables, then calling
840  * parse_config and possibly launching i3-nagbar.
841  *
842  */
843 void parse_file(const char *f) {
844  SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
845  int fd, ret, read_bytes = 0;
846  struct stat stbuf;
847  char *buf;
848  FILE *fstr;
849  char buffer[1026], key[512], value[512];
850 
851  if ((fd = open(f, O_RDONLY)) == -1)
852  die("Could not open configuration file: %s\n", strerror(errno));
853 
854  if (fstat(fd, &stbuf) == -1)
855  die("Could not fstat file: %s\n", strerror(errno));
856 
857  buf = scalloc((stbuf.st_size + 1) * sizeof(char));
858  while (read_bytes < stbuf.st_size) {
859  if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
860  die("Could not read(): %s\n", strerror(errno));
861  read_bytes += ret;
862  }
863 
864  if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
865  die("Could not lseek: %s\n", strerror(errno));
866 
867  if ((fstr = fdopen(fd, "r")) == NULL)
868  die("Could not fdopen: %s\n", strerror(errno));
869 
870  while (!feof(fstr)) {
871  if (fgets(buffer, 1024, fstr) == NULL) {
872  if (feof(fstr))
873  break;
874  die("Could not read configuration file\n");
875  }
876 
877  /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
878  if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
879  key[0] == '#' || strlen(key) < 3)
880  continue;
881 
882  if (strcasecmp(key, "set") == 0) {
883  if (value[0] != '$') {
884  ELOG("Malformed variable assignment, name has to start with $\n");
885  continue;
886  }
887 
888  /* get key/value for this variable */
889  char *v_key = value, *v_value;
890  if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) {
891  ELOG("Malformed variable assignment, need a value\n");
892  continue;
893  }
894 
895  if (!(v_value = strstr(value, " ")))
896  v_value = strstr(value, "\t");
897 
898  *(v_value++) = '\0';
899  while (*v_value == '\t' || *v_value == ' ')
900  v_value++;
901 
902  struct Variable *new = scalloc(sizeof(struct Variable));
903  new->key = sstrdup(v_key);
904  new->value = sstrdup(v_value);
905  SLIST_INSERT_HEAD(&variables, new, variables);
906  DLOG("Got new variable %s = %s\n", v_key, v_value);
907  continue;
908  }
909  }
910  fclose(fstr);
911 
912  /* For every custom variable, see how often it occurs in the file and
913  * how much extra bytes it requires when replaced. */
914  struct Variable *current, *nearest;
915  int extra_bytes = 0;
916  /* We need to copy the buffer because we need to invalidate the
917  * variables (otherwise we will count them twice, which is bad when
918  * 'extra' is negative) */
919  char *bufcopy = sstrdup(buf);
920  SLIST_FOREACH (current, &variables, variables) {
921  int extra = (strlen(current->value) - strlen(current->key));
922  char *next;
923  for (next = bufcopy;
924  next < (bufcopy + stbuf.st_size) &&
925  (next = strcasestr(next, current->key)) != NULL;
926  next += strlen(current->key)) {
927  *next = '_';
928  extra_bytes += extra;
929  }
930  }
931  FREE(bufcopy);
932 
933  /* Then, allocate a new buffer and copy the file over to the new one,
934  * but replace occurences of our variables */
935  char *walk = buf, *destwalk;
936  char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
937  destwalk = new;
938  while (walk < (buf + stbuf.st_size)) {
939  /* Find the next variable */
940  SLIST_FOREACH (current, &variables, variables)
941  current->next_match = strcasestr(walk, current->key);
942  nearest = NULL;
943  int distance = stbuf.st_size;
944  SLIST_FOREACH (current, &variables, variables) {
945  if (current->next_match == NULL)
946  continue;
947  if ((current->next_match - walk) < distance) {
948  distance = (current->next_match - walk);
949  nearest = current;
950  }
951  }
952  if (nearest == NULL) {
953  /* If there are no more variables, we just copy the rest */
954  strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
955  destwalk += (buf + stbuf.st_size) - walk;
956  *destwalk = '\0';
957  break;
958  } else {
959  /* Copy until the next variable, then copy its value */
960  strncpy(destwalk, walk, distance);
961  strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
962  walk += distance + strlen(nearest->key);
963  destwalk += distance + strlen(nearest->value);
964  }
965  }
966 
967  /* analyze the string to find out whether this is an old config file (3.x)
968  * or a new config file (4.x). If it’s old, we run the converter script. */
969  int version = detect_version(buf);
970  if (version == 3) {
971  /* We need to convert this v3 configuration */
972  char *converted = migrate_config(new, stbuf.st_size);
973  if (converted != NULL) {
974  ELOG("\n");
975  ELOG("****************************************************************\n");
976  ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n");
977  ELOG("\n");
978  ELOG("Please convert your config file to v4. You can use this command:\n");
979  ELOG(" mv %s %s.O\n", f, f);
980  ELOG(" i3-migrate-config-to-v4 %s.O > %s\n", f, f);
981  ELOG("****************************************************************\n");
982  ELOG("\n");
983  free(new);
984  new = converted;
985  } else {
986  LOG("\n");
987  LOG("**********************************************************************\n");
988  LOG("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
989  LOG("was not correctly installed on your system?\n");
990  LOG("**********************************************************************\n");
991  LOG("\n");
992  }
993  }
994 
995  context = scalloc(sizeof(struct context));
996  context->filename = f;
997 
998  struct ConfigResultIR *config_output = parse_config(new, context);
999  yajl_gen_free(config_output->json_gen);
1000 
1002 
1003  if (context->has_errors || context->has_warnings) {
1004  ELOG("FYI: You are using i3 version " I3_VERSION "\n");
1005  if (version == 3)
1006  ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
1007 
1008  char *editaction,
1009  *pageraction;
1010  sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f);
1011  sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
1012  char *argv[] = {
1013  NULL, /* will be replaced by the executable path */
1014  "-f",
1015  (config.font.pattern ? config.font.pattern : "fixed"),
1016  "-t",
1017  (context->has_errors ? "error" : "warning"),
1018  "-m",
1019  (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."),
1020  "-b",
1021  "edit config",
1022  editaction,
1023  (errorfilename ? "-b" : NULL),
1024  (context->has_errors ? "show errors" : "show warnings"),
1025  pageraction,
1026  NULL};
1027 
1029  free(editaction);
1030  free(pageraction);
1031  }
1032 
1033  FREE(context->line_copy);
1034  free(context);
1035  free(new);
1036  free(buf);
1037 
1038  while (!SLIST_EMPTY(&variables)) {
1039  current = SLIST_FIRST(&variables);
1040  FREE(current->key);
1041  FREE(current->value);
1042  SLIST_REMOVE_HEAD(&variables, variables);
1043  FREE(current);
1044  }
1045 }
1046 
1047 #endif
static char * migrate_config(char *input, off_t size)
static cmdp_state state
char * sstrdup(const char *str)
Safe-wrapper around strdup which exits if malloc returns NULL (meaning that there is no more memory a...
#define SLIST_FOREACH(var, head, field)
Definition: queue.h:114
static void GENERATED_call(const int call_identifier, struct CommandResultIR *result)
Holds a user-assigned variable for parsing the configuration file.
Definition: config.h:64
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:335
#define TAILQ_REMOVE(head, elm, field)
Definition: queue.h:386
void exec_i3_utility(char *name, char *argv[])
exec()s an i3 utility, for example the config file migration script or i3-nagbar. ...
Definition: util.c:116
Config config
Definition: config.c:19
static const char * start_of_line(const char *walk, const char *beginning)
i3Font font
Definition: config.h:92
static struct context * context
Definition: config_parser.c:47
#define LOG(fmt,...)
Definition: libi3.h:76
static void clear_stack(void)
static struct ConfigResultIR subcommand_output
static void push_string(const char *identifier, const char *str)
struct tokenptr cmdp_token_ptr
#define SLIST_INSERT_HEAD(head, elm, field)
Definition: queue.h:136
static char * single_line(const char *start)
#define TAILQ_FIRST(head)
Definition: queue.h:323
union token::@0 extra
char * value
Definition: config.h:66
uint16_t call_identifier
const char * filename
Definition: config.h:38
static int detect_version(char *buf)
static struct stack_entry stack[10]
Definition: config_parser.c:96
#define DLOG(fmt,...)
Definition: libi3.h:86
char * line_copy
Definition: config.h:37
static Match current_match
pid_t config_error_nagbar_pid
Definition: config_parser.c:46
static int criteria_next_state
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...
#define SLIST_REMOVE_HEAD(head, field)
Definition: queue.h:145
void errorlog(char *fmt,...)
Definition: log.c:295
char * identifier
#define SLIST_EMPTY(head)
Definition: queue.h:111
#define die(...)
Definition: util.h:17
char * key
Definition: config.h:65
static const char * get_string(const char *identifier)
char * errorfilename
Definition: log.c:38
bool has_errors
Definition: config.h:33
void start_nagbar(pid_t *nagbar_pid, char *argv[])
Starts an i3-nagbar instance with the given parameters.
Definition: util.c:420
Used during the config file lexing/parsing to keep the state of the lexer in order to provide useful ...
Definition: config.h:32
char * pattern
The pattern/name used to load the font.
Definition: libi3.h:50
#define TAILQ_EMPTY(head)
Definition: queue.h:331
#define TAILQ_ENTRY(type)
Definition: queue.h:314
#define ELOG(fmt,...)
Definition: libi3.h:81
A &quot;match&quot; is a data structure which acts like a mask or expression to match certain windows or not...
Definition: data.h:390
struct ConfigResultIR * parse_config(const char *input, struct context *context)
void debuglog(char *fmt,...)
Definition: log.c:315
yajl_gen json_gen
Definition: config_parser.h:24
cmdp_token * array
#define SLIST_HEAD_INITIALIZER(head)
Definition: queue.h:98
static cmdp_token_ptr tokens[51]
void * scalloc(size_t size)
Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there is no more memory a...
void parse_file(const char *f)
Parses the given file by first replacing the variables, then calling parse_config and possibly launch...
const char * identifier
struct token cmdp_token
static void next_state(const cmdp_token *token)
static int statelist_idx
#define y(x,...)
Definition: commands.c:19
static long get_long(const char *identifier)
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition: queue.h:362
static struct ConfigResultIR command_output
static void push_long(const char *identifier, long num)
#define TAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:311
#define ystr(str)
Definition: config_parser.c:43
#define SLIST_HEAD(name, type)
Definition: queue.h:93
int main(int argc, char *argv[])
Definition: main.c:268
char * next_match
Definition: config.h:67
void * smalloc(size_t size)
Safe-wrapper around malloc which exits if malloc returns NULL (meaning that there is no more memory a...
char * name
static cmdp_state statelist[10]
#define I3_CFG
The beginning of the prototype for every cfg_ function.
union stack_entry::@3 val
bool has_warnings
Definition: config.h:34
#define FREE(pointer)
Definition: util.h:46
cmdp_state next_state
enum stack_entry::@2 type
#define TAILQ_HEAD(name, type)
Definition: queue.h:305
#define SLIST_FIRST(head)
Definition: queue.h:109