diff options
| author | cbdev <cb@cbcdn.com> | 2021-06-21 22:03:17 +0200 | 
|---|---|---|
| committer | cbdev <cb@cbcdn.com> | 2021-06-21 22:03:17 +0200 | 
| commit | 921ce069a4770fbadad7bb4e806361e857469409 (patch) | |
| tree | 944dbc2c4fbb539168f9353de5eba175313762b6 /core | |
| parent | 9faaeeac95d20ea678a844de05b8b0515f19e19d (diff) | |
| download | midimonster-921ce069a4770fbadad7bb4e806361e857469409.tar.gz midimonster-921ce069a4770fbadad7bb4e806361e857469409.tar.bz2 midimonster-921ce069a4770fbadad7bb4e806361e857469409.zip | |
Repository cleanup
Diffstat (limited to 'core')
| -rw-r--r-- | core/backend.c | 361 | ||||
| -rw-r--r-- | core/backend.h | 17 | ||||
| -rw-r--r-- | core/config.c | 708 | ||||
| -rw-r--r-- | core/config.h | 54 | ||||
| -rw-r--r-- | core/plugin.c | 187 | ||||
| -rw-r--r-- | core/plugin.h | 3 | 
6 files changed, 1330 insertions, 0 deletions
| diff --git a/core/backend.c b/core/backend.c new file mode 100644 index 0000000..16e095c --- /dev/null +++ b/core/backend.c @@ -0,0 +1,361 @@ +#include <string.h> +#ifndef _WIN32 +#define MM_API __attribute__((visibility ("default"))) +#else +#define MM_API __attribute__((dllexport)) +#endif +#define BACKEND_NAME "core/be" +#include "midimonster.h" +#include "backend.h" + +static struct { +	size_t n; +	backend* backends; +	instance*** instances; +} registry = { +	.n = 0 +}; + +//the global channel store was converted from a naive list to a hashmap of lists for performance reasons +static struct { +	//channelstore hash is set up for 256 buckets +	size_t n[256]; +	channel** entry[256]; +} channels = { +	.n = { +		0 +	} +}; + +static size_t channelstore_hash(instance* inst, uint64_t ident){ +	uint64_t repr = ((uint64_t) inst) ^ ident; +	return (repr ^ (repr >> 8) ^ (repr >> 16) ^ (repr >> 24) ^ (repr >> 32)) & 0xFF; +} + +int backends_handle(size_t nfds, managed_fd* fds){ +	size_t u, p, n; +	int rv = 0; +	managed_fd xchg; + +	for(u = 0; u < registry.n && !rv; u++){ +		n = 0; + +		for(p = 0; p < nfds; p++){ +			if(fds[p].backend == registry.backends + u){ +				xchg = fds[n]; +				fds[n] = fds[p]; +				fds[p] = xchg; +				n++; +			} +		} + +		//handle if there is data ready or the backend has active instances for polling +		if(n || registry.instances[u]){ +			DBGPF("Notifying backend %s of %" PRIsize_t " waiting FDs\n", registry.backends[u].name, n); +			rv |= registry.backends[u].process(n, fds); +			if(rv){ +				fprintf(stderr, "Backend %s failed to handle input\n", registry.backends[u].name); +			} +		} +	} +	return rv; +} + +int backends_notify(size_t nev, channel** c, channel_value* v){ +	size_t u, p, n; +	int rv = 0; +	channel_value xval; +	channel* xchnl = NULL; + +	for(u = 0; u < nev && !rv; u = n){ +		//sort for this instance +		n = u + 1; +		for(p = u + 1; p < nev; p++){ +			if(c[p]->instance == c[u]->instance){ +				xval = v[p]; +				xchnl = c[p]; + +				v[p] = v[n]; +				c[p] = c[n]; + +				v[n] = xval; +				c[n] = xchnl; +				n++; +			} +		} + +		//TODO eliminate duplicates +		DBGPF("Calling handler for instance %s with %" PRIsize_t " events\n", c[u]->instance->name, n - u); +		rv |= c[u]->instance->backend->handle(c[u]->instance, n - u, c + u, v + u); +	} + +	return 0; +} + +MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){ +	size_t u, bucket = channelstore_hash(inst, ident); +	DBGPF("\tSearching for inst %" PRIu64 " ident %" PRIu64, inst, ident); +	for(u = 0; u < channels.n[bucket]; u++){ +		DBGPF("\tBucket %" PRIsize_t " entry %" PRIsize_t " inst %" PRIu64 " ident %" PRIu64, bucket, u, channels.entry[bucket][u]->instance, channels.entry[bucket][u]->ident); +		if(channels.entry[bucket][u]->instance == inst +				&& channels.entry[bucket][u]->ident == ident){ +			DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing (bucket %" PRIsize_t ", %" PRIsize_t " search steps)\n", ident, inst->name, bucket, u); +			return channels.entry[bucket][u]; +		} +	} + +	if(!create){ +		DBGPF("Requested unknown channel %" PRIu64 " (bucket %" PRIsize_t ") on instance %s\n", ident, bucket, inst->name); +		return NULL; +	} + +	DBGPF("Creating previously unknown channel %" PRIu64 " on instance %s, bucket %" PRIsize_t "\n", ident, inst->name, bucket); +	channels.entry[bucket] = realloc(channels.entry[bucket], (channels.n[bucket] + 1) * sizeof(channel*)); +	if(!channels.entry[bucket]){ +		fprintf(stderr, "Failed to allocate memory\n"); +		channels.n[bucket] = 0; +		return NULL; +	} + +	channels.entry[bucket][channels.n[bucket]] = calloc(1, sizeof(channel)); +	if(!channels.entry[bucket][channels.n[bucket]]){ +		fprintf(stderr, "Failed to allocate memory\n"); +		return NULL; +	} + +	channels.entry[bucket][channels.n[bucket]]->instance = inst; +	channels.entry[bucket][channels.n[bucket]]->ident = ident; +	return channels.entry[bucket][(channels.n[bucket]++)]; +} + +instance* mm_instance(backend* b){ +	size_t u = 0, n = 0; + +	for(u = 0; u < registry.n; u++){ +		if(registry.backends + u == b){ +			//count existing instances +			for(n = 0; registry.instances[u] && registry.instances[u][n]; n++){ +			} + +			//extend +			registry.instances[u] = realloc(registry.instances[u], (n + 2) * sizeof(instance*)); +			if(!registry.instances[u]){ +				fprintf(stderr, "Failed to allocate memory\n"); +				return NULL; +			} +			//sentinel +			registry.instances[u][n + 1] = NULL; +			registry.instances[u][n] = calloc(1, sizeof(instance)); +			if(!registry.instances[u][n]){ +				fprintf(stderr, "Failed to allocate memory\n"); +			} +			registry.instances[u][n]->backend = b; +			return registry.instances[u][n]; +		} +	} + +	//this should never happen +	return NULL; +} + +MM_API instance* mm_instance_find(char* name, uint64_t ident){ +	size_t b = 0; +	instance** iter = NULL; +	for(b = 0; b < registry.n; b++){ +		if(!strcmp(registry.backends[b].name, name)){ +			for(iter = registry.instances[b]; iter && *iter; iter++){ +				if((*iter)->ident == ident){ +					return *iter; +				} +			} +		} +	} + +	return NULL; +} + +MM_API int mm_backend_instances(char* name, size_t* ninst, instance*** inst){ +	size_t b = 0, i = 0; +	if(!ninst || !inst){ +		return 1; +	} + +	for(b = 0; b < registry.n; b++){ +		if(!strcmp(registry.backends[b].name, name)){ +			//count instances +			for(i = 0; registry.instances[b] && registry.instances[b][i]; i++){ +			} + +			*ninst = i; +			if(!i){ +				*inst = NULL; +				return 0; +			} + +			*inst = calloc(i, sizeof(instance*)); +			if(!*inst){ +				fprintf(stderr, "Failed to allocate memory\n"); +				return 1; +			} + +			memcpy(*inst, registry.instances[b], i * sizeof(instance*)); +			return 0; +		} +	} +	return 1; +} + +backend* backend_match(char* name){ +	size_t u; +	for(u = 0; u < registry.n; u++){ +		if(!strcmp(registry.backends[u].name, name)){ +			return registry.backends + u; +		} +	} +	return NULL; +} + +instance* instance_match(char* name){ +	size_t u; +	instance** iter = NULL; +	for(u = 0; u < registry.n; u++){ +		for(iter = registry.instances[u]; iter && *iter; iter++){ +			if(!strcmp(name, (*iter)->name)){ +				return *iter; +			} +		} +	} +	return NULL; +} + +struct timeval backend_timeout(){ +	size_t u; +	uint32_t res, secs = 1, msecs = 0; + +	for(u = 0; u < registry.n; u++){ +		//only call interval if backend has instances +		if(registry.instances[u] && registry.backends[u].interval){ +			res = registry.backends[u].interval(); +			if(res && (res / 1000) < secs){ +				DBGPF("Updating interval to %" PRIu32 " msecs by request from %s", res, registry.backends[u].name); +				secs = res / 1000; +				msecs = res % 1000; +			} +			else if(res && res / 1000 == secs && (res % 1000) < msecs){ +				DBGPF("Updating interval to %" PRIu32 " msecs by request from %s", res, registry.backends[u].name); +				msecs = res % 1000; +			} +		} +	} + +	struct timeval tv = { +		secs, +		msecs * 1000 +	}; +	return tv; +} + +MM_API int mm_backend_register(backend b){ +	if(!backend_match(b.name)){ +		registry.backends = realloc(registry.backends, (registry.n + 1) * sizeof(backend)); +		registry.instances = realloc(registry.instances, (registry.n + 1) * sizeof(instance**)); +		if(!registry.backends || !registry.instances){ +			fprintf(stderr, "Failed to allocate memory\n"); +			registry.n = 0; +			return 1; +		} +		registry.backends[registry.n] = b; +		registry.instances[registry.n] = NULL; +		registry.n++; + +		fprintf(stderr, "Registered backend %s\n", b.name); +		return 0; +	} +	return 1; +} + +int backends_start(){ +	int rv = 0, current; +	instance** inst = NULL; +	size_t n, u; + +	for(u = 0; u < registry.n; u++){ +		//skip backends without instances +		if(!registry.instances[u]){ +			continue; +		} + +		//fetch list of instances +		if(mm_backend_instances(registry.backends[u].name, &n, &inst)){ +			fprintf(stderr, "Failed to fetch instance list for initialization of backend %s\n", registry.backends[u].name); +			return 1; +		} + +		//start the backend +		current = registry.backends[u].start(n, inst); +		if(current){ +			fprintf(stderr, "Failed to start backend %s\n", registry.backends[u].name); +		} + +		//clean up +		free(inst); +		inst = NULL; +		rv |= current; +	} +	return rv; +} + +static void channels_free(){ +	size_t u, p; +	for(u = 0; u < sizeof(channels.n) / sizeof(channels.n[0]); u++){ +		DBGPF("Cleaning up channel registry bucket %" PRIsize_t " with %" PRIsize_t " channels", u, channels.n[u]); +		for(p = 0; p < channels.n[u]; p++){ +			DBGPF("Destroying channel %" PRIu64 " on instance %s\n", channels.entry[u][p]->ident, channels.entry[u][p]->instance->name); +			//call the channel_free function if the backend supports it +			if(channels.entry[u][p]->impl && channels.entry[u][p]->instance->backend->channel_free){ +				channels.entry[u][p]->instance->backend->channel_free(channels.entry[u][p]); +			} +			free(channels.entry[u][p]); +		} +		free(channels.entry[u]); +		channels.entry[u] = NULL; +		channels.n[u] = 0; +	} +} + +int backends_stop(){ +	size_t u, n; +	instance** inst = NULL; + +	//channels before instances to support proper shutdown procedures +	channels_free(); + +	//shut down the registry +	for(u = 0; u < registry.n; u++){ +		//fetch list of instances +		if(mm_backend_instances(registry.backends[u].name, &n, &inst)){ +			fprintf(stderr, "Failed to fetch instance list for shutdown of backend %s\n", registry.backends[u].name); +			inst = NULL; +			n = 0; +		} + +		registry.backends[u].shutdown(n, inst); +		free(inst); +		inst = NULL; + +		//free instances +		for(inst = registry.instances[u]; inst && *inst; inst++){ +			free((*inst)->name); +			(*inst)->name = NULL; +			(*inst)->backend = NULL; +			free(*inst); +		} +		free(registry.instances[u]); +		registry.instances[u] = NULL; +	} + +	free(registry.backends); +	free(registry.instances); +	registry.n = 0; +	return 0; +} diff --git a/core/backend.h b/core/backend.h new file mode 100644 index 0000000..6a69508 --- /dev/null +++ b/core/backend.h @@ -0,0 +1,17 @@ +#include <sys/types.h> + +/* Internal API */ +int backends_handle(size_t nfds, managed_fd* fds); +int backends_notify(size_t nev, channel** c, channel_value* v); +backend* backend_match(char* name); +instance* instance_match(char* name); +struct timeval backend_timeout(); +int backends_start(); +int backends_stop(); +instance* mm_instance(backend* b); + +/* Backend API */ +MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create); +MM_API instance* mm_instance_find(char* name, uint64_t ident); +MM_API int mm_backend_instances(char* name, size_t* ninst, instance*** inst); +MM_API int mm_backend_register(backend b); diff --git a/core/config.c b/core/config.c new file mode 100644 index 0000000..c1c3124 --- /dev/null +++ b/core/config.c @@ -0,0 +1,708 @@ +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> +#ifndef _WIN32 +#include <limits.h> +#endif + +#define BACKEND_NAME "core/cfg" +#include "midimonster.h" +#include "config.h" +#include "backend.h" + +static enum { +	none, +	backend_cfg, +	instance_cfg, +	map +} parser_state = none; + +typedef enum { +	map_ltr, +	map_rtl, +	map_bidir +} map_type; + +static backend* current_backend = NULL; +static instance* current_instance = NULL; +static size_t noverrides = 0; +static config_override* overrides = NULL; + +#ifdef _WIN32 +#define GETLINE_BUFFER 4096 + +static ssize_t getline(char** line, size_t* alloc, FILE* stream){ +	size_t bytes_read = 0; +	char c; +	//sanity checks +	if(!line || !alloc || !stream){ +		return -1; +	} + +	//allocate buffer if none provided +	if(!*line || !*alloc){ +		*alloc = GETLINE_BUFFER; +		*line = calloc(GETLINE_BUFFER, sizeof(char)); +		if(!*line){ +			fprintf(stderr, "Failed to allocate memory\n"); +			return -1; +		} +	} + +	if(feof(stream)){ +		return -1; +	} + +	for(c = fgetc(stream); 1; c = fgetc(stream)){ +		//end of buffer, resize +		if(bytes_read == (*alloc) - 1){ +			*alloc += GETLINE_BUFFER; +			*line = realloc(*line, (*alloc) * sizeof(char)); +			if(!*line){ +				fprintf(stderr, "Failed to allocate memory\n"); +				return -1; +			} +		} + +		//store character +		(*line)[bytes_read] = c; + +		//end of line +		if(feof(stream) || c == '\n'){ +			//terminate string +			(*line)[bytes_read + 1] = 0; +			return bytes_read; +		} + +		//input broken +		if(ferror(stream)){ +			return -1; +		} + +		bytes_read++; +	} +} +#endif + +static char* config_trim_line(char* in){ +	ssize_t n; +	//trim front +	for(; *in && !isgraph(*in); in++){ +	} + +	//trim back +	for(n = strlen(in); n >= 0 && !isgraph(in[n]); n--){ +		in[n] = 0; +	} + +	return in; +} + +static int config_glob_parse_range(channel_glob* glob, char* spec, size_t length){ +	//FIXME might want to allow negative delimiters at some point +	char* parse_offset = NULL; +	glob->type = glob_range; + +	//first interval member +	glob->limits.u64[0] = strtoul(spec, &parse_offset, 10); +	if(!parse_offset || parse_offset - spec >= length || strncmp(parse_offset, "..", 2)){ +		return 1; +	} + +	parse_offset += 2; +	//second interval member +	glob->limits.u64[1] = strtoul(parse_offset, &parse_offset, 10); +	if(!parse_offset || parse_offset - spec != length || *parse_offset != '}'){ +		return 1; +	} + +	//calculate number of channels within interval +	if(glob->limits.u64[0] < glob->limits.u64[1]){ +		glob->values = glob->limits.u64[1] - glob->limits.u64[0] + 1; +	} +	else if(glob->limits.u64[0] > glob->limits.u64[1]){ +		glob->values = glob->limits.u64[0] - glob->limits.u64[1] + 1; +	} +	else{ +		glob->values = 1; +	} + +	return 0; +} + +static int config_glob_parse_list(channel_glob* glob, char* spec, size_t length){ +	size_t u = 0; +	glob->type = glob_list; +	glob->values = 1; + +	//count number of values in list +	for(u = 0; u < length; u++){ +		if(spec[u] == ','){ +			glob->values++; +		} +	} +	return 0; +} + +static int config_glob_parse(channel_glob* glob, char* spec, size_t length){ +	size_t u = 0; + +	//detect glob type +	for(u = 0; u < length; u++){ +		if(length - u > 2 && !strncmp(spec + u, "..", 2)){ +			DBGPF("Detected glob %.*s as range type", (int) length, spec); +			return config_glob_parse_range(glob, spec, length); +		} +		else if(spec[u] == ','){ +			DBGPF("Detected glob %.*s as list type", (int) length, spec); +			return config_glob_parse_list(glob, spec, length); +		} +	} + +	LOGPF("Failed to detect glob type for spec %.*s", (int) length, spec); +	return 1; +} + +static int config_glob_scan(instance* inst, channel_spec* spec){ +	char* glob_start = spec->spec, *glob_end = NULL; +	size_t u; + +	//assume a spec is one channel as default +	spec->channels = 1; + +	//scan and mark globs +	for(glob_start = strchr(glob_start, '{'); glob_start; glob_start = strchr(glob_start, '{')){ +		glob_end = strchr(glob_start, '}'); +		if(!glob_end){ +			fprintf(stderr, "Failed to parse channel spec, unterminated glob: %s\n", spec->spec); +			return 1; +		} + +		spec->glob = realloc(spec->glob, (spec->globs + 1) * sizeof(channel_glob)); +		if(!spec->glob){ +			fprintf(stderr, "Failed to allocate memory\n"); +			return 1; +		} + +		spec->glob[spec->globs].offset[0] = glob_start - spec->spec; +		spec->glob[spec->globs].offset[1] = glob_end - spec->spec; +		spec->globs++; + +		//skip this opening brace +		glob_start++; +	} + +	//try to parse globs internally +	spec->internal = 1; +	for(u = 0; u < spec->globs; u++){ +		if(config_glob_parse(spec->glob + u, +					spec->spec + spec->glob[u].offset[0] + 1, +					spec->glob[u].offset[1] - spec->glob[u].offset[0] - 1)){ +			spec->internal = 0; +			break; +		} +	} +	if(!spec->internal){ +		//TODO try to parse globs externally +		fprintf(stderr, "Failed to parse glob %" PRIsize_t " in %s internally\n", u + 1, spec->spec); +		return 1; +	} + +	//calculate channel total +	for(u = 0; u < spec->globs; u++){ +		spec->channels *= spec->glob[u].values; +	} +	return 0; +} + +static ssize_t config_glob_resolve_range(char* spec, size_t length, channel_glob* glob, uint64_t n){ +	uint64_t current_value = glob->limits.u64[0] + (n % glob->values); +	//if counting down +	if(glob->limits.u64[0] > glob->limits.u64[1]){ +		current_value = glob->limits.u64[0] - (n % glob->values); +	} + +	//write out value +	return snprintf(spec, length, "%" PRIu64, current_value); +} + +static ssize_t config_glob_resolve_list(char* spec, size_t length, channel_glob* glob, uint64_t n){ +	uint64_t current_replacement = 0; +	size_t replacement_length = 0; +	char* source = spec + 1; +	n %= glob->values; + +	//find start of replacement value +	DBGPF("Searching instance %" PRIu64 " of spec %.*s", n, (int) length, spec); +	for(current_replacement = 0; current_replacement < n; current_replacement++){ +		for(; source[0] != ','; source++){ +		} +		source++; +	} + +	//calculate replacement length +	for(; source[replacement_length] != ',' && source[replacement_length] != '}'; replacement_length++){ +	} + +	//write out new value +	memmove(spec, source, replacement_length); +	return replacement_length; +} + +static channel* config_glob_resolve(instance* inst, channel_spec* spec, uint64_t n, uint8_t map_direction){ +	size_t glob = 0, glob_length; +	ssize_t bytes = 0; +	channel* result = NULL; +	char* resolved_spec = strdup(spec->spec); + +	if(!resolved_spec){ +		LOG("Failed to allocate memory"); +		return NULL; +	} + +	//TODO if not internal, try to resolve externally +	//iterate and resolve globs +	for(glob = spec->globs; glob > 0; glob--){ +		glob_length = spec->glob[glob - 1].offset[1] - spec->glob[glob - 1].offset[0]; + +		switch(spec->glob[glob - 1].type){ +			case glob_range: +				bytes = config_glob_resolve_range(resolved_spec + spec->glob[glob - 1].offset[0], +						glob_length, +						spec->glob + (glob - 1), +						n); +				break; +			case glob_list: +				bytes = config_glob_resolve_list(resolved_spec + spec->glob[glob - 1].offset[0], +						glob_length, +						spec->glob + (glob - 1), +						n); +				break; +		} + +		n /= spec->glob[glob - 1].values; + +		//move trailing data +		if(bytes > 0 && bytes < glob_length){ +			memmove(resolved_spec + spec->glob[glob - 1].offset[0] + bytes, +					resolved_spec + spec->glob[glob - 1].offset[1] + 1, +					strlen(spec->spec) - spec->glob[glob - 1].offset[1]); +		} +		else{ +			LOGPF("Failure parsing glob spec %s", resolved_spec); +			goto bail; +		} +	} + +	DBGPF("Resolved spec %s to %s", spec->spec, resolved_spec); +	result = inst->backend->channel(inst, resolved_spec, map_direction); +	if(spec->globs && !result){ +		LOGPF("Failed to match multichannel evaluation %s to a channel", resolved_spec); +	} + +bail: +	free(resolved_spec); +	return result; +} + +static int config_map(char* to_raw, char* from_raw){ +	//create a copy because the original pointer may be used multiple times +	char* to = strdup(to_raw), *from = strdup(from_raw); +	channel_spec spec_to = { +		.spec = to +	}, spec_from = { +		.spec = from +	}; +	instance* instance_to = NULL, *instance_from = NULL; +	channel* channel_from = NULL, *channel_to = NULL; +	uint64_t n = 0; +	int rv = 1; + +	if(!from || !to){ +		free(from); +		free(to); +		fprintf(stderr, "Failed to allocate memory\n"); +		return 1; +	} + +	//separate channel spec from instance +	for(; *(spec_to.spec) && *(spec_to.spec) != '.'; spec_to.spec++){ +	} + +	for(; *(spec_from.spec) && *(spec_from.spec) != '.'; spec_from.spec++){ +	} + +	if(!spec_from.spec[0] || !spec_to.spec[0]){ +		fprintf(stderr, "Mapping does not contain a proper instance specification\n"); +		goto done; +	} + +	//terminate +	spec_from.spec[0] = spec_to.spec[0] = 0; +	spec_from.spec++; +	spec_to.spec++; + +	//find matching instances +	instance_to = instance_match(to); +	instance_from = instance_match(from); + +	if(!instance_to || !instance_from){ +		fprintf(stderr, "No such instance %s\n", instance_from ? to : from); +		goto done; +	} + +	//scan for globs +	if(config_glob_scan(instance_to, &spec_to) +			|| config_glob_scan(instance_from, &spec_from)){ +		goto done; +	} + +	if((spec_to.channels != spec_from.channels && spec_from.channels != 1 && spec_to.channels != 1) +			|| spec_to.channels == 0 +			|| spec_from.channels == 0){ +		fprintf(stderr, "Multi-channel specification size mismatch: %s.%s (%" PRIsize_t " channels) - %s.%s (%" PRIsize_t " channels)\n", +				instance_from->name, +				spec_from.spec, +				spec_from.channels, +				instance_to->name, +				spec_to.spec, +				spec_to.channels); +		goto done; +	} + +	//iterate, resolve globs and map +	rv = 0; +	for(n = 0; !rv && n < max(spec_from.channels, spec_to.channels); n++){ +		channel_from = config_glob_resolve(instance_from, &spec_from, min(n, spec_from.channels), mmchannel_input); +		channel_to = config_glob_resolve(instance_to, &spec_to, min(n, spec_to.channels), mmchannel_output); + +		if(!channel_from || !channel_to){ +			rv = 1; +			goto done; +		} +		rv |= mm_map_channel(channel_from, channel_to); +	} + +done: +	free(spec_from.glob); +	free(spec_to.glob); +	free(from); +	free(to); +	return rv; +} + +static int config_line(char* line){ +	map_type mapping_type = map_rtl; +	char* separator = NULL; +	size_t u; + +	line = config_trim_line(line); +	if(*line == ';' || strlen(line) == 0){ +		//skip comments +		return 0; +	} +	if(*line == '[' && line[strlen(line) - 1] == ']'){ +		if(!strncmp(line, "[backend ", 9)){ +			//backend configuration +			parser_state = backend_cfg; +			line[strlen(line) - 1] = 0; +			current_backend = backend_match(line + 9); + +			if(!current_backend){ +				fprintf(stderr, "Cannot configure unknown backend %s\n", line + 9); +				return 1; +			} + +			//apply overrides +			for(u = 0; u < noverrides; u++){ +				if(!overrides[u].handled && overrides[u].type == override_backend +					       && !strcmp(overrides[u].target, current_backend->name)){ +					if(current_backend->conf(overrides[u].option, overrides[u].value)){ +						fprintf(stderr, "Configuration override for %s failed for backend %s\n", +								overrides[u].option, current_backend->name); +						return 1; +					} +					overrides[u].handled = 1; +				} +			} +		} +		else if(!strncmp(line, "[include ", 9)){ +			line[strlen(line) - 1] = 0; +			return config_read(line + 9); +		} +		else if(!strcmp(line, "[map]")){ +			//mapping configuration +			parser_state = map; +		} +		else{ +			//backend instance configuration +			parser_state = instance_cfg; + +			//trim braces +			line[strlen(line) - 1] = 0; +			line++; + +			//find separating space and terminate +			for(separator = line; *separator && *separator != ' '; separator++){ +			} +			if(!*separator){ +				fprintf(stderr, "No instance name specified for backend %s\n", line); +				return 1; +			} +			*separator = 0; +			separator++; + +			current_backend = backend_match(line); +			if(!current_backend){ +				fprintf(stderr, "No such backend %s\n", line); +				return 1; +			} + +			if(instance_match(separator)){ +				fprintf(stderr, "Duplicate instance name %s\n", separator); +				return 1; +			} + +			//validate instance name +			if(strchr(separator, ' ') || strchr(separator, '.')){ +				fprintf(stderr, "Invalid instance name %s\n", separator); +				return 1; +			} + +			current_instance = mm_instance(current_backend); +			if(!current_instance){ +				return 1; +			} + +			if(current_backend->create(current_instance)){ +				fprintf(stderr, "Failed to create %s instance %s\n", line, separator); +				return 1; +			} + +			current_instance->name = strdup(separator); +			current_instance->backend = current_backend; +			fprintf(stderr, "Created %s instance %s\n", line, separator); + +			//apply overrides +			for(u = 0; u < noverrides; u++){ +				if(!overrides[u].handled && overrides[u].type == override_instance +					       && !strcmp(overrides[u].target, current_instance->name)){ +					if(current_backend->conf_instance(current_instance, overrides[u].option, overrides[u].value)){ +						fprintf(stderr, "Configuration override for %s failed for instance %s\n", +								overrides[u].option, current_instance->name); +						return 1; +					} +					overrides[u].handled = 1; +				} +			} +		} +	} +	else if(parser_state == map){ +		mapping_type = map_rtl; +		//find separator +		for(separator = line; *separator && *separator != '<' && *separator != '>'; separator++){ +		} + +		switch(*separator){ +			case '>': +				mapping_type = map_ltr; +				//fall through +			case '<': //default +				*separator = 0; +				separator++; +				break; +			case 0: +			default: +				fprintf(stderr, "Not a channel mapping: %s\n", line); +				return 1; +		} + +		if((mapping_type == map_ltr && *separator == '<') +				|| (mapping_type == map_rtl && *separator == '>')){ +			mapping_type = map_bidir; +			separator++; +		} + +		line = config_trim_line(line); +		separator = config_trim_line(separator); + +		if(mapping_type == map_ltr || mapping_type == map_bidir){ +			if(config_map(separator, line)){ +				fprintf(stderr, "Failed to map channel %s to %s\n", line, separator); +				return 1; +			} +		} +		if(mapping_type == map_rtl || mapping_type == map_bidir){ +			if(config_map(line, separator)){ +				fprintf(stderr, "Failed to map channel %s to %s\n", separator, line); +				return 1; +			} +		} +	} +	else{ +		//pass to parser +		//find separator +		separator = strchr(line, '='); +		if(!separator){ +			fprintf(stderr, "Not an assignment (currently expecting %s configuration): %s\n", line, (parser_state == backend_cfg) ? "backend" : "instance"); +			return 1; +		} + +		*separator = 0; +		separator++; +		line = config_trim_line(line); +		separator = config_trim_line(separator); + +		if(parser_state == backend_cfg && current_backend->conf(line, separator)){ +			fprintf(stderr, "Failed to configure backend %s\n", current_backend->name); +			return 1; +		} +		else if(parser_state == instance_cfg && current_backend->conf_instance(current_instance, line, separator)){ +			fprintf(stderr, "Failed to configure instance %s\n", current_instance->name); +			return 1; +		} +	} + +	return 0; +} + +int config_read(char* cfg_filepath){ +	int rv = 1; +	size_t line_alloc = 0; +	ssize_t status; +	FILE* source = NULL; +	char* line_raw = NULL; + +	//create heap copy of file name because original might be in readonly memory +	char* source_dir = strdup(cfg_filepath), *source_file = NULL, original_dir[PATH_MAX * 2] = ""; +	#ifdef _WIN32 +	char path_separator = '\\'; +	#else +	char path_separator = '/'; +	#endif + +	if(!source_dir){ +		fprintf(stderr, "Failed to allocate memory\n"); +		return 1; +	} + +	//change working directory to the one containing the configuration file so relative paths work as expected +	source_file = strrchr(source_dir, path_separator); +	if(source_file){ +		*source_file = 0; +		source_file++; + +		if(!getcwd(original_dir, sizeof(original_dir))){ +			fprintf(stderr, "Failed to read current working directory: %s\n", strerror(errno)); +			goto bail; +		} + +		if(chdir(source_dir)){ +			fprintf(stderr, "Failed to change to configuration file directory %s: %s\n", source_dir, strerror(errno)); +			goto bail; +		} +	} +	else{ +		source_file = source_dir; +	} + +	fprintf(stderr, "Reading configuration file %s\n", cfg_filepath); +	source = fopen(source_file, "r"); + +	if(!source){ +		fprintf(stderr, "Failed to open %s for reading\n", cfg_filepath); +		goto bail; +	} + +	for(status = getline(&line_raw, &line_alloc, source); status >= 0; status = getline(&line_raw, &line_alloc, source)){ +		if(config_line(line_raw)){ +			goto bail; +		} +	} + +	//TODO check whether all overrides have been applied + +	rv = 0; +bail: +	//change back to previous directory to allow recursive configuration file parsing +	if(source_file && source_dir != source_file){ +		chdir(original_dir); +	} + +	free(source_dir); +	if(source){ +		fclose(source); +	} +	free(line_raw); +	return rv; +} + +int config_add_override(override_type type, char* data_raw){ +	int rv = 1; +	//heap a copy because the original data is probably not writable +	char* data = strdup(data_raw); + +	if(!data){ +		fprintf(stderr, "Failed to allocate memory\n"); +		goto bail; +	} + +	char* option = strchr(data, '.'); +	char* value = strchr(data, '='); + +	if(!option || !value){ +		fprintf(stderr, "Override %s is not a valid assignment\n", data_raw); +		goto bail; +	} + +	//terminate strings +	*option = 0; +	option++; + +	*value = 0; +	value++; + +	config_override new = { +		.type = type, +		.handled = 0, +		.target = strdup(config_trim_line(data)), +		.option = strdup(config_trim_line(option)), +		.value = strdup(config_trim_line(value)) +	}; + +	if(!new.target || !new.option || !new.value){ +		fprintf(stderr, "Failed to allocate memory\n"); +		goto bail; +	} + +	overrides = realloc(overrides, (noverrides + 1) * sizeof(config_override)); +	if(!overrides){ +		noverrides = 0; +		fprintf(stderr, "Failed to allocate memory\n"); +		goto bail; +	} +	overrides[noverrides] = new; +	noverrides++; + +	rv = 0; +bail: +	free(data); +	return rv; +} + +void config_free(){ +	size_t u; + +	for(u = 0; u < noverrides; u++){ +		free(overrides[u].target); +		free(overrides[u].option); +		free(overrides[u].value); +	} + +	noverrides = 0; +	free(overrides); +	overrides = NULL; + +	parser_state = none; +} diff --git a/core/config.h b/core/config.h new file mode 100644 index 0000000..b96a866 --- /dev/null +++ b/core/config.h @@ -0,0 +1,54 @@ +/* + * Channel glob type + */ +enum /*_mm_channel_glob_type */ { +	glob_range, +	glob_list +}; + +/* + * Channel specification glob + */ +typedef struct /*_mm_channel_glob*/ { +	size_t offset[2]; +	union { +		void* impl; +		uint64_t u64[2]; +	} limits; +	uint8_t type; +	uint64_t values; +} channel_glob; + +/* + * (Multi-)Channel specification + */ +typedef struct /*_mm_channel_spec*/ { +	char* spec; +	uint8_t internal; +	size_t channels; +	size_t globs; +	channel_glob* glob; +} channel_spec; + +/* + * Command-line override types + */ +typedef enum { +	override_backend, +	override_instance +} override_type; + +/* + * Command-line override data + */ +typedef struct /*_mm_config_override*/ { +	override_type type; +	uint8_t handled; +	char* target; +	char* option; +	char* value; +} config_override; + +int config_read(char* file); +int config_add_override(override_type type, char* data); +void config_free(); diff --git a/core/plugin.c b/core/plugin.c new file mode 100644 index 0000000..e7d8eba --- /dev/null +++ b/core/plugin.c @@ -0,0 +1,187 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> +#include "portability.h" +#ifdef _WIN32 +#define dlclose FreeLibrary +#define dlsym GetProcAddress +#define dlerror() "Failed" +#define dlopen(lib,ig) LoadLibrary(lib) +#else +#include <dlfcn.h> +#endif + +#include "plugin.h" + +static size_t plugins = 0; +static void** plugin_handle = NULL; + +static int plugin_attach(char* path, char* file){ +	plugin_init init = NULL; +	void* handle = NULL; +	char* lib = NULL; +	#ifdef _WIN32 +	char* path_separator = "\\"; +	#else +	char* path_separator = "/"; +	#endif + +	if(!path || !file || !strlen(path)){ +		fprintf(stderr, "Invalid plugin loader path\n"); +		return 1; +	} + +	lib = calloc(strlen(path) + strlen(file) + 2, sizeof(char)); +	if(!lib){ +		fprintf(stderr, "Failed to allocate memory\n"); +		return 1; +	} +	snprintf(lib, strlen(path) + strlen(file) + 2, "%s%s%s", +			path, +			(path[strlen(path) - 1] == path_separator[0]) ? "" : path_separator, +			file); + +	handle = dlopen(lib, RTLD_NOW); +	if(!handle){ +		#ifdef _WIN32 +		char* error = NULL; +		FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, +			NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error, 0, NULL); +		fprintf(stderr, "Failed to load plugin %s, check that all supporting libraries are present: %s\n", lib, error); +		LocalFree(error); +		#else +		fprintf(stderr, "Failed to load plugin %s: %s\n", lib, dlerror()); +		#endif +		free(lib); +		return 0; +	} + +	init = (plugin_init) dlsym(handle, "init"); +	if(init){ +		if(init()){ +			fprintf(stderr, "Plugin %s failed to initialize\n", lib); +			dlclose(handle); +			free(lib); +			return 1; +		} +	} +	else{ +		dlclose(handle); +		free(lib); +		return 0; +	} +	free(lib); + +	plugin_handle = realloc(plugin_handle, (plugins + 1) * sizeof(void*)); +	if(!plugin_handle){ +		fprintf(stderr, "Failed to allocate memory\n"); +		dlclose(handle); +		return 1; +	} + +	plugin_handle[plugins] = handle; +	plugins++; + +	return 0; +} + +int plugins_load(char* path){ +	int rv = -1; + +#ifdef _WIN32 +	char* search_expression = calloc(strlen(path) + strlen("*.dll") + 1, sizeof(char)); +	if(!search_expression){ +		fprintf(stderr, "Failed to allocate memory\n"); +		return -1; +	} +	snprintf(search_expression, strlen(path) + strlen("*.dll"), "%s*.dll", path); + +	WIN32_FIND_DATA result; +	HANDLE hSearch = FindFirstFile(search_expression, &result); + +	if(hSearch == INVALID_HANDLE_VALUE){ +		LPVOID lpMsgBuf = NULL; +		FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, +			NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL); +		fprintf(stderr, "Failed to search for backend plugin files in %s: %s\n", path, lpMsgBuf); +		LocalFree(lpMsgBuf); +		return -1; +	} + +	do { +		if(plugin_attach(path, result.cFileName)){ +			goto load_done; +		} +	} while(FindNextFile(hSearch, &result)); + +	rv = 0; +load_done: +	free(search_expression); +	FindClose(hSearch); +	return rv; +#else +	struct dirent* entry; +	struct stat file_stat; +	DIR* directory = opendir(path); +	if(!directory){ +		fprintf(stderr, "Failed to open plugin search path %s: %s\n", path, strerror(errno)); +		return 1; +	} + +	for(entry = readdir(directory); entry; entry = readdir(directory)){ +		if(strlen(entry->d_name) < 4 || strncmp(".so", entry->d_name + (strlen(entry->d_name) - 3), 3)){ +			continue; +		} + +		if(fstatat(dirfd(directory), entry->d_name, &file_stat, 0) < 0){ +			fprintf(stderr, "Failed to stat %s: %s\n", entry->d_name, strerror(errno)); +			continue; +		} + +		if(!S_ISREG(file_stat.st_mode)){ +			continue; +		} + +		if(plugin_attach(path, entry->d_name)){ +			goto load_done; +		} +	} +	rv = 0; + +load_done: +	if(closedir(directory) < 0){ +		fprintf(stderr, "Failed to close plugin directory %s: %s\n", path, strerror(errno)); +		return -1; +	} +	return rv; +#endif +} + +int plugins_close(){ +	size_t u; + +	for(u = 0; u < plugins; u++){ +#ifdef _WIN32 +		char* error = NULL; +		//FreeLibrary returns the inverse of dlclose +		if(!FreeLibrary(plugin_handle[u])){ +			FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, +				NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error, 0, NULL); +			fprintf(stderr, "Failed to unload plugin: %s\n", error); +			LocalFree(error); +		} +#else +		if(dlclose(plugin_handle[u])){ +			fprintf(stderr, "Failed to unload plugin: %s\n", dlerror()); +		} +#endif +	} + +	free(plugin_handle); +	plugins = 0; +	return 0; +} diff --git a/core/plugin.h b/core/plugin.h new file mode 100644 index 0000000..64c557f --- /dev/null +++ b/core/plugin.h @@ -0,0 +1,3 @@ +typedef int (*plugin_init)(); +int plugins_load(char* dir); +int plugins_close(); | 
