diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2018-02-17 18:20:53 -0800 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2018-02-20 13:58:14 -0800 |
commit | e223dd74bf9ffd4dee0bcbf5f3cee07643406579 (patch) | |
tree | 3414441392a35bfe158b4438ffee9a299fbb48ab /src/settings.c | |
parent | 9e6c1726de883b042725ea6630bc4d95e526879a (diff) |
settings: introduce abstract settings
Settings will be used to express configurable parameters in the
rendering modules and fb backends.
The goal is to address both commandline argument setting of parameters,
automatic use of defaults, as well as interactive configuration
including the outputting of the resulting settings in a form usable as
a commandline for future reuse.
Since settings can be numerous and highly varied from one module or
backend to another, a form similar to the Linux kernel's cmdline or
QEMU's approach has been adopted.
For example, a complete DRM backend, card selection and config would be:
rototiller --video=drm,dev=/dev/dri/card0,connector=LVDS-1,mode=1024x768@60
If any of the above were omitted, then the missing settings would be
interactively configured.
If you added --defaults, then any omissions would be automatically
filled in with the defaults.
i.e.
rototiller --video=drm,dev=/dev/dri/card4 --defaults
would use the preferred connector and mode for that card.
rototiller --video=drm --defaults
would do the same but also default to the /dev/dri/card0 path.
for brevity, I omitted rendering modules from above, but the same
approach applies simply to --module=:
rototiller --module=sparkler --video=drm --defaults
If you ran rototiller without any arguments, then a fully interactive
setup would ensue for module and video.
If you ran rototiller with just --defaults, then everything is
defaulted for you. A default rendering module will be used (the
original roto renderer, probably).
Note that this commit only adds scaffolding to make this possible,
none of this is wired up yet.
Diffstat (limited to 'src/settings.c')
-rw-r--r-- | src/settings.c | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/src/settings.c b/src/settings.c new file mode 100644 index 0000000..e958b43 --- /dev/null +++ b/src/settings.c @@ -0,0 +1,329 @@ +#include <assert.h> +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include "settings.h" + +/* Split form of key=value[,key=value...] settings string */ +typedef struct settings_t { + unsigned num; + const char **keys; + const char **values; +} settings_t; + +typedef enum settings_fsm_state_t { + SETTINGS_FSM_STATE_KEY, + SETTINGS_FSM_STATE_EQUAL, + SETTINGS_FSM_STATE_VALUE, + SETTINGS_FSM_STATE_COMMA, +} settings_fsm_state_t; + + +static int add_value(settings_t *settings, const char *key, const char *value) +{ + assert(settings); + + settings->num++; + /* TODO errors */ + settings->keys = realloc(settings->keys, settings->num * sizeof(const char **)); + settings->values = realloc(settings->values, settings->num * sizeof(const char **)); + settings->keys[settings->num - 1] = key; + settings->values[settings->num - 1] = value; + + return 0; +} + + +/* split settings_string into a data structure */ +settings_t * settings_new(const char *settings_string) +{ + settings_fsm_state_t state = SETTINGS_FSM_STATE_KEY; + const char *p, *token; + settings_t *settings; + + settings = calloc(1, sizeof(settings_t)); + if (!settings) + return NULL; + + if (!settings_string) + return settings; + + /* TODO: unescaping? */ + for (token = p = settings_string; ;p++) { + + switch (state) { + case SETTINGS_FSM_STATE_COMMA: + token = p; + state = SETTINGS_FSM_STATE_KEY; + break; + + case SETTINGS_FSM_STATE_KEY: + if (*p == '=' || *p == ',' || *p == '\0') { + add_value(settings, strndup(token, p - token), NULL); + + if (*p == '=') + state = SETTINGS_FSM_STATE_EQUAL; + else if (*p == ',') + state = SETTINGS_FSM_STATE_COMMA; + } + break; + + case SETTINGS_FSM_STATE_EQUAL: + token = p; + state = SETTINGS_FSM_STATE_VALUE; + break; + + case SETTINGS_FSM_STATE_VALUE: + if (*p == ',' || *p == '\0') { + settings->values[settings->num - 1] = strndup(token, p - token); + state = SETTINGS_FSM_STATE_COMMA; + } + break; + + default: + assert(0); + } + + if (*p == '\0') + break; + } + + /* FIXME: this should probably never leave a value or key entry NULL */ + + return settings; +} + + +/* free structure attained via settings_new() */ +void settings_free(settings_t *settings) +{ + unsigned i; + + assert(settings); + + for (i = 0; i < settings->num; i++) { + free((void *)settings->keys[i]); + free((void *)settings->values[i]); + } + + free((void *)settings->keys); + free((void *)settings->values); + free(settings); +} + + +/* find key= in settings, return dup of value side or NULL if missing */ +const char * settings_get_value(settings_t *settings, const char *key) +{ + int i; + + assert(settings); + assert(key); + + for (i = 0; i < settings->num; i++) { + if (!strcmp(key, settings->keys[i])) + return settings->values[i]; + } + + return NULL; +} + + +/* return positional key from settings */ +const char * settings_get_key(settings_t *settings, unsigned pos) +{ + assert(settings); + + if (pos < settings->num) + return settings->keys[pos]; + + return NULL; +} + + +/* add key=value to the settings, + * or just key if value is NULL. + */ +/* returns < 0 on error */ +int settings_add_value(settings_t *settings, const char *key, const char *value) +{ + assert(settings); + assert(key); + + return add_value(settings, strdup(key), value ? strdup(value) : NULL); +} + + +/* apply the supplied setting description generators to the supplied settings */ +/* returns 0 when input settings are complete */ +/* returns 1 when input settings are incomplete, storing the next setting's description needed in *next_setting */ +/* returns -errno on error */ +int settings_apply_desc_generators(settings_t *settings, const setting_desc_generator_t generators[], unsigned n_generators, void *setup_context, setting_desc_t **next_setting) +{ + unsigned i; + setting_desc_t *next; + + assert(settings); + assert(generators); + assert(n_generators > 0); + assert(next_setting); + + for (i = 0; i < n_generators; i++) { + const setting_desc_generator_t *g = &generators[i]; + const char *value; + + value = settings_get_value(settings, g->key); + if (value) { + if (g->value_ptr) + *g->value_ptr = value; + + continue; + } + + next = g->func(setup_context); + if (!next) + return -ENOMEM; + + *next_setting = next; + + return 1; + } + + return 0; +} + + +/* convenience helper for creating a new setting description */ +/* copies of everything supplied are made in newly allocated memory */ +setting_desc_t * setting_desc_new(const char *name, const char *key, const char *regex, const char *preferred, const char *values[], const char *annotations[]) +{ + setting_desc_t *desc; + + assert(name); + assert(preferred); /* XXX: require a preferred default? */ + assert(!annotations || values); + + desc = calloc(1, sizeof(setting_desc_t)); + if (!desc) + return NULL; + + desc->name = strdup(name); + if (key) /* This is inappropriately subtle, but when key is NULL, the value will be the key, and there will be no value side at all. */ + desc->key = strdup(key); + if (regex) + desc->regex = strdup(regex); + + desc->preferred = strdup(preferred); + + if (values) { + unsigned i; + + for (i = 0; values[i]; i++); + + desc->values = calloc(i + 1, sizeof(*values)); + + if (annotations) + desc->annotations = calloc(i + 1, sizeof(*annotations)); + + for (i = 0; values[i]; i++) { + desc->values[i] = strdup(values[i]); + + if (annotations) { + assert(annotations[i]); + desc->annotations[i] = strdup(annotations[i]); + } + } + } + + /* TODO: handle allocation errors above... */ + + return desc; +} + + +void setting_desc_free(setting_desc_t *desc) +{ + free((void *)desc->name); + free((void *)desc->key); + free((void *)desc->regex); + free((void *)desc->preferred); + + if (desc->values) { + unsigned i; + + for (i = 0; desc->values[i]; i++) { + free((void *)desc->values[i]); + + if (desc->annotations) + free((void *)desc->annotations[i]); + } + + free((void *)desc->values); + free((void *)desc->annotations); + } + + free(desc); +} + + +/* wrapper around sprintf for convenient buffer size computation */ +/* supply NULL buf when computing size, size and offset are ignored. + * supply non-NULL for actual writing into buf of size bytes @ offset. + * return value is number of bytes (potentially if !buf) written + */ +static int snpf(char *buf, size_t size, off_t offset, const char *format, ...) +{ + size_t avail = 0; + va_list ap; + int r; + + if (buf) { + assert(size > offset); + + avail = size - offset; + buf += offset; + } + + va_start(ap, format); + r = vsnprintf(buf, avail, format, ap); + va_end(ap); + + return r; +} + + +char * settings_as_arg(const settings_t *settings) +{ + char *buf = NULL; + size_t off, size; + + /* intentionally avoided open_memstream for portability reasons */ + for (;;) { + unsigned i; + + for (i = off = 0; i < settings->num; i++) { + if (i > 0) + off += snpf(buf, size, off, ","); + + off += snpf(buf, size, off, "%s", settings->keys[i]); + + if (settings->values[i]) + off += snpf(buf, size, off, "=%s", settings->values[i]); + } + + if (!buf) { + size = off + 1; + buf = calloc(size, sizeof(char)); + if (!buf) + return NULL; + + continue; + } + + break; + } + + return buf; +} |