#include #include #include #include #include #include "settings.h" #include "util.h" #ifdef __WIN32__ char * strndup(const char *s, size_t n) { size_t len; char *buf; for (len = 0; len < n; len++) { if (!s[len]) break; } buf = calloc(len + 1, sizeof(char)); if (!buf) return NULL; memcpy(buf, s, len); return buf; } #endif /* 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() */ settings_t * settings_free(settings_t *settings) { if (settings) { for (unsigned 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); } return NULL; } /* find key= in settings, return dup of value side or NULL if missing */ const char * settings_get_value(const 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(const 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(const 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; setting_desc_t *desc; int r; r = g->func(setup_context, &desc); if (r < 0) return r; assert(desc); value = settings_get_value(settings, g->key); if (value) { int r; r = setting_desc_check(desc, value); setting_desc_free(desc); if (r < 0) return r; if (g->value_ptr) *g->value_ptr = value; continue; } *next_setting = desc; return 1; } return 0; } /* convenience helper for creating a new setting description */ /* copies of everything supplied are made in newly allocated memory, stored @ res_desc */ /* returns < 0 on error */ int setting_desc_clone(const setting_desc_t *desc, setting_desc_t **res_desc) { setting_desc_t *d; assert(desc); assert(desc->name); assert(desc->preferred); /* XXX: require a preferred default? */ assert(!desc->annotations || desc->values); assert(res_desc); d = calloc(1, sizeof(setting_desc_t)); if (!d) return -ENOMEM; d->name = strdup(desc->name); if (desc->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. */ d->key = strdup(desc->key); if (desc->regex) d->regex = strdup(desc->regex); d->preferred = strdup(desc->preferred); if (desc->values) { unsigned i; for (i = 0; desc->values[i]; i++); d->values = calloc(i + 1, sizeof(*desc->values)); if (desc->annotations) d->annotations = calloc(i + 1, sizeof(*desc->annotations)); for (i = 0; desc->values[i]; i++) { d->values[i] = strdup(desc->values[i]); if (desc->annotations) { assert(desc->annotations[i]); d->annotations[i] = strdup(desc->annotations[i]); } } } d->random = desc->random; /* TODO: handle allocation errors above... */ *res_desc = d; return 0; } setting_desc_t * setting_desc_free(setting_desc_t *desc) { if (desc) { free((void *)desc->name); free((void *)desc->key); free((void *)desc->regex); free((void *)desc->preferred); if (desc->values) { for (unsigned 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); } return NULL; } int setting_desc_check(const setting_desc_t *desc, const char *value) { assert(desc); if (desc->values) { for (int i = 0; desc->values[i]; i++) { if (!strcasecmp(desc->values[i], value)) return 0; } return -EINVAL; } /* TODO: apply regex check */ return 0; } /* 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; }