Skip to content
Snippets Groups Projects
Commit 54cb7169 authored by swym's avatar swym
Browse files

feat(epicardium): read card10.cfg

Adds simple config parser along with config_ API that:

- supports default values for options
- allows typed querying of config values
- types supported: boolean, integer, floating point and string

unknown options are ignored and LOG_WARNed on the console
parent 37da10f9
Branches
No related tags found
No related merge requests found
#include "modules/modules.h"
#include "modules/log.h"
#include "modules/filesystem.h"
#include "modules/config.h"
#include "card10-version.h"
#include "FreeRTOS.h"
......
#include "modules/log.h"
#include "modules/config.h"
#include "modules/filesystem.h"
#include <assert.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#define CONFIG_MAX_LINE_LENGTH 80
enum OptionType {
OptionType_Boolean,
OptionType_Int,
OptionType_Float,
OptionType_String,
};
struct config_option {
const char *name;
enum OptionType type;
union {
bool boolean;
long integer;
double floating_point;
char *string;
} value;
};
static struct config_option s_options[_EpicOptionCount] = {
[OptionExecuteElf] = { .name = "execute_elf",
.type = OptionType_Boolean,
.value = { .boolean = false } },
};
static struct config_option *findOption(const char *key)
{
for (int i = 0; i < _EpicOptionCount; ++i) {
if (!strcmp(key, s_options[i].name)) {
return &s_options[i];
}
}
return NULL;
}
static bool set_bool(struct config_option *opt, const char *value)
{
bool val;
if (!strcmp(value, "1")) {
val = true;
} else if (!strcmp(value, "true")) {
val = true;
} else if (!strcmp(value, "0")) {
val = false;
} else if (!strcmp(value, "false")) {
val = false;
} else {
return false;
}
opt->value.boolean = val;
LOG_DEBUG(
"card10.cfg",
"setting '%s' to %s",
opt->name,
val ? "true" : "false"
);
return true;
}
static bool set_int(struct config_option *opt, const char *value)
{
char *endptr;
size_t len = strlen(value);
int base = 10;
if (len > 2 && value[0] == '0' && value[1] == 'x') {
base = 16;
#ifdef CONFIG_ENABLE_OCTAL_NUMBERS
} else if (len > 1 && value[0] == '0') {
base = 8;
#endif
}
int v = strtol(value, &endptr, base);
if (endptr != (value + len)) {
return false;
}
opt->value.integer = v;
LOG_DEBUG("card10.cfg", "setting '%s' to %d (0x%08x)", opt->name, v, v);
return true;
}
static bool set_float(struct config_option *opt, const char *value)
{
char *endptr;
size_t len = strlen(value);
double v = strtod(value, &endptr);
if (endptr != (value + len)) {
return false;
}
opt->value.floating_point = v;
LOG_DEBUG("card10.cfg", "setting '%s' to %f", opt->name, v);
return true;
}
const char *elide(const char *str)
{
static char ret[21];
size_t len = strlen(str);
if (len <= 20) {
return str;
}
strncpy(ret, str, 17);
ret[17] = '.';
ret[18] = '.';
ret[19] = '.';
ret[20] = '\0';
return ret;
}
static bool set_string(struct config_option *opt, const char *value)
{
//this leaks, but the lifetime of these ends when epicardium exits, so...
size_t len = strlen(value);
char *leaks = (char *)malloc(len);
strncpy(leaks, value, len);
opt->value.string = leaks;
LOG_DEBUG("card10.cfg", "setting '%s' to %s", opt->name, elide(leaks));
return true;
}
static void configure(const char *key, const char *value, int lineNumber)
{
struct config_option *opt = findOption(key);
if (!opt) {
//invalid key
LOG_WARN(
"card10.cfg",
"line %d: ignoring unknown option '%s'",
lineNumber,
key
);
return;
}
bool ok = false;
switch (opt->type) {
case OptionType_Boolean:
ok = set_bool(opt, value);
break;
case OptionType_Int:
ok = set_int(opt, value);
break;
case OptionType_Float:
ok = set_float(opt, value);
break;
case OptionType_String:
ok = set_string(opt, value);
break;
default:
assert(0 && "unreachable");
}
if (!ok) {
LOG_WARN(
"card10.cfg",
"line %d: ignoring invalid value '%s' for option '%s'",
lineNumber,
value,
key
);
}
}
static void doline(char *line, char *eol, int lineNumber)
{
//skip leading whitespace
while (*line && isspace(*line))
++line;
char *key = line;
if (*key == '#') {
//skip comments
return;
}
char *eq = strchr(line, '=');
if (!eq) {
if (*key) {
LOG_WARN(
"card10.cfg",
"line %d (%s): syntax error",
lineNumber,
elide(line)
);
}
return;
}
char *e_key = eq - 1;
//skip trailing whitespace in key
while (e_key > key && isspace(*e_key))
--e_key;
e_key[1] = '\0';
if (*key == '\0') {
LOG_WARN("card10.cfg", "line %d: empty key", lineNumber);
return;
}
char *value = eq + 1;
//skip leading whitespace
while (*value && isspace(*value))
++value;
char *e_val = eol - 1;
//skip trailing whitespace
while (e_val > value && isspace(*e_val))
--e_val;
if (*value == '\0') {
LOG_WARN(
"card10.cfg",
"line %d: empty value for option '%s'",
lineNumber,
key
);
return;
}
configure(key, value, lineNumber);
}
bool config_get_boolean(enum EpicConfigOption option)
{
struct config_option *opt = &s_options[option];
assert(opt->type == OptionType_Boolean);
return opt->value.boolean;
}
long config_get_integer(enum EpicConfigOption option)
{
struct config_option *opt = &s_options[option];
assert(opt->type == OptionType_Int);
return opt->value.integer;
}
double config_get_float(enum EpicConfigOption option)
{
struct config_option *opt = &s_options[option];
assert(opt->type == OptionType_Float);
return opt->value.floating_point;
}
const char *config_get_string(enum EpicConfigOption option)
{
struct config_option *opt = &s_options[option];
assert(opt->type == OptionType_String);
return opt->value.string;
}
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;
}
char buf[CONFIG_MAX_LINE_LENGTH];
int nread = epic_file_read(fd, buf, sizeof(buf));
if (nread < sizeof(buf)) {
//add fake EOL
buf[nread] = '\n';
}
int lineNumber = 0;
while (nread) {
char *line = buf;
char *eol = NULL;
while (line) {
eol = strchr(line, '\n');
if (!eol) {
break;
}
++lineNumber;
*eol = '\0';
doline(line, eol, lineNumber);
line = eol + 1;
if (line - buf >= sizeof(buf)) {
//eol was right at the end of buf,
//break this loop and prevent memmove:
line = NULL;
}
}
if (line) {
int head = line - buf;
if (head == 0) {
//line did not fit into buf
LOG_WARN(
"card10.cfg",
"line:%d: too long",
lineNumber
);
break;
}
int tail = (int)sizeof(buf) - head;
memmove(buf, line, tail);
nread = epic_file_read(fd, buf + tail, head);
if (nread < head) {
//add fake-eol in case of partial read
buf[tail + nread] = '\n';
}
}
}
}
#ifndef EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#define EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#include <stdbool.h>
enum EpicConfigOption {
OptionExecuteElf,
_EpicOptionCount
};
//initialize configuration values and load card10.cfg
void load_config(void);
bool config_get_boolean(enum EpicConfigOption option);
long config_get_integer(enum EpicConfigOption option);
double config_get_float(enum EpicConfigOption option);
const char* config_get_string(enum EpicConfigOption option);
#endif//EPICARDIUM_MODULES_CONFIG_H_INCLUDED
......@@ -21,5 +21,6 @@ module_sources = files(
'trng.c',
'vibra.c',
'watchdog.c',
'usb.c'
'usb.c',
'config.c',
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment