xref: /aosp_15_r20/external/minijail/config_parser.c (revision 4b9c6d91573e8b3a96609339b46361b5476dd0f9)
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