libx52/daemon/x52d_config_parser.c

366 lines
9.3 KiB
C

/*
* Saitek X52 Pro MFD & LED driver - Configuration parser
*
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
*
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#define PINELOG_MODULE X52D_MOD_CONFIG
#include "ini.h"
#include "pinelog.h"
#include "x52d_config.h"
#include "x52d_const.h"
/* Parser function typedef */
typedef int (*parser_fn)(struct x52d_config *, size_t, const char *);
// Check if the parameters are all valid
#define CHECK_PARAMS() do { if (cfg == NULL || value == NULL) { return EINVAL; } } while(0)
// Create a pointer "name" of type "type", which stores the pointer to the
// corresponding element within the config struct.
#define CONFIG_PTR(type, name) type name = (type)((uintptr_t)cfg + offset)
static int bool_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(bool *, config);
CHECK_PARAMS();
if (!strcasecmp(value, "yes") || !strcasecmp(value, "true")) {
*config = true;
} else if (!strcasecmp(value, "no") || !strcasecmp(value, "false")) {
*config = false;
} else {
return EINVAL;
}
return 0;
}
static int string_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(char *, config);
CHECK_PARAMS();
/* String parameters are all NAME_MAX len */
strncpy(config, value, NAME_MAX-1);
config[NAME_MAX-1] = '\0';
return 0;
}
static int int_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(int *, config);
char *endptr;
int retval;
CHECK_PARAMS();
errno = 0;
retval = strtol(value, &endptr, 0);
if (errno != 0) {
return errno;
}
if (*endptr != '\0') {
// Invalid characters in string
return EINVAL;
}
*config = retval;
return 0;
}
static int led_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(libx52_led_state *, config);
CHECK_PARAMS();
#define MATCH_STATE(val) if (!strcasecmp(value, #val)) { *config = LIBX52_LED_STATE_ ## val ; }
MATCH_STATE(OFF)
else MATCH_STATE(ON)
else MATCH_STATE(RED)
else MATCH_STATE(AMBER)
else MATCH_STATE(GREEN)
else return EINVAL;
#undef MATCH_STATE
return 0;
}
static int clock_format_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(libx52_clock_format *, config);
CHECK_PARAMS();
if (!strcasecmp(value, "12hr") || !strcasecmp(value, "12")) {
*config = LIBX52_CLOCK_FORMAT_12HR;
} else if (!strcasecmp(value, "24hr") || !strcasecmp(value, "24")) {
*config = LIBX52_CLOCK_FORMAT_24HR;
} else {
return EINVAL;
}
return 0;
}
static int date_format_parser(struct x52d_config *cfg, size_t offset, const char *value)
{
CONFIG_PTR(libx52_date_format *, config);
CHECK_PARAMS();
if (!strcasecmp(value, "ddmmyy") || !strcasecmp(value, "dd-mm-yy")) {
*config = LIBX52_DATE_FORMAT_DDMMYY;
} else if (!strcasecmp(value, "mmddyy") || !strcasecmp(value, "mm-dd-yy")) {
*config = LIBX52_DATE_FORMAT_MMDDYY;
} else if (!strcasecmp(value, "yymmdd") || !strcasecmp(value, "yy-mm-dd")) {
*config = LIBX52_DATE_FORMAT_YYMMDD;
} else {
return EINVAL;
}
return 0;
}
#undef CHECK_PARAMS
#undef CONFIG_PTR
/* Map for config->param */
#define CFG(section, key, name, type, def) {#section, #key, type ## _parser, offsetof(struct x52d_config, name)},
static const struct config_map {
const char *section;
const char *key;
parser_fn parser;
size_t offset;
} config_map[] = {
#include "x52d_config.def"
// Terminating entry
{NULL, NULL, NULL, 0}
};
int x52d_config_process_kv(void *user, const char *section, const char *key, const char *value)
{
int i;
int rc = 0;
bool found = false;
struct x52d_config *cfg = (struct x52d_config*)user;
for (i = 0; config_map[i].key != NULL; i++) {
rc = 0;
if (!strcasecmp(config_map[i].key, key) &&
!strcasecmp(config_map[i].section, section)) {
found = true;
PINELOG_TRACE("Setting '%s.%s'='%s'",
config_map[i].section, config_map[i].key, value);
rc = config_map[i].parser(cfg, config_map[i].offset, value);
break;
}
}
if (!found) {
// Print error message, but continue
PINELOG_INFO(_("Ignoring unknown key '%s.%s'"), section, key);
}
return rc;
}
/**
* @brief Set configuration defaults
*
* @param[in] cfg Pointer to config struct
*
* @returns 0 on success, non-zero error code on failure
*/
int x52d_config_set_defaults(struct x52d_config *cfg) {
int rc;
if (cfg == NULL) {
return EINVAL;
}
PINELOG_TRACE("Setting configuration defaults");
#define CFG(section, key, name, parser, def) \
rc = x52d_config_process_kv(cfg, #section, #key, #def); \
if (rc != 0) { \
return rc; \
}
#include "x52d_config.def"
return 0;
}
int x52d_config_load_file(struct x52d_config *cfg, const char *cfg_file)
{
int rc;
if (cfg == NULL || cfg_file == NULL) {
return EINVAL;
}
PINELOG_TRACE("Loading configuration from file %s", cfg_file);
rc = ini_parse(cfg_file, x52d_config_process_kv, cfg);
if (rc < 0) {
PINELOG_ERROR(_("Failed processing configuration file %s - code %d"),
cfg_file, rc);
return EIO;
}
return 0;
}
struct x52d_config_override {
char *section;
char *key;
char *value;
struct x52d_config_override *next;
};
static struct x52d_config_override *override_head;
static struct x52d_config_override *override_tail;
int x52d_config_save_override(const char *override_str)
{
// Parse override string of the form section.key=value
struct x52d_config_override *override;
char *string = NULL;
char *free_ptr = NULL;
char *ptr;
int rc;
PINELOG_TRACE("Allocating memory (%lu bytes) for override structure", sizeof(*override));
override = calloc(1, sizeof(*override));
if (override == NULL) {
PINELOG_ERROR(_("Failed to allocate memory for override structure"));
rc = ENOMEM;
goto cleanup;
}
errno = 0;
PINELOG_TRACE("Duplicating override string");
string = strdup(override_str);
if (string == NULL) {
PINELOG_ERROR(_("Failed to allocate memory for override string"));
rc = errno;
goto cleanup;
}
free_ptr = string;
override->section = string;
// Ensure that the string is of the form ([^.]+\.[^=]+=.*)
ptr = strchr(string, '.');
if (ptr == NULL || ptr == string) {
// No section found
PINELOG_ERROR(_("No section found in override string '%s'"), string);
rc = EINVAL;
goto cleanup;
}
// Reset the . to NUL
*ptr = '\0';
ptr++;
PINELOG_TRACE("Splitting override string to '%s' and '%s'", string, ptr);
string = ptr;
override->key = string;
ptr = strchr(string, '=');
if (ptr == NULL || ptr == string) {
// No key found
PINELOG_ERROR(_("No key found in override string '%s'"), string);
rc = EINVAL;
goto cleanup;
}
// Reset the = to NUL
*ptr = '\0';
ptr++;
PINELOG_TRACE("Splitting override string to '%s' and '%s'", string, ptr);
if (*ptr == '\0') {
// No value found
PINELOG_ERROR(_("No value found in override string '%s'"), string);
rc = EINVAL;
goto cleanup;
}
override->value = ptr;
// Add the override to the linked list
if (override_tail != NULL) {
PINELOG_TRACE("Linking override to list tail");
override_tail->next = override;
}
PINELOG_TRACE("Setting list tail to override");
override_tail = override;
if (override_head == NULL) {
PINELOG_TRACE("Setting list head to override");
override_head = override;
}
return 0;
cleanup:
if (free_ptr != NULL) {
free(free_ptr);
}
if (override != NULL) {
free(override);
}
return rc;
}
int x52d_config_apply_overrides(struct x52d_config *cfg)
{
int rc;
struct x52d_config_override *tmp = override_head;
if (cfg == NULL) {
return EINVAL;
}
while (tmp != NULL) {
PINELOG_TRACE("Processing override '%s.%s=%s'",
tmp->section,
tmp->key,
tmp->value);
rc = x52d_config_process_kv(cfg,
tmp->section,
tmp->key,
tmp->value);
if (rc != 0) {
PINELOG_ERROR(_("Error processing override '%s.%s=%s'"),
tmp->section,
tmp->key,
tmp->value);
return rc;
}
tmp = tmp->next;
}
return 0;
}
void x52d_config_clear_overrides(void)
{
struct x52d_config_override *tmp;
while (override_head != NULL) {
tmp = override_head;
override_head = override_head->next;
PINELOG_TRACE("Freeing override '%s.%s=%s'",
tmp->section,
tmp->key,
tmp->value);
free(tmp);
}
override_tail = NULL;
}