config.c 8.55 KB
Newer Older
swym's avatar
swym committed
1
2
3
#include "modules/log.h"
#include "modules/config.h"
#include "modules/filesystem.h"
4
#include "epicardium.h"
swym's avatar
swym committed
5
6
7
8
9
10
11

#include <assert.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
12
#include <stddef.h>
swym's avatar
swym committed
13

14
#define MAX_LINE_LENGTH 80
15
16
17
#define KEYS_PER_BLOCK 16
#define KEY_LENGTH 16
#define NOT_INT_MAGIC 0x80000000
swym's avatar
swym committed
18

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// one key-value pair representing a line in the config
typedef struct {
	char key[KEY_LENGTH];

	// the value in the config file, if it's an integer.
	// for strings it's set to NOT_INT_MAGIC
	int value;

	// the byte offset in the config file to read the value string
	size_t value_offset;
} config_slot;

// a block of 16 config slots
// if more are needed, this becomes a linked list
typedef struct {
	config_slot slots[KEYS_PER_BLOCK];
	void *next;
} config_block;

static config_block *config_data = NULL;

// returns the config slot for a key name
static config_slot *find_config_slot(const char *key)
swym's avatar
swym committed
42
{
43
44
45
46
47
48
49
50
51
52
53
54
55
56
	config_block *current = config_data;

	while (current) {
		for (int i = 0; i < KEYS_PER_BLOCK; i++) {
			config_slot *k = &current->slots[i];

			if (strcmp(k->key, key) == 0) {
				// found what we're looking for
				return k;

			} else if (*k->key == '\0') {
				// found the first empty key
				return NULL;
			}
swym's avatar
swym committed
57
		}
58
		current = current->next;
swym's avatar
swym committed
59
	}
60

swym's avatar
swym committed
61
62
63
	return NULL;
}

64
65
// returns the next available config slot, or allocates a new block if needed
static config_slot *allocate_config_slot()
swym's avatar
swym committed
66
{
67
68
69
70
	config_block *current;

	if (config_data == NULL) {
		config_data = malloc(sizeof(config_block));
71
		assert(config_data != NULL);
72
		memset(config_data, 0, sizeof(config_block));
swym's avatar
swym committed
73
74
	}

75
76
	current = config_data;

77
	while (true) {
78
79
80
81
82
83
84
85
86
87
		for (int i = 0; i < KEYS_PER_BLOCK; i++) {
			config_slot *k = &current->slots[i];
			if (*k->key == '\0') {
				return k;
			}
		}

		// this block is full and there's no next allocated block
		if (current->next == NULL) {
			current->next = malloc(sizeof(config_block));
88
89
			assert(current->next != NULL);
			memset(current->next, 0, sizeof(config_block));
90
		}
91
		current = current->next;
swym's avatar
swym committed
92
93
94
	}
}

95
96
// parses an int out of 'value' or returns NOT_INT_MAGIC
static int try_parse_int(const char *value)
swym's avatar
swym committed
97
98
99
{
	char *endptr;
	size_t len = strlen(value);
100
101
	int v      = strtol(value, &endptr, 0);

swym's avatar
swym committed
102
	if (endptr != (value + len)) {
103
		return NOT_INT_MAGIC;
swym's avatar
swym committed
104
105
	}

106
	return v;
swym's avatar
swym committed
107
108
}

109
110
111
112
113
// loads a key/value pair into a new config slot
static void add_config_pair(
	const char *key, const char *value, int line_number, size_t value_offset
) {
	if (strlen(key) > KEY_LENGTH - 1) {
swym's avatar
swym committed
114
115
		LOG_WARN(
			"card10.cfg",
116
117
			"line:%d: too long - aborting",
			line_number
swym's avatar
swym committed
118
119
120
		);
		return;
	}
121
122
123
124
125

	config_slot *slot = allocate_config_slot();
	strncpy(slot->key, key, KEY_LENGTH);
	slot->value        = try_parse_int(value);
	slot->value_offset = value_offset;
swym's avatar
swym committed
126
127
}

128
129
130
// parses one line of the config file
static void
parse_line(char *line, char *eol, int line_number, size_t line_offset)
swym's avatar
swym committed
131
{
132
133
	char *line_start = line;

swym's avatar
swym committed
134
	//skip leading whitespace
Rahix's avatar
Rahix committed
135
	while (*line && isspace((int)*line))
swym's avatar
swym committed
136
137
138
139
140
141
142
143
144
145
146
147
148
		++line;

	char *key = line;
	if (*key == '#') {
		//skip comments
		return;
	}

	char *eq = strchr(line, '=');
	if (!eq) {
		if (*key) {
			LOG_WARN(
				"card10.cfg",
149
150
				"line %d: syntax error",
				line_number
swym's avatar
swym committed
151
152
153
154
155
156
157
			);
		}
		return;
	}

	char *e_key = eq - 1;
	//skip trailing whitespace in key
Rahix's avatar
Rahix committed
158
	while (e_key > key && isspace((int)*e_key))
swym's avatar
swym committed
159
160
161
		--e_key;
	e_key[1] = '\0';
	if (*key == '\0') {
162
		LOG_WARN("card10.cfg", "line %d: empty key", line_number);
swym's avatar
swym committed
163
164
165
166
167
		return;
	}

	char *value = eq + 1;
	//skip leading whitespace
Rahix's avatar
Rahix committed
168
	while (*value && isspace((int)*value))
swym's avatar
swym committed
169
170
171
172
		++value;

	char *e_val = eol - 1;
	//skip trailing whitespace
Rahix's avatar
Rahix committed
173
	while (e_val > value && isspace((int)*e_val))
swym's avatar
swym committed
174
175
176
177
178
		--e_val;
	if (*value == '\0') {
		LOG_WARN(
			"card10.cfg",
			"line %d: empty value for option '%s'",
179
			line_number,
swym's avatar
swym committed
180
181
182
183
184
			key
		);
		return;
	}

185
	size_t value_offset = value - line_start + line_offset;
swym's avatar
swym committed
186

187
	add_config_pair(key, value, line_number, value_offset);
swym's avatar
swym committed
188
189
}

190
191
192
193
194
195
196
197
198
199
200
201
// convert windows line endings to unix line endings.
// we don't care about the extra empty lines
static void convert_crlf_to_lflf(char *buf, int n)
{
	while (n--) {
		if (*buf == '\r') {
			*buf = '\n';
		}
		buf++;
	}
}

202
// parses the entire config file
swym's avatar
swym committed
203
204
205
206
207
208
209
210
211
212
213
214
215
void load_config(void)
{
	LOG_DEBUG("card10.cfg", "loading...");
	int fd = epic_file_open("card10.cfg", "r");
	if (fd < 0) {
		LOG_DEBUG(
			"card10.cfg",
			"loading failed: %s (%d)",
			strerror(-fd),
			fd
		);
		return;
	}
216
	char buf[MAX_LINE_LENGTH + 1];
217
218
	int line_number    = 0;
	size_t file_offset = 0;
swym's avatar
swym committed
219
220
	int nread;
	do {
221
		nread = epic_file_read(fd, buf, MAX_LINE_LENGTH);
222
		convert_crlf_to_lflf(buf, nread);
223
		if (nread < MAX_LINE_LENGTH) {
swym's avatar
swym committed
224
			//add fake EOL to ensure termination
225
			buf[nread++] = '\n';
swym's avatar
swym committed
226
		}
227
228
		//zero-terminate buffer
		buf[nread]   = '\0';
swym's avatar
swym committed
229
230
231
232
		char *line   = buf;
		char *eol    = NULL;
		int last_eol = 0;
		while (line) {
233
			//line points one character past the last (if any) '\n' hence '- 1'
swym's avatar
swym committed
234
235
			last_eol = line - buf - 1;
			eol      = strchr(line, '\n');
236
			++line_number;
swym's avatar
swym committed
237
238
			if (eol) {
				*eol = '\0';
239
240
				parse_line(line, eol, line_number, file_offset);
				file_offset += eol - line + 1;
swym's avatar
swym committed
241
				line = eol + 1;
242
				continue;
swym's avatar
swym committed
243
			}
244
245
246
247
248
			if (line == buf) {
				//line did not fit into buf
				LOG_WARN(
					"card10.cfg",
					"line:%d: too long - aborting",
249
					line_number
250
251
252
253
				);
				return;
			}
			int seek_back = last_eol - nread;
254

255
256
257
258
259
260
261
262
263
264
265
			LOG_DEBUG(
				"card10.cfg",
				"nread, last_eol, seek_back: %d,%d,%d",
				nread,
				last_eol,
				seek_back
			);
			assert(seek_back <= 0);
			if (!seek_back) {
				break;
			}
266

267
268
269
270
271
272
273
			int rc = epic_file_seek(fd, seek_back, SEEK_CUR);
			if (rc < 0) {
				LOG_ERR("card10.cfg", "seek failed, aborting");
				return;
			}
			char newline;
			rc = epic_file_read(fd, &newline, 1);
274
			if (rc < 0 || (newline != '\n' && newline != '\r')) {
275
276
277
278
279
280
281
282
283
284
				LOG_ERR("card10.cfg", "seek failed, aborting");
				LOG_DEBUG(
					"card10.cfg",
					"seek failed at read-back of newline: rc: %d read: %d",
					rc,
					(int)newline
				);
				return;
			}
			break;
swym's avatar
swym committed
285
		}
286
	} while (nread == MAX_LINE_LENGTH);
287
	epic_file_close(fd);
swym's avatar
swym committed
288
}
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311

// opens the config file, seeks to seek_offset and reads buf_len bytes
// used for reading strings without storing them in memory
// since we don't need to optimize for that use case as much
static size_t read_config_offset(size_t seek_offset, char *buf, size_t buf_len)
{
	int fd = epic_file_open("card10.cfg", "r");
	if (fd < 0) {
		LOG_DEBUG(
			"card10.cfg",
			"opening config failed: %s (%d)",
			strerror(-fd),
			fd
		);
		return 0;
	}

	int rc = epic_file_seek(fd, seek_offset, SEEK_SET);
	if (rc < 0) {
		LOG_ERR("card10.cfg", "seek failed, aborting");
		return 0;
	}

312
313
	// one byte less to accommodate the 0 termination
	int nread = epic_file_read(fd, buf, buf_len - 1);
314
315
316
317
318
319
320
321

	buf[nread] = '\0';

	epic_file_close(fd);

	return nread;
}

322
// returns error if not found or invalid
323
int epic_config_get_integer(const char *key, int *value)
324
325
326
{
	config_slot *slot = find_config_slot(key);
	if (slot && slot->value != NOT_INT_MAGIC) {
327
328
329
330
331
332
333
334
335
336
		*value = slot->value;
		return 0;
	}
	return -ENOENT;
}

// returns default_value if not found or invalid
int config_get_integer_with_default(const char *key, int default_value)
{
	int value;
337
	int ret = epic_config_get_integer(key, &value);
338
339
340
341
	if (ret) {
		return default_value;
	} else {
		return value;
342
343
344
	}
}

345
// returns error if not found
346
int epic_config_get_string(const char *key, char *buf, size_t buf_len)
347
348
349
{
	config_slot *slot = find_config_slot(key);
	if (!(slot && slot->value_offset)) {
350
		return -ENOENT;
351
352
353
354
	}

	size_t nread = read_config_offset(slot->value_offset, buf, buf_len);
	if (nread == 0) {
355
		return -ENOENT;
356
357
358
359
360
361
362
	}

	char *eol = strchr(buf, '\n');
	if (eol) {
		*eol = '\0';
	}

363
	return 0;
364
365
}

366
367
368
369
// returns dflt if not found, otherwise same pointer as buf
char *config_get_string_with_default(
	const char *key, char *buf, size_t buf_len, char *dflt
) {
370
	int ret = epic_config_get_string(key, buf, buf_len);
371
372
373
374
375
376
377
378
	if (ret) {
		return dflt;
	} else {
		return buf;
	}
}

// returns error if not found or invalid
379
int epic_config_get_boolean(const char *key, bool *value)
380
{
381
	int int_value;
382
	int ret = epic_config_get_integer(key, &int_value);
383

384
385
386
	if (ret == 0) {
		*value = !!int_value;
		return 0;
387
388
389
	}

	char buf[MAX_LINE_LENGTH + 1];
390
	epic_config_get_string(key, buf, MAX_LINE_LENGTH);
391
392

	if (buf == NULL) {
393
		return -ENOENT;
394
395
396
	}

	if (!strcmp(buf, "true")) {
397
398
		*value = true;
		return 0;
399
	} else if (!strcmp(buf, "false")) {
400
401
		*value = false;
		return 0;
402
403
	}

404
405
406
407
408
409
410
	return -ERANGE;
}

// returns default_value if not found or invalid
bool config_get_boolean_with_default(const char *key, bool default_value)
{
	bool value;
411
	int ret = epic_config_get_boolean(key, &value);
412
413
414
415
416
	if (ret) {
		return default_value;
	} else {
		return value;
	}
417
}