281 lines
8.4 KiB
C
281 lines
8.4 KiB
C
/**
|
|
* Parsing Apache HTTPd-like configuration
|
|
*
|
|
* @author Steffen Vogel <info@steffenvogel.de>
|
|
* @copyright Copyright (c) 2011, The volkszaehler.org project
|
|
* @package vzlogger
|
|
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
|
|
*/
|
|
/*
|
|
* This file is part of volkzaehler.org
|
|
*
|
|
* volkzaehler.org is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* volkzaehler.org is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with volkszaehler.org. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
|
|
#include "config.h"
|
|
#include "channel.h"
|
|
|
|
static const char *option_type_str[] = { "null", "boolean", "double", "int", "object", "array", "string" };
|
|
|
|
int config_parse(const char *filename, list_t *mappings, config_options_t *options) {
|
|
struct json_object *json_cfg = NULL;
|
|
struct json_tokener *json_tok = json_tokener_new();
|
|
|
|
char buf[JSON_FILE_BUF_SIZE];
|
|
int line = 0;
|
|
|
|
/* open configuration file */
|
|
FILE *file = fopen(filename, "r");
|
|
if (file == NULL) {
|
|
print(log_error, "Cannot open configfile %s: %s", NULL, filename, strerror(errno)); /* why didn't the file open? */
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
else {
|
|
print(log_info, "Start parsing configuration from %s", NULL, filename);
|
|
}
|
|
|
|
/* parse JSON */
|
|
while(fgets(buf, JSON_FILE_BUF_SIZE, file)) {
|
|
line++;
|
|
|
|
json_cfg = json_tokener_parse_ex(json_tok, buf, strlen(buf));
|
|
|
|
if (json_tok->err > 1) {
|
|
print(log_error, "Error in %s:%d %s at offset %d", NULL, filename, line, json_tokener_errors[json_tok->err], json_tok->char_offset);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* householding */
|
|
fclose(file);
|
|
json_tokener_free(json_tok);
|
|
|
|
/* parse options */
|
|
json_object_object_foreach(json_cfg, key, value) {
|
|
enum json_type type = json_object_get_type(value);
|
|
|
|
if (strcmp(key, "daemon") == 0 && type == json_type_boolean) {
|
|
options->daemon = json_object_get_boolean(value);
|
|
}
|
|
else if (strcmp(key, "foreground") == 0 && type == json_type_boolean) {
|
|
options->foreground = json_object_get_boolean(value);
|
|
}
|
|
else if (strcmp(key, "log") == 0 && type == json_type_string) {
|
|
options->log = strdup(json_object_get_string(value));
|
|
}
|
|
else if (strcmp(key, "retry") == 0 && type == json_type_int) {
|
|
options->retry_pause = json_object_get_int(value);
|
|
}
|
|
else if (strcmp(key, "verbosity") == 0 && type == json_type_int) {
|
|
options->verbosity = json_object_get_int(value);
|
|
}
|
|
else if (strcmp(key, "local") == 0) {
|
|
json_object_object_foreach(value, key, local_value) {
|
|
enum json_type local_type = json_object_get_type(local_value);
|
|
|
|
if (strcmp(key, "enabled") == 0 && local_type == json_type_boolean) {
|
|
options->local = json_object_get_boolean(local_value);
|
|
}
|
|
else if (strcmp(key, "port") == 0 && local_type == json_type_int) {
|
|
options->port = json_object_get_int(local_value);
|
|
}
|
|
else if (strcmp(key, "timeout") == 0 && local_type == json_type_int) {
|
|
options->comet_timeout = json_object_get_int(local_value);
|
|
}
|
|
else if (strcmp(key, "buffer") == 0 && local_type == json_type_int) {
|
|
options->buffer_length = json_object_get_int(local_value);
|
|
}
|
|
else if (strcmp(key, "index") == 0 && local_type == json_type_boolean) {
|
|
options->channel_index = json_object_get_boolean(local_value);
|
|
}
|
|
else {
|
|
print(log_error, "Ignoring invalid field or type: %s=%s (%s)",
|
|
NULL, key, json_object_get_string(local_value), option_type_str[local_type]);
|
|
}
|
|
}
|
|
}
|
|
else if ((strcmp(key, "sensors") == 0 || strcmp(key, "meters") == 0) && type == json_type_array) {
|
|
int len = json_object_array_length(value);
|
|
for (int i = 0; i < len; i++) {
|
|
map_t *mapping = config_parse_meter(json_object_array_get_idx(value, i));
|
|
if (mapping == NULL) {
|
|
return ERR;
|
|
}
|
|
|
|
list_push(mappings, mapping);
|
|
}
|
|
}
|
|
else {
|
|
print(log_error, "Ignoring invalid field or type: %s=%s (%s)",
|
|
NULL, key, json_object_get_string(value), option_type_str[type]);
|
|
}
|
|
}
|
|
|
|
json_object_put(json_cfg); /* free allocated memory */
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
map_t * config_parse_meter(struct json_object *jso) {
|
|
list_t json_channels;
|
|
list_t options;
|
|
list_init(&json_channels);
|
|
list_init(&options);
|
|
|
|
json_object_object_foreach(jso, key, value) {
|
|
enum json_type type = json_object_get_type(value);
|
|
|
|
if (strcmp(key, "channels") == 0 && type == json_type_array) {
|
|
int len = json_object_array_length(value);
|
|
for (int i = 0; i < len; i++) {
|
|
list_push(&json_channels, json_object_array_get_idx(value, i));
|
|
}
|
|
}
|
|
else if (strcmp(key, "channel") == 0 && type == json_type_object) {
|
|
list_push(&json_channels, value);
|
|
}
|
|
else { /* all other options will be passed to meter_init() */
|
|
option_t *option = malloc(sizeof(option_t));
|
|
|
|
if (option_init(value, key, option) != SUCCESS) {
|
|
print(log_error, "Ignoring invalid type: %s=%s (%s)",
|
|
NULL, key, json_object_get_string(value), option_type_str[type]);
|
|
}
|
|
|
|
list_push(&options, option);
|
|
}
|
|
}
|
|
|
|
/* init meter */
|
|
map_t *mapping = malloc(sizeof(map_t));
|
|
list_init(&mapping->channels);
|
|
|
|
if (meter_init(&mapping->meter, options) != SUCCESS) {
|
|
print(log_error, "Failed to initialize meter. Arborting.", mapping);
|
|
free(mapping);
|
|
return NULL;
|
|
}
|
|
|
|
print(log_info, "New meter initialized (protocol=%s)", mapping, meter_get_details(mapping->meter.protocol)->name);
|
|
|
|
/* init channels */
|
|
struct json_object *json_channel;
|
|
while ((json_channel = list_pop(&json_channels)) != NULL) {
|
|
channel_t *ch = config_parse_channel(json_channel, mapping->meter.protocol);
|
|
if (ch == NULL) {
|
|
free(mapping);
|
|
return NULL;
|
|
}
|
|
|
|
list_push(&mapping->channels, ch);
|
|
}
|
|
|
|
/* householding */
|
|
list_free(&options);
|
|
|
|
return mapping;
|
|
}
|
|
|
|
channel_t * config_parse_channel(struct json_object *jso, meter_protocol_t protocol) {
|
|
const char *uuid = NULL;
|
|
const char *middleware = NULL;
|
|
const char *id_str = NULL;
|
|
|
|
json_object_object_foreach(jso, key, value) {
|
|
enum json_type type = json_object_get_type(value);
|
|
|
|
if (strcmp(key, "uuid") == 0 && type == json_type_string) {
|
|
uuid = json_object_get_string(value);
|
|
}
|
|
else if (strcmp(key, "middleware") == 0 && type == json_type_string) {
|
|
middleware = json_object_get_string(value);
|
|
}
|
|
else if (strcmp(key, "identifier") == 0 && type == json_type_string) {
|
|
id_str = json_object_get_string(value);
|
|
}
|
|
else {
|
|
print(log_error, "Ignoring invalid field or type: %s=%s (%s)",
|
|
NULL, key, json_object_get_string(value), option_type_str[type]);
|
|
}
|
|
}
|
|
|
|
/* check uuid and middleware */
|
|
if (uuid == NULL) {
|
|
print(log_error, "Missing UUID", NULL);
|
|
return NULL;
|
|
}
|
|
else if (!config_validate_uuid(uuid)) {
|
|
print(log_error, "Invalid UUID: %s", NULL, uuid);
|
|
return NULL;
|
|
}
|
|
else if (middleware == NULL) {
|
|
print(log_error, "Missing middleware", NULL);
|
|
return NULL;
|
|
}
|
|
|
|
/* parse identifier */
|
|
reading_id_t id;
|
|
if (id_str != NULL && reading_id_parse(protocol, &id, id_str) != SUCCESS) {
|
|
print(log_error, "Invalid id: %s", NULL, id_str);
|
|
return NULL; /* error occured */
|
|
}
|
|
|
|
channel_t *ch = malloc(sizeof(channel_t));
|
|
channel_init(ch, uuid, middleware, id);
|
|
print(log_info, "New channel initialized (uuid=...%s middleware=%s id=%s)", ch, uuid+30, middleware, (id_str) ? id_str : "(none)");
|
|
|
|
return ch;
|
|
}
|
|
|
|
int config_validate_uuid(const char *uuid) {
|
|
for (const char *p = uuid; *p; p++) {
|
|
switch (p - uuid) {
|
|
case 8:
|
|
case 13:
|
|
case 18:
|
|
case 23:
|
|
if (*p != '-') return FALSE;
|
|
else break;
|
|
|
|
default:
|
|
if (!isxdigit(*p)) return FALSE;
|
|
else break;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
int option_init(struct json_object *jso, char *key, option_t *option) {
|
|
option_value_t val;
|
|
|
|
switch (json_object_get_type(jso)) {
|
|
case json_type_string: val.string = json_object_get_string(jso); break;
|
|
case json_type_int: val.integer = json_object_get_int(jso); break;
|
|
case json_type_boolean: val.boolean = json_object_get_boolean(jso); break;
|
|
case json_type_double: val.floating = json_object_get_double(jso); break;
|
|
default: return ERR;
|
|
}
|
|
|
|
option->key = key;
|
|
option->type = json_object_get_type(jso);
|
|
option->value = val;
|
|
|
|
return SUCCESS;
|
|
}
|
|
|