diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | core/backend.c | 31 | ||||
-rw-r--r-- | core/backend.h | 2 | ||||
-rw-r--r-- | core/config.c | 71 | ||||
-rw-r--r-- | core/config.h | 5 | ||||
-rw-r--r-- | core/core.c | 258 | ||||
-rw-r--r-- | core/core.h | 43 | ||||
-rw-r--r-- | core/plugin.c | 40 | ||||
-rw-r--r-- | core/plugin.h | 2 | ||||
-rw-r--r-- | core/routing.c | 198 | ||||
-rw-r--r-- | core/routing.h | 9 | ||||
-rw-r--r-- | midimonster.c | 437 | ||||
-rw-r--r-- | midimonster.h | 9 |
14 files changed, 631 insertions, 492 deletions
@@ -1,5 +1,5 @@ .PHONY: all clean run sanitize backends windows full backends-full install -OBJS = core/config.o core/backend.o core/plugin.o +OBJS = core/core.o core/config.o core/backend.o core/plugin.o core/routing.o PREFIX ?= /usr PLUGIN_INSTALL = $(PREFIX)/lib/midimonster @@ -38,7 +38,7 @@ ifdef DEFAULT_CFG midimonster: CFLAGS += -DDEFAULT_CFG=\"$(DEFAULT_CFG)\" endif ifdef PLUGINS -midimonster: CFLAGS += -DPLUGINS=\"$(PLUGINS)\" +core/core.o: CFLAGS += -DPLUGINS=\"$(PLUGINS)\" PLUGIN_INSTALL = $(PLUGINS) endif @@ -300,12 +300,16 @@ for detailed information). ## Development -The architecture is split into the `midimonster` core, handling mapping -and resource management, and the backends, which are shared objects loaded -at start time, which provide a protocol mapping to instances / channels. +The architecture is split into the `core`, handling mapping and resource management, +the `frontends` which handle how the core is invoked and presented (ie. command line or graphical interface), +and the `backends`, which are shared objects loaded at start time providing a protocol mapping to instances / channels. -The API and structures are more-or-less documented in [midimonster.h](midimonster.h), -more detailed documentation may follow. +There is a general [developer information document](DEVELOPMENT.md) that outlines basic guidelines for +contribution. The [MIDIMonster knowledge base](https://kb.midimonster.net/) has a section on development, +containing additional helpful information and tutorials. + +The backend API, lifecycle and structures are documented in [midimonster.h](midimonster.h), the +frontend API and lifecycle in [core/core.h](core/core.h). To build with `clang` sanitizers and even more warnings enabled, run `make sanitize`. This is useful to check for common errors and oversights. diff --git a/core/backend.c b/core/backend.c index 83121bd..b7c2d9e 100644 --- a/core/backend.c +++ b/core/backend.c @@ -1,9 +1,10 @@ #include <string.h> #ifndef _WIN32 -#define MM_API __attribute__((visibility ("default"))) + #define MM_API __attribute__((visibility ("default"))) #else -#define MM_API __attribute__((dllexport)) + #define MM_API __attribute__((dllexport)) #endif + #define BACKEND_NAME "core/be" #include "midimonster.h" #include "backend.h" @@ -54,7 +55,7 @@ int backends_handle(size_t nfds, managed_fd* fds){ DBGPF("Notifying backend %s of %" PRIsize_t " waiting FDs", registry.backends[u].name, n); rv |= registry.backends[u].process(n, fds); if(rv){ - fprintf(stderr, "Backend %s failed to handle input\n", registry.backends[u].name); + LOGPF("Backend %s failed to handle input", registry.backends[u].name); } } } @@ -99,7 +100,7 @@ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){ DBGPF("\tBucket %" PRIsize_t " entry %" PRIsize_t " inst %" PRIu64 " ident %" PRIu64, bucket, u, (uint64_t) channels.entry[bucket][u]->instance, channels.entry[bucket][u]->ident); if(channels.entry[bucket][u]->instance == inst && channels.entry[bucket][u]->ident == ident){ - DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing (bucket %" PRIsize_t ", %" PRIsize_t " search steps)\n", ident, inst->name, bucket, u); + DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing (bucket %" PRIsize_t ", %" PRIsize_t " search steps)", ident, inst->name, bucket, u); return channels.entry[bucket][u]; } } @@ -112,14 +113,14 @@ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){ DBGPF("Creating previously unknown channel %" PRIu64 " on instance %s, bucket %" PRIsize_t, ident, inst->name, bucket); channels.entry[bucket] = realloc(channels.entry[bucket], (channels.n[bucket] + 1) * sizeof(channel*)); if(!channels.entry[bucket]){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); 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"); + LOG("Failed to allocate memory"); return NULL; } @@ -160,7 +161,7 @@ MM_API void mm_channel_update(channel* chan, uint64_t ident){ //add to new bucket channels.entry[new_bucket] = realloc(channels.entry[new_bucket], (channels.n[new_bucket] + 1) * sizeof(channel*)); if(!channels.entry[new_bucket]){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); channels.n[new_bucket] = 0; return; } @@ -183,14 +184,14 @@ instance* mm_instance(backend* b){ //extend registry.instances[u] = realloc(registry.instances[u], (n + 2) * sizeof(instance*)); if(!registry.instances[u]){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); 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"); + LOG("Failed to allocate memory"); } registry.instances[u][n]->backend = b; return registry.instances[u][n]; @@ -237,7 +238,7 @@ MM_API int mm_backend_instances(char* name, size_t* ninst, instance*** inst){ *inst = calloc(i, sizeof(instance*)); if(!*inst){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return 1; } @@ -303,7 +304,7 @@ MM_API int mm_backend_register(backend b){ 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"); + LOG("Failed to allocate memory"); registry.n = 0; return 1; } @@ -311,7 +312,7 @@ MM_API int mm_backend_register(backend b){ registry.instances[registry.n] = NULL; registry.n++; - fprintf(stderr, "Registered backend %s\n", b.name); + LOGPF("Registered backend %s", b.name); return 0; } return 1; @@ -330,14 +331,14 @@ int backends_start(){ //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); + LOGPF("Failed to fetch instance list for initialization of backend %s", 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); + LOGPF("Failed to start backend %s", registry.backends[u].name); } //clean up @@ -377,7 +378,7 @@ int backends_stop(){ 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); + LOGPF("Failed to fetch instance list for shutdown of backend %s", registry.backends[u].name); inst = NULL; n = 0; } diff --git a/core/backend.h b/core/backend.h index 46c6c3a..1f85424 100644 --- a/core/backend.h +++ b/core/backend.h @@ -10,7 +10,7 @@ int backends_start(); int backends_stop(); instance* mm_instance(backend* b); -/* Backend API */ +/* Public backend API */ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create); MM_API void mm_channel_update(channel* chan, uint64_t ident); MM_API instance* mm_instance_find(char* name, uint64_t ident); diff --git a/core/config.c b/core/config.c index c1c3124..b950b25 100644 --- a/core/config.c +++ b/core/config.c @@ -3,7 +3,10 @@ #include <unistd.h> #include <errno.h> #ifndef _WIN32 -#include <limits.h> + #include <limits.h> + #define MM_API __attribute__((visibility ("default"))) +#else + #define MM_API __attribute__((dllexport)) #endif #define BACKEND_NAME "core/cfg" @@ -45,7 +48,7 @@ static ssize_t getline(char** line, size_t* alloc, FILE* stream){ *alloc = GETLINE_BUFFER; *line = calloc(GETLINE_BUFFER, sizeof(char)); if(!*line){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return -1; } } @@ -60,7 +63,7 @@ static ssize_t getline(char** line, size_t* alloc, FILE* stream){ *alloc += GETLINE_BUFFER; *line = realloc(*line, (*alloc) * sizeof(char)); if(!*line){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return -1; } } @@ -175,13 +178,13 @@ static int config_glob_scan(instance* inst, channel_spec* spec){ 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); + LOGPF("Failed to parse channel spec, unterminated glob: %s", 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"); + LOG("Failed to allocate memory"); return 1; } @@ -205,7 +208,7 @@ static int config_glob_scan(instance* inst, channel_spec* spec){ } 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); + LOGPF("Failed to parse glob %" PRIsize_t " in %s internally", u + 1, spec->spec); return 1; } @@ -322,7 +325,7 @@ static int config_map(char* to_raw, char* from_raw){ if(!from || !to){ free(from); free(to); - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return 1; } @@ -334,7 +337,7 @@ static int config_map(char* to_raw, char* from_raw){ } if(!spec_from.spec[0] || !spec_to.spec[0]){ - fprintf(stderr, "Mapping does not contain a proper instance specification\n"); + LOG("Mapping does not contain a proper instance specification"); goto done; } @@ -348,7 +351,7 @@ static int config_map(char* to_raw, char* from_raw){ instance_from = instance_match(from); if(!instance_to || !instance_from){ - fprintf(stderr, "No such instance %s\n", instance_from ? to : from); + LOGPF("No such instance %s", instance_from ? to : from); goto done; } @@ -361,7 +364,7 @@ static int config_map(char* to_raw, char* from_raw){ 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", + LOGPF("Multi-channel specification size mismatch: %s.%s (%" PRIsize_t " channels) - %s.%s (%" PRIsize_t " channels)", instance_from->name, spec_from.spec, spec_from.channels, @@ -410,7 +413,7 @@ static int config_line(char* line){ current_backend = backend_match(line + 9); if(!current_backend){ - fprintf(stderr, "Cannot configure unknown backend %s\n", line + 9); + LOGPF("Cannot configure unknown backend %s", line + 9); return 1; } @@ -419,7 +422,7 @@ static int config_line(char* line){ 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", + LOGPF("Configuration override for %s failed for backend %s", overrides[u].option, current_backend->name); return 1; } @@ -447,7 +450,7 @@ static int config_line(char* line){ for(separator = line; *separator && *separator != ' '; separator++){ } if(!*separator){ - fprintf(stderr, "No instance name specified for backend %s\n", line); + LOGPF("No instance name specified for backend %s", line); return 1; } *separator = 0; @@ -455,18 +458,18 @@ static int config_line(char* line){ current_backend = backend_match(line); if(!current_backend){ - fprintf(stderr, "No such backend %s\n", line); + LOGPF("No such backend %s", line); return 1; } if(instance_match(separator)){ - fprintf(stderr, "Duplicate instance name %s\n", separator); + LOGPF("Duplicate instance name %s", separator); return 1; } //validate instance name if(strchr(separator, ' ') || strchr(separator, '.')){ - fprintf(stderr, "Invalid instance name %s\n", separator); + LOGPF("Invalid instance name %s", separator); return 1; } @@ -476,20 +479,20 @@ static int config_line(char* line){ } if(current_backend->create(current_instance)){ - fprintf(stderr, "Failed to create %s instance %s\n", line, separator); + LOGPF("Failed to create %s instance %s", line, separator); return 1; } current_instance->name = strdup(separator); current_instance->backend = current_backend; - fprintf(stderr, "Created %s instance %s\n", line, separator); + LOGPF("Created %s instance %s", 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", + LOGPF("Configuration override for %s failed for instance %s", overrides[u].option, current_instance->name); return 1; } @@ -514,7 +517,7 @@ static int config_line(char* line){ break; case 0: default: - fprintf(stderr, "Not a channel mapping: %s\n", line); + LOGPF("Not a channel mapping: %s", line); return 1; } @@ -529,13 +532,13 @@ static int config_line(char* line){ 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); + LOGPF("Failed to map channel %s to %s", 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); + LOGPF("Failed to map channel %s to %s", separator, line); return 1; } } @@ -545,7 +548,7 @@ static int config_line(char* line){ //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"); + LOGPF("Not an assignment (currently expecting %s configuration): %s", line, (parser_state == backend_cfg) ? "backend" : "instance"); return 1; } @@ -555,11 +558,11 @@ static int config_line(char* 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); + LOGPF("Failed to configure backend %s", 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); + LOGPF("Failed to configure instance %s", current_instance->name); return 1; } } @@ -583,7 +586,7 @@ int config_read(char* cfg_filepath){ #endif if(!source_dir){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return 1; } @@ -594,12 +597,12 @@ int config_read(char* cfg_filepath){ source_file++; if(!getcwd(original_dir, sizeof(original_dir))){ - fprintf(stderr, "Failed to read current working directory: %s\n", strerror(errno)); + LOGPF("Failed to read current working directory: %s", strerror(errno)); goto bail; } if(chdir(source_dir)){ - fprintf(stderr, "Failed to change to configuration file directory %s: %s\n", source_dir, strerror(errno)); + LOGPF("Failed to change to configuration file directory %s: %s", source_dir, strerror(errno)); goto bail; } } @@ -607,11 +610,11 @@ int config_read(char* cfg_filepath){ source_file = source_dir; } - fprintf(stderr, "Reading configuration file %s\n", cfg_filepath); + LOGPF("Reading configuration file %s", cfg_filepath); source = fopen(source_file, "r"); if(!source){ - fprintf(stderr, "Failed to open %s for reading\n", cfg_filepath); + LOGPF("Failed to open %s for reading", cfg_filepath); goto bail; } @@ -644,7 +647,7 @@ int config_add_override(override_type type, char* data_raw){ char* data = strdup(data_raw); if(!data){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); goto bail; } @@ -652,7 +655,7 @@ int config_add_override(override_type type, char* data_raw){ char* value = strchr(data, '='); if(!option || !value){ - fprintf(stderr, "Override %s is not a valid assignment\n", data_raw); + LOGPF("Override %s is not a valid assignment", data_raw); goto bail; } @@ -672,14 +675,14 @@ int config_add_override(override_type type, char* data_raw){ }; if(!new.target || !new.option || !new.value){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); goto bail; } overrides = realloc(overrides, (noverrides + 1) * sizeof(config_override)); if(!overrides){ noverrides = 0; - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); goto bail; } overrides[noverrides] = new; diff --git a/core/config.h b/core/config.h index b96a866..80e8fff 100644 --- a/core/config.h +++ b/core/config.h @@ -49,6 +49,9 @@ typedef struct /*_mm_config_override*/ { char* value; } config_override; +/* Internal API */ +void config_free(); + +/* Frontent API */ int config_read(char* file); int config_add_override(override_type type, char* data); -void config_free(); diff --git a/core/core.c b/core/core.c new file mode 100644 index 0000000..804ec4e --- /dev/null +++ b/core/core.c @@ -0,0 +1,258 @@ +#include <string.h> +#include <signal.h> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#ifndef _WIN32 + #include <sys/select.h> + #define MM_API __attribute__((visibility ("default"))) +#else + #include <fcntl.h> + #define MM_API __attribute__((dllexport)) +#endif + +#define BACKEND_NAME "core" +#include "midimonster.h" +#include "core.h" +#include "backend.h" +#include "routing.h" +#include "plugin.h" +#include "config.h" + +static size_t fds = 0; +static int max_fd = -1; +static managed_fd* fd = NULL; +static managed_fd* signaled_fds = NULL; +static volatile sig_atomic_t fd_set_dirty = 1; +static uint64_t global_timestamp = 0; + +static fd_set all_fds; + +MM_API uint64_t mm_timestamp(){ + return global_timestamp; +} + +static void core_timestamp(){ + #ifdef _WIN32 + global_timestamp = GetTickCount(); + #else + struct timespec current; + if(clock_gettime(CLOCK_MONOTONIC_COARSE, ¤t)){ + LOGPF("Failed to update global timestamp, time-based processing for some backends may be impaired: %s", strerror(errno)); + return; + } + + global_timestamp = current.tv_sec * 1000 + current.tv_nsec / 1000000; + #endif +} + +static fd_set core_collect(int* max_fd){ + size_t u = 0; + fd_set rv_fds; + + if(max_fd){ + *max_fd = -1; + } + + DBGPF("Building selector set from %" PRIsize_t " FDs registered to core", fds); + FD_ZERO(&rv_fds); + for(u = 0; u < fds; u++){ + if(fd[u].fd >= 0){ + FD_SET(fd[u].fd, &rv_fds); + if(max_fd){ + *max_fd = max(*max_fd, fd[u].fd); + } + } + } + + return rv_fds; +} + +MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl){ + backend* b = backend_match(back); + size_t u; + + if(!b){ + LOGPF("Unknown backend %s registered for managed fd", back); + return 1; + } + + //find exact match + for(u = 0; u < fds; u++){ + if(fd[u].fd == new_fd && fd[u].backend == b){ + fd[u].impl = impl; + if(!manage){ + fd[u].fd = -1; + fd[u].backend = NULL; + fd[u].impl = NULL; + fd_set_dirty = 1; + } + return 0; + } + } + + if(!manage){ + return 0; + } + + //find free slot + for(u = 0; u < fds; u++){ + if(fd[u].fd < 0){ + break; + } + } + //if necessary expand + if(u == fds){ + fd = realloc(fd, (fds + 1) * sizeof(managed_fd)); + if(!fd){ + LOG("Failed to allocate memory"); + return 1; + } + + signaled_fds = realloc(signaled_fds, (fds + 1) * sizeof(managed_fd)); + if(!signaled_fds){ + LOG("Failed to allocate memory"); + return 1; + } + fds++; + } + + //store new fd + fd[u].fd = new_fd; + fd[u].backend = b; + fd[u].impl = impl; + fd_set_dirty = 1; + return 0; +} + +int core_initialize(){ + FD_ZERO(&all_fds); + + //load initial timestamp + core_timestamp(); + + #ifdef _WIN32 + WSADATA wsa; + WORD version = MAKEWORD(2, 2); + if(WSAStartup(version, &wsa)){ + return 1; + } + _fmode = _O_BINARY; + #endif + + //attach plugins + if(plugins_load(PLUGINS)){ + LOG("Failed to initialize a backend"); + return 1; + } + + return 0; +} + +int core_start(){ + if(backends_start()){ + return 1; + } + + routing_stats(); + + if(!fds){ + LOG("No descriptors registered for multiplexing"); + } + + return 0; +} + +int core_iteration(){ + fd_set read_fds; + struct timeval tv; + int error; + size_t n, u; + #ifdef _WIN32 + char* error_message = NULL; + #else + struct timespec ts; + #endif + + //rebuild fd set if necessary + if(fd_set_dirty || !signaled_fds){ + all_fds = core_collect(&max_fd); + fd_set_dirty = 0; + } + + //wait for & translate events + read_fds = all_fds; + tv = backend_timeout(); + + //check whether there are any fds active, windows does not like select() without descriptors + if(max_fd >= 0){ + error = select(max_fd + 1, &read_fds, NULL, NULL, &tv); + if(error < 0){ + #ifndef _WIN32 + LOGPF("select failed: %s", strerror(errno)); + #else + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, WSAGetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error_message, 0, NULL); + LOGPF("select failed: %s", error_message); + LocalFree(error_message); + error_message = NULL; + #endif + return 1; + } + } + else{ + DBGPF("No descriptors, sleeping for %zu msec", tv.tv_sec * 1000 + tv.tv_usec / 1000); + #ifdef _WIN32 + Sleep(tv.tv_sec * 1000 + tv.tv_usec / 1000); + #else + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + nanosleep(&ts, NULL); + #endif + } + + //update this iteration's timestamp + core_timestamp(); + + //find all signaled fds + n = 0; + for(u = 0; u < fds; u++){ + if(fd[u].fd >= 0 && FD_ISSET(fd[u].fd, &read_fds)){ + signaled_fds[n] = fd[u]; + n++; + } + } + + //run backend processing to collect events + DBGPF("%" PRIsize_t " backend FDs signaled", n); + if(backends_handle(n, signaled_fds)){ + return 1; + } + + //route generated events + return routing_iteration(); +} + +static void fds_free(){ + size_t u; + for(u = 0; u < fds; u++){ + if(fd[u].fd >= 0){ + close(fd[u].fd); + fd[u].fd = -1; + } + } + free(fd); + fds = 0; + fd = NULL; +} + +void core_shutdown(){ + backends_stop(); + routing_cleanup(); + free(signaled_fds); + signaled_fds = NULL; + fds_free(); + plugins_close(); + config_free(); + fd_set_dirty = 1; +} diff --git a/core/core.h b/core/core.h new file mode 100644 index 0000000..3237a71 --- /dev/null +++ b/core/core.h @@ -0,0 +1,43 @@ +/* + * MIDIMonster frontend API + * + * These APIs expose the core as a linkable module. Frontends will use these calls + * as primary interface to interact with the MIDIMonster core. + * + * The lifecycle is as follows: + * + * * Initially, only the following API calls are valid: + * config_add_override() + * core_initialize() + * This allows the frontend to configure overrides for any configuration + * loaded later (e.g. by parsing command line arguments) before initializing + * the core. + * * Calling core_initialize() attaches all backend modules to the system and + * performs platform specific startup operations. From this point on, + * core_shutdown() must be called before terminating the frontend. + * All frontend API calls except `core_iteration` are now valid. + * The core is now in the configuration stage in which the frontend + * will push any configuration files. + * * Calling core_start() marks the transition from the configuration phase + * to the translation phase. The core will activate any configured backends + * and provide them with the information required to connect to their data + * sources and sinks. In this stage, only the following API calls are valid: + * core_iteration() + * core_shutdown() + * * The frontend will now repeatedly call core_iteration() to process any incoming + * events. This API will block execution until either one or more events have + * been registered or an internal timeout expires. + * * Calling core_shutdown() releases all memory allocated by the core and any + * attached modules or plugins, including all configuration, overrides, + * mappings, statistics, etc. The core is now ready to exit or be + * reinitialized using core_initialize(). + */ + +int core_initialize(); +int core_start(); +int core_iteration(); +void core_shutdown(); + +/* Public backend API */ +MM_API uint64_t mm_timestamp(); +MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl); diff --git a/core/plugin.c b/core/plugin.c index e7d8eba..cd85059 100644 --- a/core/plugin.c +++ b/core/plugin.c @@ -7,14 +7,18 @@ #include <dirent.h> #include "portability.h" #ifdef _WIN32 -#define dlclose FreeLibrary -#define dlsym GetProcAddress -#define dlerror() "Failed" -#define dlopen(lib,ig) LoadLibrary(lib) + #define dlclose FreeLibrary + #define dlsym GetProcAddress + #define dlerror() "Failed" + #define dlopen(lib,ig) LoadLibrary(lib) + #define MM_API __attribute__((dllexport)) #else -#include <dlfcn.h> + #include <dlfcn.h> + #define MM_API __attribute__((visibility ("default"))) #endif +#define BACKEND_NAME "core/pl" +#include "midimonster.h" #include "plugin.h" static size_t plugins = 0; @@ -31,13 +35,13 @@ static int plugin_attach(char* path, char* file){ #endif if(!path || !file || !strlen(path)){ - fprintf(stderr, "Invalid plugin loader path\n"); + LOG("Invalid plugin loader path"); return 1; } lib = calloc(strlen(path) + strlen(file) + 2, sizeof(char)); if(!lib){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return 1; } snprintf(lib, strlen(path) + strlen(file) + 2, "%s%s%s", @@ -51,10 +55,10 @@ static int plugin_attach(char* path, char* file){ 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); + LOGPF("Failed to load plugin %s, check that all supporting libraries are present: %s", lib, error); LocalFree(error); #else - fprintf(stderr, "Failed to load plugin %s: %s\n", lib, dlerror()); + LOGPF("Failed to load plugin %s: %s", lib, dlerror()); #endif free(lib); return 0; @@ -63,7 +67,7 @@ static int plugin_attach(char* path, char* file){ init = (plugin_init) dlsym(handle, "init"); if(init){ if(init()){ - fprintf(stderr, "Plugin %s failed to initialize\n", lib); + LOGPF("Plugin %s failed to initialize", lib); dlclose(handle); free(lib); return 1; @@ -78,7 +82,7 @@ static int plugin_attach(char* path, char* file){ plugin_handle = realloc(plugin_handle, (plugins + 1) * sizeof(void*)); if(!plugin_handle){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); dlclose(handle); return 1; } @@ -95,7 +99,7 @@ int plugins_load(char* path){ #ifdef _WIN32 char* search_expression = calloc(strlen(path) + strlen("*.dll") + 1, sizeof(char)); if(!search_expression){ - fprintf(stderr, "Failed to allocate memory\n"); + LOG("Failed to allocate memory"); return -1; } snprintf(search_expression, strlen(path) + strlen("*.dll"), "%s*.dll", path); @@ -107,7 +111,7 @@ int plugins_load(char* path){ 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); + LOGPF("Failed to search for backend plugin files in %s: %s", path, lpMsgBuf); LocalFree(lpMsgBuf); return -1; } @@ -128,7 +132,7 @@ load_done: struct stat file_stat; DIR* directory = opendir(path); if(!directory){ - fprintf(stderr, "Failed to open plugin search path %s: %s\n", path, strerror(errno)); + LOGPF("Failed to open plugin search path %s: %s", path, strerror(errno)); return 1; } @@ -138,7 +142,7 @@ load_done: } if(fstatat(dirfd(directory), entry->d_name, &file_stat, 0) < 0){ - fprintf(stderr, "Failed to stat %s: %s\n", entry->d_name, strerror(errno)); + LOGPF("Failed to stat %s: %s", entry->d_name, strerror(errno)); continue; } @@ -154,7 +158,7 @@ load_done: load_done: if(closedir(directory) < 0){ - fprintf(stderr, "Failed to close plugin directory %s: %s\n", path, strerror(errno)); + LOGPF("Failed to close plugin directory %s: %s", path, strerror(errno)); return -1; } return rv; @@ -171,12 +175,12 @@ int plugins_close(){ 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); + LOGPF("Failed to unload plugin: %s", error); LocalFree(error); } #else if(dlclose(plugin_handle[u])){ - fprintf(stderr, "Failed to unload plugin: %s\n", dlerror()); + LOGPF("Failed to unload plugin: %s", dlerror()); } #endif } diff --git a/core/plugin.h b/core/plugin.h index 64c557f..cdd544c 100644 --- a/core/plugin.h +++ b/core/plugin.h @@ -1,3 +1,5 @@ typedef int (*plugin_init)(); + +/* Internal API */ int plugins_load(char* dir); int plugins_close(); diff --git a/core/routing.c b/core/routing.c new file mode 100644 index 0000000..284569a --- /dev/null +++ b/core/routing.c @@ -0,0 +1,198 @@ +#include <string.h> +#include <signal.h> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#ifndef _WIN32 + #include <sys/select.h> + #define MM_API __attribute__((visibility ("default"))) +#else + #define MM_API __attribute__((dllexport)) +#endif + +#define BACKEND_NAME "core/rt" +#define MM_SWAP_LIMIT 20 +#include "midimonster.h" +#include "routing.h" +#include "backend.h" + +/* Core-internal structures */ +typedef struct /*_event_collection*/ { + size_t alloc; + size_t n; + channel** channel; + channel_value* value; +} event_collection; + +typedef struct /*_mm_channel_mapping*/ { + channel* from; + size_t destinations; + channel** to; +} channel_mapping; + +static struct { + //routing_hash is set up for 256 buckets + size_t entries[256]; + channel_mapping* map[256]; + + event_collection pool[2]; + event_collection* events; +} routing = { + .events = routing.pool +}; + +static size_t routing_hash(channel* key){ + uint64_t repr = (uint64_t) key; + //return 8bit hash for 256 buckets, not ideal but it works + return (repr ^ (repr >> 8) ^ (repr >> 16) ^ (repr >> 24) ^ (repr >> 32)) & 0xFF; +} + +int mm_map_channel(channel* from, channel* to){ + size_t u, m, bucket = routing_hash(from); + + //find existing source mapping + for(u = 0; u < routing.entries[bucket]; u++){ + if(routing.map[bucket][u].from == from){ + break; + } + } + + //create new entry + if(u == routing.entries[bucket]){ + routing.map[bucket] = realloc(routing.map[bucket], (routing.entries[bucket] + 1) * sizeof(channel_mapping)); + if(!routing.map[bucket]){ + routing.entries[bucket] = 0; + LOG("Failed to allocate memory"); + return 1; + } + + memset(routing.map[bucket] + routing.entries[bucket], 0, sizeof(channel_mapping)); + routing.entries[bucket]++; + routing.map[bucket][u].from = from; + } + + //check whether the target is already mapped + for(m = 0; m < routing.map[bucket][u].destinations; m++){ + if(routing.map[bucket][u].to[m] == to){ + return 0; + } + } + + //add a mapping target + routing.map[bucket][u].to = realloc(routing.map[bucket][u].to, (routing.map[bucket][u].destinations + 1) * sizeof(channel*)); + if(!routing.map[bucket][u].to){ + LOG("Failed to allocate memory"); + routing.map[bucket][u].destinations = 0; + return 1; + } + + routing.map[bucket][u].to[routing.map[bucket][u].destinations] = to; + routing.map[bucket][u].destinations++; + return 0; +} + +MM_API int mm_channel_event(channel* c, channel_value v){ + size_t u, p, bucket = routing_hash(c); + + //find mapped channels + for(u = 0; u < routing.entries[bucket]; u++){ + if(routing.map[bucket][u].from == c){ + break; + } + } + + if(u == routing.entries[bucket]){ + //target-only channel + return 0; + } + + //resize event structures to fit additional events + if(routing.events->n + routing.map[bucket][u].destinations >= routing.events->alloc){ + routing.events->channel = realloc(routing.events->channel, (routing.events->alloc + routing.map[bucket][u].destinations) * sizeof(channel*)); + routing.events->value = realloc(routing.events->value, (routing.events->alloc + routing.map[bucket][u].destinations) * sizeof(channel_value)); + + if(!routing.events->channel || !routing.events->value){ + LOG("Failed to allocate memory"); + routing.events->alloc = 0; + routing.events->n = 0; + return 1; + } + + routing.events->alloc += routing.map[bucket][u].destinations; + } + + //enqueue channel events + //FIXME this might lead to one channel being mentioned multiple times in an apply call + memcpy(routing.events->channel + routing.events->n, routing.map[bucket][u].to, routing.map[bucket][u].destinations * sizeof(channel*)); + for(p = 0; p < routing.map[bucket][u].destinations; p++){ + routing.events->value[routing.events->n + p] = v; + } + + routing.events->n += routing.map[bucket][u].destinations; + return 0; +} + +void routing_stats(){ + size_t n = 0, u, max = 0; + + //count and report mappings + for(u = 0; u < sizeof(routing.map) / sizeof(routing.map[0]); u++){ + n += routing.entries[u]; + max = max(max, routing.entries[u]); + } + + LOGPF("Routing %" PRIsize_t " sources, largest bucket has %" PRIsize_t " entries", + n, max); +} + +int routing_iteration(){ + event_collection* secondary = NULL; + size_t u, swaps = 0; + + //limit number of collector swaps per iteration to prevent complete deadlock + while(routing.events->n && swaps < MM_SWAP_LIMIT){ + //swap primary and secondary event collectors + DBGPF("Swapping event collectors, %" PRIsize_t " events in primary", routing.events->n); + for(u = 0; u < sizeof(routing.pool) / sizeof(routing.pool[0]); u++){ + if(routing.events != routing.pool + u){ + secondary = routing.events; + routing.events = routing.pool + u; + break; + } + } + + //push collected events to target backends + if(secondary->n && backends_notify(secondary->n, secondary->channel, secondary->value)){ + LOG("Backends failed to handle output"); + return 1; + } + + //reset the event count + secondary->n = 0; + } + + if(swaps == MM_SWAP_LIMIT){ + LOG("Iteration swap limit hit, a backend may be configured to route events in an infinite loop"); + } + + return 0; +} + +void routing_cleanup(){ + size_t u, n; + + for(u = 0; u < sizeof(routing.map) / sizeof(routing.map[0]); u++){ + for(n = 0; n < routing.entries[u]; n++){ + free(routing.map[u][n].to); + } + free(routing.map[u]); + routing.map[u] = NULL; + routing.entries[u] = 0; + } + + for(u = 0; u < sizeof(routing.pool) / sizeof(routing.pool[0]); u++){ + free(routing.pool[u].channel); + free(routing.pool[u].value); + routing.pool[u].alloc = 0; + } +} diff --git a/core/routing.h b/core/routing.h new file mode 100644 index 0000000..72dd768 --- /dev/null +++ b/core/routing.h @@ -0,0 +1,9 @@ +/* Internal API */ +int mm_map_channel(channel* from, channel* to); +int routing_iteration(); +void routing_stats(); +void routing_cleanup(); + +/* Public backend API */ +MM_API int mm_channel_event(channel* c, channel_value v); + diff --git a/midimonster.c b/midimonster.c index b73eeff..d00f116 100644 --- a/midimonster.c +++ b/midimonster.c @@ -1,247 +1,31 @@ #include <string.h> #include <signal.h> -#include <unistd.h> -#include <errno.h> -#include <time.h> +#include <stdarg.h> #ifndef _WIN32 - #include <sys/select.h> #define MM_API __attribute__((visibility("default"))) #else #define MM_API __attribute__((dllexport)) #endif -#define BACKEND_NAME "core" -#define MM_SWAP_LIMIT 20 + +#define BACKEND_NAME "cli" #include "midimonster.h" +#include "core/core.h" #include "core/config.h" -#include "core/backend.h" -#include "core/plugin.h" - -/* Core-internal structures */ -typedef struct /*_event_collection*/ { - size_t alloc; - size_t n; - channel** channel; - channel_value* value; -} event_collection; - -typedef struct /*_mm_channel_mapping*/ { - channel* from; - size_t destinations; - channel** to; -} channel_mapping; - -static struct { - //routing_hash is set up for 256 buckets - size_t entries[256]; - channel_mapping* map[256]; - - event_collection pool[2]; - event_collection* events; -} routing = { - .events = routing.pool -}; - -static size_t fds = 0; -static managed_fd* fd = NULL; -static volatile sig_atomic_t fd_set_dirty = 1; -static uint64_t global_timestamp = 0; volatile static sig_atomic_t shutdown_requested = 0; -static void signal_handler(int signum){ - shutdown_requested = 1; -} - -static size_t routing_hash(channel* key){ - uint64_t repr = (uint64_t) key; - //return 8bit hash for 256 buckets, not ideal but it works - return (repr ^ (repr >> 8) ^ (repr >> 16) ^ (repr >> 24) ^ (repr >> 32)) & 0xFF; -} - -MM_API uint64_t mm_timestamp(){ - return global_timestamp; -} - -static void update_timestamp(){ - #ifdef _WIN32 - global_timestamp = GetTickCount(); - #else - struct timespec current; - if(clock_gettime(CLOCK_MONOTONIC_COARSE, ¤t)){ - fprintf(stderr, "Failed to update global timestamp, time-based processing for some backends may be impaired: %s\n", strerror(errno)); - return; - } - - global_timestamp = current.tv_sec * 1000 + current.tv_nsec / 1000000; - #endif -} - -int mm_map_channel(channel* from, channel* to){ - size_t u, m, bucket = routing_hash(from); - - //find existing source mapping - for(u = 0; u < routing.entries[bucket]; u++){ - if(routing.map[bucket][u].from == from){ - break; - } - } - - //create new entry - if(u == routing.entries[bucket]){ - routing.map[bucket] = realloc(routing.map[bucket], (routing.entries[bucket] + 1) * sizeof(channel_mapping)); - if(!routing.map[bucket]){ - routing.entries[bucket] = 0; - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - - memset(routing.map[bucket] + routing.entries[bucket], 0, sizeof(channel_mapping)); - routing.entries[bucket]++; - routing.map[bucket][u].from = from; - } - - //check whether the target is already mapped - for(m = 0; m < routing.map[bucket][u].destinations; m++){ - if(routing.map[bucket][u].to[m] == to){ - return 0; - } - } - - //add a mapping target - routing.map[bucket][u].to = realloc(routing.map[bucket][u].to, (routing.map[bucket][u].destinations + 1) * sizeof(channel*)); - if(!routing.map[bucket][u].to){ - fprintf(stderr, "Failed to allocate memory\n"); - routing.map[bucket][u].destinations = 0; - return 1; - } - - routing.map[bucket][u].to[routing.map[bucket][u].destinations] = to; - routing.map[bucket][u].destinations++; - return 0; -} - -static void routing_cleanup(){ - size_t u, n; - - for(u = 0; u < sizeof(routing.map) / sizeof(routing.map[0]); u++){ - for(n = 0; n < routing.entries[u]; n++){ - free(routing.map[u][n].to); - } - free(routing.map[u]); - routing.map[u] = NULL; - routing.entries[u] = 0; - } - - for(u = 0; u < sizeof(routing.pool) / sizeof(routing.pool[0]); u++){ - free(routing.pool[u].channel); - free(routing.pool[u].value); - routing.pool[u].alloc = 0; - } -} - -MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl){ - backend* b = backend_match(back); - size_t u; - - if(!b){ - fprintf(stderr, "Unknown backend %s registered for managed fd\n", back); - return 1; - } - - //find exact match - for(u = 0; u < fds; u++){ - if(fd[u].fd == new_fd && fd[u].backend == b){ - fd[u].impl = impl; - if(!manage){ - fd[u].fd = -1; - fd[u].backend = NULL; - fd[u].impl = NULL; - fd_set_dirty = 1; - } - return 0; - } - } - - if(!manage){ - return 0; - } - - //find free slot - for(u = 0; u < fds; u++){ - if(fd[u].fd < 0){ - break; - } - } - //if necessary expand - if(u == fds){ - fd = realloc(fd, (fds + 1) * sizeof(managed_fd)); - if(!fd){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - fds++; - } - - //store new fd - fd[u].fd = new_fd; - fd[u].backend = b; - fd[u].impl = impl; - fd_set_dirty = 1; - return 0; -} - -static void fds_free(){ - size_t u; - for(u = 0; u < fds; u++){ - if(fd[u].fd >= 0){ - close(fd[u].fd); - fd[u].fd = -1; - } - } - free(fd); - fds = 0; - fd = NULL; +MM_API int log_printf(int level, char* module, char* fmt, ...){ + int rv = 0; + va_list args; + va_start(args, fmt); + fprintf(stderr, "%s%s\t", level ? "debug/" : "", module); + rv = vfprintf(stderr, fmt, args); + va_end(args); + return rv; } -MM_API int mm_channel_event(channel* c, channel_value v){ - size_t u, p, bucket = routing_hash(c); - - //find mapped channels - for(u = 0; u < routing.entries[bucket]; u++){ - if(routing.map[bucket][u].from == c){ - break; - } - } - - if(u == routing.entries[bucket]){ - //target-only channel - return 0; - } - - //resize event structures to fit additional events - if(routing.events->n + routing.map[bucket][u].destinations >= routing.events->alloc){ - routing.events->channel = realloc(routing.events->channel, (routing.events->alloc + routing.map[bucket][u].destinations) * sizeof(channel*)); - routing.events->value = realloc(routing.events->value, (routing.events->alloc + routing.map[bucket][u].destinations) * sizeof(channel_value)); - - if(!routing.events->channel || !routing.events->value){ - fprintf(stderr, "Failed to allocate memory\n"); - routing.events->alloc = 0; - routing.events->n = 0; - return 1; - } - - routing.events->alloc += routing.map[bucket][u].destinations; - } - - //enqueue channel events - //FIXME this might lead to one channel being mentioned multiple times in an apply call - memcpy(routing.events->channel + routing.events->n, routing.map[bucket][u].to, routing.map[bucket][u].destinations * sizeof(channel*)); - for(p = 0; p < routing.map[bucket][u].destinations; p++){ - routing.events->value[routing.events->n + p] = v; - } - - routing.events->n += routing.map[bucket][u].destinations; - return 0; +static void signal_handler(int signum){ + shutdown_requested = 1; } static void version(){ @@ -255,36 +39,9 @@ static int usage(char* fn){ return EXIT_FAILURE; } -static fd_set fds_collect(int* max_fd){ - size_t u = 0; - fd_set rv_fds; - - if(max_fd){ - *max_fd = -1; - } - - DBGPF("Building selector set from %" PRIsize_t " FDs registered to core", fds); - FD_ZERO(&rv_fds); - for(u = 0; u < fds; u++){ - if(fd[u].fd >= 0){ - FD_SET(fd[u].fd, &rv_fds); - if(max_fd){ - *max_fd = max(*max_fd, fd[u].fd); - } - } - } - - return rv_fds; -} static int platform_initialize(){ #ifdef _WIN32 - WSADATA wsa; - WORD version = MAKEWORD(2, 2); - if(WSAStartup(version, &wsa)){ - return 1; - } - unsigned error_mode = SetErrorMode(0); SetErrorMode(error_mode | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); #endif @@ -341,131 +98,9 @@ static int args_parse(int argc, char** argv, char** cfg_file){ return 0; } -static int core_process(size_t nfds, managed_fd* signaled_fds){ - event_collection* secondary = NULL; - size_t u, swaps = 0; - - //run backend processing, collect events - DBGPF("%" PRIsize_t " backend FDs signaled", nfds); - if(backends_handle(nfds, signaled_fds)){ - return 1; - } - - //limit number of collector swaps per iteration to prevent complete deadlock - while(routing.events->n && swaps < MM_SWAP_LIMIT){ - //swap primary and secondary event collectors - DBGPF("Swapping event collectors, %" PRIsize_t " events in primary", routing.events->n); - for(u = 0; u < sizeof(routing.pool) / sizeof(routing.pool[0]); u++){ - if(routing.events != routing.pool + u){ - secondary = routing.events; - routing.events = routing.pool + u; - break; - } - } - - //push collected events to target backends - if(secondary->n && backends_notify(secondary->n, secondary->channel, secondary->value)){ - fprintf(stderr, "Backends failed to handle output\n"); - return 1; - } - - //reset the event count - secondary->n = 0; - } - - if(swaps == MM_SWAP_LIMIT){ - LOG("Iteration swap limit hit, a backend may be configured to route events in an infinite loop"); - } - - return 0; -} - -static int core_loop(){ - fd_set all_fds, read_fds; - managed_fd* signaled_fds = NULL; - struct timeval tv; - int error, maxfd = -1; - size_t n, u; - #ifdef _WIN32 - char* error_message = NULL; - #else - struct timespec ts; - #endif - - FD_ZERO(&all_fds); - - //process events - while(!shutdown_requested){ - //rebuild fd set if necessary - if(fd_set_dirty || !signaled_fds){ - all_fds = fds_collect(&maxfd); - signaled_fds = realloc(signaled_fds, fds * sizeof(managed_fd)); - if(!signaled_fds){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } - fd_set_dirty = 0; - } - - //wait for & translate events - read_fds = all_fds; - tv = backend_timeout(); - - //check whether there are any fds active, windows does not like select() without descriptors - if(maxfd >= 0){ - error = select(maxfd + 1, &read_fds, NULL, NULL, &tv); - if(error < 0){ - #ifndef _WIN32 - fprintf(stderr, "select failed: %s\n", strerror(errno)); - #else - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, WSAGetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error_message, 0, NULL); - fprintf(stderr, "select failed: %s\n", error_message); - LocalFree(error_message); - error_message = NULL; - #endif - free(signaled_fds); - return 1; - } - } - else{ - DBGPF("No descriptors, sleeping for %zu msec", tv.tv_sec * 1000 + tv.tv_usec / 1000); - #ifdef _WIN32 - Sleep(tv.tv_sec * 1000 + tv.tv_usec / 1000); - #else - ts.tv_sec = tv.tv_sec; - ts.tv_nsec = tv.tv_usec * 1000; - nanosleep(&ts, NULL); - #endif - } - - //update this iteration's timestamp - update_timestamp(); - - //find all signaled fds - n = 0; - for(u = 0; u < fds; u++){ - if(fd[u].fd >= 0 && FD_ISSET(fd[u].fd, &read_fds)){ - signaled_fds[n] = fd[u]; - n++; - } - } - - //fetch and process events - if(core_process(n, signaled_fds)){ - free(signaled_fds); - return 1; - } - } - - free(signaled_fds); - return 0; -} - int main(int argc, char** argv){ int rv = EXIT_FAILURE; char* cfg_file = DEFAULT_CFG; - size_t u, n = 0, max = 0; //parse commandline arguments if(args_parse(argc, argv, &cfg_file)){ @@ -479,57 +114,33 @@ int main(int argc, char** argv){ } //initialize backends - if(plugins_load(PLUGINS)){ - fprintf(stderr, "Failed to initialize a backend\n"); + if(core_initialize()){ goto bail; } //read config if(config_read(cfg_file)){ fprintf(stderr, "Failed to parse master configuration file %s\n", cfg_file); - backends_stop(); - routing_cleanup(); - fds_free(); - plugins_close(); - config_free(); + core_shutdown(); return (usage(argv[0]) | platform_shutdown()); } - //load an initial timestamp - update_timestamp(); - - //start backends - if(backends_start()){ + //start core + if(core_start()){ goto bail; } signal(SIGINT, signal_handler); - //count and report mappings - for(u = 0; u < sizeof(routing.map) / sizeof(routing.map[0]); u++){ - n += routing.entries[u]; - max = max(max, routing.entries[u]); - } - LOGPF("Routing %" PRIsize_t " sources, largest bucket has %" PRIsize_t " entries", - n, max); - - if(!fds){ - fprintf(stderr, "No descriptors registered for multiplexing\n"); - } - //run the core loop - if(!core_loop()){ - rv = EXIT_SUCCESS; + while(!shutdown_requested){ + if(core_iteration()){ + goto bail; + } } + rv = EXIT_SUCCESS; bail: - //free all data - backends_stop(); - routing_cleanup(); - fds_free(); - plugins_close(); - config_free(); - platform_shutdown(); - + core_shutdown(); return rv; } diff --git a/midimonster.h b/midimonster.h index 64aa1e5..15ce34a 100644 --- a/midimonster.h +++ b/midimonster.h @@ -40,16 +40,19 @@ /* Clamp a value to a range */ #define clamp(val,max,min) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val))) +/* Log function prototype - do not use directly. Use the LOG/LOGPF/DBGPF macros below instead */ +MM_API __attribute__((format(printf, 3, 4))) int log_printf(int level, char* module, char* fmt, ...); + /* Debug messages only compile in when DEBUG is set */ #ifdef DEBUG - #define DBGPF(format, ...) fprintf(stderr, "debug/%s\t" format "\n", (BACKEND_NAME), __VA_ARGS__) + #define DBGPF(format, ...) log_printf(1, (BACKEND_NAME), format "\n", __VA_ARGS__) #else #define DBGPF(format, ...) #endif /* Log messages should be routed through these macros to ensure interoperability with different core implementations */ -#define LOGPF(format, ...) fprintf(stderr, "%s\t" format "\n", (BACKEND_NAME), __VA_ARGS__) -#define LOG(message) fprintf(stderr, "%s\t%s\n", (BACKEND_NAME), (message)) +#define LOGPF(format, ...) log_printf(0, (BACKEND_NAME), format "\n", __VA_ARGS__) +#define LOG(message) log_printf(0, (BACKEND_NAME), message "\n") /* Stop compilation if the build system reports an error */ #ifdef BUILD_ERROR |