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