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