1*4b9c6d91SCole Faust /* Copyright 2021 The ChromiumOS Authors
2*4b9c6d91SCole Faust * Use of this source code is governed by a BSD-style license that can be
3*4b9c6d91SCole Faust * found in the LICENSE file.
4*4b9c6d91SCole Faust */
5*4b9c6d91SCole Faust
6*4b9c6d91SCole Faust #include <errno.h>
7*4b9c6d91SCole Faust #include <stdbool.h>
8*4b9c6d91SCole Faust #include <stdio.h>
9*4b9c6d91SCole Faust #include <stdlib.h>
10*4b9c6d91SCole Faust #include <string.h>
11*4b9c6d91SCole Faust
12*4b9c6d91SCole Faust #include "config_parser.h"
13*4b9c6d91SCole Faust
14*4b9c6d91SCole Faust #include "util.h"
15*4b9c6d91SCole Faust
16*4b9c6d91SCole Faust #define LIST_DEFAULT_SIZE (100)
17*4b9c6d91SCole Faust
new_config_entry_list(void)18*4b9c6d91SCole Faust struct config_entry_list *new_config_entry_list(void)
19*4b9c6d91SCole Faust {
20*4b9c6d91SCole Faust /*
21*4b9c6d91SCole Faust * There are <100 CLI options, configuration file will likely have
22*4b9c6d91SCole Faust * a similar number of config entries.
23*4b9c6d91SCole Faust */
24*4b9c6d91SCole Faust struct config_entry *entries =
25*4b9c6d91SCole Faust calloc(LIST_DEFAULT_SIZE, sizeof(struct config_entry));
26*4b9c6d91SCole Faust if (!entries)
27*4b9c6d91SCole Faust return NULL;
28*4b9c6d91SCole Faust
29*4b9c6d91SCole Faust struct config_entry_list *list =
30*4b9c6d91SCole Faust calloc(1, sizeof(struct config_entry_list));
31*4b9c6d91SCole Faust if (!list) {
32*4b9c6d91SCole Faust free(entries);
33*4b9c6d91SCole Faust return NULL;
34*4b9c6d91SCole Faust }
35*4b9c6d91SCole Faust list->entries = entries;
36*4b9c6d91SCole Faust list->num_allocated_ = LIST_DEFAULT_SIZE;
37*4b9c6d91SCole Faust list->num_entries = 0;
38*4b9c6d91SCole Faust return list;
39*4b9c6d91SCole Faust }
40*4b9c6d91SCole Faust
clear_config_entry(struct config_entry * entry)41*4b9c6d91SCole Faust void clear_config_entry(struct config_entry *entry)
42*4b9c6d91SCole Faust {
43*4b9c6d91SCole Faust free((char *)entry->key);
44*4b9c6d91SCole Faust free((char *)entry->value);
45*4b9c6d91SCole Faust }
46*4b9c6d91SCole Faust
free_config_entry_list(struct config_entry_list * list)47*4b9c6d91SCole Faust void free_config_entry_list(struct config_entry_list *list)
48*4b9c6d91SCole Faust {
49*4b9c6d91SCole Faust if (!list)
50*4b9c6d91SCole Faust return;
51*4b9c6d91SCole Faust for (size_t i = 0; i < list->num_entries; i++) {
52*4b9c6d91SCole Faust clear_config_entry(&list->entries[i]);
53*4b9c6d91SCole Faust }
54*4b9c6d91SCole Faust free(list->entries);
55*4b9c6d91SCole Faust free(list);
56*4b9c6d91SCole Faust }
57*4b9c6d91SCole Faust
parse_config_line(const char * config_line,struct config_entry * entry)58*4b9c6d91SCole Faust bool parse_config_line(const char *config_line, struct config_entry *entry)
59*4b9c6d91SCole Faust {
60*4b9c6d91SCole Faust /* Parsing will modify |config_line| in place, so make a copy. */
61*4b9c6d91SCole Faust attribute_cleanup_str char *line = strdup(config_line);
62*4b9c6d91SCole Faust if (!line)
63*4b9c6d91SCole Faust return false;
64*4b9c6d91SCole Faust char *value = line;
65*4b9c6d91SCole Faust
66*4b9c6d91SCole Faust /* After tokenize call, |value| will point to a substring after '='.
67*4b9c6d91SCole Faust * If there is no '=' in the string, |key| will contain the entire
68*4b9c6d91SCole Faust * string while |value| will be NULL.
69*4b9c6d91SCole Faust */
70*4b9c6d91SCole Faust char *key = tokenize(&value, "=");
71*4b9c6d91SCole Faust if (key)
72*4b9c6d91SCole Faust key = strip(key);
73*4b9c6d91SCole Faust if (value)
74*4b9c6d91SCole Faust value = strip(value);
75*4b9c6d91SCole Faust if (!key || key[0] == '\0' || (value && value[0] == '\0')) {
76*4b9c6d91SCole Faust warn("unable to parse %s", config_line);
77*4b9c6d91SCole Faust return false;
78*4b9c6d91SCole Faust }
79*4b9c6d91SCole Faust entry->key = strdup(key);
80*4b9c6d91SCole Faust entry->value = value ? strdup(value) : NULL;
81*4b9c6d91SCole Faust if (!entry->key || (value && !entry->value)) {
82*4b9c6d91SCole Faust clear_config_entry(entry);
83*4b9c6d91SCole Faust return false;
84*4b9c6d91SCole Faust }
85*4b9c6d91SCole Faust return true;
86*4b9c6d91SCole Faust }
87*4b9c6d91SCole Faust
match_special_directive(const char * line)88*4b9c6d91SCole Faust static bool match_special_directive(const char *line)
89*4b9c6d91SCole Faust {
90*4b9c6d91SCole Faust return streq(line, "% minijail-config-file v0\n");
91*4b9c6d91SCole Faust }
92*4b9c6d91SCole Faust
parse_config_file(FILE * config_file,struct config_entry_list * list)93*4b9c6d91SCole Faust bool parse_config_file(FILE *config_file, struct config_entry_list *list)
94*4b9c6d91SCole Faust {
95*4b9c6d91SCole Faust attribute_cleanup_str char *line = NULL;
96*4b9c6d91SCole Faust size_t len = 0;
97*4b9c6d91SCole Faust
98*4b9c6d91SCole Faust /* The first line must match the special directive */
99*4b9c6d91SCole Faust if (getline(&line, &len, config_file) == -1 ||
100*4b9c6d91SCole Faust !match_special_directive(line))
101*4b9c6d91SCole Faust return false;
102*4b9c6d91SCole Faust while (getmultiline(&line, &len, config_file) != -1) {
103*4b9c6d91SCole Faust char *stripped_line = strip(line);
104*4b9c6d91SCole Faust /*
105*4b9c6d91SCole Faust * Skip blank lines and all comments. Comment lines start with
106*4b9c6d91SCole Faust * '#'.
107*4b9c6d91SCole Faust */
108*4b9c6d91SCole Faust if (stripped_line[0] == '\0' || stripped_line[0] == '#')
109*4b9c6d91SCole Faust continue;
110*4b9c6d91SCole Faust
111*4b9c6d91SCole Faust /*
112*4b9c6d91SCole Faust * Check if the list is full, and reallocate with doubled
113*4b9c6d91SCole Faust * capacity if so.
114*4b9c6d91SCole Faust */
115*4b9c6d91SCole Faust if (list->num_entries >= list->num_allocated_) {
116*4b9c6d91SCole Faust list->num_allocated_ = list->num_allocated_ * 2;
117*4b9c6d91SCole Faust list->entries = realloc(
118*4b9c6d91SCole Faust list->entries,
119*4b9c6d91SCole Faust list->num_allocated_ * sizeof(struct config_entry));
120*4b9c6d91SCole Faust if (list->entries == NULL) {
121*4b9c6d91SCole Faust return false;
122*4b9c6d91SCole Faust }
123*4b9c6d91SCole Faust }
124*4b9c6d91SCole Faust
125*4b9c6d91SCole Faust struct config_entry *entry = &list->entries[list->num_entries];
126*4b9c6d91SCole Faust if (!parse_config_line(stripped_line, entry)) {
127*4b9c6d91SCole Faust return false;
128*4b9c6d91SCole Faust }
129*4b9c6d91SCole Faust ++list->num_entries;
130*4b9c6d91SCole Faust }
131*4b9c6d91SCole Faust /*
132*4b9c6d91SCole Faust * getmultiline() behaves similarly with getline(3). It returns -1
133*4b9c6d91SCole Faust * when read into EOF or the following errors.
134*4b9c6d91SCole Faust * Caveat: EINVAL may happen when EOF is encountered in a valid stream.
135*4b9c6d91SCole Faust */
136*4b9c6d91SCole Faust if ((errno == EINVAL && config_file == NULL) || errno == ENOMEM) {
137*4b9c6d91SCole Faust return false;
138*4b9c6d91SCole Faust }
139*4b9c6d91SCole Faust
140*4b9c6d91SCole Faust /* Shrink the list to save memory. */
141*4b9c6d91SCole Faust if (list->num_entries < list->num_allocated_) {
142*4b9c6d91SCole Faust list->entries =
143*4b9c6d91SCole Faust realloc(list->entries,
144*4b9c6d91SCole Faust list->num_entries * sizeof(struct config_entry));
145*4b9c6d91SCole Faust list->num_allocated_ = list->num_entries;
146*4b9c6d91SCole Faust }
147*4b9c6d91SCole Faust
148*4b9c6d91SCole Faust return true;
149*4b9c6d91SCole Faust }
150