conf: make important symbols global
[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 <paths.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include "conf.h"
41 #include "log.h"
42
43 #define LOG_SUBSYSTEM "config"
44
45 struct conf_obj conf_global;
46 static char *def_argv[] = { NULL, "-i", NULL };
47
48 static void print_help()
49 {
50         /*
51          * Usage/Help information
52          * This should be scaled to a maximum of 80 characters per line:
53          *
54          * 80 char line:
55          *       |   10   |    20   |    30   |    40   |    50   |    60   |    70   |    80   |
56          *      "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
57          * 80 char line starting with tab:
58          *       |10|    20   |    30   |    40   |    50   |    60   |    70   |    80   |
59          *      "\t901234567890123456789012345678901234567890123456789012345678901234567890\n"
60          */
61         fprintf(stderr,
62                 "Usage:\n"
63                 "\t%1$s [options]\n"
64                 "\t%1$s -h [options]\n"
65                 "\t%1$s -l [options] -- /bin/sh [sh-arguments]\n"
66                 "\n"
67                 "You can prefix boolean options with \"no-\" to negate it. If an argument is\n"
68                 "given multiple times, only the last argument matters if not otherwise stated.\n"
69                 "\n"
70                 "General Options:\n"
71                 "\t-h, --help                  [off]   Print this help and exit\n"
72                 "\t-v, --verbose               [off]   Print verbose messages\n"
73                 "\t    --debug                 [off]   Enable debug mode\n"
74                 "\t    --silent                [off]   Suppress notices and warnings\n"
75                 "\t-s, --switchvt              [off]   Automatically switch to VT\n"
76                 "\t    --seat <seat-name>      [seat0] Select seat; default: seat0\n"
77                 "\n"
78                 "Terminal Options:\n"
79                 "\t-l, --login                 [/bin/sh]\n"
80                 "\t                              Start the given login process instead\n"
81                 "\t                              of the default process; all arguments\n"
82                 "\t                              following '--' will be be parsed as\n"
83                 "\t                              argv to this process. No more options\n"
84                 "\t                              after '--' will be parsed so use it at\n"
85                 "\t                              the end of the argument string\n"
86                 "\t-t, --term <TERM>           [vt220]\n"
87                 "\t                              Value of the TERM environment variable\n"
88                 "\t                              for the child process\n"
89                 "\n"
90                 "Video Options:\n"
91                 "\t    --fbdev                 [off]   Use fbdev instead of DRM\n"
92                 "\n"
93                 "Input Device Options:\n"
94                 "\t    --xkb-layout <layout>   [us]    Set XkbLayout for input devices\n"
95                 "\t    --xkb-variant <variant> [-]     Set XkbVariant for input devices\n"
96                 "\t    --xkb-options <options> [-]     Set XkbOptions for input devices\n"
97                 "\n"
98                 "Font Options:\n"
99                 "\t    --font-engine <engine>  [pango] Font engine\n",
100                 "kmscon");
101         /*
102          * 80 char line:
103          *       |   10   |    20   |    30   |    40   |    50   |    60   |    70   |    80   |
104          *      "12345678901234567890123456789012345678901234567890123456789012345678901234567890\n"
105          * 80 char line starting with tab:
106          *       |10|    20   |    30   |    40   |    50   |    60   |    70   |    80   |
107          *      "\t901234567890123456789012345678901234567890123456789012345678901234567890\n"
108          */
109 }
110
111 void conf_free_value(struct conf_option *opt)
112 {
113         if (*(void**)opt->mem != opt->def)
114                 free(*(void**)opt->mem);
115 }
116
117 int conf_parse_bool(struct conf_option *opt, bool on, const char *arg)
118 {
119         *(bool*)opt->mem = on;
120         return 0;
121 }
122
123 void conf_default_bool(struct conf_option *opt)
124 {
125         *(bool*)opt->mem = (bool)opt->def;
126 }
127
128 int conf_parse_string(struct conf_option *opt, bool on, const char *arg)
129 {
130         char *val = strdup(arg);
131         if (!val)
132                 return -ENOMEM;
133
134         opt->type->free(opt);
135         *(void**)opt->mem = val;
136         return 0;
137 }
138
139 void conf_default_string(struct conf_option *opt)
140 {
141         *(void**)opt->mem = opt->def;
142 }
143
144 const struct conf_type conf_bool = {
145         .flags = 0,
146         .parse = conf_parse_bool,
147         .free = NULL,
148         .set_default = conf_default_bool,
149 };
150
151 const struct conf_type conf_string = {
152         .flags = CONF_HAS_ARG,
153         .parse = conf_parse_string,
154         .free = conf_free_value,
155         .set_default = conf_default_string,
156 };
157
158 static int aftercheck_debug(struct conf_option *opt, int argc, char **argv,
159                             int idx)
160 {
161         /* --debug implies --verbose */
162         if (conf_global.debug)
163                 conf_global.verbose = 1;
164
165         return 0;
166 }
167
168 static int aftercheck_help(struct conf_option *opt, int argc, char **argv,
169                            int idx)
170 {
171         /* exit after printing --help information */
172         if (conf_global.help) {
173                 print_help();
174                 conf_global.exit = true;
175         }
176
177         return 0;
178 }
179
180 static int aftercheck_login(struct conf_option *opt, int argc, char **argv,
181                             int idx)
182 {
183         int ret;
184
185         /* parse "--login [...] -- args" arguments */
186         if (conf_global.login) {
187                 if (idx >= argc) {
188                         fprintf(stderr, "Arguments for --login missing\n");
189                         return -EFAULT;
190                 }
191
192                 conf_global.argv = &argv[idx];
193                 ret = argc - idx;
194         } else {
195                 def_argv[0] = getenv("SHELL") ? : _PATH_BSHELL;
196                 conf_global.argv = def_argv;
197                 ret = 0;
198         }
199
200         return ret;
201 }
202
203 struct conf_option options[] = {
204         CONF_OPTION_BOOL('h', "help", aftercheck_help, &conf_global.help, false),
205         CONF_OPTION_BOOL('v', "verbose", NULL, &conf_global.verbose, false),
206         CONF_OPTION_BOOL(0, "debug", aftercheck_debug, &conf_global.debug, false),
207         CONF_OPTION_BOOL(0, "silent", NULL, &conf_global.silent, false),
208         CONF_OPTION_BOOL(0, "fbdev", NULL, &conf_global.use_fbdev, false),
209         CONF_OPTION_BOOL('s', "switchvt", NULL, &conf_global.switchvt, false),
210         CONF_OPTION_BOOL('l', "login", aftercheck_login, &conf_global.login, false),
211         CONF_OPTION_STRING('t', "term", NULL, &conf_global.term, "vt220"),
212         CONF_OPTION_STRING(0, "xkb-layout", NULL, &conf_global.xkb_layout, "us"),
213         CONF_OPTION_STRING(0, "xkb-variant", NULL, &conf_global.xkb_variant, ""),
214         CONF_OPTION_STRING(0, "xkb-options", NULL, &conf_global.xkb_options, ""),
215         CONF_OPTION_STRING(0, "seat", NULL, &conf_global.seat, "seat0"),
216         CONF_OPTION_STRING(0, "font-engine", NULL, &conf_global.font_engine, "pango"),
217 };
218
219 /* free all memory that we allocated and reset to initial state */
220 void conf_free(void)
221 {
222         unsigned int i, num;
223
224         num = sizeof(options) / sizeof(*options);
225         for (i = 0; i < num; ++i) {
226                 if (options[i].type->free)
227                         options[i].type->free(&options[i]);
228         }
229
230         memset(&conf_global, 0, sizeof(conf_global));
231 }
232
233 /*
234  * Parse command line arguments
235  * This temporarily allocates the short_options and long_options arrays so we
236  * can use the getopt_long() library call. It locks all arguments after they
237  * have been set so command-line options will always overwrite config-options.
238  */
239 int conf_parse_argv(int argc, char **argv)
240 {
241         char *short_options;
242         struct option *long_options;
243         struct option *opt;
244         size_t len, i, pos;
245         int c, ret;
246
247         if (!argv || argc < 1)
248                 return -EINVAL;
249
250         len = sizeof(options) / sizeof(*options);
251
252         short_options = malloc(sizeof(char) * (len + 1) * 2);
253         if (!short_options) {
254                 log_error("cannot allocate enough memory to parse command line arguments (%d): %m");
255                 return -ENOMEM;
256         }
257
258         long_options = malloc(sizeof(struct option) * len * 2);
259         if (!long_options) {
260                 log_error("cannot allocate enough memory to parse command line arguments (%d): %m");
261                 free(short_options);
262                 return -ENOMEM;
263         }
264
265         pos = 0;
266         short_options[pos++] = ':';
267         opt = long_options;
268         for (i = 0; i < len; ++i) {
269                 if (options[i].short_name) {
270                         short_options[pos++] = options[i].short_name;
271                         if (options[i].type->flags & CONF_HAS_ARG)
272                                 short_options[pos++] = ':';
273                 }
274
275                 if (options[i].long_name) {
276                         /* skip the "no-" prefix */
277                         opt->name = &options[i].long_name[3];
278                         opt->has_arg = !!(options[i].type->flags & CONF_HAS_ARG);
279                         opt->flag = NULL;
280                         opt->val = 100000 + i;
281                         ++opt;
282
283                         /* boolean args are also added with "no-" prefix */
284                         if (!(options[i].type->flags & CONF_HAS_ARG)) {
285                                 opt->name = options[i].long_name;
286                                 opt->has_arg = 0;
287                                 opt->flag = NULL;
288                                 opt->val = 200000 + i;
289                                 ++opt;
290                         }
291                 }
292         }
293         short_options[pos++] = 0;
294
295         opterr = 0;
296         while (1) {
297                 c = getopt_long(argc, argv, short_options,
298                                 long_options, NULL);
299                 if (c <= 0) {
300                         break;
301                 } else if (c == ':') {
302                         fprintf(stderr, "Missing argument for: %s\n",
303                                 argv[optind - 1]);
304                         return -EFAULT;
305                 } else if (c == '?') {
306                         if (optopt && optopt < 100000)
307                                 fprintf(stderr, "Unknown argument: -%c\n",
308                                         optopt);
309                         else if (!optopt)
310                                 fprintf(stderr, "Unknown argument: %s\n",
311                                         argv[optind - 1]);
312                         else
313                                 fprintf(stderr, "Parameter takes no argument: %s\n",
314                                         argv[optind - 1]);
315                         return -EFAULT;
316                 } else if (c < 100000) {
317                         for (i = 0; i < len; ++i) {
318                                 if (options[i].short_name == c) {
319                                         ret = options[i].type->parse(&options[i],
320                                                                      true,
321                                                                      optarg);
322                                         if (ret)
323                                                 return ret;
324                                         options[i].flags |= CONF_LOCKED;
325                                         options[i].flags |= CONF_DONE;
326                                         break;
327                                 }
328                         }
329                 } else if (c < 200000) {
330                         i = c - 100000;
331                         ret = options[i].type->parse(&options[i], true, optarg);
332                         if (ret)
333                                 return ret;
334                         options[i].flags |= CONF_LOCKED;
335                         options[i].flags |= CONF_DONE;
336                 } else {
337                         i = c - 200000;
338                         ret = options[i].type->parse(&options[i], false, NULL);
339                         if (ret)
340                                 return ret;
341                         options[i].flags |= CONF_LOCKED;
342                         options[i].flags |= CONF_DONE;
343                 }
344         }
345
346         free(long_options);
347         free(short_options);
348
349         /* set default values if not configured */
350         for (i = 0; i < len; ++i) {
351                 if (!(options[i].flags & CONF_DONE) &&
352                     options[i].type->set_default) {
353                         options[i].type->set_default(&options[i]);
354                 }
355         }
356
357         /* Perform aftercheck:
358          * All arguments that provide an aftercheck will be passed the remaining
359          * arguments in order. If they return a negative error code, it is
360          * interpreted as fatal error and returned to the caller. A positive
361          * error code is interpreted as the amount of remaining arguments that
362          * have been consumed by this aftercheck. 0 means nothing has been
363          * consumed.
364          * The next argument's aftercheck will only get the now remaning
365          * arguments passed in. If not all arguments are consumed, then this
366          * function will report an error to the caller. */
367         for (i = 0; i < len; ++i) {
368                 if (options[i].aftercheck) {
369                         ret = options[i].aftercheck(&options[i], argc, argv, optind);
370                         if (ret < 0)
371                                 return ret;
372                         optind += ret;
373                 }
374         }
375
376         if (optind < argc) {
377                 fprintf(stderr, "Unparsed remaining arguments starting with: %s\n",
378                         argv[optind]);
379                 return -EFAULT;
380         }
381
382         return 0;
383 }
384
385 static int parse_kv_pair(const char *key, const char *value)
386 {
387         unsigned int i, num;
388         int ret;
389         bool set;
390         struct conf_option *opt;
391
392         num = sizeof(options) / sizeof(*options);
393         for (i = 0; i < num; ++i) {
394                 opt = &options[i];
395                 if (!opt->long_name)
396                         continue;
397
398                 if (!strcmp(key, opt->long_name))
399                         set = false;
400                 else if (!strcmp(key, &opt->long_name[3]))
401                         set = true;
402                 else
403                         continue;
404
405                 if (opt->type->flags & CONF_HAS_ARG && !value) {
406                         log_error("config option '%s' requires an argument", key);
407                         return -EFAULT;
408                 } else if (!(opt->type->flags & CONF_HAS_ARG) && value) {
409                         log_error("config option '%s' does not take arguments", key);
410                         return -EFAULT;
411                 }
412
413                 /* ignore if already set by command-line arguments */
414                 if (opt->flags & CONF_LOCKED)
415                         return 0;
416
417                 ret = opt->type->parse(opt, set, value);
418                 if (ret)
419                         return ret;
420
421                 opt->flags |= CONF_DONE;
422                 return 0;
423         }
424
425         log_error("unknown config option '%s'", key);
426         return -EFAULT;
427 }
428
429 static void strip_spaces(char **buf)
430 {
431         char *tail;
432
433         while (**buf == ' ' || **buf == '\r' || **buf == '\t')
434                 ++*buf;
435
436         if (!**buf)
437                 return;
438
439         tail = *buf;
440         while (*tail)
441                 ++tail;
442
443         --tail;
444
445         while (*tail == ' ' || *tail == '\r' || *tail == '\t')
446                 *tail-- = 0;
447 }
448
449 static int parse_line(char **buf, size_t *size)
450 {
451         char *key;
452         char *value = NULL;
453         char *line;
454         char c;
455         size_t len, klen;
456         int ret;
457
458         line = *buf;
459         len = *size;
460
461         /* parse key */
462         key = line;
463         while (len) {
464                 c = *line;
465                 if (c == '\n' ||
466                     c == '#' ||
467                     c == '=')
468                         break;
469                 ++line;
470                 --len;
471         }
472
473         if (!len) {
474                 *line = 0;
475                 goto done;
476         } else if (*line == '\n') {
477                 *line = 0;
478                 goto done;
479         } else if (*line == '#') {
480                 *line = 0;
481                 goto skip_comment;
482         } else if (*line != '=') {
483                 return -EFAULT;
484         }
485
486         *line++ = 0;
487         --len;
488
489         /* parse value */
490         value = line;
491         while (len) {
492                 c = *line;
493                 if (c == '\n' ||
494                     c == '#')
495                         break;
496                 ++line;
497                 --len;
498         }
499
500         if (!len) {
501                 *line = 0;
502                 goto done;
503         } else if (*line == '\n') {
504                 *line = 0;
505                 goto done;
506         } else if (*line == '#') {
507                 *line = 0;
508                 goto skip_comment;
509         } else {
510                 return -EFAULT;
511         }
512
513 skip_comment:
514         while (len) {
515                 c = *line;
516                 if (c == '\n')
517                         break;
518                 ++line;
519                 --len;
520         }
521
522 done:
523         strip_spaces(&key);
524         
525         klen = strlen(key);
526         if (klen > 0) {
527                 if (value)
528                         strip_spaces(&value);
529
530                 ret = parse_kv_pair(key, value);
531                 if (ret)
532                         return ret;
533         }
534
535         if (!len) {
536                 *buf = NULL;
537                 *size = 0;
538         } else {
539                 *buf = ++line;
540                 *size = --len;
541         }
542
543         return 0;
544 }
545
546 static int parse_buffer(char *buf, size_t size)
547 {
548         int ret = 0;
549
550         while (!ret && size > 0)
551                 ret = parse_line(&buf, &size);
552
553         return ret;
554 }
555
556 /* chunk size when reading config files */
557 #define CONF_BUFSIZE 4096
558
559 /* This reads the file at \path in memory and parses it as if it was given as
560  * command line options. */
561 int conf_parse_file(const char *path)
562 {
563         int fd, ret;
564         size_t size, pos;
565         char *buf, *tmp;
566
567         if (!path)
568                 return -EINVAL;
569
570         log_info("reading config file %s", path);
571         fd = open(path, O_RDONLY | O_CLOEXEC | O_NOCTTY);
572         if (fd < 0) {
573                 log_error("cannot open %s (%d): %m", path, errno);
574                 return -EFAULT;
575         }
576
577         buf = NULL;
578         size = 0;
579         pos = 0;
580
581         do {
582                 if (size - pos < CONF_BUFSIZE) {
583                         tmp = realloc(buf, size + CONF_BUFSIZE + 1);
584                         if (!tmp) {
585                                 log_error("cannot allocate enough memory to parse config file %s (%d): %m",
586                                           path, errno);
587                                 ret = -ENOMEM;
588                                 goto out_free;
589                         }
590                         buf = tmp;
591                         size += CONF_BUFSIZE;
592                 }
593
594                 ret = read(fd, &buf[pos], CONF_BUFSIZE);
595                 if (ret < 0) {
596                         log_error("cannot read from config file %s (%d): %m",
597                                   path, errno);
598                         ret = -EFAULT;
599                         goto out_free;
600                 }
601                 pos += ret;
602         } while (ret > 0);
603
604         buf[pos] = 0;
605         ret = parse_buffer(buf, pos);
606
607 out_free:
608         free(buf);
609         close(fd);
610         return ret;
611 }
612
613 int conf_parse_all_files(void)
614 {
615         int ret;
616         const char *file, *home;
617         char *path;
618
619         ret = 0;
620
621         file = "/etc/kmscon.conf";
622         if (!access(file, F_OK)) {
623                 if (access(file, R_OK))
624                         log_warning("config file %s exists but read access was denied",
625                                     file);
626                 else
627                         ret = conf_parse_file(file);
628         }
629
630         if (ret)
631                 goto err_out;
632
633         home = getenv("HOME");
634         if (home) {
635                 ret = asprintf(&path, "%s/.kmscon.conf", home);
636                 if (ret < 0) {
637                         log_warning("cannot allocate enough resources to build a config-path");
638                         ret = -ENOMEM;
639                 } else {
640                         ret = 0;
641                         if (!access(path, F_OK)) {
642                                 if (access(path, R_OK))
643                                         log_warning("config file %s exists but read access was denied",
644                                                     path);
645                                 else
646                                         ret = conf_parse_file(path);
647                         }
648                         free(path);
649                 }
650         }
651
652 err_out:
653         return ret;
654 }