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, "line") == 0) {
450  while (*walk != '\0' && *walk != '\n' && *walk != '\r')
451  walk++;
452  next_state(token);
453  token_handled = true;
454  linecnt++;
455  walk++;
456  break;
457  }
458 
459  if (strcmp(token->name, "end") == 0) {
460  //printf("checking for end: *%s*\n", walk);
461  if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
462  next_state(token);
463  token_handled = true;
464  /* To make sure we start with an appropriate matching
465  * datastructure for commands which do *not* specify any
466  * criteria, we re-initialize the criteria system after
467  * every command. */
468  // TODO: make this testable
469 #ifndef TEST_PARSER
470  cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
471 #endif
472  linecnt++;
473  walk++;
474  break;
475  }
476  }
477  }
478 
479  if (!token_handled) {
480  /* Figure out how much memory we will need to fill in the names of
481  * all tokens afterwards. */
482  int tokenlen = 0;
483  for (c = 0; c < ptr->n; c++)
484  tokenlen += strlen(ptr->array[c].name) + strlen("'', ");
485 
486  /* Build up a decent error message. We include the problem, the
487  * full input, and underline the position where the parser
488  * currently is. */
489  char *errormessage;
490  char *possible_tokens = smalloc(tokenlen + 1);
491  char *tokenwalk = possible_tokens;
492  for (c = 0; c < ptr->n; c++) {
493  token = &(ptr->array[c]);
494  if (token->name[0] == '\'') {
495  /* A literal is copied to the error message enclosed with
496  * single quotes. */
497  *tokenwalk++ = '\'';
498  strcpy(tokenwalk, token->name + 1);
499  tokenwalk += strlen(token->name + 1);
500  *tokenwalk++ = '\'';
501  } else {
502  /* Skip error tokens in error messages, they are used
503  * internally only and might confuse users. */
504  if (strcmp(token->name, "error") == 0)
505  continue;
506  /* Any other token is copied to the error message enclosed
507  * with angle brackets. */
508  *tokenwalk++ = '<';
509  strcpy(tokenwalk, token->name);
510  tokenwalk += strlen(token->name);
511  *tokenwalk++ = '>';
512  }
513  if (c < (ptr->n - 1)) {
514  *tokenwalk++ = ',';
515  *tokenwalk++ = ' ';
516  }
517  }
518  *tokenwalk = '\0';
519  sasprintf(&errormessage, "Expected one of these tokens: %s",
520  possible_tokens);
521  free(possible_tokens);
522 
523 
524  /* Go back to the beginning of the line */
525  const char *error_line = start_of_line(walk, input);
526 
527  /* Contains the same amount of characters as 'input' has, but with
528  * the unparseable part highlighted using ^ characters. */
529  char *position = scalloc(strlen(error_line) + 1);
530  const char *copywalk;
531  for (copywalk = error_line;
532  *copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0';
533  copywalk++)
534  position[(copywalk - error_line)] = (copywalk >= walk ? '^' : (*copywalk == '\t' ? '\t' : ' '));
535  position[(copywalk - error_line)] = '\0';
536 
537  ELOG("CONFIG: %s\n", errormessage);
538  ELOG("CONFIG: (in file %s)\n", context->filename);
539  char *error_copy = single_line(error_line);
540 
541  /* Print context lines *before* the error, if any. */
542  if (linecnt > 1) {
543  const char *context_p1_start = start_of_line(error_line-2, input);
544  char *context_p1_line = single_line(context_p1_start);
545  if (linecnt > 2) {
546  const char *context_p2_start = start_of_line(context_p1_start-2, input);
547  char *context_p2_line = single_line(context_p2_start);
548  ELOG("CONFIG: Line %3d: %s\n", linecnt - 2, context_p2_line);
549  free(context_p2_line);
550  }
551  ELOG("CONFIG: Line %3d: %s\n", linecnt - 1, context_p1_line);
552  free(context_p1_line);
553  }
554  ELOG("CONFIG: Line %3d: %s\n", linecnt, error_copy);
555  ELOG("CONFIG: %s\n", position);
556  free(error_copy);
557  /* Print context lines *after* the error, if any. */
558  for (int i = 0; i < 2; i++) {
559  char *error_line_end = strchr(error_line, '\n');
560  if (error_line_end != NULL && *(error_line_end + 1) != '\0') {
561  error_line = error_line_end + 1;
562  error_copy = single_line(error_line);
563  ELOG("CONFIG: Line %3d: %s\n", linecnt + i + 1, error_copy);
564  free(error_copy);
565  }
566  }
567 
568  context->has_errors = true;
569 
570  /* Format this error message as a JSON reply. */
571  y(map_open);
572  ystr("success");
573  y(bool, false);
574  /* We set parse_error to true to distinguish this from other
575  * errors. i3-nagbar is spawned upon keypresses only for parser
576  * errors. */
577  ystr("parse_error");
578  y(bool, true);
579  ystr("error");
580  ystr(errormessage);
581  ystr("input");
582  ystr(input);
583  ystr("errorposition");
584  ystr(position);
585  y(map_close);
586 
587  /* Skip the rest of this line, but continue parsing. */
588  while ((walk - input) <= len && *walk != '\n')
589  walk++;
590 
591  free(position);
592  free(errormessage);
593  clear_stack();
594 
595  /* To figure out in which state to go (e.g. MODE or INITIAL),
596  * we find the nearest state which contains an <error> token
597  * and follow that one. */
598  bool error_token_found = false;
599  for (int i = statelist_idx-1; (i >= 0) && !error_token_found; i--) {
600  cmdp_token_ptr *errptr = &(tokens[statelist[i]]);
601  for (int j = 0; j < errptr->n; j++) {
602  if (strcmp(errptr->array[j].name, "error") != 0)
603  continue;
604  next_state(&(errptr->array[j]));
605  error_token_found = true;
606  break;
607  }
608  }
609 
610  assert(error_token_found);
611  }
612  }
613 
614  y(array_close);
615 
616  return &command_output;
617 }
618 
619 /*******************************************************************************
620  * Code for building the stand-alone binary test.commands_parser which is used
621  * by t/187-commands-parser.t.
622  ******************************************************************************/
623 
624 #ifdef TEST_PARSER
625 
626 /*
627  * Logs the given message to stdout while prefixing the current time to it,
628  * but only if debug logging was activated.
629  * This is to be called by DLOG() which includes filename/linenumber
630  *
631  */
632 void debuglog(char *fmt, ...) {
633  va_list args;
634 
635  va_start(args, fmt);
636  fprintf(stdout, "# ");
637  vfprintf(stdout, fmt, args);
638  va_end(args);
639 }
640 
641 void errorlog(char *fmt, ...) {
642  va_list args;
643 
644  va_start(args, fmt);
645  vfprintf(stderr, fmt, args);
646  va_end(args);
647 }
648 
649 static int criteria_next_state;
650 
651 void cfg_criteria_init(I3_CFG, int _state) {
652  criteria_next_state = _state;
653 }
654 
655 void cfg_criteria_add(I3_CFG, const char *ctype, const char *cvalue) {
656 }
657 
658 void cfg_criteria_pop_state(I3_CFG) {
659  result->next_state = criteria_next_state;
660 }
661 
662 int main(int argc, char *argv[]) {
663  if (argc < 2) {
664  fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
665  return 1;
666  }
667  struct context context;
668  context.filename = "<stdin>";
669  parse_config(argv[1], &context);
670 }
671 
672 #else
673 
674 /*
675  * Goes through each line of buf (separated by \n) and checks for statements /
676  * commands which only occur in i3 v4 configuration files. If it finds any, it
677  * returns version 4, otherwise it returns version 3.
678  *
679  */
680 static int detect_version(char *buf) {
681  char *walk = buf;
682  char *line = buf;
683  while (*walk != '\0') {
684  if (*walk != '\n') {
685  walk++;
686  continue;
687  }
688 
689  /* check for some v4-only statements */
690  if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
691  strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
692  strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
693  strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
694  printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
695  return 4;
696  }
697 
698  /* if this is a bind statement, we can check the command */
699  if (strncasecmp(line, "bind", strlen("bind")) == 0) {
700  char *bind = strchr(line, ' ');
701  if (bind == NULL)
702  goto next;
703  while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
704  bind++;
705  if (*bind == '\0')
706  goto next;
707  if ((bind = strchr(bind, ' ')) == NULL)
708  goto next;
709  while ((*bind == ' ' || *bind == '\t') && *bind != '\0')
710  bind++;
711  if (*bind == '\0')
712  goto next;
713  if (strncasecmp(bind, "layout", strlen("layout")) == 0 ||
714  strncasecmp(bind, "floating", strlen("floating")) == 0 ||
715  strncasecmp(bind, "workspace", strlen("workspace")) == 0 ||
716  strncasecmp(bind, "focus left", strlen("focus left")) == 0 ||
717  strncasecmp(bind, "focus right", strlen("focus right")) == 0 ||
718  strncasecmp(bind, "focus up", strlen("focus up")) == 0 ||
719  strncasecmp(bind, "focus down", strlen("focus down")) == 0 ||
720  strncasecmp(bind, "border normal", strlen("border normal")) == 0 ||
721  strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 ||
722  strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 ||
723  strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 ||
724  strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 ||
725  strncasecmp(bind, "bar", strlen("bar")) == 0) {
726  printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
727  return 4;
728  }
729  }
730 
731 next:
732  /* advance to the next line */
733  walk++;
734  line = walk;
735  }
736 
737  return 3;
738 }
739 
740 /*
741  * Calls i3-migrate-config-to-v4 to migrate a configuration file (input
742  * buffer).
743  *
744  * Returns the converted config file or NULL if there was an error (for
745  * example the script could not be found in $PATH or the i3 executable’s
746  * directory).
747  *
748  */
749 static char *migrate_config(char *input, off_t size) {
750  int writepipe[2];
751  int readpipe[2];
752 
753  if (pipe(writepipe) != 0 ||
754  pipe(readpipe) != 0) {
755  warn("migrate_config: Could not create pipes");
756  return NULL;
757  }
758 
759  pid_t pid = fork();
760  if (pid == -1) {
761  warn("Could not fork()");
762  return NULL;
763  }
764 
765  /* child */
766  if (pid == 0) {
767  /* close writing end of writepipe, connect reading side to stdin */
768  close(writepipe[1]);
769  dup2(writepipe[0], 0);
770 
771  /* close reading end of readpipe, connect writing side to stdout */
772  close(readpipe[0]);
773  dup2(readpipe[1], 1);
774 
775  static char *argv[] = {
776  NULL, /* will be replaced by the executable path */
777  NULL
778  };
779  exec_i3_utility("i3-migrate-config-to-v4", argv);
780  }
781 
782  /* parent */
783 
784  /* close reading end of the writepipe (connected to the script’s stdin) */
785  close(writepipe[0]);
786 
787  /* write the whole config file to the pipe, the script will read everything
788  * immediately */
789  int written = 0;
790  int ret;
791  while (written < size) {
792  if ((ret = write(writepipe[1], input + written, size - written)) < 0) {
793  warn("Could not write to pipe");
794  return NULL;
795  }
796  written += ret;
797  }
798  close(writepipe[1]);
799 
800  /* close writing end of the readpipe (connected to the script’s stdout) */
801  close(readpipe[1]);
802 
803  /* read the script’s output */
804  int conv_size = 65535;
805  char *converted = malloc(conv_size);
806  int read_bytes = 0;
807  do {
808  if (read_bytes == conv_size) {
809  conv_size += 65535;
810  converted = realloc(converted, conv_size);
811  }
812  ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
813  if (ret == -1) {
814  warn("Cannot read from pipe");
815  FREE(converted);
816  return NULL;
817  }
818  read_bytes += ret;
819  } while (ret > 0);
820 
821  /* get the returncode */
822  int status;
823  wait(&status);
824  if (!WIFEXITED(status)) {
825  fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
826  return NULL;
827  }
828 
829  int returncode = WEXITSTATUS(status);
830  if (returncode != 0) {
831  fprintf(stderr, "Migration process exit code was != 0\n");
832  if (returncode == 2) {
833  fprintf(stderr, "could not start the migration script\n");
834  /* TODO: script was not found. tell the user to fix his system or create a v4 config */
835  } else if (returncode == 1) {
836  fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n");
837  fprintf(stderr, "# i3 config file (v4)\n");
838  /* TODO: nag the user with a message to include a hint for i3 in his config file */
839  }
840  return NULL;
841  }
842 
843  return converted;
844 }
845 
846 /*
847  * Checks for duplicate key bindings (the same keycode or keysym is configured
848  * more than once). If a duplicate binding is found, a message is printed to
849  * stderr and the has_errors variable is set to true, which will start
850  * i3-nagbar.
851  *
852  */
853 static void check_for_duplicate_bindings(struct context *context) {
854  Binding *bind, *current;
855  TAILQ_FOREACH(current, bindings, bindings) {
857  /* Abort when we reach the current keybinding, only check the
858  * bindings before */
859  if (bind == current)
860  break;
861 
862  /* Check if one is using keysym while the other is using bindsym.
863  * If so, skip. */
864  /* XXX: It should be checked at a later place (when translating the
865  * keysym to keycodes) if there are any duplicates */
866  if ((bind->symbol == NULL && current->symbol != NULL) ||
867  (bind->symbol != NULL && current->symbol == NULL))
868  continue;
869 
870  /* If bind is NULL, current has to be NULL, too (see above).
871  * If the keycodes differ, it can't be a duplicate. */
872  if (bind->symbol != NULL &&
873  strcasecmp(bind->symbol, current->symbol) != 0)
874  continue;
875 
876  /* Check if the keycodes or modifiers are different. If so, they
877  * can't be duplicate */
878  if (bind->keycode != current->keycode ||
879  bind->mods != current->mods ||
880  bind->release != current->release)
881  continue;
882 
883  context->has_errors = true;
884  if (current->keycode != 0) {
885  ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
886  current->mods, current->keycode, current->command);
887  } else {
888  ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
889  current->mods, current->symbol, current->command);
890  }
891  }
892  }
893 }
894 
895 /*
896  * Parses the given file by first replacing the variables, then calling
897  * parse_config and possibly launching i3-nagbar.
898  *
899  */
900 void parse_file(const char *f) {
901  SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
902  int fd, ret, read_bytes = 0;
903  struct stat stbuf;
904  char *buf;
905  FILE *fstr;
906  char buffer[1026], key[512], value[512];
907 
908  if ((fd = open(f, O_RDONLY)) == -1)
909  die("Could not open configuration file: %s\n", strerror(errno));
910 
911  if (fstat(fd, &stbuf) == -1)
912  die("Could not fstat file: %s\n", strerror(errno));
913 
914  buf = scalloc((stbuf.st_size + 1) * sizeof(char));
915  while (read_bytes < stbuf.st_size) {
916  if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
917  die("Could not read(): %s\n", strerror(errno));
918  read_bytes += ret;
919  }
920 
921  if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
922  die("Could not lseek: %s\n", strerror(errno));
923 
924  if ((fstr = fdopen(fd, "r")) == NULL)
925  die("Could not fdopen: %s\n", strerror(errno));
926 
927  while (!feof(fstr)) {
928  if (fgets(buffer, 1024, fstr) == NULL) {
929  if (feof(fstr))
930  break;
931  die("Could not read configuration file\n");
932  }
933 
934  /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
935  if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
936  key[0] == '#' || strlen(key) < 3)
937  continue;
938 
939  if (strcasecmp(key, "set") == 0) {
940  if (value[0] != '$') {
941  ELOG("Malformed variable assignment, name has to start with $\n");
942  continue;
943  }
944 
945  /* get key/value for this variable */
946  char *v_key = value, *v_value;
947  if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) {
948  ELOG("Malformed variable assignment, need a value\n");
949  continue;
950  }
951 
952  if (!(v_value = strstr(value, " ")))
953  v_value = strstr(value, "\t");
954 
955  *(v_value++) = '\0';
956  while (*v_value == '\t' || *v_value == ' ')
957  v_value++;
958 
959  struct Variable *new = scalloc(sizeof(struct Variable));
960  new->key = sstrdup(v_key);
961  new->value = sstrdup(v_value);
962  SLIST_INSERT_HEAD(&variables, new, variables);
963  DLOG("Got new variable %s = %s\n", v_key, v_value);
964  continue;
965  }
966  }
967  fclose(fstr);
968 
969  /* For every custom variable, see how often it occurs in the file and
970  * how much extra bytes it requires when replaced. */
971  struct Variable *current, *nearest;
972  int extra_bytes = 0;
973  /* We need to copy the buffer because we need to invalidate the
974  * variables (otherwise we will count them twice, which is bad when
975  * 'extra' is negative) */
976  char *bufcopy = sstrdup(buf);
977  SLIST_FOREACH(current, &variables, variables) {
978  int extra = (strlen(current->value) - strlen(current->key));
979  char *next;
980  for (next = bufcopy;
981  next < (bufcopy + stbuf.st_size) &&
982  (next = strcasestr(next, current->key)) != NULL;
983  next += strlen(current->key)) {
984  *next = '_';
985  extra_bytes += extra;
986  }
987  }
988  FREE(bufcopy);
989 
990  /* Then, allocate a new buffer and copy the file over to the new one,
991  * but replace occurences of our variables */
992  char *walk = buf, *destwalk;
993  char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char));
994  destwalk = new;
995  while (walk < (buf + stbuf.st_size)) {
996  /* Find the next variable */
997  SLIST_FOREACH(current, &variables, variables)
998  current->next_match = strcasestr(walk, current->key);
999  nearest = NULL;
1000  int distance = stbuf.st_size;
1001  SLIST_FOREACH(current, &variables, variables) {
1002  if (current->next_match == NULL)
1003  continue;
1004  if ((current->next_match - walk) < distance) {
1005  distance = (current->next_match - walk);
1006  nearest = current;
1007  }
1008  }
1009  if (nearest == NULL) {
1010  /* If there are no more variables, we just copy the rest */
1011  strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
1012  destwalk += (buf + stbuf.st_size) - walk;
1013  *destwalk = '\0';
1014  break;
1015  } else {
1016  /* Copy until the next variable, then copy its value */
1017  strncpy(destwalk, walk, distance);
1018  strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
1019  walk += distance + strlen(nearest->key);
1020  destwalk += distance + strlen(nearest->value);
1021  }
1022  }
1023 
1024  /* analyze the string to find out whether this is an old config file (3.x)
1025  * or a new config file (4.x). If it’s old, we run the converter script. */
1026  int version = detect_version(buf);
1027  if (version == 3) {
1028  /* We need to convert this v3 configuration */
1029  char *converted = migrate_config(new, stbuf.st_size);
1030  if (converted != NULL) {
1031  ELOG("\n");
1032  ELOG("****************************************************************\n");
1033  ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n");
1034  ELOG("\n");
1035  ELOG("Please convert your config file to v4. You can use this command:\n");
1036  ELOG(" mv %s %s.O\n", f, f);
1037  ELOG(" i3-migrate-config-to-v4 %s.O > %s\n", f, f);
1038  ELOG("****************************************************************\n");
1039  ELOG("\n");
1040  free(new);
1041  new = converted;
1042  } else {
1043  printf("\n");
1044  printf("**********************************************************************\n");
1045  printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
1046  printf("was not correctly installed on your system?\n");
1047  printf("**********************************************************************\n");
1048  printf("\n");
1049  }
1050  }
1051 
1052 
1053  context = scalloc(sizeof(struct context));
1054  context->filename = f;
1055 
1056  struct ConfigResult *config_output = parse_config(new, context);
1057  yajl_gen_free(config_output->json_gen);
1058 
1060 
1061  if (context->has_errors || context->has_warnings) {
1062  ELOG("FYI: You are using i3 version " I3_VERSION "\n");
1063  if (version == 3)
1064  ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
1065 
1066  char *editaction,
1067  *pageraction;
1068  sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", f);
1069  sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
1070  char *argv[] = {
1071  NULL, /* will be replaced by the executable path */
1072  "-f",
1073  (config.font.pattern ? config.font.pattern : "fixed"),
1074  "-t",
1075  (context->has_errors ? "error" : "warning"),
1076  "-m",
1077  (context->has_errors ?
1078  "You have an error in your i3 config file!" :
1079  "Your config is outdated. Please fix the warnings to make sure everything works."),
1080  "-b",
1081  "edit config",
1082  editaction,
1083  (errorfilename ? "-b" : NULL),
1084  (context->has_errors ? "show errors" : "show warnings"),
1085  pageraction,
1086  NULL
1087  };
1088 
1090  free(editaction);
1091  free(pageraction);
1092  }
1093 
1094  FREE(context->line_copy);
1095  free(context);
1096  free(new);
1097  free(buf);
1098 
1099  while (!SLIST_EMPTY(&variables)) {
1100  current = SLIST_FIRST(&variables);
1101  FREE(current->key);
1102  FREE(current->value);
1103  SLIST_REMOVE_HEAD(&variables, variables);
1104  FREE(current);
1105  }
1106 }
1107 
1108 #endif