Switch to a JSON-based config format

We can get rid of a lot of code by ditching out INI format parser. We also
remove the support for "subtype" and "generator" templates for now; rather
than implementing this in NeCo itself, templates could be implemented as
a Lua DSL later.
This commit is contained in:
Matthias Schiffer 2019-05-31 18:42:01 +02:00
parent 44cb17317c
commit bcef77a7fb
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
14 changed files with 128 additions and 490 deletions

View file

@ -3,6 +3,7 @@ project('neco', 'c', default_options : ['c_std=gnu11'])
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
libubox_dep = cc.find_library('ubox') libubox_dep = cc.find_library('ubox')
libjson_c_dep = dependency('json-c', method : 'pkg-config')
libmnl_dep = dependency('libmnl', method : 'pkg-config') libmnl_dep = dependency('libmnl', method : 'pkg-config')
subdir('src') subdir('src')

View file

@ -1,140 +0,0 @@
#include "config-ini.h"
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
static void free_field(ini_field_t *field) {
free(field->key);
free(field->value);
free(field);
}
static void free_section(ini_section_t *section) {
ini_field_t *field, *tmp;
list_for_each_entry_safe(field, tmp, &section->fields, node)
free_field(field);
free(section->name);
free(section);
}
void free_ini_file(ini_file_t *file) {
ini_section_t *section, *tmp;
list_for_each_entry_safe(section, tmp, &file->sections, node)
free_section(section);
free(file);
}
static bool add_field(ini_section_t *section, const char *key, const char *value) {
ini_field_t *field = calloc(1, sizeof(*field));
if (!field)
return false;
field->key = strdup(key);
field->value = strdup(value);
if (!field->key || !field->value) {
free_field(field);
return false;
}
list_add_tail(&field->node, &section->fields);
return true;
}
static ini_section_t * add_section(ini_file_t *file, const char *name) {
ini_section_t *section = calloc(1, sizeof(*section));
if (!section)
return NULL;
section->name = strdup(name);
if (!section->name) {
free(section);
return false;
}
INIT_LIST_HEAD(&section->fields);
list_add_tail(&section->node, &file->sections);
return section;
}
ini_file_t * read_ini_file(FILE *f) {
ini_file_t *file = calloc(1, sizeof(*file));
if (!file)
goto error;
INIT_LIST_HEAD(&file->sections);
ini_section_t *section = NULL;
int err = 0;
char *line = NULL;
size_t n = 0;
while (getline(&line, &n, f) >= 0) {
char *input = line;
while (isspace(input[0]))
input++;
if (input[0] == '#' || input[0] == '\0')
continue;
size_t len = strlen(input);
while (isspace(input[len-1]))
len--;
if (input[0] == '[') {
if (input[len-1] != ']') {
err = EINVAL;
goto error;
}
input[len-1] = '\0';
section = add_section(file, input+1);
if (!section)
goto error;
} else {
if (!section) {
err = EINVAL;
goto error;
}
input[len] = '\0';
char *delim = strchr(input, '=');
if (!delim) {
err = EINVAL;
goto error;
}
*delim = '\0';
if (!add_field(section, input, delim+1))
goto error;
}
}
if (ferror(f))
err = EIO;
error:
free(line);
if (err) {
free_ini_file(file);
errno = err;
return NULL;
}
return file;
}

View file

@ -1,24 +0,0 @@
#pragma once
#include <libubox/list.h>
#include <stdio.h>
typedef struct _ini_field {
struct list_head node;
char *key;
char *value;
} ini_field_t;
typedef struct _ini_section {
struct list_head node;
struct list_head fields;
char *name;
} ini_section_t;
typedef struct _ini_file {
struct list_head sections;
} ini_file_t;
ini_file_t * read_ini_file(FILE *f);
void free_ini_file(ini_file_t *file);

View file

@ -14,110 +14,16 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
typedef struct _load_ctx {
struct avl_tree subtypes;
struct avl_tree devices;
} load_ctx_t;
static const char * extname(const char *filename) {
const char *dot = strrchr(filename, '.');
return dot ? (dot+1) : NULL;
}
static bool isfile(int fd) {
struct stat buf;
if (fstat(fd, &buf) < 0)
return false;
return (buf.st_mode & S_IFMT) == S_IFREG;
}
static bool read_config_file(load_ctx_t *ctx, int dirfd, const char *filename) {
const char *ext = extname(filename);
if (!ext)
return true;
struct avl_tree *target_tree;
if (strcmp(ext, "sub") == 0 || strcmp(ext, "gen") == 0)
target_tree = &ctx->subtypes;
else if (get_device_type(ext))
target_tree = &ctx->devices;
else
return true;
int fd = openat(dirfd, filename, O_RDONLY);
if (fd < 0)
return false;
if (!isfile(fd)) {
close(fd);
return true;
}
FILE *f = fdopen(fd, "r");
if (!f) {
close(fd);
return false;
}
ini_file_t *data = read_ini_file(f);
fclose(f);
if (!data)
return false;
config_object_t *obj = calloc(1, sizeof(*obj));
if (!obj) {
free_ini_file(data);
return false;
}
obj->data = data;
char *name = strndup(filename, (ext - filename) - 1);
if (!name) {
config_object_free(obj);
return false;
}
NODE_NAME(obj) = name;
obj->type = strdup(ext);
if (!obj->type) {
config_object_free(obj);
return false;
}
avl_insert(target_tree, &obj->node);
return true;
}
static bool read_config_dir(load_ctx_t *ctx, const char *path) {
DIR *dir = opendir(path);
if (!dir)
return false;
int fd = dirfd(dir);
struct dirent *ent;
while ((ent = readdir(dir)) != NULL)
read_config_file(ctx, fd, ent->d_name);
closedir(dir);
return true;
}
bool read_config(const char *path) { bool read_config(const char *path) {
load_ctx_t ctx; struct json_object *config = json_object_from_file(path);
avl_init(&ctx.subtypes, avl_strcmp, true, NULL); if (!config)
avl_init(&ctx.devices, avl_strcmp, true, NULL); return false;
bool ret = read_config_dir(&ctx, path); struct avl_tree *devices = config_process(config);
json_object_put(config);
struct avl_tree *subtypes = config_process_subtypes(&ctx.subtypes); if (!devices)
struct avl_tree *devices = config_process_devices(&ctx.devices); return false;
free(subtypes);
device_t *dev, *tmp; device_t *dev, *tmp;
avl_for_each_element(devices, dev, node) avl_for_each_element(devices, dev, node)
@ -131,5 +37,5 @@ bool read_config(const char *path) {
free(devices); free(devices);
return ret; return true;
} }

View file

@ -1,6 +1,5 @@
#include "config-process.h" #include "config-process.h"
#include "device.h" #include "device.h"
#include "keywords.h"
#include "util.h" #include "util.h"
#include <libubox/avl-cmp.h> #include <libubox/avl-cmp.h>
@ -9,6 +8,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
typedef struct _process_ctx { typedef struct _process_ctx {
struct avl_tree *ret; struct avl_tree *ret;
} process_ctx_t; } process_ctx_t;
@ -17,52 +17,28 @@ typedef struct _config_subtype {
struct avl_node node; struct avl_node node;
} config_subtype_t; } config_subtype_t;
void config_object_free(config_object_t *obj) {
free_ini_file(obj->data);
free(obj->type);
free(NODE_NAME(obj));
free(obj);
}
static bool subtype_supported(const process_ctx_t *ctx, const char *type) { static device_t * config_process_device(const char *name, struct json_object *obj) {
switch (lookup_keyword(type)) { const char *typename = NULL;
case KW_Properties: struct json_object *device = neco_json_get_value(obj, "device", json_type_object);
case KW_Generate: if (device)
case KW_Static: typename = neco_json_get_string(obj, "type");
case KW_DHCP:
case KW_DHCPv6:
return true;
default: const device_type_t *type = get_device_type(typename ?: "interface");
return avl_find(ctx->ret, type); if (!type)
}
}
static config_subtype_t * config_process_subtype(const process_ctx_t *ctx, config_object_t *obj) {
ini_section_t *section;
list_for_each_entry(section, &obj->data->sections, node) {
printf("%s %s %i\n", NODE_NAME(obj), section->name, subtype_supported(ctx, section->name));
if (!subtype_supported(ctx, section->name))
return NULL;
}
config_subtype_t *ret = calloc(1, sizeof(*ret));
if (!ret)
return NULL; return NULL;
char *name = strdup(NODE_NAME(obj)); return type->process_config(name, obj);
if (!name) {
free(ret);
return NULL;
}
NODE_NAME(ret) = name;
return ret;
} }
struct avl_tree * config_process_subtypes(struct avl_tree *sub) { struct avl_tree * config_process(struct json_object *config) {
if (!json_object_is_type(config, json_type_object))
return NULL;
struct json_object *devices = neco_json_get_value(config, "devices", json_type_object);
if (!devices)
return NULL;
process_ctx_t ctx; process_ctx_t ctx;
ctx.ret = calloc(1, sizeof(*ctx.ret)); ctx.ret = calloc(1, sizeof(*ctx.ret));
if (!ctx.ret) if (!ctx.ret)
@ -70,65 +46,12 @@ struct avl_tree * config_process_subtypes(struct avl_tree *sub) {
avl_init(ctx.ret, avl_strcmp, false, NULL); avl_init(ctx.ret, avl_strcmp, false, NULL);
while (true) { json_object_object_foreach(devices, name, obj) {
size_t processed = 0; device_t *device = config_process_device(name, obj);
config_object_t *obj, *tmp;
avl_for_each_element_safe(sub, obj, node, tmp) {
config_subtype_t *t = config_process_subtype(&ctx, obj);
if (!t)
continue;
avl_delete(sub, &obj->node);
config_object_free(obj);
avl_insert(ctx.ret, &t->node);
processed++;
}
if (!processed)
break;
}
return ctx.ret;
}
static device_t * config_process_device(config_object_t *obj) {
const device_type_t *type = get_device_type(obj->type);
assert(type != NULL);
return type->process_config(NODE_NAME(obj), obj->data);
}
struct avl_tree * config_process_devices(struct avl_tree *devices) {
process_ctx_t ctx;
ctx.ret = calloc(1, sizeof(*ctx.ret));
if (!ctx.ret)
return NULL;
avl_init(ctx.ret, avl_strcmp, false, NULL);
while (true) {
size_t processed = 0;
config_object_t *obj, *tmp;
avl_for_each_element_safe(devices, obj, node, tmp) {
device_t *device = config_process_device(obj);
if (!device) if (!device)
continue; continue;
avl_delete(devices, &obj->node);
config_object_free(obj);
avl_insert(ctx.ret, &device->node); avl_insert(ctx.ret, &device->node);
processed++;
}
if (!processed)
break;
} }
return ctx.ret; return ctx.ret;

View file

@ -1,15 +1,6 @@
#pragma once #pragma once
#include "config-ini.h" #include <json-c/json.h>
#include <libubox/avl.h> #include <libubox/avl.h>
typedef struct _config_object { struct avl_tree * config_process(struct json_object *config);
struct avl_node node;
char *type;
ini_file_t *data;
} config_object_t;
void config_object_free(config_object_t *obj);
struct avl_tree * config_process_subtypes(struct avl_tree *sub);
struct avl_tree * config_process_devices(struct avl_tree *devices);

View file

@ -1,6 +1,8 @@
#include "device.h" #include "device.h"
static device_t * bridge_process_config(const char *name, const ini_file_t *config) { #include <stdio.h>
static device_t * bridge_process_config(const char *name, struct json_object *config) {
printf("Bridge: %s\n", name); printf("Bridge: %s\n", name);
return NULL; return NULL;
} }

View file

@ -1,5 +1,4 @@
#include "device.h" #include "device.h"
#include "keywords.h"
#include "netlink.h" #include "netlink.h"
#include "util.h" #include "util.h"
#include "vector.h" #include "vector.h"
@ -109,22 +108,43 @@ static bool parse_prefix(ipaddr_prefix_t *prefix, const char *str, bool allow_ho
return true; return true;
} }
static bool process_section_device(device_interface_t *iface, const ini_section_t *section) { static void interface_free(device_t *dev) {
ini_field_t *field; device_interface_t *iface = container_of(dev, device_interface_t, device);
list_for_each_entry(field, &section->fields, node) { VECTOR_FREE(iface->addrs);
switch (lookup_keyword(field->key)) { free(NODE_NAME(dev));
default: free(iface);
fprintf(stderr, "interface: [Device]: unknown field %s\n", field->key); }
}
}
static bool process_section_device(device_interface_t *iface, struct json_object *section) {
return true; return true;
} }
static bool process_section_static(device_interface_t *iface, const ini_section_t *section) { static bool process_section_static(device_interface_t *iface, struct json_object *section) {
ini_field_t *field; struct json_object *addresses = neco_json_get_value(section, "addresses", json_type_array);
if (addresses) {
for (size_t i = 0; i < json_object_array_length(addresses); i++) {
struct json_object *address = json_object_array_get_idx(addresses, i);
if (!json_object_is_type(address, json_type_string)) {
fprintf(stderr, "interface: static: invalid address entry of type %s\n", json_type_to_name(json_object_get_type(address)));
continue;
}
ipaddr_prefix_t p;
if (!parse_prefix(&p, json_object_get_string(address), true)) {
fprintf(stderr, "interface: static: unable to parse Address %s\n", json_object_get_string(address));
break;
}
if (!VECTOR_ADD(iface->addrs, p)) {
fprintf(stderr, "interface: static: adding address failed\n");
return false;
}
}
}
/*
list_for_each_entry(field, &section->fields, node) { list_for_each_entry(field, &section->fields, node) {
switch (lookup_keyword(field->key)) { switch (lookup_keyword(field->key)) {
case KW_Address: { case KW_Address: {
@ -146,16 +166,42 @@ static bool process_section_static(device_interface_t *iface, const ini_section_
fprintf(stderr, "interface: [Static]: unknown field %s\n", field->key); fprintf(stderr, "interface: [Static]: unknown field %s\n", field->key);
} }
} }
*/
return true; return true;
} }
static void interface_free(device_t *dev) { static device_t * interface_process_config(const char *name, struct json_object *config) {
device_interface_t *iface = container_of(dev, device_interface_t, device); device_interface_t *iface = calloc(1, sizeof(*iface));
if (!iface)
return NULL;
VECTOR_FREE(iface->addrs); device_t *dev = &iface->device;
free(NODE_NAME(dev)); dev->type = &device_type_interface;
NODE_NAME(dev) = strdup(name);
if (!NODE_NAME(dev)) {
free(iface); free(iface);
return NULL;
}
struct json_object *sec_device = neco_json_get_value(config, "device", json_type_object);
if (sec_device) {
if (!process_section_device(iface, sec_device))
goto err;
}
struct json_object *sec_static = neco_json_get_value(config, "static", json_type_object);
if (sec_static) {
if (!process_section_static(iface, sec_static))
goto err;
}
return dev;
err:
interface_free(dev);
return NULL;
} }
static bool interface_set_link_flags(unsigned index, unsigned change, unsigned flags) { static bool interface_set_link_flags(unsigned index, unsigned change, unsigned flags) {
@ -281,45 +327,6 @@ static void interface_release(device_t *dev) {
interface_set_link_state(index, false); interface_set_link_state(index, false);
} }
static device_t * interface_process_config(const char *name, const ini_file_t *config) {
device_interface_t *iface = calloc(1, sizeof(*iface));
if (!iface)
return NULL;
device_t *dev = &iface->device;
dev->type = &device_type_interface;
NODE_NAME(dev) = strdup(name);
if (!NODE_NAME(dev)) {
free(iface);
return NULL;
}
const ini_section_t *section;
list_for_each_entry(section, &config->sections, node) {
switch (lookup_keyword(section->name)) {
case KW_Device:
if (!process_section_device(iface, section))
goto err;
break;
case KW_Static:
if (!process_section_static(iface, section))
goto err;
break;
default:
fprintf(stderr, "interface: unknown section %s\n", section->name);
}
}
return dev;
err:
interface_free(dev);
return NULL;
}
static device_type_t device_type_interface = { static device_type_t device_type_interface = {
.process_config = interface_process_config, .process_config = interface_process_config,
.free = interface_free, .free = interface_free,

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "config-ini.h" #include <json-c/json.h>
#include <libubox/avl.h> #include <libubox/avl.h>
extern struct avl_tree device_types; extern struct avl_tree device_types;
@ -15,7 +14,7 @@ typedef struct _device {
struct _device_type { struct _device_type {
struct avl_node node; struct avl_node node;
device_t * (*process_config)(const char *name, const ini_file_t *config); device_t * (*process_config)(const char *name, struct json_object *config);
void (*free)(device_t *device); void (*free)(device_t *device);
void (*init)(device_t *device); void (*init)(device_t *device);

View file

@ -1,31 +0,0 @@
#include "keywords.h"
#include <libubox/utils.h>
#include <stdlib.h>
#include <string.h>
static const char *const keywords[] = {
#define KW(kw) #kw,
#include "keywords.def"
#undef KW
};
static int compare_keywords(const void *a, const void *b) {
const char *const *ea = a, *const *eb = b;
return strcmp(*ea, *eb);
}
keyword_t lookup_keyword(const char *keyword) {
const char *const *entry = bsearch(
&keyword, keywords, ARRAY_SIZE(keywords), sizeof(const char *),
compare_keywords
);
if (!entry)
return UNKNOWN_KEYWORD;
return (keyword_t) (entry - keywords + 1);
}

View file

@ -1,9 +0,0 @@
/* Config section and field names; keep sorted! */
KW(Address)
KW(DHCP)
KW(DHCPv6)
KW(Device)
KW(Generate)
KW(Properties)
KW(Static)

View file

@ -1,12 +0,0 @@
#pragma once
typedef enum _keyword {
UNKNOWN_KEYWORD = 0,
#define KW(kw) KW_##kw,
#include "keywords.def"
#undef KW
} keyword_t;
keyword_t lookup_keyword(const char *keyword);

View file

@ -1,19 +1,18 @@
src = [ src = [
'neco.c', 'neco.c',
'config-ini.c',
'config-load.c', 'config-load.c',
'config-process.c', 'config-process.c',
'device.c', 'device.c',
'device-bridge.c', 'device-bridge.c',
'device-interface.c', 'device-interface.c',
'keywords.c',
'netlink.c', 'netlink.c',
'vector.c', 'vector.c',
] ]
dep = [ dep = [
libubox_dep, libjson_c_dep,
libmnl_dep, libmnl_dep,
libubox_dep,
] ]
executable('neco', sources: src, dependencies: dep) executable('neco', sources: src, dependencies: dep)

View file

@ -1,3 +1,29 @@
#pragma once #pragma once
#include <json-c/json.h>
#define NODE_NAME(c) (*(char **)&(c)->node.key) #define NODE_NAME(c) (*(char **)&(c)->node.key)
static inline struct json_object * neco_json_get_value(
struct json_object *obj,
const char *key,
enum json_type type
) {
struct json_object *value;
if (!json_object_object_get_ex(obj, key, &value))
return NULL;
if (!json_object_is_type(value, type))
return NULL;
return value;
}
static inline const char * neco_json_get_string(struct json_object *obj, const char *key) {
struct json_object *value = neco_json_get_value(obj, key, json_type_string);
if (!value)
return NULL;
return json_object_get_string(value);
}