diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/backend.c | 361 | ||||
-rw-r--r-- | core/backend.h | 17 | ||||
-rw-r--r-- | core/config.c | 708 | ||||
-rw-r--r-- | core/config.h | 54 | ||||
-rw-r--r-- | core/plugin.c | 187 | ||||
-rw-r--r-- | core/plugin.h | 3 |
6 files changed, 1330 insertions, 0 deletions
diff --git a/core/backend.c b/core/backend.c new file mode 100644 index 0000000..16e095c --- /dev/null +++ b/core/backend.c @@ -0,0 +1,361 @@ +#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\n", 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\n", 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, inst, ident); + for(u = 0; u < channels.n[bucket]; u++){ + DBGPF("\tBucket %" PRIsize_t " entry %" PRIsize_t " inst %" PRIu64 " ident %" PRIu64, bucket, u, 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\n", ident, bucket, inst->name); + return NULL; + } + + DBGPF("Creating previously unknown channel %" PRIu64 " on instance %s, bucket %" PRIsize_t "\n", 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]++)]; +} + +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\n", 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..6a69508 --- /dev/null +++ b/core/backend.h @@ -0,0 +1,17 @@ +#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 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(); |