From d757a9c63371fcf5f9aa832d58ed4b8bdf634909 Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 10 Mar 2020 20:42:55 +0100 Subject: Restructure core loop, add check for zero-descriptor case --- midimonster.c | 197 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 121 insertions(+), 76 deletions(-) (limited to 'midimonster.c') diff --git a/midimonster.c b/midimonster.c index b8594b4..3212f7f 100644 --- a/midimonster.c +++ b/midimonster.c @@ -9,6 +9,7 @@ #else #define MM_API __attribute__((dllexport)) #endif +#define BACKEND_NAME "core" #include "midimonster.h" #include "config.h" #include "backend.h" @@ -325,18 +326,126 @@ static int args_parse(int argc, char** argv, char** cfg_file){ return 0; } -int main(int argc, char** argv){ - fd_set all_fds, read_fds; +static int core_process(size_t nfds, managed_fd* signaled_fds){ event_collection* secondary = NULL; - struct timeval tv; - size_t u, n; + size_t u; + + //run backend processing, collect events + DBGPF("%lu backend FDs signaled\n", nfds); + if(backends_handle(nfds, signaled_fds)){ + return 1; + } + + while(primary->n){ + //swap primary and secondary event collectors + DBGPF("Swapping event collectors, %lu events in primary\n", primary->n); + for(u = 0; u < sizeof(event_pool) / sizeof(event_collection); u++){ + if(primary != event_pool + u){ + secondary = primary; + primary = event_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; + } + + return 0; +} + +static int core_loop(){ + fd_set all_fds, read_fds; managed_fd* signaled_fds = NULL; - int rv = EXIT_FAILURE, error, maxfd = -1; - char* cfg_file = DEFAULT_CFG; + 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){ + 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; + error = 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; + //parse commandline arguments if(args_parse(argc, argv, &cfg_file)){ return EXIT_FAILURE; @@ -347,7 +456,6 @@ int main(int argc, char** argv){ return EXIT_FAILURE; } - FD_ZERO(&all_fds); //initialize backends if(plugins_load(PLUGINS)){ fprintf(stderr, "Failed to initialize a backend\n"); @@ -377,80 +485,17 @@ int main(int argc, char** argv){ signal(SIGINT, signal_handler); - //process events - while(!shutdown_requested){ - //rebuild fd set if necessary - if(fd_set_dirty){ - all_fds = fds_collect(&maxfd); - signaled_fds = realloc(signaled_fds, fds * sizeof(managed_fd)); - if(!signaled_fds){ - fprintf(stderr, "Failed to allocate memory\n"); - goto bail; - } - fd_set_dirty = 0; - } - - //wait for & translate events - read_fds = all_fds; - tv = backend_timeout(); - 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 - break; - } - - //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++; - } - } - - //update this iteration's timestamp - update_timestamp(); - - //run backend processing, collect events - DBGPF("%lu backend FDs signaled\n", n); - if(backends_handle(n, signaled_fds)){ - goto bail; - } - - while(primary->n){ - //swap primary and secondary event collectors - DBGPF("Swapping event collectors, %lu events in primary\n", primary->n); - for(u = 0; u < sizeof(event_pool) / sizeof(event_collection); u++){ - if(primary != event_pool + u){ - secondary = primary; - primary = event_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"); - goto bail; - } + if(!fds){ + fprintf(stderr, "No descriptors registered for multiplexing\n"); + } - //reset the event count - secondary->n = 0; - } + //run the core loop + if(!core_loop()){ + rv = EXIT_SUCCESS; } - rv = EXIT_SUCCESS; bail: //free all data - free(signaled_fds); backends_stop(); channels_free(); instances_free(); -- cgit v1.2.3 From c15ac89a33b6f6f51ac0c3da7a86dc43c8a9321a Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 10 Mar 2020 22:31:17 +0100 Subject: Fix Coverity CID 354930 & CID 354929 --- midimonster.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'midimonster.c') diff --git a/midimonster.c b/midimonster.c index 3212f7f..2b0b7a0 100644 --- a/midimonster.c +++ b/midimonster.c @@ -377,7 +377,7 @@ static int core_loop(){ //process events while(!shutdown_requested){ //rebuild fd set if necessary - if(fd_set_dirty){ + if(fd_set_dirty || !signaled_fds){ all_fds = fds_collect(&maxfd); signaled_fds = realloc(signaled_fds, fds * sizeof(managed_fd)); if(!signaled_fds){ @@ -415,7 +415,7 @@ static int core_loop(){ #else ts.tv_sec = tv.tv_sec; ts.tv_nsec = tv.tv_usec * 1000; - error = nanosleep(&ts, NULL); + nanosleep(&ts, NULL); #endif } -- cgit v1.2.3 From 6da00154f7745a4705047fc73ce42b7036b0bbdc Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 15 Mar 2020 12:19:50 +0100 Subject: Fix build and CI exits --- .travis-ci.sh | 14 ++++++++++---- backend.c | 4 +++- midimonster.c | 4 ---- 3 files changed, 13 insertions(+), 9 deletions(-) (limited to 'midimonster.c') diff --git a/.travis-ci.sh b/.travis-ci.sh index 3b7d7f5..c832f2c 100644 --- a/.travis-ci.sh +++ b/.travis-ci.sh @@ -21,7 +21,7 @@ if [ "$TASK" = "spellcheck" ]; then fi # Run codespell to find some more - cs_results=$(xargs codespell --quiet 2 --regex "[a-zA-Z0-9][\\-'a-zA-Z0-9]+[a-zA-Z0-9]" <<< "$spellcheck_files" 2>&1) + cs_results=$(xargs codespell --quiet 2 <<< "$spellcheck_files" 2>&1) cs_errors=$(wc -l <<< "$cs_results") if [ "$cs_errors" -ne 0 ]; then printf "Codespell found %s errors:\n\n" "$cs_errors" @@ -62,11 +62,15 @@ elif [ "$TASK" = "codesmell" ]; then elif [ "$TASK" = "sanitize" ]; then # Run sanitized compile travis_fold start "make_sanitize" - make sanitize; + if make sanitize; then + exit "$?" + fi travis_fold end "make_sanitize" elif [ "$TASK" = "windows" ]; then travis_fold start "make_windows" - make windows; + if make windows; then + exit "$?" + fi make -C backends lua.dll travis_fold end "make_windows" if [ "$(git describe)" == "$(git describe --abbrev=0)" ]; then @@ -87,7 +91,9 @@ elif [ "$TASK" = "windows" ]; then else # Otherwise compile as normal travis_fold start "make" - make full; + if make full; then + exit "$?" + fi travis_fold end "make" if [ "$(git describe)" == "$(git describe --abbrev=0)" ]; then travis_fold start "deploy_unix" diff --git a/backend.c b/backend.c index 65cbd7d..cb8bf3f 100644 --- a/backend.c +++ b/backend.c @@ -311,6 +311,9 @@ 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 @@ -335,7 +338,6 @@ int backends_stop(){ registry.instances[u] = NULL; } - channels_free(); free(registry.backends); free(registry.instances); registry.n = 0; diff --git a/midimonster.c b/midimonster.c index 2b0b7a0..8e217e7 100644 --- a/midimonster.c +++ b/midimonster.c @@ -466,8 +466,6 @@ int main(int argc, char** argv){ if(config_read(cfg_file)){ fprintf(stderr, "Failed to read configuration file %s\n", cfg_file); backends_stop(); - channels_free(); - instances_free(); map_free(); fds_free(); plugins_close(); @@ -497,8 +495,6 @@ int main(int argc, char** argv){ bail: //free all data backends_stop(); - channels_free(); - instances_free(); map_free(); fds_free(); event_free(); -- cgit v1.2.3 From 846a49199ba011a00e4814bacac524a6fe9b299a Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 16 Mar 2020 22:19:27 +0100 Subject: Hash-index event routing table --- installer.sh | 87 +++++++++++++---------------------- midimonster.c | 142 +++++++++++++++++++++++++++++++--------------------------- midimonster.h | 2 + 3 files changed, 109 insertions(+), 122 deletions(-) (limited to 'midimonster.c') diff --git a/installer.sh b/installer.sh index 56021c4..111c783 100755 --- a/installer.sh +++ b/installer.sh @@ -26,13 +26,13 @@ latest_version="$(curl --silent "https://api.github.com/repos/cbdevnet/midimonst # make invocation arguments makeargs="all" -normal=$(tput sgr0) -dim=$(tput dim) -bold=$(tput bold) -uline=$(tput smul) -c_red=$(tput setaf 1) -c_green=$(tput setaf 2) -c_mag=$(tput setaf 5) +normal="$(tput sgr0)" +dim="$(tput dim)" +bold="$(tput bold)" +uline="$(tput smul)" +c_red="$(tput setaf 1)" +c_green="$(tput setaf 2)" +c_mag="$(tput setaf 5)" DEFAULT_PREFIX="/usr" DEFAULT_PLUGINPATH="/lib/midimonster" @@ -49,7 +49,7 @@ assign_defaults(){ ARGS(){ for i in "$@"; do - case $i in + case "$i" in --prefix=*) VAR_PREFIX="${i#*=}" ;; @@ -71,37 +71,28 @@ ARGS(){ -fu|--forceupdate) UPDATER_FORCE="1" ;; - -supd|--selfupdate) - NIGHTLY="" - self_update - rmdir "$tmp_path" + --install-updater) + NIGHTLY=1 prepare_repo + install_script exit 0 ;; - -insup|--installupdater) - NIGHTLY="" - install_updater - rmdir "$tmp_path" - exit 0 - ;; - -insdep|--installdeps) + --install-dependencies) install_dependencies - rmdir "$tmp_path" exit 0 ;; -h|--help|*) assign_defaults printf "${bold}Usage:${normal} ${0} ${c_green}[OPTIONS]${normal}" - printf "\n\t${c_green} --prefix${normal} ${c_red}${normal}\tSet the installation prefix\t\t${c_mag}Default:${normal} ${dim}%s${normal}" "$VAR_PREFIX" - printf "\n\t${c_green} --plugins${normal} ${c_red}${normal}\tSet the plugin install path\t\t${c_mag}Default:${normal} ${dim}%s${normal}" "$VAR_PLUGINS" - printf "\n\t${c_green} --defcfg${normal} ${c_red}${normal}\tSet the default configuration path\t${c_mag}Default:${normal} ${dim}%s${normal}" "$VAR_DEFAULT_CFG" - printf "\n\t${c_green} --examples${normal} ${c_red}${normal}\tSet the path for example configurations\t${c_mag}Default:${normal} ${dim}%s${normal}\n" "$VAR_EXAMPLE_CFGS" - printf "\n\t${c_green} --dev${normal}\t\t\tInstall nightly version" - printf "\n ${c_green}-d,\t --default${normal}\t\tUse default values to install" - printf "\n ${c_green}-fu,\t --forceupdate${normal}\t\tForce the updater to update without a version check" - printf "\n ${c_green}-supd,\t --selfupdate${normal}\t\tUpdates this script to the newest version" - printf "\n ${c_green}-insup, --installupdater${normal}\tInstall the updater (Run with midimonster-updater)" - printf "\n ${c_green}-insdep, --installdeps${normal}\t\tInstall dependencies and exit" - printf "\n ${c_green}-h,\t --help${normal}\t\tShow this message and exit" + printf "\n\t${c_green}--prefix${normal} ${c_red}${normal}\t\tSet the installation prefix\t\t${c_mag}Default:${normal} ${dim}%s${normal}" "$VAR_PREFIX" + printf "\n\t${c_green}--plugins${normal} ${c_red}${normal}\tSet the plugin install path\t\t${c_mag}Default:${normal} ${dim}%s${normal}" "$VAR_PLUGINS" + printf "\n\t${c_green}--defcfg${normal} ${c_red}${normal}\t\tSet the default configuration path\t${c_mag}Default:${normal} ${dim}%s${normal}" "$VAR_DEFAULT_CFG" + printf "\n\t${c_green}--examples${normal} ${c_red}${normal}\tSet the path for example configurations\t${c_mag}Default:${normal} ${dim}%s${normal}\n" "$VAR_EXAMPLE_CFGS" + printf "\n\t${c_green}--dev${normal}\t\t\t\tInstall nightly version" + printf "\n\t${c_green}-d, --default${normal}\t\t\tUse default values to install" + printf "\n\t${c_green}-fu, --forceupdate${normal}\t\tForce the updater to update without a version check" + printf "\n\t${c_green}--install-updater${normal}\t\tInstall the updater (Run with midimonster-updater)" + printf "\n\t${c_green}--install-dependencies${normal}\t\tInstall dependencies and exit" + printf "\n\t${c_green}-h, --help${normal}\t\t\tShow this message and exit" printf "\n\t${uline}${bold}${c_mag}Each argument can be overwritten by another, the last one is used!${normal}\n" rmdir "$tmp_path" exit 1 @@ -116,7 +107,7 @@ install_dependencies(){ for dependency in ${deps[@]}; do if [ "$(dpkg-query -W -f='${Status}' "$dependency" 2>/dev/null | grep -c "ok installed")" -eq 0 ]; then printf "Installing %s\n" "$dependency" - apt-get install $dependency + apt-get install "$dependency" else printf "%s already installed!\n" "$dependency" fi @@ -181,7 +172,7 @@ prepare_repo(){ printf "Finding latest stable version...\n" last_tag=$(git describe --abbrev=0) printf "Checking out %s...\n" "$last_tag" - git checkout -f -q $last_tag + git checkout -f -q "$last_tag" fi } @@ -207,30 +198,14 @@ save_config(){ printf "VAR_PREFIX=%s\nVAR_PLUGINS=%s\nVAR_DEFAULT_CFG=%s\nVAR_DESTDIR=%s\nVAR_EXAMPLE_CFGS=%s\n" "$VAR_PREFIX" "$VAR_PLUGINS" "$VAR_DEFAULT_CFG" "$VAR_DESTDIR" "$VAR_EXAMPLE_CFGS" > "$updater_dir/updater.conf" } -# Updates this script either from the downloaded sources or the remote repository. -self_update(){ +# Updates this script using the one from the checked out repo (containing the requested version) +install_script(){ mkdir -p "$updater_dir" - if [ "$NIGHTLY" = "1" ]; then - cp -u "$tmp_path/installer.sh" "$0" - elif [ "$NIGHTLY" != "1" ]; then - wget https://raw.githubusercontent.com/cbdevnet/midimonster/master/installer.sh -O "$0" - fi - chmod +x "$0" -} - -install_updater(){ - mkdir -p "$updater_dir" - if [ "$NIGHTLY" = "1" ]; then - printf "Copying updater to %s/updater.sh\n" "$updater_dir" - cp -u "$tmp_path/installer.sh" "$updater_dir/updater.sh" - else - printf "Downloading updater to %s/updater.sh\n" "$updater_dir" - wget https://raw.githubusercontent.com/cbdevnet/midimonster/master/installer.sh -O "$updater_dir/updater.sh" - fi + printf "Copying updater to %s/updater.sh\n" "$updater_dir" + cp "$tmp_path/installer.sh" "$updater_dir/updater.sh" chmod +x "$updater_dir/updater.sh" printf "Creating symlink /usr/bin/midimonster-updater\n" ln -s "$updater_dir/updater.sh" "/usr/bin/midimonster-updater" - printf "Done.\n" } error_handler(){ @@ -238,7 +213,7 @@ error_handler(){ exit 1 } -cleanup() { +cleanup(){ if [ -d "$tmp_path" ]; then printf "Cleaning up temporary files...\n" rm -rf "$tmp_path" @@ -288,7 +263,7 @@ if [ -f "$updater_dir/updater.conf" ] || [ "$UPDATER_FORCE" = "1" ]; then # Run updater steps prepare_repo - install_updater + install_script save_config build else @@ -296,7 +271,7 @@ else install_dependencies prepare_repo ask_questions - install_updater + install_script save_config build fi diff --git a/midimonster.c b/midimonster.c index 8e217e7..afb6a2b 100644 --- a/midimonster.c +++ b/midimonster.c @@ -29,25 +29,34 @@ typedef struct /*_mm_channel_mapping*/ { channel** to; } channel_mapping; -static size_t mappings = 0; -static channel_mapping* map = NULL; +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; -static event_collection event_pool[2] = { - {0}, - {0} -}; -static event_collection* primary = event_pool; - 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; } @@ -67,53 +76,66 @@ static void update_timestamp(){ } int mm_map_channel(channel* from, channel* to){ - size_t u, m; + size_t u, m, bucket = routing_hash(from); + //find existing source mapping - for(u = 0; u < mappings; u++){ - if(map[u].from == from){ + for(u = 0; u < routing.entries[bucket]; u++){ + if(routing.map[bucket][u].from == from){ break; } } //create new entry - if(u == mappings){ - map = realloc(map, (mappings + 1) * sizeof(channel_mapping)); - if(!map){ + 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(map + mappings, 0, sizeof(channel_mapping)); - mappings++; - map[u].from = from; + + 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 < map[u].destinations; m++){ - if(map[u].to[m] == to){ + for(m = 0; m < routing.map[bucket][u].destinations; m++){ + if(routing.map[bucket][u].to[m] == to){ return 0; } } - map[u].to = realloc(map[u].to, (map[u].destinations + 1) * sizeof(channel*)); - if(!map[u].to){ + //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"); - map[u].destinations = 0; + routing.map[bucket][u].destinations = 0; return 1; } - map[u].to[map[u].destinations] = to; - map[u].destinations++; + routing.map[bucket][u].to[routing.map[bucket][u].destinations] = to; + routing.map[bucket][u].destinations++; return 0; } -static void map_free(){ - size_t u; - for(u = 0; u < mappings; u++){ - free(map[u].to); +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; } - free(map); - mappings = 0; - map = NULL; } MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl){ @@ -170,7 +192,6 @@ MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl){ static void fds_free(){ size_t u; for(u = 0; u < fds; u++){ - //TODO free impl if(fd[u].fd >= 0){ close(fd[u].fd); fd[u].fd = -1; @@ -182,56 +203,46 @@ static void fds_free(){ } MM_API int mm_channel_event(channel* c, channel_value v){ - size_t u, p; + size_t u, p, bucket = routing_hash(c); //find mapped channels - for(u = 0; u < mappings; u++){ - if(map[u].from == c){ + for(u = 0; u < routing.entries[bucket]; u++){ + if(routing.map[bucket][u].from == c){ break; } } - if(u == mappings){ + if(u == routing.entries[bucket]){ //target-only channel return 0; } //resize event structures to fit additional events - if(primary->n + map[u].destinations >= primary->alloc){ - primary->channel = realloc(primary->channel, (primary->alloc + map[u].destinations) * sizeof(channel*)); - primary->value = realloc(primary->value, (primary->alloc + map[u].destinations) * sizeof(channel_value)); + 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(!primary->channel || !primary->value){ + if(!routing.events->channel || !routing.events->value){ fprintf(stderr, "Failed to allocate memory\n"); - primary->alloc = 0; - primary->n = 0; + routing.events->alloc = 0; + routing.events->n = 0; return 1; } - primary->alloc += map[u].destinations; + 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 - for(p = 0; p < map[u].destinations; p++){ - primary->channel[primary->n + p] = map[u].to[p]; - primary->value[primary->n + p] = v; + 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; } - primary->n += map[u].destinations; + routing.events->n += routing.map[bucket][u].destinations; return 0; } -static void event_free(){ - size_t u; - - for(u = 0; u < sizeof(event_pool) / sizeof(event_collection); u++){ - free(event_pool[u].channel); - free(event_pool[u].value); - event_pool[u].alloc = 0; - } -} - static void version(){ printf("MIDIMonster %s\n", MIDIMONSTER_VERSION); } @@ -336,13 +347,13 @@ static int core_process(size_t nfds, managed_fd* signaled_fds){ return 1; } - while(primary->n){ + while(routing.events->n){ //swap primary and secondary event collectors - DBGPF("Swapping event collectors, %lu events in primary\n", primary->n); - for(u = 0; u < sizeof(event_pool) / sizeof(event_collection); u++){ - if(primary != event_pool + u){ - secondary = primary; - primary = event_pool + u; + DBGPF("Swapping event collectors, %lu events in primary\n", 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; } } @@ -466,7 +477,7 @@ int main(int argc, char** argv){ if(config_read(cfg_file)){ fprintf(stderr, "Failed to read configuration file %s\n", cfg_file); backends_stop(); - map_free(); + routing_cleanup(); fds_free(); plugins_close(); config_free(); @@ -495,9 +506,8 @@ int main(int argc, char** argv){ bail: //free all data backends_stop(); - map_free(); + routing_cleanup(); fds_free(); - event_free(); plugins_close(); config_free(); platform_shutdown(); diff --git a/midimonster.h b/midimonster.h index dc0e255..61467c1 100644 --- a/midimonster.h +++ b/midimonster.h @@ -247,6 +247,8 @@ MM_API channel* mm_channel(instance* i, uint64_t ident, uint8_t create); * Register (manage = 1) or unregister (manage = 0) a file descriptor * to be selected on. The backend will be notified when the descriptor * becomes ready to read via its registered mmbackend_process_fd call. + * The `impl` argument will be provided within the corresponding + * managed_fd structure upon callback. */ MM_API int mm_manage_fd(int fd, char* backend, int manage, void* impl); -- cgit v1.2.3 From 636a9592eab1963c2b1a77bd59eaff317fa55b75 Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 17 Mar 2020 01:09:52 +0100 Subject: Fix registry apply bug, fix openpixelcontrol broadcast --- backend.c | 3 +-- backends/openpixelcontrol.c | 10 +++++----- midimonster.c | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) (limited to 'midimonster.c') diff --git a/backend.c b/backend.c index cb8bf3f..9e7989e 100644 --- a/backend.c +++ b/backend.c @@ -55,7 +55,7 @@ int backends_notify(size_t nev, channel** c, channel_value* v){ channel_value xval; channel* xchnl = NULL; - for(u = 0; u < nev && !rv; u++){ + for(u = 0; u < nev && !rv; u = n){ //sort for this instance n = u + 1; for(p = u + 1; p < nev; p++){ @@ -75,7 +75,6 @@ int backends_notify(size_t nev, channel** c, channel_value* v){ //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); - u = n; } return 0; diff --git a/backends/openpixelcontrol.c b/backends/openpixelcontrol.c index 168e077..2a5e01f 100644 --- a/backends/openpixelcontrol.c +++ b/backends/openpixelcontrol.c @@ -495,11 +495,11 @@ static ssize_t openpixel_client_headerdata(instance* inst, openpixel_client* cli } else{ client->buffer = openpixel_buffer_find(data, client->hdr.strip, 1); - } - //if no buffer or mode mismatch, ignore data - if(client->buffer < 0 - || data->mode != client->hdr.mode){ - client->buffer = -2; //mark for ignore + //if no buffer or mode mismatch, ignore data + if(client->buffer < 0 + || data->mode != client->hdr.mode){ + client->buffer = -2; //mark for ignore + } } client->left = be16toh(client->hdr.length); client->offset = 0; diff --git a/midimonster.c b/midimonster.c index afb6a2b..1691671 100644 --- a/midimonster.c +++ b/midimonster.c @@ -409,7 +409,7 @@ static int core_loop(){ #ifndef _WIN32 fprintf(stderr, "select failed: %s\n", strerror(errno)); #else - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + 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); -- cgit v1.2.3 From daf9ac5b883cf981a163654d58932bfbdce619d9 Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 17 Mar 2020 23:58:06 +0100 Subject: Output routing info to log --- configs/launchctl-sacn.cfg | 2 +- midimonster.c | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'midimonster.c') diff --git a/configs/launchctl-sacn.cfg b/configs/launchctl-sacn.cfg index dedfc0f..10a736a 100644 --- a/configs/launchctl-sacn.cfg +++ b/configs/launchctl-sacn.cfg @@ -7,7 +7,7 @@ name = MIDIMonster [backend sacn] -bind = 0.0.0.0 +bind = 0.0.0.0 5568 local [midi lc] read = Launch Control diff --git a/midimonster.c b/midimonster.c index 1691671..5109eab 100644 --- a/midimonster.c +++ b/midimonster.c @@ -456,6 +456,7 @@ static int core_loop(){ 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)){ @@ -494,6 +495,14 @@ int main(int argc, char** argv){ 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"); } -- cgit v1.2.3 From f9ae5d3a21f520380b3f761b71e211e6c438227a Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 13 Apr 2020 11:15:20 +0200 Subject: Introduce core iteration limit to break deadlocks --- midimonster.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'midimonster.c') diff --git a/midimonster.c b/midimonster.c index 5109eab..8100607 100644 --- a/midimonster.c +++ b/midimonster.c @@ -10,6 +10,7 @@ #define MM_API __attribute__((dllexport)) #endif #define BACKEND_NAME "core" +#define MM_SWAP_LIMIT 20 #include "midimonster.h" #include "config.h" #include "backend.h" @@ -339,7 +340,7 @@ static int args_parse(int argc, char** argv, char** cfg_file){ static int core_process(size_t nfds, managed_fd* signaled_fds){ event_collection* secondary = NULL; - size_t u; + size_t u, swaps = 0; //run backend processing, collect events DBGPF("%lu backend FDs signaled\n", nfds); @@ -347,7 +348,8 @@ static int core_process(size_t nfds, managed_fd* signaled_fds){ return 1; } - while(routing.events->n){ + //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, %lu events in primary\n", routing.events->n); for(u = 0; u < sizeof(routing.pool) / sizeof(routing.pool[0]); u++){ @@ -368,6 +370,10 @@ static int core_process(size_t nfds, managed_fd* signaled_fds){ 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; } -- cgit v1.2.3 From 918fb606174dcf42553be65e3d2306996c52488f Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 21 Apr 2020 00:13:51 +0200 Subject: Implement recursive configuration file parsing (Fixes #59) --- README.md | 4 ++++ config.c | 25 +++++++++++++++++++++++-- midimonster.c | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) (limited to 'midimonster.c') diff --git a/README.md b/README.md index 12d87eb..a7ca212 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ lines of the form `option = value`. Lines starting with a semicolon are treated as comments and ignored. Inline comments are not currently supported. +Configuration files may be included recursively in other configuration files using +the syntax `[include ]`. This will read the referenced configuration file as +if it were inserted at that point. + Example configuration files may be found in [configs/](configs/). ### Backend and instance configuration diff --git a/config.c b/config.c index aef4f87..b5e0747 100644 --- a/config.c +++ b/config.c @@ -2,6 +2,11 @@ #include #include #include +#ifndef _WIN32 +#include +#endif + +#define BACKEND_NAME "core/cfg" #include "midimonster.h" #include "config.h" #include "backend.h" @@ -349,6 +354,10 @@ static int config_line(char* line){ } } } + 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; @@ -493,7 +502,7 @@ int config_read(char* cfg_filepath){ 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; + char* source_dir = strdup(cfg_filepath), *source_file = NULL, original_dir[PATH_MAX * 2] = ""; #ifdef _WIN32 char path_separator = '\\'; #else @@ -510,6 +519,12 @@ int config_read(char* cfg_filepath){ 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; @@ -519,10 +534,11 @@ int config_read(char* cfg_filepath){ 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 configuration file for reading\n"); + fprintf(stderr, "Failed to open %s for reading\n", cfg_filepath); goto bail; } @@ -536,6 +552,11 @@ int config_read(char* cfg_filepath){ 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); diff --git a/midimonster.c b/midimonster.c index 8100607..1065e5c 100644 --- a/midimonster.c +++ b/midimonster.c @@ -482,7 +482,7 @@ int main(int argc, char** argv){ //read config if(config_read(cfg_file)){ - fprintf(stderr, "Failed to read configuration file %s\n", cfg_file); + fprintf(stderr, "Failed to parse master configuration file %s\n", cfg_file); backends_stop(); routing_cleanup(); fds_free(); -- cgit v1.2.3 From 6503d83dee9c2c7dd30572b84f11b95f832e9309 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 26 Apr 2020 21:50:00 +0200 Subject: Disable stop-the-world windows error messages --- .travis-ci.sh | 1 - midimonster.c | 3 +++ plugin.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'midimonster.c') diff --git a/.travis-ci.sh b/.travis-ci.sh index 5272fde..39652a1 100644 --- a/.travis-ci.sh +++ b/.travis-ci.sh @@ -73,7 +73,6 @@ elif [ "$TASK" = "windows" ]; then fi # Build the lua backend but disable it by default to avoid scary error messages make -C backends lua.dll - mv backends/lua.dll backends/lua.dll.disabled travis_fold end "make_windows" if [ "$(git describe)" == "$(git describe --abbrev=0)" ]; then travis_fold start "deploy_windows" diff --git a/midimonster.c b/midimonster.c index 1065e5c..b418711 100644 --- a/midimonster.c +++ b/midimonster.c @@ -284,6 +284,9 @@ static int platform_initialize(){ if(WSAStartup(version, &wsa)){ return 1; } + + unsigned error_mode = SetErrorMode(0); + SetErrorMode(error_mode | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); #endif return 0; } diff --git a/plugin.c b/plugin.c index 56d6651..e7d8eba 100644 --- a/plugin.c +++ b/plugin.c @@ -51,7 +51,7 @@ 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: %s\n", lib, error); + 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()); -- cgit v1.2.3