From 921ce069a4770fbadad7bb4e806361e857469409 Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 21 Jun 2021 22:03:17 +0200 Subject: Repository cleanup --- Makefile | 4 +- README.md | 1 - backend.c | 361 ---------------------------- backend.h | 17 -- backends/mqtt.md | 4 +- backends/osc.c | 3 +- backends/osc.md | 2 +- config.c | 708 ------------------------------------------------------- config.h | 54 ----- core/backend.c | 361 ++++++++++++++++++++++++++++ core/backend.h | 17 ++ core/config.c | 708 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ core/config.h | 54 +++++ core/plugin.c | 187 +++++++++++++++ core/plugin.h | 3 + plugin.c | 187 --------------- plugin.h | 3 - 17 files changed, 1337 insertions(+), 1337 deletions(-) delete mode 100644 backend.c delete mode 100644 backend.h delete mode 100644 config.c delete mode 100644 config.h create mode 100644 core/backend.c create mode 100644 core/backend.h create mode 100644 core/config.c create mode 100644 core/config.h create mode 100644 core/plugin.c create mode 100644 core/plugin.h delete mode 100644 plugin.c delete mode 100644 plugin.h diff --git a/Makefile b/Makefile index bda7bb1..9b95f1b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .PHONY: all clean run sanitize backends windows full backends-full install -OBJS = config.o backend.o plugin.o +OBJS = core/config.o core/backend.o core/plugin.o PREFIX ?= /usr PLUGIN_INSTALL = $(PREFIX)/lib/midimonster @@ -11,7 +11,7 @@ GITVERSION = $(shell git describe) CFLAGS ?= -g -Wall -Wpedantic #CFLAGS += -DDEBUG # Hide all non-API symbols for export -CFLAGS += -fvisibility=hidden +CFLAGS += -fvisibility=hidden -I./ midimonster: LDLIBS = -ldl # Replace version string with current git-describe if possible diff --git a/README.md b/README.md index 2379691..f958b25 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # The MIDIMonster MIDIMonster Logo -[![Build Status](https://travis-ci.com/cbdevnet/midimonster.svg?branch=master)](https://travis-ci.com/cbdevnet/midimonster) [![Coverity Scan Build Status](https://scan.coverity.com/projects/15168/badge.svg)](https://scan.coverity.com/projects/15168) [![IRC Channel](https://static.midimonster.net/hackint-badge.svg)](https://webirc.hackint.org/#irc://irc.hackint.org/#midimonster) diff --git a/backend.c b/backend.c deleted file mode 100644 index 16e095c..0000000 --- a/backend.c +++ /dev/null @@ -1,361 +0,0 @@ -#include -#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/backend.h b/backend.h deleted file mode 100644 index 6a69508..0000000 --- a/backend.h +++ /dev/null @@ -1,17 +0,0 @@ -#include - -/* 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/backends/mqtt.md b/backends/mqtt.md index 6623438..85784ef 100644 --- a/backends/mqtt.md +++ b/backends/mqtt.md @@ -1,6 +1,6 @@ ### The `mqtt` backend -This backend provides input from and output to an message queueing telemetry transport (MQTT) +This backend provides input from and output to a message queueing telemetry transport (MQTT) broker. The MQTT protocol is used in lightweight sensor/actor applications, a wide selection of smart home implementations and as a generic message bus in many other domains. @@ -18,7 +18,7 @@ This backend does not take any global configuration. | `host` | `mqtt://10.23.23.1` | none | Host or URI of the MQTT broker | | `user` | `midimonster` | none | User name for broker authentication | | `password` | `mm` | none | Password for broker authentication | -| `clientid` | `MM-main` | random | MQTT client identifier (generated randomly at start) | +| `clientid` | `MM-main` | random | MQTT client identifier (generated randomly at start if unset) | | `protocol` | `3.1.1` | `5` | MQTT protocol version (`5` or `3.1.1`) to use for the connection | The `host` option can be specified as an URI of the form `mqtt[s]://[username][:password]@host.domain[:port]`. diff --git a/backends/osc.c b/backends/osc.c index 5887a50..e8673bb 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -1,4 +1,5 @@ #define BACKEND_NAME "osc" +//#define DEBUG #include #include @@ -629,7 +630,7 @@ static channel* osc_map_channel(instance* inst, char* spec, uint8_t flags){ data->channel[u].out = calloc(data->channel[u].params, sizeof(osc_parameter_value)); } else if(data->patterns){ - LOGPF("No pattern match found for %s", spec); + LOGPF("No preconfigured pattern match found for %s", spec); } if(!data->channel[u].path diff --git a/backends/osc.md b/backends/osc.md index 1446e06..61b3324 100644 --- a/backends/osc.md +++ b/backends/osc.md @@ -78,7 +78,7 @@ configuration. #### Supported types & value ranges OSC allows controls to have individual value ranges and supports different parameter types. -The following types are currently supported by the MIDImonster: +The following types are currently supported by the MIDIMonster: * **i**: 32-bit signed integer * **f**: 32-bit IEEE floating point diff --git a/config.c b/config.c deleted file mode 100644 index c1c3124..0000000 --- a/config.c +++ /dev/null @@ -1,708 +0,0 @@ -#include -#include -#include -#include -#ifndef _WIN32 -#include -#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/config.h b/config.h deleted file mode 100644 index b96a866..0000000 --- a/config.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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/backend.c b/core/backend.c new file mode 100644 index 0000000..16e095c --- /dev/null +++ b/core/backend.c @@ -0,0 +1,361 @@ +#include +#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 + +/* 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 +#include +#include +#include +#ifndef _WIN32 +#include +#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 +#include +#include +#include +#include +#include +#include +#include "portability.h" +#ifdef _WIN32 +#define dlclose FreeLibrary +#define dlsym GetProcAddress +#define dlerror() "Failed" +#define dlopen(lib,ig) LoadLibrary(lib) +#else +#include +#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(); diff --git a/plugin.c b/plugin.c deleted file mode 100644 index e7d8eba..0000000 --- a/plugin.c +++ /dev/null @@ -1,187 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "portability.h" -#ifdef _WIN32 -#define dlclose FreeLibrary -#define dlsym GetProcAddress -#define dlerror() "Failed" -#define dlopen(lib,ig) LoadLibrary(lib) -#else -#include -#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/plugin.h b/plugin.h deleted file mode 100644 index 64c557f..0000000 --- a/plugin.h +++ /dev/null @@ -1,3 +0,0 @@ -typedef int (*plugin_init)(); -int plugins_load(char* dir); -int plugins_close(); -- cgit v1.2.3