Select Git revision
Forked from
processes / LabProcesses.jl
Source project has a limited visibility.
moberg.c 11.46 KiB
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE /* scandirat */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <basedir.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <moberg.h>
#include <moberg_config.h>
#include <moberg_parser.h>
#include <moberg_module.h>
struct moberg {
struct moberg_config *config;
struct channel_list {
int capacity;
struct moberg_channel **value;
} analog_in, analog_out, digital_in, digital_out, encoder_in;
struct deferred_action {
struct deferred_action *next;
int (*action)(void *param);
void *param;
} *deferred_action;
};
static void run_deferred_actions(struct moberg *moberg)
{
while (moberg->deferred_action) {
struct deferred_action *deferred = moberg->deferred_action;
moberg->deferred_action = deferred->next;
deferred->action(deferred->param);
free(deferred);
}
}
static int channel_list_set(struct channel_list *list,
int index,
struct moberg_channel *value)
{
if (list->capacity <= index) {
int capacity;
for (capacity = 2 ; capacity <= index ; capacity *= 2);
void *new = realloc(list->value, capacity * sizeof(**list->value));
if (!new) {
goto err;
}
void *p = new + list->capacity * sizeof(*list->value);
memset(p, 0, (capacity - list->capacity) * sizeof(**list->value));
list->value = new;
list->capacity = capacity;
}
if (0 <= index && index < list->capacity) {
list->value[index] = value;
return 1;
}
err:
return 0;
}
static int channel_list_get(struct channel_list *list,
int index,
struct moberg_channel **value)
{
if (0 <= index && index < list->capacity) {
*value = list->value[index];
return 1;
}
return 0;
}
static void channel_list_free(struct channel_list *list)
{
for (int i = 0 ; i < list->capacity ; i++) {
if (list->value[i]) {
list->value[i]->down(list->value[i]);
}
}
free(list->value);
}
static void parse_config_at(
struct moberg *moberg,
int dirfd,
const char *pathname)
{
if (dirfd >= 0) {
int fd = openat(dirfd, pathname, O_RDONLY);
if (fd >= 0) {
struct stat statbuf;
if (fstat(fd, &statbuf) == 0) {
char *buf = malloc(statbuf.st_size + 1);
if (buf) {
if (read(fd, buf, statbuf.st_size) == statbuf.st_size) {
buf[statbuf.st_size] = 0;
}
struct moberg_config *config = moberg_parse(moberg, buf);
if (config) {
if (! moberg->config) {
moberg->config = config;
} else {
moberg_config_join(moberg->config, config);
moberg_config_free(config);
}
}
free(buf);
}
}
close(fd);
}
}
}
static int conf_filter(
const struct dirent *entry)
{
char *dot = strrchr(entry->d_name, '.');
if (dot != NULL && strcmp(dot, ".conf") == 0) {
return 1;
} else {
return 0;
}
}
static void parse_config_dir_at(
struct moberg *config,
int dirfd)
{
if (dirfd >= 0) {
struct dirent **entry = NULL;
int n = scandirat(dirfd, ".", &entry, conf_filter, alphasort);
for (int i = 0 ; i < n ; i++) {
parse_config_at(config, dirfd, entry[i]->d_name);
free(entry[i]);
}
free(entry);
}
}
static int install_channel(struct moberg *moberg,
int index,
struct moberg_device* device,
struct moberg_channel *channel)
{
if (channel) {
struct moberg_channel *old = NULL;
switch (channel->kind) {
case chan_ANALOGIN:
channel_list_get(&moberg->analog_in, index, &old);
break;
case chan_ANALOGOUT:
channel_list_get(&moberg->analog_out, index, &old);
break;
case chan_DIGITALIN:
channel_list_get(&moberg->digital_in, index, &old);
break;
case chan_DIGITALOUT:
channel_list_get(&moberg->digital_out, index, &old);
break;
case chan_ENCODERIN:
channel_list_get(&moberg->encoder_in, index, &old);
break;
}
if (old) {
old->down(old);
}
channel->up(channel);
/* TODO: Clean up old channel */
switch (channel->kind) {
case chan_ANALOGIN:
if (! channel_list_set(&moberg->analog_in, index, channel)) {
goto err;
}
break;
case chan_ANALOGOUT:
if (! channel_list_set(&moberg->analog_out, index, channel)) {
goto err;
}
break;
case chan_DIGITALIN:
if (! channel_list_set(&moberg->digital_in, index, channel)) {
goto err;
}
break;
case chan_DIGITALOUT:
if (! channel_list_set(&moberg->digital_out, index, channel)) {
goto err;
}
break;
case chan_ENCODERIN:
if (! channel_list_set(&moberg->encoder_in, index, channel)) {
goto err;
}
break;
}
}
return 1;
err:
return 0;
}
static int install_config(struct moberg *moberg)
{
struct moberg_channel_install install = {
.context=moberg,
.channel=install_channel
};
if (! moberg->config) {
fprintf(stderr, "No moberg configuration found\n");
return 0;
} else {
return moberg_config_install_channels(moberg->config, &install);
}
}
struct moberg *moberg_new()
{
struct moberg *result = malloc(sizeof(*result));
if (! result) {
fprintf(stderr, "Failed to allocate moberg struct\n");
goto err;
}
result->analog_in.capacity = 0;
result->analog_in.value = NULL;
result->analog_out.capacity = 0;
result->analog_out.value = NULL;
result->digital_in.capacity = 0;
result->digital_in.value = NULL;
result->digital_out.capacity = 0;
result->digital_out.value = NULL;
result->encoder_in.capacity = 0;
result->encoder_in.value = NULL;
result->deferred_action = NULL;
result->config = NULL;
/* Parse default configuration(s) */
const char * const *config_paths = xdgSearchableConfigDirectories(NULL);
const char * const *path;
for (path = config_paths ; *path ; path++) {
int dirfd1 = open(*path, O_DIRECTORY);
if (dirfd >= 0) {
parse_config_at(result, dirfd1, "moberg.conf");
int dirfd2 = openat(dirfd1, "moberg.d", O_DIRECTORY);
if (dirfd2 >= 0) {
parse_config_dir_at(result, dirfd2);
close(dirfd2);
}
close(dirfd1);
}
free((char*)*path);
}
free((const char **)config_paths);
/* TODO: Read & parse environment overrides */
install_config(result);
run_deferred_actions(result);
err:
return result;
}
void moberg_free(struct moberg *moberg)
{
if (moberg) {
moberg_config_free(moberg->config);
channel_list_free(&moberg->analog_in);
channel_list_free(&moberg->analog_out);
channel_list_free(&moberg->digital_in);
channel_list_free(&moberg->digital_out);
channel_list_free(&moberg->encoder_in);
run_deferred_actions(moberg);
free(moberg);
}
}
/* Input/output */
int moberg_analog_in_open(struct moberg *moberg,
int index,
struct moberg_analog_in *analog_in)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->analog_in, index, &channel);
if (channel) {
channel->open(channel);
*analog_in = channel->action.analog_in;
return 1;
}
return 0;
}
int moberg_analog_in_close(struct moberg *moberg,
int index,
struct moberg_analog_in analog_in)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->analog_in, index, &channel);
if (channel && channel->action.analog_in.context == analog_in.context) {
channel->close(channel);
}
return 1;
}
int moberg_analog_out_open(struct moberg *moberg,
int index,
struct moberg_analog_out *analog_out)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->analog_out, index, &channel);
if (channel) {
channel->open(channel);
*analog_out = channel->action.analog_out;
return 1;
}
return 0;
}
int moberg_analog_out_close(struct moberg *moberg,
int index,
struct moberg_analog_out analog_out)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->analog_out, index, &channel);
if (channel && channel->action.analog_out.context == analog_out.context) {
channel->close(channel);
}
return 1;
}
int moberg_digital_in_open(struct moberg *moberg,
int index,
struct moberg_digital_in *digital_in)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->digital_in, index, &channel);
if (channel) {
channel->open(channel);
*digital_in = channel->action.digital_in;
return 1;
}
return 0;
}
int moberg_digital_in_close(struct moberg *moberg,
int index,
struct moberg_digital_in digital_in)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->digital_in, index, &channel);
if (channel && channel->action.digital_in.context == digital_in.context) {
channel->close(channel);
}
return 1;
}
int moberg_digital_out_open(struct moberg *moberg,
int index,
struct moberg_digital_out *digital_out)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->digital_out, index, &channel);
if (channel) {
channel->open(channel);
*digital_out = channel->action.digital_out;
return 1;
}
return 0;
}
int moberg_digital_out_close(struct moberg *moberg,
int index,
struct moberg_digital_out digital_out)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->digital_out, index, &channel);
if (channel && channel->action.digital_out.context == digital_out.context) {
channel->close(channel);
}
return 1;
}
int moberg_encoder_in_open(struct moberg *moberg,
int index,
struct moberg_encoder_in *encoder_in)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->encoder_in, index, &channel);
if (channel) {
channel->open(channel);
*encoder_in = channel->action.encoder_in;
return 1;
}
return 0;
}
int moberg_encoder_in_close(struct moberg *moberg,
int index,
struct moberg_encoder_in encoder_in)
{
struct moberg_channel *channel = NULL;
channel_list_get(&moberg->encoder_in, index, &channel);
if (channel && channel->action.encoder_in.context == encoder_in.context) {
channel->close(channel);
}
return 1;
}
/* System init functionality (systemd/init/...) */
int moberg_start(
struct moberg *moberg,
FILE *f)
{
return moberg_config_start(moberg->config, f);
}
int moberg_stop(
struct moberg *moberg,
FILE *f)
{
return moberg_config_stop(moberg->config, f);
}
/* Intended for final cleanup actions (dlclose so far...) */
void moberg_deferred_action(struct moberg *moberg,
int (*action)(void *param),
void *param)
{
struct deferred_action *deferred = malloc(sizeof(*deferred));
if (deferred) {
deferred->next = moberg->deferred_action;
deferred->action = action;
deferred->param = param;
moberg->deferred_action = deferred;
}
}