aboutsummaryrefslogtreecommitdiffhomepage
path: root/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'config.c')
-rw-r--r--config.c294
1 files changed, 274 insertions, 20 deletions
diff --git a/config.c b/config.c
index b81aeaf..0b9173e 100644
--- a/config.c
+++ b/config.c
@@ -1,5 +1,7 @@
#include <string.h>
#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
#include "midimonster.h"
#include "config.h"
#include "backend.h"
@@ -20,26 +22,221 @@ typedef enum {
static backend* current_backend = NULL;
static instance* current_instance = 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) || c < 0){
+ return -1;
+ }
+
+ bytes_read++;
+ }
+}
+#endif
+
static char* config_trim_line(char* in){
- ssize_t u;
+ ssize_t n;
//trim front
for(; *in && !isgraph(*in); in++){
}
//trim back
- for(u = strlen(in); u >= 0 && !isgraph(in[u]); u--){
- in[u] = 0;
+ for(n = strlen(in); n >= 0 && !isgraph(in[n]); n--){
+ in[n] = 0;
}
return in;
}
+static int config_glob_parse(channel_glob* glob, char* spec, size_t length){
+ char* parse_offset = NULL;
+ //FIXME might want to allow negative delimiters at some point
+
+ //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_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 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;
+ uint64_t current_value = 0;
+ channel* result = NULL;
+ char* resolved_spec = strdup(spec->spec);
+
+ if(!resolved_spec){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return NULL;
+ }
+
+ //TODO if not internal, try to resolve externally
+
+ //iterate and resolve globs
+ for(glob = spec->globs; glob > 0; glob--){
+ current_value = spec->glob[glob - 1].limits.u64[0] + (n % spec->glob[glob - 1].values);
+ if(spec->glob[glob - 1].limits.u64[0] > spec->glob[glob - 1].limits.u64[1]){
+ current_value = spec->glob[glob - 1].limits.u64[0] - (n % spec->glob[glob - 1].values);
+ }
+ glob_length = spec->glob[glob - 1].offset[1] - spec->glob[glob - 1].offset[0];
+ n /= spec->glob[glob - 1].values;
+
+ //write out value
+ bytes = snprintf(resolved_spec + spec->glob[glob - 1].offset[0],
+ glob_length,
+ "%" PRIu64,
+ current_value);
+ if(bytes > glob_length){
+ fprintf(stderr, "Internal error resolving glob %s\n", spec->spec);
+ goto bail;
+ }
+
+ //move trailing data
+ if(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]);
+ }
+ }
+
+ result = inst->backend->channel(inst, resolved_spec, map_direction);
+ if(spec->globs && !result){
+ fprintf(stderr, "Failed to match multichannel evaluation %s to a channel\n", 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);
- char* chanspec_to = to, *chanspec_from = from;
+ 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){
@@ -50,21 +247,21 @@ static int config_map(char* to_raw, char* from_raw){
}
//separate channel spec from instance
- for(; *chanspec_to && *chanspec_to != '.'; chanspec_to++){
+ for(; *(spec_to.spec) && *(spec_to.spec) != '.'; spec_to.spec++){
}
- for(; *chanspec_from && *chanspec_from != '.'; chanspec_from++){
+ for(; *(spec_from.spec) && *(spec_from.spec) != '.'; spec_from.spec++){
}
- if(!*chanspec_to || !*chanspec_from){
+ if(!spec_from.spec[0] || !spec_to.spec[0]){
fprintf(stderr, "Mapping does not contain a proper instance specification\n");
goto done;
}
//terminate
- *chanspec_to = *chanspec_from = 0;
- chanspec_to++;
- chanspec_from++;
+ spec_from.spec[0] = spec_to.spec[0] = 0;
+ spec_from.spec++;
+ spec_to.spec++;
//find matching instances
instance_to = instance_match(to);
@@ -75,31 +272,85 @@ static int config_map(char* to_raw, char* from_raw){
goto done;
}
- channel_from = instance_from->backend->channel(instance_from, chanspec_from);
- channel_to = instance_to->backend->channel(instance_to, chanspec_to);
-
- if(!channel_from || !channel_to){
- fprintf(stderr, "Failed to parse channel specifications\n");
+ //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;
}
- rv = mm_map_channel(channel_from, channel_to);
+ //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;
}
-int config_read(char* cfg_file){
+int config_read(char* cfg_filepath){
int rv = 1;
size_t line_alloc = 0;
ssize_t status;
map_type mapping_type = map_rtl;
char* line_raw = NULL, *line, *separator;
- FILE* source = fopen(cfg_file, "r");
+
+ //create heap copy of file name because original might be in readonly memory
+ char* source_dir = strdup(cfg_filepath), *source_file = NULL;
+ #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(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;
+ }
+
+ FILE* source = fopen(source_file, "r");
+
if(!source){
fprintf(stderr, "Failed to open configuration file for reading\n");
- return 1;
+ goto bail;
}
for(status = getline(&line_raw, &line_alloc, source); status >= 0; status = getline(&line_raw, &line_alloc, source)){
@@ -239,7 +490,10 @@ int config_read(char* cfg_file){
rv = 0;
bail:
- fclose(source);
+ free(source_dir);
+ if(source){
+ fclose(source);
+ }
free(line_raw);
return rv;
}