aboutsummaryrefslogtreecommitdiff
path: root/config_parser.c
blob: 9b3aa22a8649be510d89e02a96f6b1bc50ecdb36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/* Copyright 2021 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "config_parser.h"

#include "util.h"

#define LIST_DEFAULT_SIZE (100)

struct config_entry_list *new_config_entry_list(void)
{
	/*
	 * There are <100 CLI options, configuration file will likely have
	 * a similar number of config entries.
	 */
	struct config_entry *entries =
	    calloc(LIST_DEFAULT_SIZE, sizeof(struct config_entry));
	if (!entries)
		return NULL;

	struct config_entry_list *list =
	    calloc(1, sizeof(struct config_entry_list));
	if (!list) {
		free(entries);
		return NULL;
	}
	list->entries = entries;
	list->num_allocated_ = LIST_DEFAULT_SIZE;
	list->num_entries = 0;
	return list;
}

void clear_config_entry(struct config_entry *entry)
{
	free((char *)entry->key);
	free((char *)entry->value);
}

void free_config_entry_list(struct config_entry_list *list)
{
	if (!list)
		return;
	for (size_t i = 0; i < list->num_entries; i++) {
		clear_config_entry(&list->entries[i]);
	}
	free(list->entries);
	free(list);
}

bool parse_config_line(const char *config_line, struct config_entry *entry)
{
	/* Parsing will modify |config_line| in place, so make a copy. */
	attribute_cleanup_str char *line = strdup(config_line);
	if (!line)
		return false;
	char *value = line;

	/* After tokenize call, |value| will point to a substring after '='.
	 * If there is no '=' in the string, |key| will contain the entire
	 * string while |value| will be NULL.
	 */
	char *key = tokenize(&value, "=");
	if (key)
		key = strip(key);
	if (value)
		value = strip(value);
	if (!key || key[0] == '\0' || (value && value[0] == '\0')) {
		warn("unable to parse %s", config_line);
		return false;
	}
	entry->key = strdup(key);
	entry->value = value ? strdup(value) : NULL;
	if (!entry->key || (value && !entry->value)) {
		clear_config_entry(entry);
		return false;
	}
	return true;
}

static bool match_special_directive(const char *line)
{
	return streq(line, "% minijail-config-file v0\n");
}

bool parse_config_file(FILE *config_file, struct config_entry_list *list)
{
	attribute_cleanup_str char *line = NULL;
	size_t len = 0;

	/* The first line must match the special directive */
	if (getline(&line, &len, config_file) == -1 ||
	    !match_special_directive(line))
		return false;
	while (getmultiline(&line, &len, config_file) != -1) {
		char *stripped_line = strip(line);
		/*
		 * Skip blank lines and all comments. Comment lines start with
		 * '#'.
		 */
		if (stripped_line[0] == '\0' || stripped_line[0] == '#')
			continue;

		/*
		 * Check if the list is full, and reallocate with doubled
		 * capacity if so.
		 */
		if (list->num_entries >= list->num_allocated_) {
			list->num_allocated_ = list->num_allocated_ * 2;
			list->entries = realloc(
			    list->entries,
			    list->num_allocated_ * sizeof(struct config_entry));
			if (list->entries == NULL) {
				return false;
			}
		}

		struct config_entry *entry = &list->entries[list->num_entries];
		if (!parse_config_line(stripped_line, entry)) {
			return false;
		}
		++list->num_entries;
	}
	/*
	 * getmultiline() behaves similarly with getline(3). It returns -1
	 * when read into EOF or the following errors.
	 * Caveat: EINVAL may happen when EOF is encountered in a valid stream.
	 */
	if ((errno == EINVAL && config_file == NULL) || errno == ENOMEM) {
		return false;
	}

	/* Shrink the list to save memory. */
	if (list->num_entries < list->num_allocated_) {
		list->entries =
		    realloc(list->entries,
			    list->num_entries * sizeof(struct config_entry));
		list->num_allocated_ = list->num_entries;
	}

	return true;
}