Commit f1d63669 authored by swym's avatar swym Committed by Rahix

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 956c9c91
#include "modules/modules.h" #include "modules/modules.h"
#include "modules/log.h" #include "modules/log.h"
#include "modules/filesystem.h" #include "modules/filesystem.h"
#include "modules/config.h"
#include "card10-version.h" #include "card10-version.h"
#include "FreeRTOS.h" #include "FreeRTOS.h"
...@@ -20,6 +21,8 @@ int main(void) ...@@ -20,6 +21,8 @@ int main(void)
LOG_DEBUG("startup", "Initializing hardware ..."); LOG_DEBUG("startup", "Initializing hardware ...");
hardware_early_init(); hardware_early_init();
load_config();
/* /*
* Version Splash * Version Splash
*/ */
......
...@@ -70,7 +70,7 @@ subdir('ble/') ...@@ -70,7 +70,7 @@ subdir('ble/')
subdir('l0der/') subdir('l0der/')
epicardium_cargs = [] epicardium_cargs = ['-D_POSIX_C_SOURCE=200809']
if get_option('jailbreak_card10') if get_option('jailbreak_card10')
epicardium_cargs += [ epicardium_cargs += [
'-DJAILBREAK_CARD10=1', '-DJAILBREAK_CARD10=1',
......
#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>
#include <unistd.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] = {
/* clang-format off */
#define INIT_Boolean(v) { .boolean = (v) }
#define INIT_Int(v) { .integer = (v) }
#define INIT_Float(v) { .floating_point = (v) }
#define INIT_String(v) { .string = (v) }
#define INIT_(tp, v) INIT_ ## tp (v)
#define INIT(tp, v) INIT_ (tp, v)
#define CARD10_SETTING(identifier, spelling, tp, default_value) \
[Option ## identifier] = { .name = (spelling), \
.type = OptionType_ ## tp, \
.value = INIT(tp, (default_value)) },
#include "modules/config.def"
/* clang-format on */
};
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 v = strtol(value, &endptr, 0);
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...
char *leaks = strdup(value);
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 lineNumber = 0;
int nread;
do {
//zero-terminate in case file is empty
buf[0] = '\0';
nread = epic_file_read(fd, buf, sizeof(buf));
if (nread < sizeof(buf)) {
//add fake EOL to ensure termination
buf[nread] = '\n';
}
char *line = buf;
char *eol = NULL;
int last_eol = 0;
while (line) {
//line points one character past the las (if any) '\n' hence '- 1'
last_eol = line - buf - 1;
eol = strchr(line, '\n');
++lineNumber;
if (eol) {
*eol = '\0';
doline(line, eol, lineNumber);
line = eol + 1;
} else {
if (line == buf) {
//line did not fit into buf
LOG_WARN(
"card10.cfg",
"line:%d: too long - aborting",
lineNumber
);
return;
} else {
int seek_back = last_eol - nread;
LOG_DEBUG(
"card10.cfg",
"nread, last_eol, seek_back: %d,%d,%d",
nread,
last_eol,
seek_back
);
assert(seek_back <= 0);
if (seek_back) {
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
);
if (rc < 0 || newline != '\n') {
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;
}
}
}
} while (nread == sizeof(buf));
}
#ifndef CARD10_SETTING
# define CARD10_SETTING(identifier, spelling, type, default_value)
#endif
CARD10_SETTING(ExecuteElf, "execute_elf", Boolean, false)
//CARD10_SETTING(Nick, "nick", String, "an0n")
//CARD10_SETTING(Timeout, "timeout", Integer, 123)
//CARD10_SETTING(Dampening, "dampening", Float, 420)
#undef CARD10_SETTING
#ifndef EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#define EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#include <stdbool.h>
enum EpicConfigOption {
#define CARD10_SETTING(identifier, spelling, type, default_value) Option ## identifier,
#include "modules/config.def"
_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
#include "epicardium.h" #include "epicardium.h"
#include "modules/log.h" #include "modules/log.h"
#include "modules/modules.h" #include "modules/modules.h"
#include "modules/config.h"
#include "api/dispatcher.h" #include "api/dispatcher.h"
#include "api/interrupt-sender.h" #include "api/interrupt-sender.h"
#include "l0der/l0der.h" #include "l0der/l0der.h"
...@@ -49,6 +50,7 @@ static volatile struct load_info async_load = { ...@@ -49,6 +50,7 @@ static volatile struct load_info async_load = {
/* Whether to write the menu script before attempting to load. */ /* Whether to write the menu script before attempting to load. */
static volatile bool write_menu = false; static volatile bool write_menu = false;
static bool execute_elfs = false;
/* Helpers {{{ */ /* Helpers {{{ */
...@@ -88,9 +90,7 @@ static int load_stat(char *name) ...@@ -88,9 +90,7 @@ static int load_stat(char *name)
*/ */
static int do_load(struct load_info *info) static int do_load(struct load_info *info)
{ {
#if defined(JAILBREAK_CARD10) && (JAILBREAK_CARD10 == 1)
struct l0dable_info l0dable; struct l0dable_info l0dable;
#endif
int res; int res;
if (*info->name == '\0') { if (*info->name == '\0') {
...@@ -129,18 +129,22 @@ static int do_load(struct load_info *info) ...@@ -129,18 +129,22 @@ static int do_load(struct load_info *info)
case PL_PYTHON_INTERP: case PL_PYTHON_INTERP:
core1_load(PYCARDIUM_IVT, info->name); core1_load(PYCARDIUM_IVT, info->name);
break; break;
#if defined(JAILBREAK_CARD10) && (JAILBREAK_CARD10 == 1)
case PL_L0DABLE: case PL_L0DABLE:
res = l0der_load_path(info->name, &l0dable); if (execute_elfs) {
if (res != 0) { res = l0der_load_path(info->name, &l0dable);
LOG_ERR("lifecycle", "l0der failed: %d\n", res); if (res != 0) {
xSemaphoreGive(api_mutex); LOG_ERR("lifecycle", "l0der failed: %d\n", res);
return -ENOEXEC; xSemaphoreGive(api_mutex);
return -ENOEXEC;
}
core1_load(l0dable.isr_vector, "");
} else {
LOG_WARN(
"lifecycle",
"Execution of .elf l0dables is disabled"
);
} }
core1_load(l0dable.isr_vector, "");
break; break;
#endif
default: default:
LOG_ERR("lifecyle", LOG_ERR("lifecyle",
"Attempted to load invalid payload (%s)", "Attempted to load invalid payload (%s)",
...@@ -379,6 +383,8 @@ void vLifecycleTask(void *pvParameters) ...@@ -379,6 +383,8 @@ void vLifecycleTask(void *pvParameters)
hardware_init(); hardware_init();
execute_elfs = config_get_boolean(OptionExecuteElf);
/* When triggered, reset core 1 to menu */ /* When triggered, reset core 1 to menu */
while (1) { while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
......
...@@ -21,5 +21,6 @@ module_sources = files( ...@@ -21,5 +21,6 @@ module_sources = files(
'trng.c', 'trng.c',
'vibra.c', 'vibra.c',
'watchdog.c', 'watchdog.c',
'usb.c' 'usb.c',
'config.c',
) )
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment