aboutsummaryrefslogtreecommitdiffhomepage
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/backend.c404
-rw-r--r--core/backend.h18
-rw-r--r--core/config.c708
-rw-r--r--core/config.h54
-rw-r--r--core/plugin.c187
-rw-r--r--core/plugin.h3
6 files changed, 1374 insertions, 0 deletions
diff --git a/core/backend.c b/core/backend.c
new file mode 100644
index 0000000..83121bd
--- /dev/null
+++ b/core/backend.c
@@ -0,0 +1,404 @@
+#include <string.h>
+#ifndef _WIN32
+#define MM_API __attribute__((visibility ("default")))
+#else
+#define MM_API __attribute__((dllexport))
+#endif
+#define BACKEND_NAME "core/be"
+#include "midimonster.h"
+#include "backend.h"
+
+static struct {
+ size_t n;
+ backend* backends;
+ instance*** instances;
+} registry = {
+ .n = 0
+};
+
+//the global channel store was converted from a naive list to a hashmap of lists for performance reasons
+static struct {
+ //channelstore hash is set up for 256 buckets
+ size_t n[256];
+ channel** entry[256];
+} channels = {
+ .n = {
+ 0
+ }
+};
+
+static size_t channelstore_hash(instance* inst, uint64_t ident){
+ uint64_t repr = ((uint64_t) inst) ^ ident;
+ return (repr ^ (repr >> 8) ^ (repr >> 16) ^ (repr >> 24) ^ (repr >> 32)) & 0xFF;
+}
+
+int backends_handle(size_t nfds, managed_fd* fds){
+ size_t u, p, n;
+ int rv = 0;
+ managed_fd xchg;
+
+ for(u = 0; u < registry.n && !rv; u++){
+ n = 0;
+
+ for(p = 0; p < nfds; p++){
+ if(fds[p].backend == registry.backends + u){
+ xchg = fds[n];
+ fds[n] = fds[p];
+ fds[p] = xchg;
+ n++;
+ }
+ }
+
+ //handle if there is data ready or the backend has active instances for polling
+ if(n || registry.instances[u]){
+ DBGPF("Notifying backend %s of %" PRIsize_t " waiting FDs", registry.backends[u].name, n);
+ rv |= registry.backends[u].process(n, fds);
+ if(rv){
+ fprintf(stderr, "Backend %s failed to handle input\n", registry.backends[u].name);
+ }
+ }
+ }
+ return rv;
+}
+
+int backends_notify(size_t nev, channel** c, channel_value* v){
+ size_t u, p, n;
+ int rv = 0;
+ channel_value xval;
+ channel* xchnl = NULL;
+
+ for(u = 0; u < nev && !rv; u = n){
+ //sort for this instance
+ n = u + 1;
+ for(p = u + 1; p < nev; p++){
+ if(c[p]->instance == c[u]->instance){
+ xval = v[p];
+ xchnl = c[p];
+
+ v[p] = v[n];
+ c[p] = c[n];
+
+ v[n] = xval;
+ c[n] = xchnl;
+ n++;
+ }
+ }
+
+ //TODO eliminate duplicates
+ DBGPF("Calling handler for instance %s with %" PRIsize_t " events", c[u]->instance->name, n - u);
+ rv |= c[u]->instance->backend->handle(c[u]->instance, n - u, c + u, v + u);
+ }
+
+ return 0;
+}
+
+MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){
+ size_t u, bucket = channelstore_hash(inst, ident);
+ DBGPF("\tSearching for inst %" PRIu64 " ident %" PRIu64, (uint64_t) inst, ident);
+ for(u = 0; u < channels.n[bucket]; u++){
+ DBGPF("\tBucket %" PRIsize_t " entry %" PRIsize_t " inst %" PRIu64 " ident %" PRIu64, bucket, u, (uint64_t) channels.entry[bucket][u]->instance, channels.entry[bucket][u]->ident);
+ if(channels.entry[bucket][u]->instance == inst
+ && channels.entry[bucket][u]->ident == ident){
+ DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing (bucket %" PRIsize_t ", %" PRIsize_t " search steps)\n", ident, inst->name, bucket, u);
+ return channels.entry[bucket][u];
+ }
+ }
+
+ if(!create){
+ DBGPF("Requested unknown channel %" PRIu64 " (bucket %" PRIsize_t ") on instance %s", ident, bucket, inst->name);
+ return NULL;
+ }
+
+ DBGPF("Creating previously unknown channel %" PRIu64 " on instance %s, bucket %" PRIsize_t, ident, inst->name, bucket);
+ channels.entry[bucket] = realloc(channels.entry[bucket], (channels.n[bucket] + 1) * sizeof(channel*));
+ if(!channels.entry[bucket]){
+ fprintf(stderr, "Failed to allocate memory\n");
+ channels.n[bucket] = 0;
+ return NULL;
+ }
+
+ channels.entry[bucket][channels.n[bucket]] = calloc(1, sizeof(channel));
+ if(!channels.entry[bucket][channels.n[bucket]]){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ channels.entry[bucket][channels.n[bucket]]->instance = inst;
+ channels.entry[bucket][channels.n[bucket]]->ident = ident;
+ return channels.entry[bucket][(channels.n[bucket]++)];
+}
+
+MM_API void mm_channel_update(channel* chan, uint64_t ident){
+ size_t bucket = channelstore_hash(chan->instance, chan->ident), new_bucket = channelstore_hash(chan->instance, ident);
+ size_t u;
+
+ DBGPF("Updating identifier for inst %" PRIu64 " ident %" PRIu64 " (bucket %" PRIsize_t " to %" PRIsize_t ") to %" PRIu64, (uint64_t) chan->instance, chan->ident, bucket, new_bucket, ident);
+
+ if(bucket == new_bucket){
+ chan->ident = ident;
+ return;
+ }
+
+ for(u = 0; u < channels.n[bucket]; u++){
+ if(channels.entry[bucket][u]->instance == chan->instance
+ && channels.entry[bucket][u]->ident == chan->ident){
+ break;
+ }
+ }
+
+ if(u == channels.n[bucket]){
+ DBGPF("Failed to find channel to update in bucket %" PRIsize_t, bucket);
+ return;
+ }
+
+ DBGPF("Removing channel from slot %" PRIsize_t " of %" PRIsize_t " of bucket %" PRIsize_t, u, channels.n[bucket], bucket);
+ //remove channel from old bucket
+ for(; u < channels.n[bucket] - 1; u++){
+ channels.entry[bucket][u] = channels.entry[bucket][u + 1];
+ }
+
+ //add to new bucket
+ channels.entry[new_bucket] = realloc(channels.entry[new_bucket], (channels.n[new_bucket] + 1) * sizeof(channel*));
+ if(!channels.entry[new_bucket]){
+ fprintf(stderr, "Failed to allocate memory\n");
+ channels.n[new_bucket] = 0;
+ return;
+ }
+
+ channels.entry[new_bucket][channels.n[new_bucket]] = chan;
+ chan->ident = ident;
+ channels.n[bucket]--;
+ channels.n[new_bucket]++;
+}
+
+instance* mm_instance(backend* b){
+ size_t u = 0, n = 0;
+
+ for(u = 0; u < registry.n; u++){
+ if(registry.backends + u == b){
+ //count existing instances
+ for(n = 0; registry.instances[u] && registry.instances[u][n]; n++){
+ }
+
+ //extend
+ registry.instances[u] = realloc(registry.instances[u], (n + 2) * sizeof(instance*));
+ if(!registry.instances[u]){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return NULL;
+ }
+ //sentinel
+ registry.instances[u][n + 1] = NULL;
+ registry.instances[u][n] = calloc(1, sizeof(instance));
+ if(!registry.instances[u][n]){
+ fprintf(stderr, "Failed to allocate memory\n");
+ }
+ registry.instances[u][n]->backend = b;
+ return registry.instances[u][n];
+ }
+ }
+
+ //this should never happen
+ return NULL;
+}
+
+MM_API instance* mm_instance_find(char* name, uint64_t ident){
+ size_t b = 0;
+ instance** iter = NULL;
+ for(b = 0; b < registry.n; b++){
+ if(!strcmp(registry.backends[b].name, name)){
+ for(iter = registry.instances[b]; iter && *iter; iter++){
+ if((*iter)->ident == ident){
+ return *iter;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+MM_API int mm_backend_instances(char* name, size_t* ninst, instance*** inst){
+ size_t b = 0, i = 0;
+ if(!ninst || !inst){
+ return 1;
+ }
+
+ for(b = 0; b < registry.n; b++){
+ if(!strcmp(registry.backends[b].name, name)){
+ //count instances
+ for(i = 0; registry.instances[b] && registry.instances[b][i]; i++){
+ }
+
+ *ninst = i;
+ if(!i){
+ *inst = NULL;
+ return 0;
+ }
+
+ *inst = calloc(i, sizeof(instance*));
+ if(!*inst){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+
+ memcpy(*inst, registry.instances[b], i * sizeof(instance*));
+ return 0;
+ }
+ }
+ return 1;
+}
+
+backend* backend_match(char* name){
+ size_t u;
+ for(u = 0; u < registry.n; u++){
+ if(!strcmp(registry.backends[u].name, name)){
+ return registry.backends + u;
+ }
+ }
+ return NULL;
+}
+
+instance* instance_match(char* name){
+ size_t u;
+ instance** iter = NULL;
+ for(u = 0; u < registry.n; u++){
+ for(iter = registry.instances[u]; iter && *iter; iter++){
+ if(!strcmp(name, (*iter)->name)){
+ return *iter;
+ }
+ }
+ }
+ return NULL;
+}
+
+struct timeval backend_timeout(){
+ size_t u;
+ uint32_t res, secs = 1, msecs = 0;
+
+ for(u = 0; u < registry.n; u++){
+ //only call interval if backend has instances
+ if(registry.instances[u] && registry.backends[u].interval){
+ res = registry.backends[u].interval();
+ if(res && (res / 1000) < secs){
+ DBGPF("Updating interval to %" PRIu32 " msecs by request from %s", res, registry.backends[u].name);
+ secs = res / 1000;
+ msecs = res % 1000;
+ }
+ else if(res && res / 1000 == secs && (res % 1000) < msecs){
+ DBGPF("Updating interval to %" PRIu32 " msecs by request from %s", res, registry.backends[u].name);
+ msecs = res % 1000;
+ }
+ }
+ }
+
+ struct timeval tv = {
+ secs,
+ msecs * 1000
+ };
+ return tv;
+}
+
+MM_API int mm_backend_register(backend b){
+ if(!backend_match(b.name)){
+ registry.backends = realloc(registry.backends, (registry.n + 1) * sizeof(backend));
+ registry.instances = realloc(registry.instances, (registry.n + 1) * sizeof(instance**));
+ if(!registry.backends || !registry.instances){
+ fprintf(stderr, "Failed to allocate memory\n");
+ registry.n = 0;
+ return 1;
+ }
+ registry.backends[registry.n] = b;
+ registry.instances[registry.n] = NULL;
+ registry.n++;
+
+ fprintf(stderr, "Registered backend %s\n", b.name);
+ return 0;
+ }
+ return 1;
+}
+
+int backends_start(){
+ int rv = 0, current;
+ instance** inst = NULL;
+ size_t n, u;
+
+ for(u = 0; u < registry.n; u++){
+ //skip backends without instances
+ if(!registry.instances[u]){
+ continue;
+ }
+
+ //fetch list of instances
+ if(mm_backend_instances(registry.backends[u].name, &n, &inst)){
+ fprintf(stderr, "Failed to fetch instance list for initialization of backend %s\n", registry.backends[u].name);
+ return 1;
+ }
+
+ //start the backend
+ current = registry.backends[u].start(n, inst);
+ if(current){
+ fprintf(stderr, "Failed to start backend %s\n", registry.backends[u].name);
+ }
+
+ //clean up
+ free(inst);
+ inst = NULL;
+ rv |= current;
+ }
+ return rv;
+}
+
+static void channels_free(){
+ size_t u, p;
+ for(u = 0; u < sizeof(channels.n) / sizeof(channels.n[0]); u++){
+ DBGPF("Cleaning up channel registry bucket %" PRIsize_t " with %" PRIsize_t " channels", u, channels.n[u]);
+ for(p = 0; p < channels.n[u]; p++){
+ DBGPF("Destroying channel %" PRIu64 " on instance %s", channels.entry[u][p]->ident, channels.entry[u][p]->instance->name);
+ //call the channel_free function if the backend supports it
+ if(channels.entry[u][p]->impl && channels.entry[u][p]->instance->backend->channel_free){
+ channels.entry[u][p]->instance->backend->channel_free(channels.entry[u][p]);
+ }
+ free(channels.entry[u][p]);
+ }
+ free(channels.entry[u]);
+ channels.entry[u] = NULL;
+ channels.n[u] = 0;
+ }
+}
+
+int backends_stop(){
+ size_t u, n;
+ instance** inst = NULL;
+
+ //channels before instances to support proper shutdown procedures
+ channels_free();
+
+ //shut down the registry
+ for(u = 0; u < registry.n; u++){
+ //fetch list of instances
+ if(mm_backend_instances(registry.backends[u].name, &n, &inst)){
+ fprintf(stderr, "Failed to fetch instance list for shutdown of backend %s\n", registry.backends[u].name);
+ inst = NULL;
+ n = 0;
+ }
+
+ registry.backends[u].shutdown(n, inst);
+ free(inst);
+ inst = NULL;
+
+ //free instances
+ for(inst = registry.instances[u]; inst && *inst; inst++){
+ free((*inst)->name);
+ (*inst)->name = NULL;
+ (*inst)->backend = NULL;
+ free(*inst);
+ }
+ free(registry.instances[u]);
+ registry.instances[u] = NULL;
+ }
+
+ free(registry.backends);
+ free(registry.instances);
+ registry.n = 0;
+ return 0;
+}
diff --git a/core/backend.h b/core/backend.h
new file mode 100644
index 0000000..46c6c3a
--- /dev/null
+++ b/core/backend.h
@@ -0,0 +1,18 @@
+#include <sys/types.h>
+
+/* Internal API */
+int backends_handle(size_t nfds, managed_fd* fds);
+int backends_notify(size_t nev, channel** c, channel_value* v);
+backend* backend_match(char* name);
+instance* instance_match(char* name);
+struct timeval backend_timeout();
+int backends_start();
+int backends_stop();
+instance* mm_instance(backend* b);
+
+/* Backend API */
+MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create);
+MM_API void mm_channel_update(channel* chan, uint64_t ident);
+MM_API instance* mm_instance_find(char* name, uint64_t ident);
+MM_API int mm_backend_instances(char* name, size_t* ninst, instance*** inst);
+MM_API int mm_backend_register(backend b);
diff --git a/core/config.c b/core/config.c
new file mode 100644
index 0000000..c1c3124
--- /dev/null
+++ b/core/config.c
@@ -0,0 +1,708 @@
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#ifndef _WIN32
+#include <limits.h>
+#endif
+
+#define BACKEND_NAME "core/cfg"
+#include "midimonster.h"
+#include "config.h"
+#include "backend.h"
+
+static enum {
+ none,
+ backend_cfg,
+ instance_cfg,
+ map
+} parser_state = none;
+
+typedef enum {
+ map_ltr,
+ map_rtl,
+ map_bidir
+} map_type;
+
+static backend* current_backend = NULL;
+static instance* current_instance = NULL;
+static size_t noverrides = 0;
+static config_override* overrides = NULL;
+
+#ifdef _WIN32
+#define GETLINE_BUFFER 4096
+
+static ssize_t getline(char** line, size_t* alloc, FILE* stream){
+ size_t bytes_read = 0;
+ char c;
+ //sanity checks
+ if(!line || !alloc || !stream){
+ return -1;
+ }
+
+ //allocate buffer if none provided
+ if(!*line || !*alloc){
+ *alloc = GETLINE_BUFFER;
+ *line = calloc(GETLINE_BUFFER, sizeof(char));
+ if(!*line){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return -1;
+ }
+ }
+
+ if(feof(stream)){
+ return -1;
+ }
+
+ for(c = fgetc(stream); 1; c = fgetc(stream)){
+ //end of buffer, resize
+ if(bytes_read == (*alloc) - 1){
+ *alloc += GETLINE_BUFFER;
+ *line = realloc(*line, (*alloc) * sizeof(char));
+ if(!*line){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return -1;
+ }
+ }
+
+ //store character
+ (*line)[bytes_read] = c;
+
+ //end of line
+ if(feof(stream) || c == '\n'){
+ //terminate string
+ (*line)[bytes_read + 1] = 0;
+ return bytes_read;
+ }
+
+ //input broken
+ if(ferror(stream)){
+ return -1;
+ }
+
+ bytes_read++;
+ }
+}
+#endif
+
+static char* config_trim_line(char* in){
+ ssize_t n;
+ //trim front
+ for(; *in && !isgraph(*in); in++){
+ }
+
+ //trim back
+ for(n = strlen(in); n >= 0 && !isgraph(in[n]); n--){
+ in[n] = 0;
+ }
+
+ return in;
+}
+
+static int config_glob_parse_range(channel_glob* glob, char* spec, size_t length){
+ //FIXME might want to allow negative delimiters at some point
+ char* parse_offset = NULL;
+ glob->type = glob_range;
+
+ //first interval member
+ glob->limits.u64[0] = strtoul(spec, &parse_offset, 10);
+ if(!parse_offset || parse_offset - spec >= length || strncmp(parse_offset, "..", 2)){
+ return 1;
+ }
+
+ parse_offset += 2;
+ //second interval member
+ glob->limits.u64[1] = strtoul(parse_offset, &parse_offset, 10);
+ if(!parse_offset || parse_offset - spec != length || *parse_offset != '}'){
+ return 1;
+ }
+
+ //calculate number of channels within interval
+ if(glob->limits.u64[0] < glob->limits.u64[1]){
+ glob->values = glob->limits.u64[1] - glob->limits.u64[0] + 1;
+ }
+ else if(glob->limits.u64[0] > glob->limits.u64[1]){
+ glob->values = glob->limits.u64[0] - glob->limits.u64[1] + 1;
+ }
+ else{
+ glob->values = 1;
+ }
+
+ return 0;
+}
+
+static int config_glob_parse_list(channel_glob* glob, char* spec, size_t length){
+ size_t u = 0;
+ glob->type = glob_list;
+ glob->values = 1;
+
+ //count number of values in list
+ for(u = 0; u < length; u++){
+ if(spec[u] == ','){
+ glob->values++;
+ }
+ }
+ return 0;
+}
+
+static int config_glob_parse(channel_glob* glob, char* spec, size_t length){
+ size_t u = 0;
+
+ //detect glob type
+ for(u = 0; u < length; u++){
+ if(length - u > 2 && !strncmp(spec + u, "..", 2)){
+ DBGPF("Detected glob %.*s as range type", (int) length, spec);
+ return config_glob_parse_range(glob, spec, length);
+ }
+ else if(spec[u] == ','){
+ DBGPF("Detected glob %.*s as list type", (int) length, spec);
+ return config_glob_parse_list(glob, spec, length);
+ }
+ }
+
+ LOGPF("Failed to detect glob type for spec %.*s", (int) length, spec);
+ return 1;
+}
+
+static int config_glob_scan(instance* inst, channel_spec* spec){
+ char* glob_start = spec->spec, *glob_end = NULL;
+ size_t u;
+
+ //assume a spec is one channel as default
+ spec->channels = 1;
+
+ //scan and mark globs
+ for(glob_start = strchr(glob_start, '{'); glob_start; glob_start = strchr(glob_start, '{')){
+ glob_end = strchr(glob_start, '}');
+ if(!glob_end){
+ fprintf(stderr, "Failed to parse channel spec, unterminated glob: %s\n", spec->spec);
+ return 1;
+ }
+
+ spec->glob = realloc(spec->glob, (spec->globs + 1) * sizeof(channel_glob));
+ if(!spec->glob){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+
+ spec->glob[spec->globs].offset[0] = glob_start - spec->spec;
+ spec->glob[spec->globs].offset[1] = glob_end - spec->spec;
+ spec->globs++;
+
+ //skip this opening brace
+ glob_start++;
+ }
+
+ //try to parse globs internally
+ spec->internal = 1;
+ for(u = 0; u < spec->globs; u++){
+ if(config_glob_parse(spec->glob + u,
+ spec->spec + spec->glob[u].offset[0] + 1,
+ spec->glob[u].offset[1] - spec->glob[u].offset[0] - 1)){
+ spec->internal = 0;
+ break;
+ }
+ }
+ if(!spec->internal){
+ //TODO try to parse globs externally
+ fprintf(stderr, "Failed to parse glob %" PRIsize_t " in %s internally\n", u + 1, spec->spec);
+ return 1;
+ }
+
+ //calculate channel total
+ for(u = 0; u < spec->globs; u++){
+ spec->channels *= spec->glob[u].values;
+ }
+ return 0;
+}
+
+static ssize_t config_glob_resolve_range(char* spec, size_t length, channel_glob* glob, uint64_t n){
+ uint64_t current_value = glob->limits.u64[0] + (n % glob->values);
+ //if counting down
+ if(glob->limits.u64[0] > glob->limits.u64[1]){
+ current_value = glob->limits.u64[0] - (n % glob->values);
+ }
+
+ //write out value
+ return snprintf(spec, length, "%" PRIu64, current_value);
+}
+
+static ssize_t config_glob_resolve_list(char* spec, size_t length, channel_glob* glob, uint64_t n){
+ uint64_t current_replacement = 0;
+ size_t replacement_length = 0;
+ char* source = spec + 1;
+ n %= glob->values;
+
+ //find start of replacement value
+ DBGPF("Searching instance %" PRIu64 " of spec %.*s", n, (int) length, spec);
+ for(current_replacement = 0; current_replacement < n; current_replacement++){
+ for(; source[0] != ','; source++){
+ }
+ source++;
+ }
+
+ //calculate replacement length
+ for(; source[replacement_length] != ',' && source[replacement_length] != '}'; replacement_length++){
+ }
+
+ //write out new value
+ memmove(spec, source, replacement_length);
+ return replacement_length;
+}
+
+static channel* config_glob_resolve(instance* inst, channel_spec* spec, uint64_t n, uint8_t map_direction){
+ size_t glob = 0, glob_length;
+ ssize_t bytes = 0;
+ channel* result = NULL;
+ char* resolved_spec = strdup(spec->spec);
+
+ if(!resolved_spec){
+ LOG("Failed to allocate memory");
+ return NULL;
+ }
+
+ //TODO if not internal, try to resolve externally
+ //iterate and resolve globs
+ for(glob = spec->globs; glob > 0; glob--){
+ glob_length = spec->glob[glob - 1].offset[1] - spec->glob[glob - 1].offset[0];
+
+ switch(spec->glob[glob - 1].type){
+ case glob_range:
+ bytes = config_glob_resolve_range(resolved_spec + spec->glob[glob - 1].offset[0],
+ glob_length,
+ spec->glob + (glob - 1),
+ n);
+ break;
+ case glob_list:
+ bytes = config_glob_resolve_list(resolved_spec + spec->glob[glob - 1].offset[0],
+ glob_length,
+ spec->glob + (glob - 1),
+ n);
+ break;
+ }
+
+ n /= spec->glob[glob - 1].values;
+
+ //move trailing data
+ if(bytes > 0 && bytes < glob_length){
+ memmove(resolved_spec + spec->glob[glob - 1].offset[0] + bytes,
+ resolved_spec + spec->glob[glob - 1].offset[1] + 1,
+ strlen(spec->spec) - spec->glob[glob - 1].offset[1]);
+ }
+ else{
+ LOGPF("Failure parsing glob spec %s", resolved_spec);
+ goto bail;
+ }
+ }
+
+ DBGPF("Resolved spec %s to %s", spec->spec, resolved_spec);
+ result = inst->backend->channel(inst, resolved_spec, map_direction);
+ if(spec->globs && !result){
+ LOGPF("Failed to match multichannel evaluation %s to a channel", resolved_spec);
+ }
+
+bail:
+ free(resolved_spec);
+ return result;
+}
+
+static int config_map(char* to_raw, char* from_raw){
+ //create a copy because the original pointer may be used multiple times
+ char* to = strdup(to_raw), *from = strdup(from_raw);
+ channel_spec spec_to = {
+ .spec = to
+ }, spec_from = {
+ .spec = from
+ };
+ instance* instance_to = NULL, *instance_from = NULL;
+ channel* channel_from = NULL, *channel_to = NULL;
+ uint64_t n = 0;
+ int rv = 1;
+
+ if(!from || !to){
+ free(from);
+ free(to);
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+
+ //separate channel spec from instance
+ for(; *(spec_to.spec) && *(spec_to.spec) != '.'; spec_to.spec++){
+ }
+
+ for(; *(spec_from.spec) && *(spec_from.spec) != '.'; spec_from.spec++){
+ }
+
+ if(!spec_from.spec[0] || !spec_to.spec[0]){
+ fprintf(stderr, "Mapping does not contain a proper instance specification\n");
+ goto done;
+ }
+
+ //terminate
+ spec_from.spec[0] = spec_to.spec[0] = 0;
+ spec_from.spec++;
+ spec_to.spec++;
+
+ //find matching instances
+ instance_to = instance_match(to);
+ instance_from = instance_match(from);
+
+ if(!instance_to || !instance_from){
+ fprintf(stderr, "No such instance %s\n", instance_from ? to : from);
+ goto done;
+ }
+
+ //scan for globs
+ if(config_glob_scan(instance_to, &spec_to)
+ || config_glob_scan(instance_from, &spec_from)){
+ goto done;
+ }
+
+ if((spec_to.channels != spec_from.channels && spec_from.channels != 1 && spec_to.channels != 1)
+ || spec_to.channels == 0
+ || spec_from.channels == 0){
+ fprintf(stderr, "Multi-channel specification size mismatch: %s.%s (%" PRIsize_t " channels) - %s.%s (%" PRIsize_t " channels)\n",
+ instance_from->name,
+ spec_from.spec,
+ spec_from.channels,
+ instance_to->name,
+ spec_to.spec,
+ spec_to.channels);
+ goto done;
+ }
+
+ //iterate, resolve globs and map
+ rv = 0;
+ for(n = 0; !rv && n < max(spec_from.channels, spec_to.channels); n++){
+ channel_from = config_glob_resolve(instance_from, &spec_from, min(n, spec_from.channels), mmchannel_input);
+ channel_to = config_glob_resolve(instance_to, &spec_to, min(n, spec_to.channels), mmchannel_output);
+
+ if(!channel_from || !channel_to){
+ rv = 1;
+ goto done;
+ }
+ rv |= mm_map_channel(channel_from, channel_to);
+ }
+
+done:
+ free(spec_from.glob);
+ free(spec_to.glob);
+ free(from);
+ free(to);
+ return rv;
+}
+
+static int config_line(char* line){
+ map_type mapping_type = map_rtl;
+ char* separator = NULL;
+ size_t u;
+
+ line = config_trim_line(line);
+ if(*line == ';' || strlen(line) == 0){
+ //skip comments
+ return 0;
+ }
+ if(*line == '[' && line[strlen(line) - 1] == ']'){
+ if(!strncmp(line, "[backend ", 9)){
+ //backend configuration
+ parser_state = backend_cfg;
+ line[strlen(line) - 1] = 0;
+ current_backend = backend_match(line + 9);
+
+ if(!current_backend){
+ fprintf(stderr, "Cannot configure unknown backend %s\n", line + 9);
+ return 1;
+ }
+
+ //apply overrides
+ for(u = 0; u < noverrides; u++){
+ if(!overrides[u].handled && overrides[u].type == override_backend
+ && !strcmp(overrides[u].target, current_backend->name)){
+ if(current_backend->conf(overrides[u].option, overrides[u].value)){
+ fprintf(stderr, "Configuration override for %s failed for backend %s\n",
+ overrides[u].option, current_backend->name);
+ return 1;
+ }
+ overrides[u].handled = 1;
+ }
+ }
+ }
+ else if(!strncmp(line, "[include ", 9)){
+ line[strlen(line) - 1] = 0;
+ return config_read(line + 9);
+ }
+ else if(!strcmp(line, "[map]")){
+ //mapping configuration
+ parser_state = map;
+ }
+ else{
+ //backend instance configuration
+ parser_state = instance_cfg;
+
+ //trim braces
+ line[strlen(line) - 1] = 0;
+ line++;
+
+ //find separating space and terminate
+ for(separator = line; *separator && *separator != ' '; separator++){
+ }
+ if(!*separator){
+ fprintf(stderr, "No instance name specified for backend %s\n", line);
+ return 1;
+ }
+ *separator = 0;
+ separator++;
+
+ current_backend = backend_match(line);
+ if(!current_backend){
+ fprintf(stderr, "No such backend %s\n", line);
+ return 1;
+ }
+
+ if(instance_match(separator)){
+ fprintf(stderr, "Duplicate instance name %s\n", separator);
+ return 1;
+ }
+
+ //validate instance name
+ if(strchr(separator, ' ') || strchr(separator, '.')){
+ fprintf(stderr, "Invalid instance name %s\n", separator);
+ return 1;
+ }
+
+ current_instance = mm_instance(current_backend);
+ if(!current_instance){
+ return 1;
+ }
+
+ if(current_backend->create(current_instance)){
+ fprintf(stderr, "Failed to create %s instance %s\n", line, separator);
+ return 1;
+ }
+
+ current_instance->name = strdup(separator);
+ current_instance->backend = current_backend;
+ fprintf(stderr, "Created %s instance %s\n", line, separator);
+
+ //apply overrides
+ for(u = 0; u < noverrides; u++){
+ if(!overrides[u].handled && overrides[u].type == override_instance
+ && !strcmp(overrides[u].target, current_instance->name)){
+ if(current_backend->conf_instance(current_instance, overrides[u].option, overrides[u].value)){
+ fprintf(stderr, "Configuration override for %s failed for instance %s\n",
+ overrides[u].option, current_instance->name);
+ return 1;
+ }
+ overrides[u].handled = 1;
+ }
+ }
+ }
+ }
+ else if(parser_state == map){
+ mapping_type = map_rtl;
+ //find separator
+ for(separator = line; *separator && *separator != '<' && *separator != '>'; separator++){
+ }
+
+ switch(*separator){
+ case '>':
+ mapping_type = map_ltr;
+ //fall through
+ case '<': //default
+ *separator = 0;
+ separator++;
+ break;
+ case 0:
+ default:
+ fprintf(stderr, "Not a channel mapping: %s\n", line);
+ return 1;
+ }
+
+ if((mapping_type == map_ltr && *separator == '<')
+ || (mapping_type == map_rtl && *separator == '>')){
+ mapping_type = map_bidir;
+ separator++;
+ }
+
+ line = config_trim_line(line);
+ separator = config_trim_line(separator);
+
+ if(mapping_type == map_ltr || mapping_type == map_bidir){
+ if(config_map(separator, line)){
+ fprintf(stderr, "Failed to map channel %s to %s\n", line, separator);
+ return 1;
+ }
+ }
+ if(mapping_type == map_rtl || mapping_type == map_bidir){
+ if(config_map(line, separator)){
+ fprintf(stderr, "Failed to map channel %s to %s\n", separator, line);
+ return 1;
+ }
+ }
+ }
+ else{
+ //pass to parser
+ //find separator
+ separator = strchr(line, '=');
+ if(!separator){
+ fprintf(stderr, "Not an assignment (currently expecting %s configuration): %s\n", line, (parser_state == backend_cfg) ? "backend" : "instance");
+ return 1;
+ }
+
+ *separator = 0;
+ separator++;
+ line = config_trim_line(line);
+ separator = config_trim_line(separator);
+
+ if(parser_state == backend_cfg && current_backend->conf(line, separator)){
+ fprintf(stderr, "Failed to configure backend %s\n", current_backend->name);
+ return 1;
+ }
+ else if(parser_state == instance_cfg && current_backend->conf_instance(current_instance, line, separator)){
+ fprintf(stderr, "Failed to configure instance %s\n", current_instance->name);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int config_read(char* cfg_filepath){
+ int rv = 1;
+ size_t line_alloc = 0;
+ ssize_t status;
+ FILE* source = NULL;
+ char* line_raw = NULL;
+
+ //create heap copy of file name because original might be in readonly memory
+ char* source_dir = strdup(cfg_filepath), *source_file = NULL, original_dir[PATH_MAX * 2] = "";
+ #ifdef _WIN32
+ char path_separator = '\\';
+ #else
+ char path_separator = '/';
+ #endif
+
+ if(!source_dir){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+
+ //change working directory to the one containing the configuration file so relative paths work as expected
+ source_file = strrchr(source_dir, path_separator);
+ if(source_file){
+ *source_file = 0;
+ source_file++;
+
+ if(!getcwd(original_dir, sizeof(original_dir))){
+ fprintf(stderr, "Failed to read current working directory: %s\n", strerror(errno));
+ goto bail;
+ }
+
+ if(chdir(source_dir)){
+ fprintf(stderr, "Failed to change to configuration file directory %s: %s\n", source_dir, strerror(errno));
+ goto bail;
+ }
+ }
+ else{
+ source_file = source_dir;
+ }
+
+ fprintf(stderr, "Reading configuration file %s\n", cfg_filepath);
+ source = fopen(source_file, "r");
+
+ if(!source){
+ fprintf(stderr, "Failed to open %s for reading\n", cfg_filepath);
+ goto bail;
+ }
+
+ for(status = getline(&line_raw, &line_alloc, source); status >= 0; status = getline(&line_raw, &line_alloc, source)){
+ if(config_line(line_raw)){
+ goto bail;
+ }
+ }
+
+ //TODO check whether all overrides have been applied
+
+ rv = 0;
+bail:
+ //change back to previous directory to allow recursive configuration file parsing
+ if(source_file && source_dir != source_file){
+ chdir(original_dir);
+ }
+
+ free(source_dir);
+ if(source){
+ fclose(source);
+ }
+ free(line_raw);
+ return rv;
+}
+
+int config_add_override(override_type type, char* data_raw){
+ int rv = 1;
+ //heap a copy because the original data is probably not writable
+ char* data = strdup(data_raw);
+
+ if(!data){
+ fprintf(stderr, "Failed to allocate memory\n");
+ goto bail;
+ }
+
+ char* option = strchr(data, '.');
+ char* value = strchr(data, '=');
+
+ if(!option || !value){
+ fprintf(stderr, "Override %s is not a valid assignment\n", data_raw);
+ goto bail;
+ }
+
+ //terminate strings
+ *option = 0;
+ option++;
+
+ *value = 0;
+ value++;
+
+ config_override new = {
+ .type = type,
+ .handled = 0,
+ .target = strdup(config_trim_line(data)),
+ .option = strdup(config_trim_line(option)),
+ .value = strdup(config_trim_line(value))
+ };
+
+ if(!new.target || !new.option || !new.value){
+ fprintf(stderr, "Failed to allocate memory\n");
+ goto bail;
+ }
+
+ overrides = realloc(overrides, (noverrides + 1) * sizeof(config_override));
+ if(!overrides){
+ noverrides = 0;
+ fprintf(stderr, "Failed to allocate memory\n");
+ goto bail;
+ }
+ overrides[noverrides] = new;
+ noverrides++;
+
+ rv = 0;
+bail:
+ free(data);
+ return rv;
+}
+
+void config_free(){
+ size_t u;
+
+ for(u = 0; u < noverrides; u++){
+ free(overrides[u].target);
+ free(overrides[u].option);
+ free(overrides[u].value);
+ }
+
+ noverrides = 0;
+ free(overrides);
+ overrides = NULL;
+
+ parser_state = none;
+}
diff --git a/core/config.h b/core/config.h
new file mode 100644
index 0000000..b96a866
--- /dev/null
+++ b/core/config.h
@@ -0,0 +1,54 @@
+/*
+ * Channel glob type
+ */
+enum /*_mm_channel_glob_type */ {
+ glob_range,
+ glob_list
+};
+
+/*
+ * Channel specification glob
+ */
+typedef struct /*_mm_channel_glob*/ {
+ size_t offset[2];
+ union {
+ void* impl;
+ uint64_t u64[2];
+ } limits;
+ uint8_t type;
+ uint64_t values;
+} channel_glob;
+
+/*
+ * (Multi-)Channel specification
+ */
+typedef struct /*_mm_channel_spec*/ {
+ char* spec;
+ uint8_t internal;
+ size_t channels;
+ size_t globs;
+ channel_glob* glob;
+} channel_spec;
+
+/*
+ * Command-line override types
+ */
+typedef enum {
+ override_backend,
+ override_instance
+} override_type;
+
+/*
+ * Command-line override data
+ */
+typedef struct /*_mm_config_override*/ {
+ override_type type;
+ uint8_t handled;
+ char* target;
+ char* option;
+ char* value;
+} config_override;
+
+int config_read(char* file);
+int config_add_override(override_type type, char* data);
+void config_free();
diff --git a/core/plugin.c b/core/plugin.c
new file mode 100644
index 0000000..e7d8eba
--- /dev/null
+++ b/core/plugin.c
@@ -0,0 +1,187 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include "portability.h"
+#ifdef _WIN32
+#define dlclose FreeLibrary
+#define dlsym GetProcAddress
+#define dlerror() "Failed"
+#define dlopen(lib,ig) LoadLibrary(lib)
+#else
+#include <dlfcn.h>
+#endif
+
+#include "plugin.h"
+
+static size_t plugins = 0;
+static void** plugin_handle = NULL;
+
+static int plugin_attach(char* path, char* file){
+ plugin_init init = NULL;
+ void* handle = NULL;
+ char* lib = NULL;
+ #ifdef _WIN32
+ char* path_separator = "\\";
+ #else
+ char* path_separator = "/";
+ #endif
+
+ if(!path || !file || !strlen(path)){
+ fprintf(stderr, "Invalid plugin loader path\n");
+ return 1;
+ }
+
+ lib = calloc(strlen(path) + strlen(file) + 2, sizeof(char));
+ if(!lib){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+ snprintf(lib, strlen(path) + strlen(file) + 2, "%s%s%s",
+ path,
+ (path[strlen(path) - 1] == path_separator[0]) ? "" : path_separator,
+ file);
+
+ handle = dlopen(lib, RTLD_NOW);
+ if(!handle){
+ #ifdef _WIN32
+ char* error = NULL;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error, 0, NULL);
+ fprintf(stderr, "Failed to load plugin %s, check that all supporting libraries are present: %s\n", lib, error);
+ LocalFree(error);
+ #else
+ fprintf(stderr, "Failed to load plugin %s: %s\n", lib, dlerror());
+ #endif
+ free(lib);
+ return 0;
+ }
+
+ init = (plugin_init) dlsym(handle, "init");
+ if(init){
+ if(init()){
+ fprintf(stderr, "Plugin %s failed to initialize\n", lib);
+ dlclose(handle);
+ free(lib);
+ return 1;
+ }
+ }
+ else{
+ dlclose(handle);
+ free(lib);
+ return 0;
+ }
+ free(lib);
+
+ plugin_handle = realloc(plugin_handle, (plugins + 1) * sizeof(void*));
+ if(!plugin_handle){
+ fprintf(stderr, "Failed to allocate memory\n");
+ dlclose(handle);
+ return 1;
+ }
+
+ plugin_handle[plugins] = handle;
+ plugins++;
+
+ return 0;
+}
+
+int plugins_load(char* path){
+ int rv = -1;
+
+#ifdef _WIN32
+ char* search_expression = calloc(strlen(path) + strlen("*.dll") + 1, sizeof(char));
+ if(!search_expression){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return -1;
+ }
+ snprintf(search_expression, strlen(path) + strlen("*.dll"), "%s*.dll", path);
+
+ WIN32_FIND_DATA result;
+ HANDLE hSearch = FindFirstFile(search_expression, &result);
+
+ if(hSearch == INVALID_HANDLE_VALUE){
+ LPVOID lpMsgBuf = NULL;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
+ fprintf(stderr, "Failed to search for backend plugin files in %s: %s\n", path, lpMsgBuf);
+ LocalFree(lpMsgBuf);
+ return -1;
+ }
+
+ do {
+ if(plugin_attach(path, result.cFileName)){
+ goto load_done;
+ }
+ } while(FindNextFile(hSearch, &result));
+
+ rv = 0;
+load_done:
+ free(search_expression);
+ FindClose(hSearch);
+ return rv;
+#else
+ struct dirent* entry;
+ struct stat file_stat;
+ DIR* directory = opendir(path);
+ if(!directory){
+ fprintf(stderr, "Failed to open plugin search path %s: %s\n", path, strerror(errno));
+ return 1;
+ }
+
+ for(entry = readdir(directory); entry; entry = readdir(directory)){
+ if(strlen(entry->d_name) < 4 || strncmp(".so", entry->d_name + (strlen(entry->d_name) - 3), 3)){
+ continue;
+ }
+
+ if(fstatat(dirfd(directory), entry->d_name, &file_stat, 0) < 0){
+ fprintf(stderr, "Failed to stat %s: %s\n", entry->d_name, strerror(errno));
+ continue;
+ }
+
+ if(!S_ISREG(file_stat.st_mode)){
+ continue;
+ }
+
+ if(plugin_attach(path, entry->d_name)){
+ goto load_done;
+ }
+ }
+ rv = 0;
+
+load_done:
+ if(closedir(directory) < 0){
+ fprintf(stderr, "Failed to close plugin directory %s: %s\n", path, strerror(errno));
+ return -1;
+ }
+ return rv;
+#endif
+}
+
+int plugins_close(){
+ size_t u;
+
+ for(u = 0; u < plugins; u++){
+#ifdef _WIN32
+ char* error = NULL;
+ //FreeLibrary returns the inverse of dlclose
+ if(!FreeLibrary(plugin_handle[u])){
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error, 0, NULL);
+ fprintf(stderr, "Failed to unload plugin: %s\n", error);
+ LocalFree(error);
+ }
+#else
+ if(dlclose(plugin_handle[u])){
+ fprintf(stderr, "Failed to unload plugin: %s\n", dlerror());
+ }
+#endif
+ }
+
+ free(plugin_handle);
+ plugins = 0;
+ return 0;
+}
diff --git a/core/plugin.h b/core/plugin.h
new file mode 100644
index 0000000..64c557f
--- /dev/null
+++ b/core/plugin.h
@@ -0,0 +1,3 @@
+typedef int (*plugin_init)();
+int plugins_load(char* dir);
+int plugins_close();