summaryrefslogtreecommitdiff
path: root/src/settings.c
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2018-02-17 18:20:53 -0800
committerVito Caputo <vcaputo@pengaru.com>2018-02-20 13:58:14 -0800
commite223dd74bf9ffd4dee0bcbf5f3cee07643406579 (patch)
tree3414441392a35bfe158b4438ffee9a299fbb48ab /src/settings.c
parent9e6c1726de883b042725ea6630bc4d95e526879a (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.c329
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;
+}
© All Rights Reserved