conf: allow multiple grabs and keysyms per grab-option
[platform/upstream/kmscon.git] / src / conf.c
1 /*
2  * App Configuration
3  *
4  * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files
8  * (the "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 /*
27  * Configuration
28  * Implementation of the configuration parsers.
29  */
30
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <getopt.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <xkbcommon/xkbcommon.h>
40 #include "conf.h"
41 #include "log.h"
42 #include "shl_misc.h"
43
44 #define LOG_SUBSYSTEM "config"
45
46 void conf_free_value(struct conf_option *opt)
47 {
48         if (*(void**)opt->mem && *(void**)opt->mem != opt->def) {
49                 free(*(void**)opt->mem);
50                 *(void**)opt->mem = NULL;
51         }
52 }
53
54 int conf_parse_bool(struct conf_option *opt, bool on, const char *arg)
55 {
56         *(bool*)opt->mem = on;
57         return 0;
58 }
59
60 void conf_default_bool(struct conf_option *opt)
61 {
62         *(bool*)opt->mem = (bool)opt->def;
63 }
64
65 int conf_parse_int(struct conf_option *opt, bool on, const char *arg)
66 {
67         *(int*)opt->mem = atoi(arg);
68         return 0;
69 }
70
71 void conf_default_int(struct conf_option *opt)
72 {
73         *(int*)opt->mem = (int)(unsigned long)opt->def;
74 }
75
76 int conf_parse_uint(struct conf_option *opt, bool on, const char *arg)
77 {
78         *(unsigned int*)opt->mem = atoi(arg);
79         return 0;
80 }
81
82 void conf_default_uint(struct conf_option *opt)
83 {
84         *(unsigned int*)opt->mem = (unsigned int)(unsigned long)opt->def;
85 }
86
87 int conf_parse_string(struct conf_option *opt, bool on, const char *arg)
88 {
89         char *val = strdup(arg);
90         if (!val)
91                 return -ENOMEM;
92
93         opt->type->free(opt);
94         *(void**)opt->mem = val;
95         return 0;
96 }
97
98 void conf_default_string(struct conf_option *opt)
99 {
100         *(void**)opt->mem = opt->def;
101 }
102
103 int conf_parse_string_list(struct conf_option *opt, bool on, const char *arg)
104 {
105         int ret;
106         char **list;
107
108         ret = shl_split_string(arg, &list, NULL, ',', true);
109         if (ret)
110                 return ret;
111
112         opt->type->free(opt);
113         *(void**)opt->mem = list;
114         return 0;
115 }
116
117 void conf_default_string_list(struct conf_option *opt)
118 {
119         *(void**)opt->mem = opt->def;
120 }
121
122 static int parse_single_grab(char *arg, unsigned int *mods,
123                              uint32_t *keysym, bool allow_mods)
124 {
125         char *tmp, *start, *end;
126
127         tmp = arg;
128         do {
129                 while (*tmp == ' ')
130                         ++tmp;
131                 if (!allow_mods)
132                         break;
133                 if (*tmp != '<')
134                         break;
135
136                 start = tmp;
137                 while (*tmp && *tmp != '>')
138                         ++tmp;
139
140                 if (*tmp != '>') {
141                         log_error("missing '>' near '%s'", start);
142                         return -EFAULT;
143                 }
144
145                 *tmp++ = 0;
146                 ++start;
147                 if (!strcasecmp(start, "shift")) {
148                         *mods |= SHL_SHIFT_MASK;
149                 } else if (!strcasecmp(start, "lock")) {
150                         *mods |= SHL_LOCK_MASK;
151                 } else if (!strcasecmp(start, "control") ||
152                            !strcasecmp(start, "ctrl")) {
153                         *mods |= SHL_CONTROL_MASK;
154                 } else if (!strcasecmp(start, "alt")) {
155                         *mods |= SHL_ALT_MASK;
156                 } else if (!strcasecmp(start, "logo")) {
157                         *mods |= SHL_LOGO_MASK;
158                 } else {
159                         log_error("invalid modifier '%s'", start);
160                         return -EFAULT;
161                 }
162         } while (1);
163
164         while (*tmp == ' ')
165                 ++tmp;
166
167         start = tmp;
168         end = start;
169         do {
170                 while (*tmp && *tmp != ' ')
171                         ++tmp;
172                 end = tmp;
173                 if (!*tmp)
174                         break;
175                 while (*tmp == ' ')
176                         ++tmp;
177         } while (1);
178
179         if (start == end)
180                 return 0;
181         if (*end)
182                 *end = 0;
183
184         *keysym = xkb_keysym_from_name(start);
185         if (!*keysym) {
186                 log_error("invalid key '%s'", start);
187                 return -EFAULT;
188         }
189
190         return 1;
191 }
192
193 int conf_parse_grab(struct conf_option *opt, bool on, const char *arg)
194 {
195         char **list, **keys;
196         unsigned int list_num, key_num, i, j, k, l;
197         int ret;
198         struct conf_grab *grab;
199
200         ret = shl_split_string(arg, &list, &list_num, ',', false);
201         if (ret)
202                 return ret;
203
204         grab = malloc(sizeof(*grab));
205         if (!grab) {
206                 ret = -ENOMEM;
207                 goto err_list;
208         }
209         memset(grab, 0, sizeof(*grab));
210
211         if (list_num) {
212                 grab->mods = malloc(sizeof(*grab->mods) * list_num);
213                 if (!grab->mods) {
214                         ret = -ENOMEM;
215                         goto err_grab;
216                 }
217                 memset(grab->mods, 0, sizeof(*grab->mods) * list_num);
218
219                 grab->num_syms = malloc(sizeof(*grab->num_syms) * list_num);
220                 if (!grab->num_syms) {
221                         ret = -ENOMEM;
222                         goto err_grab;
223                 }
224                 memset(grab->num_syms, 0, sizeof(*grab->num_syms) * list_num);
225
226                 grab->keysyms = malloc(sizeof(*grab->keysyms) * list_num);
227                 if (!grab->keysyms) {
228                         ret = -ENOMEM;
229                         goto err_grab;
230                 }
231                 memset(grab->keysyms, 0, sizeof(*grab->keysyms) * list_num);
232         }
233
234         l = 0;
235         for (i = 0; i < list_num; ++i) {
236                 ret = shl_split_string(list[i], &keys, &key_num, '+', false);
237                 if (ret)
238                         goto err_all;
239                 if (!key_num) {
240                         free(keys);
241                         continue;
242                 }
243
244                 grab->keysyms[l] = malloc(sizeof(*grab->keysyms[l] * key_num));
245                 if (!grab->keysyms[l]) {
246                         ret = -ENOMEM;
247                         free(keys);
248                         goto err_all;
249                 }
250
251                 k = 0;
252                 for (j = 0; j < key_num; ++j) {
253                         ret = parse_single_grab(keys[j], &grab->mods[l],
254                                                 &grab->keysyms[l][k],
255                                                 j == 0);
256                         if (ret < 0) {
257                                 log_error("cannot parse grab '%s' in '%s'",
258                                           list[i], arg);
259                                 free(keys);
260                                 goto err_all;
261                         }
262                         k += ret;
263                 }
264
265                 free(keys);
266                 if (!k)
267                         continue;
268                 grab->num_syms[l] = k;
269                 ++l;
270                 ++grab->num;
271         }
272
273         free(list);
274         opt->type->free(opt);
275         *(void**)opt->mem = grab;
276         return 0;
277
278 err_all:
279         for (i = 0; i < list_num; ++i)
280                 free(grab->keysyms[i]);
281 err_grab:
282         free(grab->keysyms);
283         free(grab->num_syms);
284         free(grab->mods);
285         free(grab);
286 err_list:
287         free(list);
288         return ret;
289 }
290
291 void conf_free_grab(struct conf_option *opt)
292 {
293         struct conf_grab *grab;
294         unsigned int i;
295
296         if (!*(void**)opt->mem || *(void**)opt->mem == opt->def)
297                 return;
298
299         grab = *(void**)opt->mem;
300         *(void**)opt->mem = NULL;
301
302         for (i = 0; i < grab->num; ++i)
303                 free(grab->keysyms[i]);
304
305         free(grab->keysyms);
306         free(grab->num_syms);
307         free(grab->mods);
308         free(grab);
309 }
310
311 void conf_default_grab(struct conf_option *opt)
312 {
313         *(void**)opt->mem = opt->def;
314 }
315
316 const struct conf_type conf_bool = {
317         .flags = 0,
318         .parse = conf_parse_bool,
319         .free = NULL,
320         .set_default = conf_default_bool,
321 };
322
323 const struct conf_type conf_int = {
324         .flags = CONF_HAS_ARG,
325         .parse = conf_parse_int,
326         .free = NULL,
327         .set_default = conf_default_int,
328 };
329
330 const struct conf_type conf_uint = {
331         .flags = CONF_HAS_ARG,
332         .parse = conf_parse_uint,
333         .free = NULL,
334         .set_default = conf_default_uint,
335 };
336
337 const struct conf_type conf_string = {
338         .flags = CONF_HAS_ARG,
339         .parse = conf_parse_string,
340         .free = conf_free_value,
341         .set_default = conf_default_string,
342 };
343
344 const struct conf_type conf_string_list = {
345         .flags = CONF_HAS_ARG,
346         .parse = conf_parse_string_list,
347         .free = conf_free_value,
348         .set_default = conf_default_string_list,
349 };
350
351 const struct conf_type conf_grab = {
352         .flags = CONF_HAS_ARG,
353         .parse = conf_parse_grab,
354         .free = conf_free_grab,
355         .set_default = conf_default_grab,
356 };
357
358 /* free all memory that we allocated and reset to initial state */
359 void conf_free(struct conf_option *opts, size_t len)
360 {
361         unsigned int i;
362
363         for (i = 0; i < len; ++i) {
364                 if (opts[i].type->free)
365                         opts[i].type->free(&opts[i]);
366         }
367 }
368
369 /*
370  * Parse command line arguments
371  * This temporarily allocates the short_options and long_options arrays so we
372  * can use the getopt_long() library call. It locks all arguments after they
373  * have been set so command-line options will always overwrite config-options.
374  */
375 int conf_parse_argv(struct conf_option *opts, size_t len,
376                     int argc, char **argv)
377 {
378         char *short_options;
379         struct option *long_options;
380         struct option *opt;
381         size_t i, pos;
382         int c, ret;
383
384         if (!argv || argc < 1)
385                 return -EINVAL;
386
387         short_options = malloc(sizeof(char) * (len + 1) * 2);
388         if (!short_options) {
389                 log_error("cannot allocate enough memory to parse command line arguments (%d): %m", errno);
390                 return -ENOMEM;
391         }
392
393         long_options = malloc(sizeof(struct option) * len * 2);
394         if (!long_options) {
395                 log_error("cannot allocate enough memory to parse command line arguments (%d): %m", errno);
396                 free(short_options);
397                 return -ENOMEM;
398         }
399
400         pos = 0;
401         short_options[pos++] = ':';
402         opt = long_options;
403         for (i = 0; i < len; ++i) {
404                 if (opts[i].short_name) {
405                         short_options[pos++] = opts[i].short_name;
406                         if (opts[i].type->flags & CONF_HAS_ARG)
407                                 short_options[pos++] = ':';
408                 }
409
410                 if (opts[i].long_name) {
411                         /* skip the "no-" prefix */
412                         opt->name = &opts[i].long_name[3];
413                         opt->has_arg = !!(opts[i].type->flags & CONF_HAS_ARG);
414                         opt->flag = NULL;
415                         opt->val = 100000 + i;
416                         ++opt;
417
418                         /* boolean args are also added with "no-" prefix */
419                         if (!(opts[i].type->flags & CONF_HAS_ARG)) {
420                                 opt->name = opts[i].long_name;
421                                 opt->has_arg = 0;
422                                 opt->flag = NULL;
423                                 opt->val = 200000 + i;
424                                 ++opt;
425                         }
426                 }
427         }
428         short_options[pos++] = 0;
429
430         opterr = 0;
431         while (1) {
432                 c = getopt_long(argc, argv, short_options,
433                                 long_options, NULL);
434                 if (c <= 0) {
435                         break;
436                 } else if (c == ':') {
437                         fprintf(stderr, "Missing argument for: %s\n",
438                                 argv[optind - 1]);
439                         return -EFAULT;
440                 } else if (c == '?') {
441                         if (optopt && optopt < 100000)
442                                 fprintf(stderr, "Unknown argument: -%c\n",
443                                         optopt);
444                         else if (!optopt)
445                                 fprintf(stderr, "Unknown argument: %s\n",
446                                         argv[optind - 1]);
447                         else
448                                 fprintf(stderr, "Parameter takes no argument: %s\n",
449                                         argv[optind - 1]);
450                         return -EFAULT;
451                 } else if (c < 100000) {
452                         for (i = 0; i < len; ++i) {
453                                 if (opts[i].short_name == c) {
454                                         ret = opts[i].type->parse(&opts[i],
455                                                                   true,
456                                                                   optarg);
457                                         if (ret)
458                                                 return ret;
459                                         opts[i].flags |= CONF_LOCKED;
460                                         opts[i].flags |= CONF_DONE;
461                                         break;
462                                 }
463                         }
464                 } else if (c < 200000) {
465                         i = c - 100000;
466                         ret = opts[i].type->parse(&opts[i], true, optarg);
467                         if (ret)
468                                 return ret;
469                         opts[i].flags |= CONF_LOCKED;
470                         opts[i].flags |= CONF_DONE;
471                 } else {
472                         i = c - 200000;
473                         ret = opts[i].type->parse(&opts[i], false, NULL);
474                         if (ret)
475                                 return ret;
476                         opts[i].flags |= CONF_LOCKED;
477                         opts[i].flags |= CONF_DONE;
478                 }
479         }
480
481         free(long_options);
482         free(short_options);
483
484         /* set default values if not configured */
485         for (i = 0; i < len; ++i) {
486                 if (!(opts[i].flags & CONF_DONE) &&
487                     opts[i].type->set_default) {
488                         opts[i].type->set_default(&opts[i]);
489                 }
490         }
491
492         /* Perform aftercheck:
493          * All arguments that provide an aftercheck will be passed the remaining
494          * arguments in order. If they return a negative error code, it is
495          * interpreted as fatal error and returned to the caller. A positive
496          * error code is interpreted as the amount of remaining arguments that
497          * have been consumed by this aftercheck. 0 means nothing has been
498          * consumed.
499          * The next argument's aftercheck will only get the now remaning
500          * arguments passed in. If not all arguments are consumed, then this
501          * function will report an error to the caller. */
502         for (i = 0; i < len; ++i) {
503                 if (opts[i].aftercheck) {
504                         ret = opts[i].aftercheck(&opts[i], argc, argv, optind);
505                         if (ret < 0)
506                                 return ret;
507                         optind += ret;
508                 }
509         }
510
511         if (optind < argc) {
512                 fprintf(stderr, "Unparsed remaining arguments starting with: %s\n",
513                         argv[optind]);
514                 return -EFAULT;
515         }
516
517         return 0;
518 }
519
520 static int parse_kv_pair(struct conf_option *opts, size_t len,
521                          const char *key, const char *value)
522 {
523         unsigned int i;
524         int ret;
525         bool set;
526         struct conf_option *opt;
527
528         for (i = 0; i < len; ++i) {
529                 opt = &opts[i];
530                 if (!opt->long_name)
531                         continue;
532
533                 if (!strcmp(key, opt->long_name))
534                         set = false;
535                 else if (!strcmp(key, &opt->long_name[3]))
536                         set = true;
537                 else
538                         continue;
539
540                 if (opt->type->flags & CONF_HAS_ARG && !value) {
541                         log_error("config option '%s' requires an argument", key);
542                         return -EFAULT;
543                 } else if (!(opt->type->flags & CONF_HAS_ARG) && value) {
544                         log_error("config option '%s' does not take arguments", key);
545                         return -EFAULT;
546                 }
547
548                 /* ignore if already set by command-line arguments */
549                 if (opt->flags & CONF_LOCKED)
550                         return 0;
551
552                 ret = opt->type->parse(opt, set, value);
553                 if (ret)
554                         return ret;
555
556                 opt->flags |= CONF_DONE;
557                 return 0;
558         }
559
560         log_error("unknown config option '%s'", key);
561         return -EFAULT;
562 }
563
564 static void strip_spaces(char **buf)
565 {
566         char *tail;
567
568         while (**buf == ' ' || **buf == '\r' || **buf == '\t')
569                 ++*buf;
570
571         if (!**buf)
572                 return;
573
574         tail = *buf;
575         while (*tail)
576                 ++tail;
577
578         --tail;
579
580         while (*tail == ' ' || *tail == '\r' || *tail == '\t')
581                 *tail-- = 0;
582 }
583
584 static int parse_line(struct conf_option *opts, size_t olen,
585                       char **buf, size_t *size)
586 {
587         char *key;
588         char *value = NULL;
589         char *line;
590         char c;
591         size_t len, klen;
592         int ret;
593
594         line = *buf;
595         len = *size;
596
597         /* parse key */
598         key = line;
599         while (len) {
600                 c = *line;
601                 if (c == '\n' ||
602                     c == '#' ||
603                     c == '=')
604                         break;
605                 ++line;
606                 --len;
607         }
608
609         if (!len) {
610                 *line = 0;
611                 goto done;
612         } else if (*line == '\n') {
613                 *line = 0;
614                 goto done;
615         } else if (*line == '#') {
616                 *line = 0;
617                 goto skip_comment;
618         } else if (*line != '=') {
619                 return -EFAULT;
620         }
621
622         *line++ = 0;
623         --len;
624
625         /* parse value */
626         value = line;
627         while (len) {
628                 c = *line;
629                 if (c == '\n' ||
630                     c == '#')
631                         break;
632                 ++line;
633                 --len;
634         }
635
636         if (!len) {
637                 *line = 0;
638                 goto done;
639         } else if (*line == '\n') {
640                 *line = 0;
641                 goto done;
642         } else if (*line == '#') {
643                 *line = 0;
644                 goto skip_comment;
645         } else {
646                 return -EFAULT;
647         }
648
649 skip_comment:
650         while (len) {
651                 c = *line;
652                 if (c == '\n')
653                         break;
654                 ++line;
655                 --len;
656         }
657
658 done:
659         strip_spaces(&key);
660         
661         klen = strlen(key);
662         if (klen > 0) {
663                 if (value)
664                         strip_spaces(&value);
665
666                 ret = parse_kv_pair(opts, olen, key, value);
667                 if (ret)
668                         return ret;
669         }
670
671         if (!len) {
672                 *buf = NULL;
673                 *size = 0;
674         } else {
675                 *buf = ++line;
676                 *size = --len;
677         }
678
679         return 0;
680 }
681
682 static int parse_buffer(struct conf_option *opts, size_t len,
683                         char *buf, size_t size)
684 {
685         int ret = 0;
686
687         while (!ret && size > 0)
688                 ret = parse_line(opts, len, &buf, &size);
689
690         return ret;
691 }
692
693 /* chunk size when reading config files */
694 #define CONF_BUFSIZE 4096
695
696 /* This reads the file at \path in memory and parses it as if it was given as
697  * command line options. */
698 int conf_parse_file(struct conf_option *opts, size_t len, const char *path)
699 {
700         int fd, ret;
701         size_t size, pos;
702         char *buf, *tmp;
703
704         if (!path)
705                 return -EINVAL;
706
707         log_info("reading config file %s", path);
708         fd = open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY);
709         if (fd < 0) {
710                 log_error("cannot open %s (%d): %m", path, errno);
711                 return -EFAULT;
712         }
713
714         buf = NULL;
715         size = 0;
716         pos = 0;
717
718         do {
719                 if (size - pos < CONF_BUFSIZE) {
720                         tmp = realloc(buf, size + CONF_BUFSIZE + 1);
721                         if (!tmp) {
722                                 log_error("cannot allocate enough memory to parse config file %s (%d): %m",
723                                           path, errno);
724                                 ret = -ENOMEM;
725                                 goto out_free;
726                         }
727                         buf = tmp;
728                         size += CONF_BUFSIZE;
729                 }
730
731                 ret = read(fd, &buf[pos], CONF_BUFSIZE);
732                 if (ret < 0) {
733                         log_error("cannot read from config file %s (%d): %m",
734                                   path, errno);
735                         ret = -EFAULT;
736                         goto out_free;
737                 }
738                 pos += ret;
739         } while (ret > 0);
740
741         buf[pos] = 0;
742         ret = parse_buffer(opts, len, buf, pos);
743
744 out_free:
745         free(buf);
746         close(fd);
747         return ret;
748 }
749
750 int conf_parse_all_files(struct conf_option *opts, size_t len)
751 {
752         int ret;
753         const char *file, *home;
754         char *path;
755
756         ret = 0;
757
758         file = "/etc/kmscon.conf";
759         if (!access(file, F_OK)) {
760                 if (access(file, R_OK))
761                         log_warning("config file %s exists but read access was denied",
762                                     file);
763                 else
764                         ret = conf_parse_file(opts, len, file);
765         }
766
767         if (ret)
768                 goto err_out;
769
770         home = getenv("HOME");
771         if (home) {
772                 ret = asprintf(&path, "%s/.kmscon.conf", home);
773                 if (ret < 0) {
774                         log_warning("cannot allocate enough resources to build a config-path");
775                         ret = -ENOMEM;
776                 } else {
777                         ret = 0;
778                         if (!access(path, F_OK)) {
779                                 if (access(path, R_OK))
780                                         log_warning("config file %s exists but read access was denied",
781                                                     path);
782                                 else
783                                         ret = conf_parse_file(opts, len, path);
784                         }
785                         free(path);
786                 }
787         }
788
789 err_out:
790         return ret;
791 }