more cleanups

This commit is contained in:
Johannes Findeisen 2018-01-07 23:59:42 +01:00
commit 2e6d469170
45 changed files with 0 additions and 8975 deletions

View file

@ -1,91 +0,0 @@
VERSION=0.8
# optional features
PLUGIN_SUPPORT=1
DEBUG=0
DEBUG_TYPECAST=0
include Makefile.inc
LIBS=-lc -ldl
SHLIB_FILE=libuci.$(SHLIB_EXT).$(VERSION)
define add_feature
@echo "$(if $(findstring 1,$($(1))),#define UCI_$(1) 1,#undef UCI_$(1))" >> $@.tmp
endef
LIBUCI_DEPS=file.c history.c list.c util.c uci.h uci_config.h uci_internal.h
all: uci libuci.$(SHLIB_EXT) uci-static ucimap-example
cli.o: cli.c uci.h uci_config.h
ucimap.o: ucimap.c uci.h uci_config.h ucimap.h
uci_config.h: FORCE
@rm -f "$@.tmp"
@echo "#define UCI_PREFIX \"$(prefix)\"" > "$@.tmp"
$(call add_feature,PLUGIN_SUPPORT)
$(call add_feature,DEBUG)
$(call add_feature,DEBUG_TYPECAST)
@if [ \! -f "$@" ] || ! cmp "$@.tmp" "$@" >/dev/null; then \
mv "$@.tmp" "$@"; \
else \
rm -f "$@.tmp"; \
fi
%.o: %.c
$(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $<
%-shared.o: %.c
$(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(FPIC) $<
%-static.o: %.c
$(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $<
uci: cli.o libuci.$(SHLIB_EXT)
$(CC) -o $@ $< -L. -luci $(LIBS)
uci-static: cli.o libuci.a
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
libuci-static.o: libuci.c $(LIBUCI_DEPS)
libuci-shared.o: libuci.c $(LIBUCI_DEPS)
ucimap-static.o: ucimap.c $(LIBUCI_DEPS) ucimap.h
ucimap-shared.o: ucimap.c $(LIBUCI_DEPS) ucimap.h
libuci.a: libuci-static.o ucimap-static.o
rm -f $@
$(AR) rc $@ $^
$(RANLIB) $@
libuci.$(SHLIB_EXT): libuci-shared.o ucimap-shared.o
$(LINK) $(SHLIB_FLAGS) -o $(SHLIB_FILE) $^ $(LIBS)
ln -sf $(SHLIB_FILE) $@
ucimap-example.o: ucimap-example.c list.h
ucimap-example: ucimap-example.o libuci.a
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
clean:
rm -f uci uci-static *.[oa] *.so* *.dylib* uci_config.h ucimap-example
install: install-bin install-dev
install-dev: all
$(MKDIR) -p $(DESTDIR)$(prefix)/lib
$(MKDIR) -p $(DESTDIR)$(prefix)/include
$(INSTALL) -m0644 libuci.a $(DESTDIR)$(prefix)/lib/
$(INSTALL) -m0644 uci_config.h uci.h ucimap.h $(DESTDIR)$(prefix)/include/
install-bin: all
$(MKDIR) -p $(DESTDIR)$(prefix)/lib
$(INSTALL) -m0755 $(SHLIB_FILE) $(DESTDIR)$(prefix)/lib/
ln -sf $(SHLIB_FILE) $(DESTDIR)$(prefix)/lib/libuci.$(SHLIB_EXT)
$(MKDIR) -p $(DESTDIR)$(prefix)/bin
$(INSTALL) -m0755 uci $(DESTDIR)$(prefix)/bin/
test: all ucimap-example
make -C test
FORCE: ;
.PHONY: FORCE

View file

@ -1,31 +0,0 @@
prefix=/usr
DESTDIR=
COPTS=-O2
WOPTS=-pedantic -Werror -Wall
FPIC=-fPIC
CFLAGS=$(COPTS) $(WOPTS) -std=gnu99
CPPFLAGS=-I.
AR=ar
LD=ld
CC=gcc
RANLIB=ranlib
INSTALL=install
MKDIR=mkdir
ifeq ($(DEBUG),1)
COPTS = -O0
CFLAGS += -g3
endif
OS=$(shell uname)
ifeq ($(OS),Darwin)
LINK=$(LD)
SHLIB_EXT=dylib
SHLIB_FLAGS=-dylib
else
LINK=$(CC)
SHLIB_EXT=so
SHLIB_FLAGS=-shared -Wl,-soname,$(SHLIB_FILE)
endif

View file

@ -1,698 +0,0 @@
/*
* cli - Command Line Interface for the Unified Configuration Interface
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation
*
* This program 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.
*/
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "uci.h"
#define MAX_ARGS 4 /* max command line arguments for batch mode */
static const char *delimiter = " ";
static const char *appname;
static enum {
CLI_FLAG_MERGE = (1 << 0),
CLI_FLAG_QUIET = (1 << 1),
CLI_FLAG_NOCOMMIT = (1 << 2),
CLI_FLAG_BATCH = (1 << 3),
CLI_FLAG_SHOW_EXT = (1 << 4),
CLI_FLAG_NOPLUGINS= (1 << 5),
} flags;
static FILE *input;
static struct uci_context *ctx;
enum {
/* section cmds */
CMD_GET,
CMD_SET,
CMD_ADD_LIST,
CMD_DEL,
CMD_RENAME,
CMD_REVERT,
CMD_REORDER,
/* package cmds */
CMD_SHOW,
CMD_CHANGES,
CMD_EXPORT,
CMD_COMMIT,
/* other cmds */
CMD_ADD,
CMD_IMPORT,
CMD_HELP,
};
struct uci_type_list {
unsigned int idx;
const char *name;
struct uci_type_list *next;
};
static struct uci_type_list *type_list = NULL;
static char *typestr = NULL;
static const char *cur_section_ref = NULL;
static int uci_cmd(int argc, char **argv);
static void
uci_reset_typelist(void)
{
struct uci_type_list *type;
while (type_list != NULL) {
type = type_list;
type_list = type_list->next;
free(type);
}
if (typestr) {
free(typestr);
typestr = NULL;
}
cur_section_ref = NULL;
}
static char *
uci_lookup_section_ref(struct uci_section *s)
{
struct uci_type_list *ti = type_list;
int maxlen;
if (!s->anonymous || !(flags & CLI_FLAG_SHOW_EXT))
return s->e.name;
/* look up in section type list */
while (ti) {
if (strcmp(ti->name, s->type) == 0)
break;
ti = ti->next;
}
if (!ti) {
ti = malloc(sizeof(struct uci_type_list));
memset(ti, 0, sizeof(struct uci_type_list));
ti->next = type_list;
type_list = ti;
ti->name = s->type;
}
maxlen = strlen(s->type) + 1 + 2 + 10;
if (!typestr) {
typestr = malloc(maxlen);
} else {
typestr = realloc(typestr, maxlen);
}
sprintf(typestr, "@%s[%d]", ti->name, ti->idx);
ti->idx++;
return typestr;
}
static void uci_usage(void)
{
fprintf(stderr,
"Usage: %s [<options>] <command> [<arguments>]\n\n"
"Commands:\n"
"\tbatch\n"
"\texport [<config>]\n"
"\timport [<config>]\n"
"\tchanges [<config>]\n"
"\tcommit [<config>]\n"
"\tadd <config> <section-type>\n"
"\tadd_list <config>.<section>.<option>=<string>\n"
"\tshow [<config>[.<section>[.<option>]]]\n"
"\tget <config>.<section>[.<option>]\n"
"\tset <config>.<section>[.<option>]=<value>\n"
"\tdelete <config>[.<section[.<option>]]\n"
"\trename <config>.<section>[.<option>]=<name>\n"
"\trevert <config>[.<section>[.<option>]]\n"
"\treorder <config>.<section>=<position>\n"
"\n"
"Options:\n"
"\t-c <path> set the search path for config files (default: /etc/config)\n"
"\t-d <str> set the delimiter for list values in uci show\n"
"\t-f <file> use <file> as input instead of stdin\n"
"\t-L do not load any plugins\n"
"\t-m when importing, merge data into an existing package\n"
"\t-n name unnamed sections on export (default)\n"
"\t-N don't name unnamed sections\n"
"\t-p <path> add a search path for config change files\n"
"\t-P <path> add a search path for config change files and use as default\n"
"\t-q quiet mode (don't print error messages)\n"
"\t-s force strict mode (stop on parser errors, default)\n"
"\t-S disable strict mode\n"
"\t-X do not use extended syntax on 'show'\n"
"\n",
appname
);
}
static void cli_perror(void)
{
if (flags & CLI_FLAG_QUIET)
return;
uci_perror(ctx, appname);
}
static void uci_show_value(struct uci_option *o)
{
struct uci_element *e;
bool sep = false;
switch(o->type) {
case UCI_TYPE_STRING:
printf("%s\n", o->v.string);
break;
case UCI_TYPE_LIST:
uci_foreach_element(&o->v.list, e) {
printf("%s%s", (sep ? delimiter : ""), e->name);
sep = true;
}
printf("\n");
break;
default:
printf("<unknown>\n");
break;
}
}
static void uci_show_option(struct uci_option *o)
{
printf("%s.%s.%s=",
o->section->package->e.name,
(cur_section_ref ? cur_section_ref : o->section->e.name),
o->e.name);
uci_show_value(o);
}
static void uci_show_section(struct uci_section *s)
{
struct uci_element *e;
const char *cname;
const char *sname;
cname = s->package->e.name;
sname = (cur_section_ref ? cur_section_ref : s->e.name);
printf("%s.%s=%s\n", cname, sname, s->type);
uci_foreach_element(&s->options, e) {
uci_show_option(uci_to_option(e));
}
}
static void uci_show_package(struct uci_package *p)
{
struct uci_element *e;
uci_reset_typelist();
uci_foreach_element( &p->sections, e) {
struct uci_section *s = uci_to_section(e);
cur_section_ref = uci_lookup_section_ref(s);
uci_show_section(s);
}
uci_reset_typelist();
}
static void uci_show_changes(struct uci_package *p)
{
struct uci_element *e;
uci_foreach_element(&p->saved_history, e) {
struct uci_history *h = uci_to_history(e);
char *prefix = "";
char *op = "=";
switch(h->cmd) {
case UCI_CMD_REMOVE:
prefix = "-";
break;
case UCI_CMD_LIST_ADD:
op = "+=";
break;
default:
break;
}
printf("%s%s.%s", prefix, p->e.name, h->section);
if (e->name)
printf(".%s", e->name);
if (h->cmd != UCI_CMD_REMOVE)
printf("%s%s", op, h->value);
printf("\n");
}
}
static int package_cmd(int cmd, char *tuple)
{
struct uci_element *e = NULL;
struct uci_ptr ptr;
if (uci_lookup_ptr(ctx, &ptr, tuple, true) != UCI_OK) {
cli_perror();
return 1;
}
e = ptr.last;
switch(cmd) {
case CMD_CHANGES:
uci_show_changes(ptr.p);
break;
case CMD_COMMIT:
if (flags & CLI_FLAG_NOCOMMIT)
return 0;
if (uci_commit(ctx, &ptr.p, false) != UCI_OK)
cli_perror();
break;
case CMD_EXPORT:
uci_export(ctx, stdout, ptr.p, true);
break;
case CMD_SHOW:
if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) {
ctx->err = UCI_ERR_NOTFOUND;
cli_perror();
return 1;
}
switch(e->type) {
case UCI_TYPE_PACKAGE:
uci_show_package(ptr.p);
break;
case UCI_TYPE_SECTION:
uci_show_section(ptr.s);
break;
case UCI_TYPE_OPTION:
uci_show_option(ptr.o);
break;
default:
/* should not happen */
return 1;
}
break;
}
uci_unload(ctx, ptr.p);
return 0;
}
static int uci_do_import(int argc, char **argv)
{
struct uci_package *package = NULL;
char *name = NULL;
int ret = UCI_OK;
bool merge = false;
if (argc > 2)
return 255;
if (argc == 2)
name = argv[1];
else if (flags & CLI_FLAG_MERGE)
/* need a package to merge */
return 255;
if (flags & CLI_FLAG_MERGE) {
if (uci_load(ctx, name, &package) != UCI_OK)
package = NULL;
else
merge = true;
}
ret = uci_import(ctx, input, name, &package, (name != NULL));
if (ret == UCI_OK) {
if (merge) {
ret = uci_save(ctx, package);
} else {
struct uci_element *e;
/* loop through all config sections and overwrite existing data */
uci_foreach_element(&ctx->root, e) {
struct uci_package *p = uci_to_package(e);
ret = uci_commit(ctx, &p, true);
}
}
}
if (ret != UCI_OK) {
cli_perror();
return 1;
}
return 0;
}
static int uci_do_package_cmd(int cmd, int argc, char **argv)
{
char **configs = NULL;
char **p;
if (argc > 2)
return 255;
if (argc == 2)
return package_cmd(cmd, argv[1]);
if ((uci_list_configs(ctx, &configs) != UCI_OK) || !configs) {
cli_perror();
return 1;
}
for (p = configs; *p; p++) {
package_cmd(cmd, *p);
}
return 0;
}
static int uci_do_add(int argc, char **argv)
{
struct uci_package *p = NULL;
struct uci_section *s = NULL;
int ret;
if (argc != 3)
return 255;
ret = uci_load(ctx, argv[1], &p);
if (ret != UCI_OK)
goto done;
ret = uci_add_section(ctx, p, argv[2], &s);
if (ret != UCI_OK)
goto done;
ret = uci_save(ctx, p);
done:
if (ret != UCI_OK)
cli_perror();
else if (s)
fprintf(stdout, "%s\n", s->e.name);
return ret;
}
static int uci_do_section_cmd(int cmd, int argc, char **argv)
{
struct uci_element *e;
struct uci_ptr ptr;
int ret = UCI_OK;
if (argc != 2)
return 255;
if (uci_lookup_ptr(ctx, &ptr, argv[1], true) != UCI_OK) {
cli_perror();
return 1;
}
if (ptr.value && (cmd != CMD_SET) && (cmd != CMD_ADD_LIST) && (cmd != CMD_RENAME) && (cmd != CMD_REORDER))
return 1;
e = ptr.last;
switch(cmd) {
case CMD_GET:
if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) {
ctx->err = UCI_ERR_NOTFOUND;
cli_perror();
return 1;
}
switch(e->type) {
case UCI_TYPE_SECTION:
printf("%s\n", ptr.s->type);
break;
case UCI_TYPE_OPTION:
uci_show_value(ptr.o);
break;
default:
break;
}
/* throw the value to stdout */
break;
case CMD_RENAME:
ret = uci_rename(ctx, &ptr);
break;
case CMD_REVERT:
ret = uci_revert(ctx, &ptr);
break;
case CMD_SET:
ret = uci_set(ctx, &ptr);
break;
case CMD_ADD_LIST:
ret = uci_add_list(ctx, &ptr);
break;
case CMD_REORDER:
if (!ptr.s) {
ctx->err = UCI_ERR_NOTFOUND;
cli_perror();
return 1;
}
ret = uci_reorder_section(ctx, ptr.s, strtoul(ptr.value, NULL, 10));
break;
case CMD_DEL:
ret = uci_delete(ctx, &ptr);
break;
}
/* no save necessary for get */
if ((cmd == CMD_GET) || (cmd == CMD_REVERT))
return 0;
/* save changes, but don't commit them yet */
if (ret == UCI_OK)
ret = uci_save(ctx, ptr.p);
if (ret != UCI_OK) {
cli_perror();
return 1;
}
return 0;
}
static int uci_batch_cmd(void)
{
char *argv[MAX_ARGS + 2];
char *str = NULL;
int ret = 0;
int i, j;
for(i = 0; i <= MAX_ARGS; i++) {
if (i == MAX_ARGS) {
fprintf(stderr, "Too many arguments\n");
return 1;
}
argv[i] = NULL;
if ((ret = uci_parse_argument(ctx, input, &str, &argv[i])) != UCI_OK) {
cli_perror();
i = 0;
break;
}
if (!argv[i][0])
break;
argv[i] = strdup(argv[i]);
if (!argv[i]) {
perror("uci");
return 1;
}
}
argv[i] = NULL;
if (i > 0) {
if (!strcasecmp(argv[0], "exit"))
return 254;
ret = uci_cmd(i, argv);
} else
return 0;
for (j = 0; j < i; j++) {
if (argv[j])
free(argv[j]);
}
return ret;
}
static int uci_batch(void)
{
int ret = 0;
flags |= CLI_FLAG_BATCH;
while (!feof(input)) {
struct uci_element *e, *tmp;
ret = uci_batch_cmd();
if (ret == 254)
return 0;
else if (ret == 255)
fprintf(stderr, "Unknown command\n");
/* clean up */
uci_foreach_element_safe(&ctx->root, tmp, e) {
uci_unload(ctx, uci_to_package(e));
}
}
flags &= ~CLI_FLAG_BATCH;
return 0;
}
static int uci_cmd(int argc, char **argv)
{
int cmd = 0;
if (!strcasecmp(argv[0], "batch") && !(flags & CLI_FLAG_BATCH))
return uci_batch();
else if (!strcasecmp(argv[0], "show"))
cmd = CMD_SHOW;
else if (!strcasecmp(argv[0], "changes"))
cmd = CMD_CHANGES;
else if (!strcasecmp(argv[0], "export"))
cmd = CMD_EXPORT;
else if (!strcasecmp(argv[0], "commit"))
cmd = CMD_COMMIT;
else if (!strcasecmp(argv[0], "get"))
cmd = CMD_GET;
else if (!strcasecmp(argv[0], "set"))
cmd = CMD_SET;
else if (!strcasecmp(argv[0], "ren") ||
!strcasecmp(argv[0], "rename"))
cmd = CMD_RENAME;
else if (!strcasecmp(argv[0], "revert"))
cmd = CMD_REVERT;
else if (!strcasecmp(argv[0], "reorder"))
cmd = CMD_REORDER;
else if (!strcasecmp(argv[0], "del") ||
!strcasecmp(argv[0], "delete"))
cmd = CMD_DEL;
else if (!strcasecmp(argv[0], "import"))
cmd = CMD_IMPORT;
else if (!strcasecmp(argv[0], "help"))
cmd = CMD_HELP;
else if (!strcasecmp(argv[0], "add"))
cmd = CMD_ADD;
else if (!strcasecmp(argv[0], "add_list"))
cmd = CMD_ADD_LIST;
else
cmd = -1;
switch(cmd) {
case CMD_ADD_LIST:
case CMD_GET:
case CMD_SET:
case CMD_DEL:
case CMD_RENAME:
case CMD_REVERT:
case CMD_REORDER:
return uci_do_section_cmd(cmd, argc, argv);
case CMD_SHOW:
case CMD_EXPORT:
case CMD_COMMIT:
case CMD_CHANGES:
return uci_do_package_cmd(cmd, argc, argv);
case CMD_IMPORT:
return uci_do_import(argc, argv);
case CMD_ADD:
return uci_do_add(argc, argv);
case CMD_HELP:
uci_usage();
return 0;
default:
return 255;
}
}
int main(int argc, char **argv)
{
int ret;
int c;
flags = CLI_FLAG_SHOW_EXT;
appname = argv[0];
input = stdin;
ctx = uci_alloc_context();
if (!ctx) {
fprintf(stderr, "Out of memory\n");
return 1;
}
while((c = getopt(argc, argv, "c:d:f:LmnNp:P:sSqX")) != -1) {
switch(c) {
case 'c':
uci_set_confdir(ctx, optarg);
break;
case 'd':
delimiter = optarg;
break;
case 'f':
input = fopen(optarg, "r");
if (!input) {
perror("uci");
return 1;
}
break;
case 'L':
flags |= CLI_FLAG_NOPLUGINS;
break;
case 'm':
flags |= CLI_FLAG_MERGE;
break;
case 's':
ctx->flags |= UCI_FLAG_STRICT;
break;
case 'S':
ctx->flags &= ~UCI_FLAG_STRICT;
ctx->flags |= UCI_FLAG_PERROR;
break;
case 'n':
ctx->flags |= UCI_FLAG_EXPORT_NAME;
break;
case 'N':
ctx->flags &= ~UCI_FLAG_EXPORT_NAME;
break;
case 'p':
uci_add_history_path(ctx, optarg);
break;
case 'P':
uci_add_history_path(ctx, ctx->savedir);
uci_set_savedir(ctx, optarg);
flags |= CLI_FLAG_NOCOMMIT;
break;
case 'q':
flags |= CLI_FLAG_QUIET;
break;
case 'X':
flags &= ~CLI_FLAG_SHOW_EXT;
break;
default:
uci_usage();
return 0;
}
}
if (optind > 1)
argv[optind - 1] = argv[0];
argv += optind - 1;
argc -= optind - 1;
if (argc < 2) {
uci_usage();
return 0;
}
if (!(flags & CLI_FLAG_NOPLUGINS))
uci_load_plugins(ctx, NULL);
ret = uci_cmd(argc - 1, argv + 1);
if (input != stdin)
fclose(input);
if (ret == 255) {
uci_usage();
return 0;
}
uci_free_context(ctx);
return ret;
}

View file

@ -1,581 +0,0 @@
/*
* libuci - Library for the Unified Configuration Interface
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
/*
* This file contains the code for parsing uci config files
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/file.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
#include <glob.h>
static struct uci_backend uci_file_backend;
/*
* verify that the end of the line or command is reached.
* throw an error if extra arguments are given on the command line
*/
static void assert_eol(struct uci_context *ctx, char **str)
{
char *tmp;
skip_whitespace(ctx, str);
tmp = next_arg(ctx, str, false, false);
if (*tmp && (ctx->flags & UCI_FLAG_STRICT))
uci_parse_error(ctx, *str, "too many arguments");
}
/*
* switch to a different config, either triggered by uci_load, or by a
* 'package <...>' statement in the import file
*/
static void uci_switch_config(struct uci_context *ctx)
{
struct uci_parse_context *pctx;
struct uci_element *e;
const char *name;
pctx = ctx->pctx;
name = pctx->name;
/* add the last config to main config file list */
if (pctx->package) {
pctx->package->backend = ctx->backend;
uci_list_add(&ctx->root, &pctx->package->e.list);
pctx->package = NULL;
pctx->section = NULL;
}
if (!name)
return;
/*
* if an older config under the same name exists, unload it
* ignore errors here, e.g. if the config was not found
*/
e = uci_lookup_list(&ctx->root, name);
if (e)
UCI_THROW(ctx, UCI_ERR_DUPLICATE);
pctx->package = uci_alloc_package(ctx, name);
}
/*
* parse the 'package' uci command (next config package)
*/
static void uci_parse_package(struct uci_context *ctx, char **str, bool single)
{
char *name = NULL;
/* command string null-terminated by strtok */
*str += strlen(*str) + 1;
name = next_arg(ctx, str, true, true);
assert_eol(ctx, str);
if (single)
return;
ctx->pctx->name = name;
uci_switch_config(ctx);
}
/*
* parse the 'config' uci command (open a section)
*/
static void uci_parse_config(struct uci_context *ctx, char **str)
{
struct uci_parse_context *pctx = ctx->pctx;
struct uci_element *e;
struct uci_ptr ptr;
char *name = NULL;
char *type = NULL;
uci_fixup_section(ctx, ctx->pctx->section);
if (!ctx->pctx->package) {
if (!ctx->pctx->name)
uci_parse_error(ctx, *str, "attempting to import a file without a package name");
uci_switch_config(ctx);
}
/* command string null-terminated by strtok */
*str += strlen(*str) + 1;
type = next_arg(ctx, str, true, false);
if (!uci_validate_type(type))
uci_parse_error(ctx, type, "invalid character in field");
name = next_arg(ctx, str, false, true);
assert_eol(ctx, str);
if (!name) {
ctx->internal = !pctx->merge;
UCI_NESTED(uci_add_section, ctx, pctx->package, type, &pctx->section);
} else {
UCI_NESTED(uci_fill_ptr, ctx, &ptr, &pctx->package->e, false);
e = uci_lookup_list(&pctx->package->sections, name);
if (e)
ptr.s = uci_to_section(e);
ptr.section = name;
ptr.value = type;
ctx->internal = !pctx->merge;
UCI_NESTED(uci_set, ctx, &ptr);
pctx->section = uci_to_section(ptr.last);
}
}
/*
* parse the 'option' uci command (open a value)
*/
static void uci_parse_option(struct uci_context *ctx, char **str, bool list)
{
struct uci_parse_context *pctx = ctx->pctx;
struct uci_element *e;
struct uci_ptr ptr;
char *name = NULL;
char *value = NULL;
if (!pctx->section)
uci_parse_error(ctx, *str, "option/list command found before the first section");
/* command string null-terminated by strtok */
*str += strlen(*str) + 1;
name = next_arg(ctx, str, true, true);
value = next_arg(ctx, str, false, false);
assert_eol(ctx, str);
UCI_NESTED(uci_fill_ptr, ctx, &ptr, &pctx->section->e, false);
e = uci_lookup_list(&pctx->section->options, name);
if (e)
ptr.o = uci_to_option(e);
ptr.option = name;
ptr.value = value;
ctx->internal = !pctx->merge;
if (list)
UCI_NESTED(uci_add_list, ctx, &ptr);
else
UCI_NESTED(uci_set, ctx, &ptr);
}
/*
* parse a complete input line, split up combined commands by ';'
*/
static void uci_parse_line(struct uci_context *ctx, bool single)
{
struct uci_parse_context *pctx = ctx->pctx;
char *word, *brk;
word = pctx->buf;
do {
brk = NULL;
word = strtok_r(word, " \t", &brk);
if (!word)
return;
switch(word[0]) {
case 0:
case '#':
return;
case 'p':
if ((word[1] == 0) || !strcmp(word + 1, "ackage"))
uci_parse_package(ctx, &word, single);
else
goto invalid;
break;
case 'c':
if ((word[1] == 0) || !strcmp(word + 1, "onfig"))
uci_parse_config(ctx, &word);
else
goto invalid;
break;
case 'o':
if ((word[1] == 0) || !strcmp(word + 1, "ption"))
uci_parse_option(ctx, &word, false);
else
goto invalid;
break;
case 'l':
if ((word[1] == 0) || !strcmp(word + 1, "ist"))
uci_parse_option(ctx, &word, true);
else
goto invalid;
break;
default:
goto invalid;
}
continue;
invalid:
uci_parse_error(ctx, word, "invalid command");
} while (1);
}
/* max number of characters that escaping adds to the string */
#define UCI_QUOTE_ESCAPE "'\\''"
/*
* escape an uci string for export
*/
static char *uci_escape(struct uci_context *ctx, const char *str)
{
const char *end;
int ofs = 0;
if (!ctx->buf) {
ctx->bufsz = LINEBUF;
ctx->buf = malloc(LINEBUF);
}
while (1) {
int len;
end = strchr(str, '\'');
if (!end)
end = str + strlen(str);
len = end - str;
/* make sure that we have enough room in the buffer */
while (ofs + len + sizeof(UCI_QUOTE_ESCAPE) + 1 > ctx->bufsz) {
ctx->bufsz *= 2;
ctx->buf = uci_realloc(ctx, ctx->buf, ctx->bufsz);
}
/* copy the string until the character before the quote */
memcpy(&ctx->buf[ofs], str, len);
ofs += len;
/* end of string? return the buffer */
if (*end == 0)
break;
memcpy(&ctx->buf[ofs], UCI_QUOTE_ESCAPE, sizeof(UCI_QUOTE_ESCAPE));
ofs += strlen(&ctx->buf[ofs]);
str = end + 1;
}
ctx->buf[ofs] = 0;
return ctx->buf;
}
/*
* export a single config package to a file stream
*/
static void uci_export_package(struct uci_package *p, FILE *stream, bool header)
{
struct uci_context *ctx = p->ctx;
struct uci_element *s, *o, *i;
if (header)
fprintf(stream, "package '%s'\n", uci_escape(ctx, p->e.name));
uci_foreach_element(&p->sections, s) {
struct uci_section *sec = uci_to_section(s);
fprintf(stream, "\nconfig '%s'", uci_escape(ctx, sec->type));
if (!sec->anonymous || (ctx->flags & UCI_FLAG_EXPORT_NAME))
fprintf(stream, " '%s'", uci_escape(ctx, sec->e.name));
fprintf(stream, "\n");
uci_foreach_element(&sec->options, o) {
struct uci_option *opt = uci_to_option(o);
switch(opt->type) {
case UCI_TYPE_STRING:
fprintf(stream, "\toption '%s'", uci_escape(ctx, opt->e.name));
fprintf(stream, " '%s'\n", uci_escape(ctx, opt->v.string));
break;
case UCI_TYPE_LIST:
uci_foreach_element(&opt->v.list, i) {
fprintf(stream, "\tlist '%s'", uci_escape(ctx, opt->e.name));
fprintf(stream, " '%s'\n", uci_escape(ctx, i->name));
}
break;
default:
fprintf(stream, "\t# unknown type for option '%s'\n", uci_escape(ctx, opt->e.name));
break;
}
}
}
fprintf(stream, "\n");
}
int uci_export(struct uci_context *ctx, FILE *stream, struct uci_package *package, bool header)
{
struct uci_element *e;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, stream != NULL);
if (package)
uci_export_package(package, stream, header);
else {
uci_foreach_element(&ctx->root, e) {
uci_export_package(uci_to_package(e), stream, header);
}
}
return 0;
}
int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct uci_package **package, bool single)
{
struct uci_parse_context *pctx;
UCI_HANDLE_ERR(ctx);
/* make sure no memory from previous parse attempts is leaked */
uci_cleanup(ctx);
uci_alloc_parse_context(ctx);
pctx = ctx->pctx;
pctx->file = stream;
if (*package && single) {
pctx->package = *package;
pctx->merge = true;
}
/*
* If 'name' was supplied, assume that the supplied stream does not contain
* the appropriate 'package <name>' string to specify the config name
* NB: the config file can still override the package name
*/
if (name) {
UCI_ASSERT(ctx, uci_validate_package(name));
pctx->name = name;
}
while (!feof(pctx->file)) {
uci_getln(ctx, 0);
UCI_TRAP_SAVE(ctx, error);
if (pctx->buf[0])
uci_parse_line(ctx, single);
UCI_TRAP_RESTORE(ctx);
continue;
error:
if (ctx->flags & UCI_FLAG_PERROR)
uci_perror(ctx, NULL);
if ((ctx->err != UCI_ERR_PARSE) ||
(ctx->flags & UCI_FLAG_STRICT))
UCI_THROW(ctx, ctx->err);
}
uci_fixup_section(ctx, ctx->pctx->section);
if (!pctx->package && name)
uci_switch_config(ctx);
if (package)
*package = pctx->package;
if (pctx->merge)
pctx->package = NULL;
pctx->name = NULL;
uci_switch_config(ctx);
/* no error happened, we can get rid of the parser context now */
uci_cleanup(ctx);
return 0;
}
static char *uci_config_path(struct uci_context *ctx, const char *name)
{
char *filename;
UCI_ASSERT(ctx, uci_validate_package(name));
filename = uci_malloc(ctx, strlen(name) + strlen(ctx->confdir) + 2);
sprintf(filename, "%s/%s", ctx->confdir, name);
return filename;
}
void uci_file_commit(struct uci_context *ctx, struct uci_package **package, bool overwrite)
{
struct uci_package *p = *package;
FILE *f = NULL;
char *name = NULL;
char *path = NULL;
if (!p->path) {
if (overwrite)
p->path = uci_config_path(ctx, p->e.name);
else
UCI_THROW(ctx, UCI_ERR_INVAL);
}
/* open the config file for writing now, so that it is locked */
f = uci_open_stream(ctx, p->path, SEEK_SET, true, true);
/* flush unsaved changes and reload from history file */
UCI_TRAP_SAVE(ctx, done);
if (p->has_history) {
if (!overwrite) {
name = uci_strdup(ctx, p->e.name);
path = uci_strdup(ctx, p->path);
/* dump our own changes to the history file */
if (!uci_list_empty(&p->history))
UCI_INTERNAL(uci_save, ctx, p);
/*
* other processes might have modified the config
* as well. dump and reload
*/
uci_free_package(&p);
uci_cleanup(ctx);
UCI_INTERNAL(uci_import, ctx, f, name, &p, true);
p->path = path;
p->has_history = true;
*package = p;
/* freed together with the uci_package */
path = NULL;
}
/* flush history */
if (!uci_load_history(ctx, p, true))
goto done;
}
rewind(f);
if (ftruncate(fileno(f), 0) < 0)
UCI_THROW(ctx, UCI_ERR_IO);
uci_export(ctx, f, p, false);
UCI_TRAP_RESTORE(ctx);
done:
if (name)
free(name);
if (path)
free(path);
uci_close_stream(f);
if (ctx->err)
UCI_THROW(ctx, ctx->err);
}
/*
* This function returns the filename by returning the string
* after the last '/' character. By checking for a non-'\0'
* character afterwards, directories are ignored (glob marks
* those with a trailing '/'
*/
static inline char *get_filename(char *path)
{
char *p;
p = strrchr(path, '/');
p++;
if (!*p)
return NULL;
return p;
}
static char **uci_list_config_files(struct uci_context *ctx)
{
char **configs;
glob_t globbuf;
int size, i;
char *buf;
char *dir;
dir = uci_malloc(ctx, strlen(ctx->confdir) + 1 + sizeof("/*"));
sprintf(dir, "%s/*", ctx->confdir);
if (glob(dir, GLOB_MARK, NULL, &globbuf) != 0)
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
size = sizeof(char *) * (globbuf.gl_pathc + 1);
for(i = 0; i < globbuf.gl_pathc; i++) {
char *p;
p = get_filename(globbuf.gl_pathv[i]);
if (!p)
continue;
size += strlen(p) + 1;
}
configs = uci_malloc(ctx, size);
buf = (char *) &configs[globbuf.gl_pathc + 1];
for(i = 0; i < globbuf.gl_pathc; i++) {
char *p;
p = get_filename(globbuf.gl_pathv[i]);
if (!p)
continue;
if (!uci_validate_package(p))
continue;
configs[i] = buf;
strcpy(buf, p);
buf += strlen(buf) + 1;
}
free(dir);
globfree(&globbuf);
return configs;
}
static struct uci_package *uci_file_load(struct uci_context *ctx, const char *name)
{
struct uci_package *package = NULL;
char *filename;
bool confdir;
FILE *file = NULL;
switch (name[0]) {
case '.':
/* relative path outside of /etc/config */
if (name[1] != '/')
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
/* fall through */
case '/':
/* absolute path outside of /etc/config */
filename = uci_strdup(ctx, name);
name = strrchr(name, '/') + 1;
confdir = false;
break;
default:
/* config in /etc/config */
filename = uci_config_path(ctx, name);
confdir = true;
break;
}
file = uci_open_stream(ctx, filename, SEEK_SET, false, false);
ctx->err = 0;
UCI_TRAP_SAVE(ctx, done);
UCI_INTERNAL(uci_import, ctx, file, name, &package, true);
UCI_TRAP_RESTORE(ctx);
if (package) {
package->path = filename;
package->has_history = confdir;
uci_load_history(ctx, package, false);
}
done:
uci_close_stream(file);
if (ctx->err)
UCI_THROW(ctx, ctx->err);
return package;
}
static UCI_BACKEND(uci_file_backend, "file",
.load = uci_file_load,
.commit = uci_file_commit,
.list_configs = uci_list_config_files,
);

View file

@ -1,483 +0,0 @@
/*
* libuci - Library for the Unified Configuration Interface
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
/*
* This file contains the code for handling uci config history files
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
/* record a change that was done to a package */
void
uci_add_history(struct uci_context *ctx, struct uci_list *list, int cmd, const char *section, const char *option, const char *value)
{
struct uci_history *h;
int size = strlen(section) + 1;
char *ptr;
if (value)
size += strlen(value) + 1;
h = uci_alloc_element(ctx, history, option, size);
ptr = uci_dataptr(h);
h->cmd = cmd;
h->section = strcpy(ptr, section);
if (value) {
ptr += strlen(ptr) + 1;
h->value = strcpy(ptr, value);
}
uci_list_add(list, &h->e.list);
}
void
uci_free_history(struct uci_history *h)
{
if (!h)
return;
if ((h->section != NULL) &&
(h->section != uci_dataptr(h))) {
free(h->section);
free(h->value);
}
uci_free_element(&h->e);
}
int uci_set_savedir(struct uci_context *ctx, const char *dir)
{
char *sdir;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, dir != NULL);
sdir = uci_strdup(ctx, dir);
if (ctx->savedir != uci_savedir)
free(ctx->savedir);
ctx->savedir = sdir;
return 0;
}
int uci_add_history_path(struct uci_context *ctx, const char *dir)
{
struct uci_element *e;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, dir != NULL);
e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element));
uci_list_add(&ctx->history_path, &e->list);
return 0;
}
static inline int uci_parse_history_tuple(struct uci_context *ctx, char **buf, struct uci_ptr *ptr)
{
int c = UCI_CMD_CHANGE;
switch(**buf) {
case '^':
c = UCI_CMD_REORDER;
break;
case '-':
c = UCI_CMD_REMOVE;
break;
case '@':
c = UCI_CMD_RENAME;
break;
case '+':
/* UCI_CMD_ADD is used for anonymous sections or list values */
c = UCI_CMD_ADD;
break;
case '|':
c = UCI_CMD_LIST_ADD;
break;
}
if (c != UCI_CMD_CHANGE)
*buf += 1;
UCI_INTERNAL(uci_parse_ptr, ctx, ptr, *buf);
if (!ptr->section)
goto error;
if (ptr->flags & UCI_LOOKUP_EXTENDED)
goto error;
switch(c) {
case UCI_CMD_REORDER:
if (!ptr->value || ptr->option)
goto error;
break;
case UCI_CMD_RENAME:
if (!ptr->value || !uci_validate_name(ptr->value))
goto error;
break;
case UCI_CMD_LIST_ADD:
if (!ptr->option)
goto error;
}
return c;
error:
UCI_THROW(ctx, UCI_ERR_INVAL);
return 0;
}
static void uci_parse_history_line(struct uci_context *ctx, struct uci_package *p, char *buf)
{
struct uci_element *e = NULL;
struct uci_ptr ptr;
int cmd;
cmd = uci_parse_history_tuple(ctx, &buf, &ptr);
if (strcmp(ptr.package, p->e.name) != 0)
goto error;
if (ctx->flags & UCI_FLAG_SAVED_HISTORY)
uci_add_history(ctx, &p->saved_history, cmd, ptr.section, ptr.option, ptr.value);
switch(cmd) {
case UCI_CMD_REORDER:
expand_ptr(ctx, &ptr, true);
if (!ptr.s)
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
UCI_INTERNAL(uci_reorder_section, ctx, ptr.s, strtoul(ptr.value, NULL, 10));
break;
case UCI_CMD_RENAME:
UCI_INTERNAL(uci_rename, ctx, &ptr);
break;
case UCI_CMD_REMOVE:
UCI_INTERNAL(uci_delete, ctx, &ptr);
break;
case UCI_CMD_LIST_ADD:
UCI_INTERNAL(uci_add_list, ctx, &ptr);
break;
case UCI_CMD_ADD:
case UCI_CMD_CHANGE:
UCI_INTERNAL(uci_set, ctx, &ptr);
e = ptr.last;
if (!ptr.option && e && (cmd == UCI_CMD_ADD))
uci_to_section(e)->anonymous = true;
break;
}
return;
error:
UCI_THROW(ctx, UCI_ERR_PARSE);
}
/* returns the number of changes that were successfully parsed */
static int uci_parse_history(struct uci_context *ctx, FILE *stream, struct uci_package *p)
{
struct uci_parse_context *pctx;
int changes = 0;
/* make sure no memory from previous parse attempts is leaked */
uci_cleanup(ctx);
pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
ctx->pctx = pctx;
pctx->file = stream;
while (!feof(pctx->file)) {
uci_getln(ctx, 0);
if (!pctx->buf[0])
continue;
/*
* ignore parse errors in single lines, we want to preserve as much
* history as possible
*/
UCI_TRAP_SAVE(ctx, error);
uci_parse_history_line(ctx, p, pctx->buf);
UCI_TRAP_RESTORE(ctx);
changes++;
error:
continue;
}
/* no error happened, we can get rid of the parser context now */
uci_cleanup(ctx);
return changes;
}
/* returns the number of changes that were successfully parsed */
static int uci_load_history_file(struct uci_context *ctx, struct uci_package *p, char *filename, FILE **f, bool flush)
{
FILE *stream = NULL;
int changes = 0;
UCI_TRAP_SAVE(ctx, done);
stream = uci_open_stream(ctx, filename, SEEK_SET, flush, false);
if (p)
changes = uci_parse_history(ctx, stream, p);
UCI_TRAP_RESTORE(ctx);
done:
if (f)
*f = stream;
else if (stream)
uci_close_stream(stream);
return changes;
}
/* returns the number of changes that were successfully parsed */
static int uci_load_history(struct uci_context *ctx, struct uci_package *p, bool flush)
{
struct uci_element *e;
char *filename = NULL;
FILE *f = NULL;
int changes = 0;
if (!p->has_history)
return 0;
uci_foreach_element(&ctx->history_path, e) {
if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename)
UCI_THROW(ctx, UCI_ERR_MEM);
uci_load_history_file(ctx, p, filename, NULL, false);
free(filename);
}
if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
UCI_THROW(ctx, UCI_ERR_MEM);
changes = uci_load_history_file(ctx, p, filename, &f, flush);
if (flush && f && (changes > 0)) {
rewind(f);
if (ftruncate(fileno(f), 0) < 0) {
uci_close_stream(f);
UCI_THROW(ctx, UCI_ERR_IO);
}
}
if (filename)
free(filename);
uci_close_stream(f);
ctx->err = 0;
return changes;
}
static void uci_filter_history(struct uci_context *ctx, const char *name, const char *section, const char *option)
{
struct uci_parse_context *pctx;
struct uci_element *e, *tmp;
struct uci_list list;
char *filename = NULL;
struct uci_ptr ptr;
FILE *f = NULL;
uci_list_init(&list);
uci_alloc_parse_context(ctx);
pctx = ctx->pctx;
if ((asprintf(&filename, "%s/%s", ctx->savedir, name) < 0) || !filename)
UCI_THROW(ctx, UCI_ERR_MEM);
UCI_TRAP_SAVE(ctx, done);
f = uci_open_stream(ctx, filename, SEEK_SET, true, false);
pctx->file = f;
while (!feof(f)) {
struct uci_element *e;
char *buf;
uci_getln(ctx, 0);
buf = pctx->buf;
if (!buf[0])
continue;
/* NB: need to allocate the element before the call to
* uci_parse_history_tuple, otherwise the original string
* gets modified before it is saved */
e = uci_alloc_generic(ctx, UCI_TYPE_HISTORY, pctx->buf, sizeof(struct uci_element));
uci_list_add(&list, &e->list);
uci_parse_history_tuple(ctx, &buf, &ptr);
if (section) {
if (!ptr.section || (strcmp(section, ptr.section) != 0))
continue;
}
if (option) {
if (!ptr.option || (strcmp(option, ptr.option) != 0))
continue;
}
/* match, drop this element again */
uci_free_element(e);
}
/* rebuild the history file */
rewind(f);
if (ftruncate(fileno(f), 0) < 0)
UCI_THROW(ctx, UCI_ERR_IO);
uci_foreach_element_safe(&list, tmp, e) {
fprintf(f, "%s\n", e->name);
uci_free_element(e);
}
UCI_TRAP_RESTORE(ctx);
done:
if (filename)
free(filename);
uci_close_stream(pctx->file);
uci_foreach_element_safe(&list, tmp, e) {
uci_free_element(e);
}
uci_cleanup(ctx);
}
int uci_revert(struct uci_context *ctx, struct uci_ptr *ptr)
{
char *package = NULL;
char *section = NULL;
char *option = NULL;
UCI_HANDLE_ERR(ctx);
expand_ptr(ctx, ptr, false);
UCI_ASSERT(ctx, ptr->p->has_history);
/*
* - flush unwritten changes
* - save the package name
* - unload the package
* - filter the history
* - reload the package
*/
UCI_TRAP_SAVE(ctx, error);
UCI_INTERNAL(uci_save, ctx, ptr->p);
/* NB: need to clone package, section and option names,
* as they may get freed on uci_free_package() */
package = uci_strdup(ctx, ptr->p->e.name);
if (ptr->section)
section = uci_strdup(ctx, ptr->section);
if (ptr->option)
option = uci_strdup(ctx, ptr->option);
uci_free_package(&ptr->p);
uci_filter_history(ctx, package, section, option);
UCI_INTERNAL(uci_load, ctx, package, &ptr->p);
UCI_TRAP_RESTORE(ctx);
ctx->err = 0;
error:
if (package)
free(package);
if (section)
free(section);
if (option)
free(option);
if (ctx->err)
UCI_THROW(ctx, ctx->err);
return 0;
}
int uci_save(struct uci_context *ctx, struct uci_package *p)
{
FILE *f = NULL;
char *filename = NULL;
struct uci_element *e, *tmp;
struct stat statbuf;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, p != NULL);
/*
* if the config file was outside of the /etc/config path,
* don't save the history to a file, update the real file
* directly.
* does not modify the uci_package pointer
*/
if (!p->has_history)
return uci_commit(ctx, &p, false);
if (uci_list_empty(&p->history))
return 0;
if (stat(ctx->savedir, &statbuf) < 0)
mkdir(ctx->savedir, UCI_DIRMODE);
else if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
UCI_THROW(ctx, UCI_ERR_IO);
if ((asprintf(&filename, "%s/%s", ctx->savedir, p->e.name) < 0) || !filename)
UCI_THROW(ctx, UCI_ERR_MEM);
uci_foreach_element(&ctx->hooks, tmp) {
struct uci_hook *hook = uci_to_hook(tmp);
if (!hook->ops->set)
continue;
uci_foreach_element(&p->history, e) {
hook->ops->set(hook->ops, p, uci_to_history(e));
}
}
ctx->err = 0;
UCI_TRAP_SAVE(ctx, done);
f = uci_open_stream(ctx, filename, SEEK_END, true, true);
UCI_TRAP_RESTORE(ctx);
uci_foreach_element_safe(&p->history, tmp, e) {
struct uci_history *h = uci_to_history(e);
char *prefix = "";
switch(h->cmd) {
case UCI_CMD_REMOVE:
prefix = "-";
break;
case UCI_CMD_RENAME:
prefix = "@";
break;
case UCI_CMD_ADD:
prefix = "+";
break;
case UCI_CMD_REORDER:
prefix = "^";
break;
case UCI_CMD_LIST_ADD:
prefix = "|";
break;
default:
break;
}
fprintf(f, "%s%s.%s", prefix, p->e.name, h->section);
if (e->name)
fprintf(f, ".%s", e->name);
if (h->cmd == UCI_CMD_REMOVE)
fprintf(f, "\n");
else
fprintf(f, "=%s\n", h->value);
uci_free_history(h);
}
done:
uci_close_stream(f);
if (filename)
free(filename);
if (ctx->err)
UCI_THROW(ctx, ctx->err);
return 0;
}

View file

@ -1,391 +0,0 @@
/*
* libuci - Library for the Unified Configuration Interface
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
/*
* This file contains some common code for the uci library
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <glob.h>
#include "uci.h"
static const char *uci_confdir = UCI_CONFDIR;
static const char *uci_savedir = UCI_SAVEDIR;
static const char *uci_errstr[] = {
[UCI_OK] = "Success",
[UCI_ERR_MEM] = "Out of memory",
[UCI_ERR_INVAL] = "Invalid argument",
[UCI_ERR_NOTFOUND] = "Entry not found",
[UCI_ERR_IO] = "I/O error",
[UCI_ERR_PARSE] = "Parse error",
[UCI_ERR_DUPLICATE] = "Duplicate entry",
[UCI_ERR_UNKNOWN] = "Unknown error",
};
static void uci_cleanup(struct uci_context *ctx);
static void uci_unload_plugin(struct uci_context *ctx, struct uci_plugin *p);
#include "uci_internal.h"
#include "util.c"
#include "list.c"
#include "history.c"
#include "file.c"
/* exported functions */
struct uci_context *uci_alloc_context(void)
{
struct uci_context *ctx;
ctx = (struct uci_context *) malloc(sizeof(struct uci_context));
if (!ctx)
return NULL;
memset(ctx, 0, sizeof(struct uci_context));
uci_list_init(&ctx->root);
uci_list_init(&ctx->history_path);
uci_list_init(&ctx->backends);
uci_list_init(&ctx->hooks);
uci_list_init(&ctx->plugins);
ctx->flags = UCI_FLAG_STRICT | UCI_FLAG_SAVED_HISTORY;
ctx->confdir = (char *) uci_confdir;
ctx->savedir = (char *) uci_savedir;
uci_list_add(&ctx->backends, &uci_file_backend.e.list);
ctx->backend = &uci_file_backend;
return ctx;
}
void uci_free_context(struct uci_context *ctx)
{
struct uci_element *e, *tmp;
if (ctx->confdir != uci_confdir)
free(ctx->confdir);
if (ctx->savedir != uci_savedir)
free(ctx->savedir);
uci_cleanup(ctx);
UCI_TRAP_SAVE(ctx, ignore);
uci_foreach_element_safe(&ctx->root, tmp, e) {
struct uci_package *p = uci_to_package(e);
uci_free_package(&p);
}
uci_foreach_element_safe(&ctx->history_path, tmp, e) {
uci_free_element(e);
}
UCI_TRAP_RESTORE(ctx);
uci_foreach_element_safe(&ctx->root, tmp, e) {
uci_unload_plugin(ctx, uci_to_plugin(e));
}
free(ctx);
ignore:
return;
}
int uci_set_confdir(struct uci_context *ctx, const char *dir)
{
char *cdir;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, dir != NULL);
cdir = uci_strdup(ctx, dir);
if (ctx->confdir != uci_confdir)
free(ctx->confdir);
ctx->confdir = cdir;
return 0;
}
static void uci_cleanup(struct uci_context *ctx)
{
struct uci_parse_context *pctx;
if (ctx->buf) {
free(ctx->buf);
ctx->buf = NULL;
ctx->bufsz = 0;
}
pctx = ctx->pctx;
if (!pctx)
return;
ctx->pctx = NULL;
if (pctx->package)
uci_free_package(&pctx->package);
if (pctx->buf)
free(pctx->buf);
free(pctx);
}
void
uci_perror(struct uci_context *ctx, const char *str)
{
uci_get_errorstr(ctx, NULL, str);
}
void
uci_get_errorstr(struct uci_context *ctx, char **dest, const char *prefix)
{
static char error_info[128];
int err;
const char *format =
"%s%s" /* prefix */
"%s%s" /* function */
"%s" /* error */
"%s"; /* details */
error_info[0] = 0;
if (!ctx)
err = UCI_ERR_INVAL;
else
err = ctx->err;
if ((err < 0) || (err >= UCI_ERR_LAST))
err = UCI_ERR_UNKNOWN;
switch (err) {
case UCI_ERR_PARSE:
if (ctx->pctx) {
snprintf(error_info, sizeof(error_info) - 1, " (%s) at line %d, byte %d", (ctx->pctx->reason ? ctx->pctx->reason : "unknown"), ctx->pctx->line, ctx->pctx->byte);
break;
}
break;
default:
break;
}
if (dest) {
err = asprintf(dest, format,
(prefix ? prefix : ""), (prefix ? ": " : ""),
(ctx->func ? ctx->func : ""), (ctx->func ? ": " : ""),
uci_errstr[err],
error_info);
if (err < 0)
*dest = NULL;
} else {
strcat(error_info, "\n");
fprintf(stderr, format,
(prefix ? prefix : ""), (prefix ? ": " : ""),
(ctx->func ? ctx->func : ""), (ctx->func ? ": " : ""),
uci_errstr[err],
error_info);
}
}
int uci_list_configs(struct uci_context *ctx, char ***list)
{
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, list != NULL);
UCI_ASSERT(ctx, ctx->backend && ctx->backend->list_configs);
*list = ctx->backend->list_configs(ctx);
return 0;
}
int uci_commit(struct uci_context *ctx, struct uci_package **package, bool overwrite)
{
struct uci_package *p;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, package != NULL);
p = *package;
UCI_ASSERT(ctx, p != NULL);
UCI_ASSERT(ctx, p->backend && p->backend->commit);
p->backend->commit(ctx, package, overwrite);
return 0;
}
int uci_load(struct uci_context *ctx, const char *name, struct uci_package **package)
{
struct uci_package *p;
struct uci_element *e;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, ctx->backend && ctx->backend->load);
p = ctx->backend->load(ctx, name);
uci_foreach_element(&ctx->hooks, e) {
struct uci_hook *h = uci_to_hook(e);
if (h->ops->load)
h->ops->load(h->ops, p);
}
if (package)
*package = p;
return 0;
}
#ifdef UCI_PLUGIN_SUPPORT
__plugin int uci_add_backend(struct uci_context *ctx, struct uci_backend *b)
{
struct uci_element *e;
UCI_HANDLE_ERR(ctx);
e = uci_lookup_list(&ctx->backends, b->e.name);
if (e)
UCI_THROW(ctx, UCI_ERR_DUPLICATE);
e = uci_malloc(ctx, sizeof(struct uci_backend));
memcpy(e, b, sizeof(struct uci_backend));
uci_list_add(&ctx->backends, &e->list);
return 0;
}
__plugin int uci_del_backend(struct uci_context *ctx, struct uci_backend *b)
{
struct uci_element *e, *tmp;
UCI_HANDLE_ERR(ctx);
e = uci_lookup_list(&ctx->backends, b->e.name);
if (!e || uci_to_backend(e)->ptr != b->ptr)
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
b = uci_to_backend(e);
if (ctx->backend && ctx->backend->ptr == b->ptr)
ctx->backend = &uci_file_backend;
uci_foreach_element_safe(&ctx->root, tmp, e) {
struct uci_package *p = uci_to_package(e);
if (!p->backend)
continue;
if (p->backend->ptr == b->ptr)
UCI_INTERNAL(uci_unload, ctx, p);
}
uci_list_del(&b->e.list);
free(b);
return 0;
}
#endif
int uci_set_backend(struct uci_context *ctx, const char *name)
{
struct uci_element *e;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, name != NULL);
e = uci_lookup_list(&ctx->backends, name);
if (!e)
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
ctx->backend = uci_to_backend(e);
return 0;
}
int uci_add_hook(struct uci_context *ctx, const struct uci_hook_ops *ops)
{
struct uci_element *e;
struct uci_hook *h;
UCI_HANDLE_ERR(ctx);
/* check for duplicate elements */
uci_foreach_element(&ctx->hooks, e) {
h = uci_to_hook(e);
if (h->ops == ops)
return UCI_ERR_INVAL;
}
h = uci_alloc_element(ctx, hook, "", 0);
h->ops = ops;
uci_list_init(&h->e.list);
uci_list_add(&ctx->hooks, &h->e.list);
return 0;
}
int uci_remove_hook(struct uci_context *ctx, const struct uci_hook_ops *ops)
{
struct uci_element *e;
uci_foreach_element(&ctx->hooks, e) {
struct uci_hook *h = uci_to_hook(e);
if (h->ops == ops) {
uci_list_del(&e->list);
return 0;
}
}
return UCI_ERR_NOTFOUND;
}
int uci_load_plugin(struct uci_context *ctx, const char *filename)
{
struct uci_plugin *p;
const struct uci_plugin_ops *ops;
void *dlh;
UCI_HANDLE_ERR(ctx);
dlh = dlopen(filename, RTLD_GLOBAL|RTLD_NOW);
if (!dlh)
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
ops = dlsym(dlh, "uci_plugin");
if (!ops || !ops->attach || (ops->attach(ctx) != 0)) {
if (!ops)
fprintf(stderr, "No ops\n");
else if (!ops->attach)
fprintf(stderr, "No attach\n");
else
fprintf(stderr, "Other weirdness\n");
dlclose(dlh);
UCI_THROW(ctx, UCI_ERR_INVAL);
}
p = uci_alloc_element(ctx, plugin, filename, 0);
p->dlh = dlh;
p->ops = ops;
uci_list_add(&ctx->plugins, &p->e.list);
return 0;
}
static void uci_unload_plugin(struct uci_context *ctx, struct uci_plugin *p)
{
if (p->ops->detach)
p->ops->detach(ctx);
dlclose(p->dlh);
uci_free_element(&p->e);
}
int uci_load_plugins(struct uci_context *ctx, const char *pattern)
{
glob_t gl;
int i;
if (!pattern)
pattern = UCI_PREFIX "/lib/uci_*.so";
memset(&gl, 0, sizeof(gl));
glob(pattern, 0, NULL, &gl);
for (i = 0; i < gl.gl_pathc; i++)
uci_load_plugin(ctx, gl.gl_pathv[i]);
return 0;
}

View file

@ -1,719 +0,0 @@
/*
* libuci - Library for the Unified Configuration Interface
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
/* initialize a list head/item */
static inline void uci_list_init(struct uci_list *ptr)
{
ptr->prev = ptr;
ptr->next = ptr;
}
/* inserts a new list entry after a given entry */
static inline void uci_list_insert(struct uci_list *list, struct uci_list *ptr)
{
list->next->prev = ptr;
ptr->prev = list;
ptr->next = list->next;
list->next = ptr;
}
/* inserts a new list entry at the tail of the list */
static inline void uci_list_add(struct uci_list *head, struct uci_list *ptr)
{
/* NB: head->prev points at the tail */
uci_list_insert(head->prev, ptr);
}
static inline void uci_list_del(struct uci_list *ptr)
{
struct uci_list *next, *prev;
next = ptr->next;
prev = ptr->prev;
prev->next = next;
next->prev = prev;
uci_list_init(ptr);
}
static inline void uci_list_set_pos(struct uci_list *head, struct uci_list *ptr, int pos)
{
struct uci_list *new_head = head;
struct uci_element *p = NULL;
uci_list_del(ptr);
uci_foreach_element(head, p) {
new_head = &p->list;
if (pos-- <= 0)
break;
}
uci_list_add(new_head, ptr);
}
static inline void uci_list_fixup(struct uci_list *ptr)
{
ptr->prev->next = ptr;
ptr->next->prev = ptr;
}
/*
* uci_alloc_generic allocates a new uci_element with payload
* payload is appended to the struct to save memory and reduce fragmentation
*/
static struct uci_element *
uci_alloc_generic(struct uci_context *ctx, int type, const char *name, int size)
{
struct uci_element *e;
int datalen = size;
void *ptr;
ptr = uci_malloc(ctx, datalen);
e = (struct uci_element *) ptr;
e->type = type;
if (name) {
UCI_TRAP_SAVE(ctx, error);
e->name = uci_strdup(ctx, name);
UCI_TRAP_RESTORE(ctx);
}
uci_list_init(&e->list);
goto done;
error:
free(ptr);
UCI_THROW(ctx, ctx->err);
done:
return e;
}
static void
uci_free_element(struct uci_element *e)
{
if (e->name)
free(e->name);
if (!uci_list_empty(&e->list))
uci_list_del(&e->list);
free(e);
}
static struct uci_option *
uci_alloc_option(struct uci_section *s, const char *name, const char *value)
{
struct uci_package *p = s->package;
struct uci_context *ctx = p->ctx;
struct uci_option *o;
o = uci_alloc_element(ctx, option, name, strlen(value) + 1);
o->type = UCI_TYPE_STRING;
o->v.string = uci_dataptr(o);
o->section = s;
strcpy(o->v.string, value);
uci_list_add(&s->options, &o->e.list);
return o;
}
static inline void
uci_free_option(struct uci_option *o)
{
struct uci_element *e, *tmp;
switch(o->type) {
case UCI_TYPE_STRING:
if ((o->v.string != uci_dataptr(o)) &&
(o->v.string != NULL))
free(o->v.string);
break;
case UCI_TYPE_LIST:
uci_foreach_element_safe(&o->v.list, tmp, e) {
uci_free_element(e);
}
break;
default:
break;
}
uci_free_element(&o->e);
}
static struct uci_option *
uci_alloc_list(struct uci_section *s, const char *name)
{
struct uci_package *p = s->package;
struct uci_context *ctx = p->ctx;
struct uci_option *o;
o = uci_alloc_element(ctx, option, name, 0);
o->type = UCI_TYPE_LIST;
o->section = s;
uci_list_init(&o->v.list);
uci_list_add(&s->options, &o->e.list);
return o;
}
/* fix up an unnamed section, e.g. after adding options to it */
static void uci_fixup_section(struct uci_context *ctx, struct uci_section *s)
{
unsigned int hash = ~0;
struct uci_element *e;
char buf[16];
if (!s || s->e.name)
return;
/*
* Generate a name for unnamed sections. This is used as reference
* when locating or updating the section from apps/scripts.
* To make multiple concurrent versions somewhat safe for updating,
* the name is generated from a hash of its type and name/value
* pairs of its option, and it is prefixed by a counter value.
* If the order of the unnamed sections changes for some reason,
* updates to them will be rejected.
*/
hash = djbhash(hash, s->type);
uci_foreach_element(&s->options, e) {
struct uci_option *o;
hash = djbhash(hash, e->name);
o = uci_to_option(e);
switch(o->type) {
case UCI_TYPE_STRING:
hash = djbhash(hash, o->v.string);
break;
default:
break;
}
}
sprintf(buf, "cfg%02x%04x", ++s->package->n_section, hash % (1 << 16));
s->e.name = uci_strdup(ctx, buf);
}
static struct uci_section *
uci_alloc_section(struct uci_package *p, const char *type, const char *name)
{
struct uci_context *ctx = p->ctx;
struct uci_section *s;
if (name && !name[0])
name = NULL;
s = uci_alloc_element(ctx, section, name, strlen(type) + 1);
uci_list_init(&s->options);
s->type = uci_dataptr(s);
s->package = p;
strcpy(s->type, type);
if (name == NULL)
s->anonymous = true;
p->n_section++;
uci_list_add(&p->sections, &s->e.list);
return s;
}
static void
uci_free_section(struct uci_section *s)
{
struct uci_element *o, *tmp;
uci_foreach_element_safe(&s->options, tmp, o) {
uci_free_option(uci_to_option(o));
}
if ((s->type != uci_dataptr(s)) &&
(s->type != NULL))
free(s->type);
uci_free_element(&s->e);
}
__plugin struct uci_package *
uci_alloc_package(struct uci_context *ctx, const char *name)
{
struct uci_package *p;
p = uci_alloc_element(ctx, package, name, 0);
p->ctx = ctx;
uci_list_init(&p->sections);
uci_list_init(&p->history);
uci_list_init(&p->saved_history);
return p;
}
static void
uci_free_package(struct uci_package **package)
{
struct uci_element *e, *tmp;
struct uci_package *p = *package;
if(!p)
return;
if (p->path)
free(p->path);
uci_foreach_element_safe(&p->sections, tmp, e) {
uci_free_section(uci_to_section(e));
}
uci_foreach_element_safe(&p->history, tmp, e) {
uci_free_history(uci_to_history(e));
}
uci_foreach_element_safe(&p->saved_history, tmp, e) {
uci_free_history(uci_to_history(e));
}
uci_free_element(&p->e);
*package = NULL;
}
static void
uci_free_any(struct uci_element **e)
{
switch((*e)->type) {
case UCI_TYPE_SECTION:
uci_free_section(uci_to_section(*e));
break;
case UCI_TYPE_OPTION:
uci_free_option(uci_to_option(*e));
break;
default:
break;
}
*e = NULL;
}
static inline struct uci_element *
uci_lookup_list(struct uci_list *list, const char *name)
{
struct uci_element *e;
uci_foreach_element(list, e) {
if (!strcmp(e->name, name))
return e;
}
return NULL;
}
static struct uci_element *
uci_lookup_ext_section(struct uci_context *ctx, struct uci_ptr *ptr)
{
char *idxstr, *t, *section, *name;
struct uci_element *e = NULL;
struct uci_section *s;
int idx, c;
section = uci_strdup(ctx, ptr->section);
name = idxstr = section + 1;
if (section[0] != '@')
goto error;
/* parse the section index part */
idxstr = strchr(idxstr, '[');
if (!idxstr)
goto error;
*idxstr = 0;
idxstr++;
t = strchr(idxstr, ']');
if (!t)
goto error;
if (t[1] != 0)
goto error;
*t = 0;
t = NULL;
idx = strtol(idxstr, &t, 10);
if (t && *t)
goto error;
if (!*name)
name = NULL;
else if (!uci_validate_type(name))
goto error;
/* if the given index is negative, it specifies the section number from
* the end of the list */
if (idx < 0) {
c = 0;
uci_foreach_element(&ptr->p->sections, e) {
s = uci_to_section(e);
if (name && (strcmp(s->type, name) != 0))
continue;
c++;
}
idx += c;
}
c = 0;
uci_foreach_element(&ptr->p->sections, e) {
s = uci_to_section(e);
if (name && (strcmp(s->type, name) != 0))
continue;
if (idx == c)
goto done;
c++;
}
e = NULL;
goto done;
error:
e = NULL;
memset(ptr, 0, sizeof(struct uci_ptr));
UCI_THROW(ctx, UCI_ERR_INVAL);
done:
free(section);
if (e)
ptr->section = e->name;
return e;
}
int
uci_lookup_ptr(struct uci_context *ctx, struct uci_ptr *ptr, char *str, bool extended)
{
struct uci_element *e;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, ptr != NULL);
if (str)
UCI_INTERNAL(uci_parse_ptr, ctx, ptr, str);
ptr->flags |= UCI_LOOKUP_DONE;
/* look up the package first */
e = uci_lookup_list(&ctx->root, ptr->package);
if (!e) {
UCI_INTERNAL(uci_load, ctx, ptr->package, &ptr->p);
if (!ptr->p)
goto notfound;
ptr->last = &ptr->p->e;
} else {
ptr->p = uci_to_package(e);
ptr->last = e;
}
if (!ptr->section)
goto complete;
/* if the section name validates as a regular name, pass through
* to the regular uci_lookup function call */
if (ptr->flags & UCI_LOOKUP_EXTENDED)
e = uci_lookup_ext_section(ctx, ptr);
else
e = uci_lookup_list(&ptr->p->sections, ptr->section);
if (!e)
goto abort;
ptr->last = e;
ptr->s = uci_to_section(e);
if (ptr->option) {
e = uci_lookup_list(&ptr->s->options, ptr->option);
if (!e)
goto abort;
ptr->o = uci_to_option(e);
ptr->last = e;
}
complete:
ptr->flags |= UCI_LOOKUP_COMPLETE;
abort:
return 0;
notfound:
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
return 0;
}
int
uci_fill_ptr(struct uci_context *ctx, struct uci_ptr *ptr, struct uci_element *e, bool complete)
{
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, ptr != NULL);
UCI_ASSERT(ctx, e != NULL);
memset(ptr, 0, sizeof(struct uci_ptr));
switch(e->type) {
case UCI_TYPE_OPTION:
ptr->o = uci_to_option(e);
goto fill_option;
case UCI_TYPE_SECTION:
ptr->s = uci_to_section(e);
goto fill_section;
case UCI_TYPE_PACKAGE:
ptr->p = uci_to_package(e);
goto fill_package;
default:
UCI_THROW(ctx, UCI_ERR_INVAL);
}
fill_option:
ptr->option = ptr->o->e.name;
ptr->s = ptr->o->section;
fill_section:
ptr->section = ptr->s->e.name;
ptr->p = ptr->s->package;
fill_package:
ptr->package = ptr->p->e.name;
ptr->flags |= UCI_LOOKUP_DONE;
if (complete)
ptr->flags |= UCI_LOOKUP_COMPLETE;
return 0;
}
static struct uci_element *
expand_ptr(struct uci_context *ctx, struct uci_ptr *ptr, bool complete)
{
UCI_ASSERT(ctx, ptr != NULL);
if (!(ptr->flags & UCI_LOOKUP_DONE))
UCI_INTERNAL(uci_lookup_ptr, ctx, ptr, NULL, 1);
if (complete && !(ptr->flags & UCI_LOOKUP_COMPLETE))
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
UCI_ASSERT(ctx, ptr->p != NULL);
/* fill in missing string info */
if (ptr->p && !ptr->package)
ptr->package = ptr->p->e.name;
if (ptr->s && !ptr->section)
ptr->section = ptr->s->e.name;
if (ptr->o && !ptr->option)
ptr->option = ptr->o->e.name;
if (ptr->o)
return &ptr->o->e;
if (ptr->s)
return &ptr->s->e;
if (ptr->p)
return &ptr->p->e;
else
return NULL;
}
static void uci_add_element_list(struct uci_context *ctx, struct uci_ptr *ptr, bool internal)
{
struct uci_element *e;
struct uci_package *p;
p = ptr->p;
if (!internal && p->has_history)
uci_add_history(ctx, &p->history, UCI_CMD_LIST_ADD, ptr->section, ptr->option, ptr->value);
e = uci_alloc_generic(ctx, UCI_TYPE_ITEM, ptr->value, sizeof(struct uci_option));
uci_list_add(&ptr->o->v.list, &e->list);
}
int uci_rename(struct uci_context *ctx, struct uci_ptr *ptr)
{
/* NB: UCI_INTERNAL use means without history tracking */
bool internal = ctx->internal;
struct uci_element *e;
struct uci_package *p;
char *n;
UCI_HANDLE_ERR(ctx);
e = expand_ptr(ctx, ptr, true);
p = ptr->p;
UCI_ASSERT(ctx, ptr->s);
UCI_ASSERT(ctx, ptr->value);
if (!internal && p->has_history)
uci_add_history(ctx, &p->history, UCI_CMD_RENAME, ptr->section, ptr->option, ptr->value);
n = uci_strdup(ctx, ptr->value);
if (e->name)
free(e->name);
e->name = n;
if (e->type == UCI_TYPE_SECTION)
uci_to_section(e)->anonymous = false;
return 0;
}
int uci_reorder_section(struct uci_context *ctx, struct uci_section *s, int pos)
{
struct uci_package *p = s->package;
char order[32];
UCI_HANDLE_ERR(ctx);
uci_list_set_pos(&s->package->sections, &s->e.list, pos);
if (!ctx->internal && p->has_history) {
sprintf(order, "%d", pos);
uci_add_history(ctx, &p->history, UCI_CMD_REORDER, s->e.name, NULL, order);
}
return 0;
}
int uci_add_section(struct uci_context *ctx, struct uci_package *p, const char *type, struct uci_section **res)
{
bool internal = ctx->internal;
struct uci_section *s;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, p != NULL);
s = uci_alloc_section(p, type, NULL);
uci_fixup_section(ctx, s);
*res = s;
if (!internal && p->has_history)
uci_add_history(ctx, &p->history, UCI_CMD_ADD, s->e.name, NULL, type);
return 0;
}
int uci_delete(struct uci_context *ctx, struct uci_ptr *ptr)
{
/* NB: pass on internal flag to uci_del_element */
bool internal = ctx->internal;
struct uci_package *p;
struct uci_element *e;
UCI_HANDLE_ERR(ctx);
e = expand_ptr(ctx, ptr, true);
p = ptr->p;
UCI_ASSERT(ctx, ptr->s);
if (!internal && p->has_history)
uci_add_history(ctx, &p->history, UCI_CMD_REMOVE, ptr->section, ptr->option, NULL);
uci_free_any(&e);
if (ptr->option)
ptr->o = NULL;
else if (ptr->section)
ptr->s = NULL;
return 0;
}
int uci_add_list(struct uci_context *ctx, struct uci_ptr *ptr)
{
/* NB: UCI_INTERNAL use means without history tracking */
bool internal = ctx->internal;
struct uci_option *prev = NULL;
const char *value2 = NULL;
UCI_HANDLE_ERR(ctx);
expand_ptr(ctx, ptr, false);
UCI_ASSERT(ctx, ptr->s);
UCI_ASSERT(ctx, ptr->value);
if (ptr->o) {
switch (ptr->o->type) {
case UCI_TYPE_STRING:
/* we already have a string value, convert that to a list */
prev = ptr->o;
value2 = ptr->value;
ptr->value = ptr->o->v.string;
break;
case UCI_TYPE_LIST:
uci_add_element_list(ctx, ptr, internal);
return 0;
default:
UCI_THROW(ctx, UCI_ERR_INVAL);
break;
}
}
ptr->o = uci_alloc_list(ptr->s, ptr->option);
if (prev) {
uci_add_element_list(ctx, ptr, true);
uci_free_option(prev);
ptr->value = value2;
}
uci_add_element_list(ctx, ptr, internal);
return 0;
}
int uci_set(struct uci_context *ctx, struct uci_ptr *ptr)
{
/* NB: UCI_INTERNAL use means without history tracking */
bool internal = ctx->internal;
UCI_HANDLE_ERR(ctx);
expand_ptr(ctx, ptr, false);
UCI_ASSERT(ctx, ptr->value);
UCI_ASSERT(ctx, ptr->s || (!ptr->option && ptr->section));
if (!ptr->option && ptr->value[0]) {
UCI_ASSERT(ctx, uci_validate_type(ptr->value));
}
if (!ptr->o && ptr->s && ptr->option) {
struct uci_element *e;
e = uci_lookup_list(&ptr->s->options, ptr->option);
if (e)
ptr->o = uci_to_option(e);
}
if (!ptr->value[0]) {
/* if setting a nonexistant option/section to a nonexistant value,
* exit without errors */
if (!(ptr->flags & UCI_LOOKUP_COMPLETE))
return 0;
return uci_delete(ctx, ptr);
} else if (!ptr->o && ptr->option) { /* new option */
ptr->o = uci_alloc_option(ptr->s, ptr->option, ptr->value);
ptr->last = &ptr->o->e;
} else if (!ptr->s && ptr->section) { /* new section */
ptr->s = uci_alloc_section(ptr->p, ptr->value, ptr->section);
ptr->last = &ptr->s->e;
} else if (ptr->o && ptr->option) { /* update option */
if ((ptr->o->type == UCI_TYPE_STRING) &&
!strcmp(ptr->o->v.string, ptr->value))
return 0;
uci_free_option(ptr->o);
ptr->o = uci_alloc_option(ptr->s, ptr->option, ptr->value);
ptr->last = &ptr->o->e;
} else if (ptr->s && ptr->section) { /* update section */
char *s = uci_strdup(ctx, ptr->value);
if (ptr->s->type == uci_dataptr(ptr->s)) {
ptr->last = NULL;
ptr->last = uci_realloc(ctx, ptr->s, sizeof(struct uci_section));
ptr->s = uci_to_section(ptr->last);
uci_list_fixup(&ptr->s->e.list);
} else {
free(ptr->s->type);
}
ptr->s->type = s;
} else {
UCI_THROW(ctx, UCI_ERR_INVAL);
}
if (!internal && ptr->p->has_history)
uci_add_history(ctx, &ptr->p->history, UCI_CMD_CHANGE, ptr->section, ptr->option, ptr->value);
return 0;
}
int uci_unload(struct uci_context *ctx, struct uci_package *p)
{
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, p != NULL);
uci_free_package(&p);
return 0;
}

View file

@ -1,601 +0,0 @@
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
#include <stddef.h>
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#ifndef container_of
#define container_of(ptr, type, member) ( \
(type *)( (char *)ptr - offsetof(type,member) ))
#endif
/*
* Simple doubly linked list implementation.
*
* Some of the internal functions ("__xxx") are useful when
* manipulating whole lists rather than single entries, as
* sometimes we already know the next/prev entries and we can
* generate better code by using them directly rather than
* using the generic single-entry routines.
*/
struct list_head {
struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = NULL;
entry->prev = NULL;
}
/**
* list_replace - replace old entry by new one
* @old : the element to be replaced
* @new : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
/**
* list_del_init - deletes entry from list and reinitialize it.
* @entry: the element to delete from the list.
*/
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
/**
* list_move - delete from one list and add as another's head
* @list: the entry to move
* @head: the head that will precede our entry
*/
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del(list->prev, list->next);
list_add(list, head);
}
/**
* list_move_tail - delete from one list and add as another's tail
* @list: the entry to move
* @head: the head that will follow our entry
*/
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}
/**
* list_is_last - tests whether @list is the last entry in list @head
* @list: the entry to test
* @head: the head of the list
*/
static inline int list_is_last(const struct list_head *list,
const struct list_head *head)
{
return list->next == head;
}
/**
* list_empty - tests whether a list is empty
* @head: the list to test.
*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
/**
* list_empty_careful - tests whether a list is empty and not being modified
* @head: the list to test
*
* Description:
* tests whether a list is empty _and_ checks that no other CPU might be
* in the process of modifying either member (next or prev)
*
* NOTE: using list_empty_careful() without synchronization
* can only be safe if the only activity that can happen
* to the list entry is list_del_init(). Eg. it cannot be used
* if another CPU could re-list_add() it.
*/
static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
static inline void __list_splice(struct list_head *list,
struct list_head *head)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
struct list_head *at = head->next;
first->prev = head;
head->next = first;
last->next = at;
at->prev = last;
}
/**
* list_splice - join two lists
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static inline void list_splice(struct list_head *list, struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head);
}
/**
* list_splice_init - join two lists and reinitialise the emptied list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head);
INIT_LIST_HEAD(list);
}
}
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*
* Note, that list is expected to be not empty.
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); \
pos = pos->next)
/**
* __list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*
* This variant differs from list_for_each() in that it's the
* simplest possible list iteration code, no prefetching is done.
* Use this for code that knows the list to be very short (empty
* or 1 entry) most of the time.
*/
#define __list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* list_for_each_prev - iterate over a list backwards
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); \
pos = pos->prev)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_prev_safe(pos, n, head) \
for (pos = (head)->prev, n = pos->prev; \
pos != (head); \
pos = n, n = pos->prev)
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_reverse - iterate backwards over list of given type.
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
* list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
* @pos: the type * to use as a start point
* @head: the head of the list
* @member: the name of the list_struct within the struct.
*
* Prepares a pos entry for use as a start point in list_for_each_entry_continue().
*/
#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, typeof(*pos), member))
/**
* list_for_each_entry_continue - continue iteration over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Continue to iterate over list of given type, continuing after
* the current position.
*/
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_continue_reverse - iterate backwards from the given point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Start to iterate over list of given type backwards, continuing after
* the current position.
*/
#define list_for_each_entry_continue_reverse(pos, head, member) \
for (pos = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
* list_for_each_entry_from - iterate over list of given type from the current point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type, continuing from current position.
*/
#define list_for_each_entry_from(pos, head, member) \
for (; &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_continue
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type, continuing after current point,
* safe against removal of list entry.
*/
#define list_for_each_entry_safe_continue(pos, n, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_from
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate over list of given type from current point, safe against
* removal of list entry.
*/
#define list_for_each_entry_safe_from(pos, n, head, member) \
for (n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
* list_for_each_entry_safe_reverse
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*
* Iterate backwards over list of given type, safe against removal
* of list entry.
*/
#define list_for_each_entry_safe_reverse(pos, n, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member), \
n = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.prev, typeof(*n), member))
/*
* Double linked lists with a single pointer list head.
* Mostly useful for hash tables where the two pointer list head is
* too wasteful.
* You lose the ability to access the tail in O(1).
*/
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
h->next = NULL;
h->pprev = NULL;
}
static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}
static inline int hlist_empty(const struct hlist_head *h)
{
return !h->first;
}
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next;
if (next)
next->pprev = pprev;
}
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = NULL;
n->pprev = NULL;
}
static inline void hlist_del_init(struct hlist_node *n)
{
if (!hlist_unhashed(n)) {
__hlist_del(n);
INIT_HLIST_NODE(n);
}
}
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first;
if (first)
first->pprev = &n->next;
h->first = n;
n->pprev = &h->first;
}
/* next must be != NULL */
static inline void hlist_add_before(struct hlist_node *n,
struct hlist_node *next)
{
n->pprev = next->pprev;
n->next = next;
next->pprev = &n->next;
*(n->pprev) = n;
}
static inline void hlist_add_after(struct hlist_node *n,
struct hlist_node *next)
{
next->next = n->next;
n->next = next;
next->pprev = &n->next;
if(next->next)
next->next->pprev = &next->next;
}
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos; pos = pos->next)
#define hlist_for_each_safe(pos, n, head) \
for (pos = (head)->first; pos; pos = n)
/**
* hlist_for_each_entry - iterate over list of given type
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry(tpos, pos, head, member) \
for (pos = (head)->first; pos && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
/**
* hlist_for_each_entry_continue - iterate over a hlist continuing after current point
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_continue(tpos, pos, member) \
for (pos = (pos)->next; pos && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
/**
* hlist_for_each_entry_from - iterate over a hlist continuing from current point
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_from(tpos, pos, member) \
for (; pos && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = pos->next)
/**
* hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @n: another &struct hlist_node to use as temporary storage
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
for (pos = (head)->first; \
pos && ({ n = pos->next; 1; }) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = n)
#endif

View file

@ -1,44 +0,0 @@
include ../Makefile.inc
LUA_VERSION=5.1
PREFIX_SEARCH=/usr /usr/local /opt/local
LUA_PLUGINDIR=$(firstword \
$(foreach ldir,$(subst ;, ,$(shell lua -e 'print(package.cpath)')), \
$(if $(findstring lib/lua/,$(ldir)),$(patsubst %/?.so,%,$(ldir))) \
) \
)
# find lua prefix
LUA_PREFIX=$(firstword \
$(foreach prefix,$(PREFIX_SEARCH),\
$(if $(wildcard $(prefix)/include/lua.h),$(prefix)) \
) \
)
libdir=$(prefix)/libs
luadir=$(if $(LUA_PLUGINDIR),$(LUA_PLUGINDIR),$(libdir)/lua/$(LUA_VERSION))
luainc=$(shell pkg-config --silence-errors --cflags lua$(LUA_VERSION))
CPPFLAGS=-I.. $(if $(luainc),$(luainc), -I$(LUA_PREFIX)/include)
LIBS=-L.. -luci $(shell pkg-config --silence-errors --libs lua$(LUA_VERSION))
PLUGIN_LD=$(CC)
ifeq ($(OS),Darwin)
PLUGIN_LDFLAGS=-bundle -undefined dynamic_lookup
else
PLUGIN_LDFLAGS=-shared -Wl,-soname,$(SHLIB_FILE)
endif
all: uci.so
uci.so: uci.o
$(PLUGIN_LD) $(PLUGIN_LDFLAGS) -o $@ $^ $(LIBS)
%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(FPIC) -c -o $@ $<
install:
mkdir -p $(DESTDIR)$(luadir)
$(INSTALL) -m0644 uci.so $(DESTDIR)$(luadir)/
clean:
rm -f *.so *.o uci.so

View file

@ -1,928 +0,0 @@
/*
* libuci plugin for Lua
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation
*
* This program 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.
*/
#include <sys/types.h>
#include <sys/time.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <lauxlib.h>
#include <uci.h>
#define MODNAME "uci"
#define METANAME MODNAME ".meta"
//#define DEBUG 1
#ifdef DEBUG
#define DPRINTF(...) fprintf(stderr, __VA_ARGS__)
#else
#define DPRINTF(...) do {} while (0)
#endif
static struct uci_context *global_ctx = NULL;
static struct uci_context *
find_context(lua_State *L, int *offset)
{
struct uci_context **ctx;
if (!lua_isuserdata(L, 1)) {
if (!global_ctx) {
global_ctx = uci_alloc_context();
if (!global_ctx)
luaL_error(L, "failed to allocate UCI context");
}
if (offset)
*offset = 0;
return global_ctx;
}
if (offset)
*offset = 1;
ctx = luaL_checkudata(L, 1, METANAME);
if (!ctx || !*ctx)
luaL_error(L, "failed to get UCI context");
return *ctx;
}
static struct uci_package *
find_package(lua_State *L, struct uci_context *ctx, const char *str, bool al)
{
struct uci_package *p = NULL;
struct uci_element *e;
char *sep;
char *name;
sep = strchr(str, '.');
if (sep) {
name = malloc(1 + sep - str);
if (!name)
luaL_error(L, "out of memory");
strncpy(name, str, sep - str);
name[sep - str] = 0;
} else
name = (char *) str;
uci_foreach_element(&ctx->root, e) {
if (strcmp(e->name, name) != 0)
continue;
p = uci_to_package(e);
goto done;
}
if (al == true)
uci_load(ctx, name, &p);
else if (al) {
uci_load(ctx, name, &p);
}
done:
if (name != str)
free(name);
return p;
}
static int
lookup_args(lua_State *L, struct uci_context *ctx, int offset, struct uci_ptr *ptr, char **buf)
{
char *s = NULL;
int n;
n = lua_gettop(L);
luaL_checkstring(L, 1 + offset);
s = strdup(lua_tostring(L, 1 + offset));
if (!s)
goto error;
memset(ptr, 0, sizeof(struct uci_ptr));
if (!find_package(L, ctx, s, true))
goto error;
switch (n - offset) {
case 4:
case 3:
ptr->option = luaL_checkstring(L, 3 + offset);
/* fall through */
case 2:
ptr->section = luaL_checkstring(L, 2 + offset);
ptr->package = luaL_checkstring(L, 1 + offset);
if (uci_lookup_ptr(ctx, ptr, NULL, false) != UCI_OK)
goto error;
break;
case 1:
if (uci_lookup_ptr(ctx, ptr, s, false) != UCI_OK)
goto error;
break;
default:
luaL_error(L, "invalid argument count");
goto error;
}
*buf = s;
return 0;
error:
if (s)
free(s);
return 1;
}
static int
uci_push_status(lua_State *L, struct uci_context *ctx, bool hasarg)
{
char *str = NULL;
if (!hasarg)
lua_pushboolean(L, (ctx->err == UCI_OK));
if (ctx->err) {
uci_get_errorstr(ctx, &str, MODNAME);
if (str) {
lua_pushstring(L, str);
free(str);
return 2;
}
}
return 1;
}
static void
uci_push_option(lua_State *L, struct uci_option *o)
{
struct uci_element *e;
int i = 0;
switch(o->type) {
case UCI_TYPE_STRING:
lua_pushstring(L, o->v.string);
break;
case UCI_TYPE_LIST:
lua_newtable(L);
uci_foreach_element(&o->v.list, e) {
i++;
lua_pushstring(L, e->name);
lua_rawseti(L, -2, i);
}
break;
default:
lua_pushnil(L);
break;
}
}
static void
uci_push_section(lua_State *L, struct uci_section *s, int index)
{
struct uci_element *e;
lua_newtable(L);
lua_pushboolean(L, s->anonymous);
lua_setfield(L, -2, ".anonymous");
lua_pushstring(L, s->type);
lua_setfield(L, -2, ".type");
lua_pushstring(L, s->e.name);
lua_setfield(L, -2, ".name");
if (index >= 0) {
lua_pushinteger(L, index);
lua_setfield(L, -2, ".index");
}
uci_foreach_element(&s->options, e) {
struct uci_option *o = uci_to_option(e);
uci_push_option(L, o);
lua_setfield(L, -2, o->e.name);
}
}
static void
uci_push_package(lua_State *L, struct uci_package *p)
{
struct uci_element *e;
int i = 0;
lua_newtable(L);
uci_foreach_element(&p->sections, e) {
uci_push_section(L, uci_to_section(e), i);
lua_setfield(L, -2, e->name);
i++;
}
}
static int
uci_lua_unload(lua_State *L)
{
struct uci_context *ctx;
struct uci_package *p;
const char *s;
int offset = 0;
ctx = find_context(L, &offset);
luaL_checkstring(L, 1 + offset);
s = lua_tostring(L, 1 + offset);
p = find_package(L, ctx, s, false);
if (p) {
uci_unload(ctx, p);
return uci_push_status(L, ctx, false);
} else {
lua_pushboolean(L, 0);
}
return 1;
}
static int
uci_lua_load(lua_State *L)
{
struct uci_context *ctx;
struct uci_package *p = NULL;
const char *s;
int offset = 0;
ctx = find_context(L, &offset);
uci_lua_unload(L);
lua_pop(L, 1); /* bool ret value of unload */
s = lua_tostring(L, -1);
uci_load(ctx, s, &p);
return uci_push_status(L, ctx, false);
}
static int
uci_lua_foreach(lua_State *L)
{
struct uci_context *ctx;
struct uci_package *p;
struct uci_element *e;
const char *package, *type;
bool ret = false;
int offset = 0;
int i = 0;
ctx = find_context(L, &offset);
package = luaL_checkstring(L, 1 + offset);
if (lua_isnil(L, 2))
type = NULL;
else
type = luaL_checkstring(L, 2 + offset);
if (!lua_isfunction(L, 3 + offset) || !package)
luaL_error(L, "Invalid argument");
p = find_package(L, ctx, package, true);
if (!p)
goto done;
uci_foreach_element(&p->sections, e) {
struct uci_section *s = uci_to_section(e);
i++;
if (type && (strcmp(s->type, type) != 0))
continue;
lua_pushvalue(L, 3 + offset); /* iterator function */
uci_push_section(L, s, i - 1);
if (lua_pcall(L, 1, 1, 0) == 0) {
ret = true;
if (lua_isboolean(L, -1) && !lua_toboolean(L, -1))
break;
}
}
done:
lua_pushboolean(L, ret);
return 1;
}
static int
uci_lua_get_any(lua_State *L, bool all)
{
struct uci_context *ctx;
struct uci_element *e = NULL;
struct uci_ptr ptr;
int offset = 0;
char *s = NULL;
int err = UCI_ERR_NOTFOUND;
ctx = find_context(L, &offset);
if (lookup_args(L, ctx, offset, &ptr, &s))
goto error;
uci_lookup_ptr(ctx, &ptr, NULL, false);
if (!all && !ptr.s) {
err = UCI_ERR_INVAL;
goto error;
}
if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) {
err = UCI_ERR_NOTFOUND;
goto error;
}
err = UCI_OK;
e = ptr.last;
switch(e->type) {
case UCI_TYPE_PACKAGE:
uci_push_package(L, ptr.p);
break;
case UCI_TYPE_SECTION:
if (all)
uci_push_section(L, ptr.s, -1);
else
lua_pushstring(L, ptr.s->type);
break;
case UCI_TYPE_OPTION:
uci_push_option(L, ptr.o);
break;
default:
err = UCI_ERR_INVAL;
goto error;
}
if (!err)
return 1;
error:
if (s)
free(s);
lua_pushnil(L);
return uci_push_status(L, ctx, true);
}
static int
uci_lua_get(lua_State *L)
{
return uci_lua_get_any(L, false);
}
static int
uci_lua_get_all(lua_State *L)
{
return uci_lua_get_any(L, true);
}
static int
uci_lua_add(lua_State *L)
{
struct uci_context *ctx;
struct uci_section *s = NULL;
struct uci_package *p;
const char *package;
const char *type;
const char *name = NULL;
int offset = 0;
ctx = find_context(L, &offset);
package = luaL_checkstring(L, 1 + offset);
type = luaL_checkstring(L, 2 + offset);
p = find_package(L, ctx, package, true);
if (!p)
goto fail;
if (uci_add_section(ctx, p, type, &s) || !s)
goto fail;
name = s->e.name;
lua_pushstring(L, name);
return 1;
fail:
lua_pushnil(L);
return uci_push_status(L, ctx, true);
}
static int
uci_lua_delete(lua_State *L)
{
struct uci_context *ctx;
struct uci_ptr ptr;
int offset = 0;
char *s = NULL;
int err = UCI_ERR_NOTFOUND;
ctx = find_context(L, &offset);
if (lookup_args(L, ctx, offset, &ptr, &s))
goto error;
err = uci_delete(ctx, &ptr);
error:
if (s)
free(s);
return uci_push_status(L, ctx, false);
}
static int
uci_lua_rename(lua_State *L)
{
struct uci_context *ctx;
struct uci_ptr ptr;
int err = UCI_ERR_MEM;
char *s = NULL;
int nargs, offset = 0;
ctx = find_context(L, &offset);
nargs = lua_gettop(L);
if (lookup_args(L, ctx, offset, &ptr, &s))
goto error;
switch(nargs - offset) {
case 1:
/* Format: uci.set("p.s.o=v") or uci.set("p.s=v") */
break;
case 4:
/* Format: uci.set("p", "s", "o", "v") */
ptr.value = luaL_checkstring(L, nargs);
break;
case 3:
/* Format: uci.set("p", "s", "v") */
ptr.value = ptr.option;
ptr.option = NULL;
break;
default:
err = UCI_ERR_INVAL;
goto error;
}
err = uci_lookup_ptr(ctx, &ptr, NULL, false);
if (err)
goto error;
if (((ptr.s == NULL) && (ptr.option != NULL)) || (ptr.value == NULL)) {
err = UCI_ERR_INVAL;
goto error;
}
err = uci_rename(ctx, &ptr);
if (err)
goto error;
error:
return uci_push_status(L, ctx, false);
}
static int
uci_lua_reorder(lua_State *L)
{
struct uci_context *ctx;
struct uci_ptr ptr;
int err = UCI_ERR_MEM;
char *s = NULL;
int nargs, offset = 0;
ctx = find_context(L, &offset);
nargs = lua_gettop(L);
if (lookup_args(L, ctx, offset, &ptr, &s))
goto error;
switch(nargs - offset) {
case 1:
/* Format: uci.set("p.s=v") or uci.set("p.s=v") */
if (ptr.option) {
err = UCI_ERR_INVAL;
goto error;
}
break;
case 3:
/* Format: uci.set("p", "s", "v") */
ptr.value = ptr.option;
ptr.option = NULL;
break;
default:
err = UCI_ERR_INVAL;
goto error;
}
err = uci_lookup_ptr(ctx, &ptr, NULL, false);
if (err)
goto error;
if ((ptr.s == NULL) || (ptr.value == NULL)) {
err = UCI_ERR_INVAL;
goto error;
}
err = uci_reorder_section(ctx, ptr.s, strtoul(ptr.value, NULL, 10));
if (err)
goto error;
error:
return uci_push_status(L, ctx, false);
}
static int
uci_lua_set(lua_State *L)
{
struct uci_context *ctx;
struct uci_ptr ptr;
bool istable = false;
int err = UCI_ERR_MEM;
char *s = NULL;
int i, nargs, offset = 0;
ctx = find_context(L, &offset);
nargs = lua_gettop(L);
if (lookup_args(L, ctx, offset, &ptr, &s))
goto error;
switch(nargs - offset) {
case 1:
/* Format: uci.set("p.s.o=v") or uci.set("p.s=v") */
break;
case 4:
/* Format: uci.set("p", "s", "o", "v") */
if (lua_istable(L, nargs)) {
if (lua_objlen(L, nargs) < 1)
luaL_error(L, "Cannot set an uci option to an empty table value");
lua_rawgeti(L, nargs, 1);
ptr.value = luaL_checkstring(L, -1);
lua_pop(L, 1);
istable = true;
} else {
ptr.value = luaL_checkstring(L, nargs);
}
break;
case 3:
/* Format: uci.set("p", "s", "v") */
ptr.value = ptr.option;
ptr.option = NULL;
break;
default:
err = UCI_ERR_INVAL;
goto error;
}
err = uci_lookup_ptr(ctx, &ptr, NULL, false);
if (err)
goto error;
if (((ptr.s == NULL) && (ptr.option != NULL)) || (ptr.value == NULL)) {
err = UCI_ERR_INVAL;
goto error;
}
if (istable) {
if (lua_objlen(L, nargs) == 1) {
i = 1;
if (ptr.o)
err = uci_delete(ctx, &ptr);
} else {
i = 2;
err = uci_set(ctx, &ptr);
if (err)
goto error;
}
for (; i <= lua_objlen(L, nargs); i++) {
lua_rawgeti(L, nargs, i);
ptr.value = luaL_checkstring(L, -1);
err = uci_add_list(ctx, &ptr);
lua_pop(L, 1);
if (err)
goto error;
}
} else {
err = uci_set(ctx, &ptr);
if (err)
goto error;
}
error:
return uci_push_status(L, ctx, false);
}
enum pkg_cmd {
CMD_SAVE,
CMD_COMMIT,
CMD_REVERT
};
static int
uci_lua_package_cmd(lua_State *L, enum pkg_cmd cmd)
{
struct uci_context *ctx;
struct uci_element *e, *tmp;
struct uci_ptr ptr;
char *s = NULL;
int failed = 0;
int nargs, offset = 0;
ctx = find_context(L, &offset);
nargs = lua_gettop(L);
if ((cmd != CMD_REVERT) && (nargs - offset > 1))
goto err;
if (lookup_args(L, ctx, offset, &ptr, &s))
goto err;
uci_lookup_ptr(ctx, &ptr, NULL, false);
uci_foreach_element_safe(&ctx->root, tmp, e) {
struct uci_package *p = uci_to_package(e);
int ret = UCI_ERR_INVAL;
if (ptr.p && (ptr.p != p))
continue;
ptr.p = p;
switch(cmd) {
case CMD_COMMIT:
ret = uci_commit(ctx, &p, false);
break;
case CMD_SAVE:
ret = uci_save(ctx, p);
break;
case CMD_REVERT:
ret = uci_revert(ctx, &ptr);
break;
}
if (ret != 0)
failed = 1;
}
err:
return uci_push_status(L, ctx, false);
}
static int
uci_lua_save(lua_State *L)
{
return uci_lua_package_cmd(L, CMD_SAVE);
}
static int
uci_lua_commit(lua_State *L)
{
return uci_lua_package_cmd(L, CMD_COMMIT);
}
static int
uci_lua_revert(lua_State *L)
{
return uci_lua_package_cmd(L, CMD_REVERT);
}
static void
uci_lua_add_change(lua_State *L, struct uci_element *e)
{
struct uci_history *h;
const char *name;
h = uci_to_history(e);
if (!h->section)
return;
lua_getfield(L, -1, h->section);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1); /* copy for setfield */
lua_setfield(L, -3, h->section);
}
name = (h->e.name ? h->e.name : ".type");
if (h->value)
lua_pushstring(L, h->value);
else
lua_pushstring(L, "");
lua_setfield(L, -2, name);
lua_pop(L, 1);
}
static void
uci_lua_changes_pkg(lua_State *L, struct uci_context *ctx, const char *package)
{
struct uci_package *p = NULL;
struct uci_element *e;
bool autoload = false;
p = find_package(L, ctx, package, false);
if (!p) {
autoload = true;
p = find_package(L, ctx, package, true);
if (!p)
return;
}
if (uci_list_empty(&p->history) && uci_list_empty(&p->saved_history))
goto done;
lua_newtable(L);
uci_foreach_element(&p->saved_history, e) {
uci_lua_add_change(L, e);
}
uci_foreach_element(&p->history, e) {
uci_lua_add_change(L, e);
}
lua_setfield(L, -2, p->e.name);
done:
if (autoload)
uci_unload(ctx, p);
}
static int
uci_lua_changes(lua_State *L)
{
struct uci_context *ctx;
const char *package = NULL;
char **config = NULL;
int nargs;
int i, offset = 0;
ctx = find_context(L, &offset);
nargs = lua_gettop(L);
switch(nargs - offset) {
case 1:
package = luaL_checkstring(L, 1 + offset);
case 0:
break;
default:
luaL_error(L, "invalid argument count");
}
lua_newtable(L);
if (package) {
uci_lua_changes_pkg(L, ctx, package);
} else {
if (uci_list_configs(ctx, &config) != 0)
goto done;
for(i = 0; config[i] != NULL; i++) {
uci_lua_changes_pkg(L, ctx, config[i]);
}
}
done:
return 1;
}
static int
uci_lua_get_confdir(lua_State *L)
{
struct uci_context *ctx = find_context(L, NULL);
lua_pushstring(L, ctx->confdir);
return 1;
}
static int
uci_lua_set_confdir(lua_State *L)
{
struct uci_context *ctx;
int ret, offset = 0;
ctx = find_context(L, &offset);
luaL_checkstring(L, 1 + offset);
ret = uci_set_confdir(ctx, lua_tostring(L, -1));
return uci_push_status(L, ctx, false);
}
static int
uci_lua_get_savedir(lua_State *L)
{
struct uci_context *ctx = find_context(L, NULL);
lua_pushstring(L, ctx->savedir);
return 1;
}
static int
uci_lua_add_history(lua_State *L)
{
struct uci_context *ctx;
int ret, offset = 0;
ctx = find_context(L, &offset);
luaL_checkstring(L, 1 + offset);
ret = uci_add_history_path(ctx, lua_tostring(L, -1));
return uci_push_status(L, ctx, false);
}
static int
uci_lua_load_plugins(lua_State *L)
{
struct uci_context *ctx;
int ret, offset = 0;
const char *str = NULL;
ctx = find_context(L, &offset);
if (lua_isstring(L, -1))
str = lua_tostring(L, -1);
ret = uci_load_plugins(ctx, str);
return uci_push_status(L, ctx, false);
}
static int
uci_lua_set_savedir(lua_State *L)
{
struct uci_context *ctx;
int ret, offset = 0;
ctx = find_context(L, &offset);
luaL_checkstring(L, 1 + offset);
ret = uci_set_savedir(ctx, lua_tostring(L, -1));
return uci_push_status(L, ctx, false);
}
static int
uci_lua_gc(lua_State *L)
{
struct uci_context *ctx = find_context(L, NULL);
uci_free_context(ctx);
return 0;
}
static int
uci_lua_cursor(lua_State *L)
{
struct uci_context **u;
int argc = lua_gettop(L);
u = lua_newuserdata(L, sizeof(struct uci_context *));
luaL_getmetatable(L, METANAME);
lua_setmetatable(L, -2);
*u = uci_alloc_context();
if (!*u)
luaL_error(L, "Cannot allocate UCI context");
switch (argc) {
case 2:
if (lua_isstring(L, 2) &&
(uci_set_savedir(*u, luaL_checkstring(L, 2)) != UCI_OK))
luaL_error(L, "Unable to set savedir");
/* fall through */
case 1:
if (lua_isstring(L, 1) &&
(uci_set_confdir(*u, luaL_checkstring(L, 1)) != UCI_OK))
luaL_error(L, "Unable to set savedir");
break;
default:
break;
}
return 1;
}
static const luaL_Reg uci[] = {
{ "__gc", uci_lua_gc },
{ "cursor", uci_lua_cursor },
{ "load", uci_lua_load },
{ "unload", uci_lua_unload },
{ "get", uci_lua_get },
{ "get_all", uci_lua_get_all },
{ "add", uci_lua_add },
{ "set", uci_lua_set },
{ "rename", uci_lua_rename },
{ "save", uci_lua_save },
{ "delete", uci_lua_delete },
{ "commit", uci_lua_commit },
{ "revert", uci_lua_revert },
{ "reorder", uci_lua_reorder },
{ "changes", uci_lua_changes },
{ "foreach", uci_lua_foreach },
{ "add_history", uci_lua_add_history },
{ "load_plugins", uci_lua_load_plugins },
{ "get_confdir", uci_lua_get_confdir },
{ "set_confdir", uci_lua_set_confdir },
{ "get_savedir", uci_lua_get_savedir },
{ "set_savedir", uci_lua_set_savedir },
{ NULL, NULL },
};
int
luaopen_uci(lua_State *L)
{
/* create metatable */
luaL_newmetatable(L, METANAME);
/* metatable.__index = metatable */
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
/* fill metatable */
luaL_register(L, NULL, uci);
lua_pop(L, 1);
/* create module */
luaL_register(L, MODNAME, uci);
return 0;
}

View file

@ -1,178 +0,0 @@
#!/bin/sh
# Copyright (C) 2006 Fokus Fraunhofer <carsten.tittel@fokus.fraunhofer.de>
# Copyright (C) 2006-2008 OpenWrt.org
# newline
N="
"
_C=0
NO_EXPORT=1
LOAD_STATE=1
LIST_SEP=" "
hotplug_dev() {
env -i ACTION=$1 INTERFACE=$2 /sbin/hotplug-call net
}
append() {
local var="$1"
local value="$2"
local sep="${3:- }"
eval "export ${NO_EXPORT:+-n} -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\""
}
list_contains() {
local var="$1"
local str="$2"
local val
eval "val=\" \${$var} \""
[ "${val%% $str *}" != "$val" ]
}
list_remove() {
local var="$1"
local remove="$2"
local val
eval "val=\" \${$var} \""
val1="${val%% $remove *}"
[ "$val1" = "$val" ] && return
val2="${val##* $remove }"
[ "$val2" = "$val" ] && return
val="${val1## } ${val2%% }"
val="${val%% }"
eval "export ${NO_EXPORT:+-n} -- \"$var=\$val\""
}
config_load() {
[ -n "$IPKG_INSTROOT" ] && return 0
uci_load "$@"
}
reset_cb() {
config_cb() { return 0; }
option_cb() { return 0; }
list_cb() { return 0; }
}
reset_cb
package() {
return 0
}
config () {
local cfgtype="$1"
local name="$2"
export ${NO_EXPORT:+-n} CONFIG_NUM_SECTIONS=$(($CONFIG_NUM_SECTIONS + 1))
name="${name:-cfg$CONFIG_NUM_SECTIONS}"
append CONFIG_SECTIONS "$name"
[ -n "$NO_CALLBACK" ] || config_cb "$cfgtype" "$name"
export ${NO_EXPORT:+-n} CONFIG_SECTION="$name"
export ${NO_EXPORT:+-n} "CONFIG_${CONFIG_SECTION}_TYPE=$cfgtype"
}
option () {
local varname="$1"; shift
local value="$*"
export ${NO_EXPORT:+-n} "CONFIG_${CONFIG_SECTION}_${varname}=$value"
[ -n "$NO_CALLBACK" ] || option_cb "$varname" "$*"
}
list() {
local varname="$1"; shift
local value="$*"
local len
config_get len "$CONFIG_SECTION" "${varname}_LENGTH"
len="$((${len:-0} + 1))"
config_set "$CONFIG_SECTION" "${varname}_ITEM$len" "$value"
config_set "$CONFIG_SECTION" "${varname}_LENGTH" "$len"
append "CONFIG_${CONFIG_SECTION}_${varname}" "$value" "$LIST_SEP"
list_cb "$varname" "$*"
}
config_unset() {
config_set "$1" "$2" ""
}
config_clear() {
local SECTION="$1"
local oldvar
list_remove CONFIG_SECTIONS "$SECTION"
export ${NO_EXPORT:+-n} CONFIG_SECTIONS="${SECTION:+$CONFIG_SECTIONS}"
for oldvar in `set | grep ^CONFIG_${SECTION:+${SECTION}_} | \
sed -e 's/\(.*\)=.*$/\1/'` ; do
unset $oldvar
done
}
config_get() {
case "$3" in
"") eval "echo \"\${CONFIG_${1}_${2}}\"";;
*) eval "export ${NO_EXPORT:+-n} -- \"$1=\${CONFIG_${2}_${3}}\"";;
esac
}
# config_get_bool <variable> <section> <option> [<default>]
config_get_bool() {
local _tmp
config_get "_tmp" "$2" "$3"
case "$_tmp" in
1|on|true|enabled) export ${NO_EXPORT:+-n} "$1=1";;
0|off|false|disabled) export ${NO_EXPORT:+-n} "$1=0";;
*) eval "$1=$4";;
esac
}
config_set() {
local section="$1"
local option="$2"
local value="$3"
local old_section="$CONFIG_SECTION"
CONFIG_SECTION="$section"
option "$option" "$value"
CONFIG_SECTION="$old_section"
}
config_foreach() {
local function="$1"
[ "$#" -ge 1 ] && shift
local type="$1"
[ "$#" -ge 1 ] && shift
local section cfgtype
[ -z "$CONFIG_SECTIONS" ] && return 0
for section in ${CONFIG_SECTIONS}; do
config_get cfgtype "$section" TYPE
[ -n "$type" -a "x$cfgtype" != "x$type" ] && continue
eval "$function \"\$section\" \"\$@\""
done
}
config_list_foreach() {
[ "$#" -ge 3 ] || return 0
local section="$1"; shift
local option="$1"; shift
local function="$1"; shift
local val
local len
local c=1
config_get len "${section}" "${option}_LENGTH"
[ -z "$len" ] && return 0
while [ $c -le "$len" ]; do
config_get val "${section}" "${option}_ITEM$c"
eval "$function \"\$val\" \"$@\""
c="$(($c + 1))"
done
}

View file

@ -1,2 +0,0 @@
all:
sh ./tests.sh

View file

@ -1,34 +0,0 @@
This test script uses shunit2 :
http://code.google.com/p/shunit2/
uci-static binary is used during tests.
How to add a test
=================
* Test files are located in './test/tests.d'
* These files contain shell functions beginning with 'test' :
"
test_get_option ()
{
...
}
...
"
* shunit2 functions can be used in these functions :
http://shunit2.googlecode.com/svn/trunk/source/2.1/doc/shunit2.html
* Additional environment variables are available :
- ${CONFIG_DIR} : uci search path for config files. This directory is
reset after each test.
- ${CHANGES_DIR} : uci search path for config change files. This directory is
reset after each test.
- ${TMP_DIR} : path to a directory where can be stored temporary files
during tests. This directory is reset after each test.
- ${UCI} : uci static binary called with above config and changes
directories as parameters.
- ${REF_DIR} : path to a directory where can be stored reference files
or data.

View file

@ -1,25 +0,0 @@
config 'alias' 'a'
option 'interface' 'lan'
config 'alias' 'b'
option 'interface' 'lan'
config 'interface' 'lan'
option 'proto' 'static'
option 'ifname' 'eth0'
option 'test' '123'
option 'enabled' 'off'
option 'ipaddr' '2.3.4.5'
config 'interface' 'wan'
option 'proto' 'dhcp'
option 'ifname' 'eth1'
option 'enabled' 'on'
option 'aliases' 'c d'
config 'alias' 'c'
option 'interface' 'wan'
config 'alias' 'd'
option 'interface' 'wan'

View file

@ -1 +0,0 @@
+add.section=type

View file

@ -1,6 +0,0 @@
config 'type' 'section'
option 'opt' 'val'
list 'list_opt' 'val0'
list 'list_opt' 'val1'

View file

@ -1,7 +0,0 @@
package 'export'
config 'type' 'section'
option 'opt' 'val'
list 'list_opt' 'val0'
list 'list_opt' 'val1'

View file

@ -1,2 +0,0 @@
config 'type' 'section'
option 'opt' 'val'

View file

@ -1,5 +0,0 @@
config 'type' 'section'
option 'opt' 'val'
config 'unnamed'
option 'opt1' 'val1'

View file

@ -1,7 +0,0 @@
package 'import'
config 'type' 'section'
option 'opt' 'val'
list 'list_opt' 'val0'
list 'list_opt' 'val1'

View file

@ -1,6 +0,0 @@
config 'type' 'section'
option 'opt' 'val'
list 'list_opt' 'val0'
list 'list_opt' 'val1'

View file

@ -1,2 +0,0 @@
config 'named' 'section'
option 'opt' 'err'

View file

@ -1 +0,0 @@
set.section.opt=val

View file

@ -1 +0,0 @@
set.section=named

View file

@ -1 +0,0 @@
config 'named' 'section'

View file

@ -1 +0,0 @@
set.section.opt=val

View file

@ -1,2 +0,0 @@
config 'type' 'section'
option 'opt' 'val'

View file

@ -1,2 +0,0 @@
config 'type' 'section'
option 'opt' 'val'

View file

@ -1,15 +0,0 @@
New network section 'lan'
type: static
ifname: eth0
ipaddr: 2.3.4.5
test: 123
enabled: off
New alias: a
New alias: b
New network section 'wan'
type: dhcp
ifname: eth1
ipaddr: 0.0.0.0
test: -1
enabled: on
Configured aliases: c d

View file

@ -1,14 +0,0 @@
New network section 'lan'
type: static
ifname: eth0
ipaddr: 0.0.0.0
test: 123
enabled: off
Configured aliases: a b
New network section 'wan'
type: dhcp
ifname: eth1
ipaddr: 0.0.0.0
test: -1
enabled: on
Configured aliases: c d

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
test_import ()
{
${UCI} import < ${REF_DIR}/import.data
assertSameFile ${REF_DIR}/import.result ${CONFIG_DIR}/import
}

View file

@ -1,6 +0,0 @@
test_export ()
{
cp ${REF_DIR}/export.data ${CONFIG_DIR}/export
${UCI} export > ${TMP_DIR}/export.result
assertSameFile ${REF_DIR}/export.result ${TMP_DIR}/export.result
}

View file

@ -1,39 +0,0 @@
test_get_parsing()
{
cp ${REF_DIR}/get_parsing.data ${CONFIG_DIR}/test
assertFailWithNoReturn "${UCI_Q} get test."
assertFailWithNoReturn "${UCI_Q} get test.section."
assertFailWithNoReturn "${UCI_Q} get test.section.opt."
assertFailWithNoReturn "${UCI_Q} get test.section.opt.val."
assertFailWithNoReturn "${UCI_Q} get test.section.opt.val.qsdf.qsd"
assertFailWithNoReturn "${UCI_Q} get test.section.opt.valqsqsd"
}
test_get_section_index_parsing()
{
cp ${REF_DIR}/get_parsing.data ${CONFIG_DIR}/test
assertFailWithNoReturn "${UCI_Q} get test.@"
assertFailWithNoReturn "${UCI_Q} get test.@zer."
assertFailWithNoReturn "${UCI_Q} get test.@."
assertFailWithNoReturn "${UCI_Q} get test.@zer[1]"
assertFailWithNoReturn "${UCI_Q} get test.@.opt"
assertFailWithNoReturn "${UCI_Q} get test.@[28]"
assertFailWithNoReturn "${UCI_Q} get test.@[1]."
assertFailWithNoReturn "${UCI_Q} get test.@[1].val."
}
test_get_option()
{
cp ${REF_DIR}/get.data ${CONFIG_DIR}/test
value=$($UCI get test.section.opt)
assertEquals 'val' "$value"
}
test_get_section()
{
cp ${REF_DIR}/get.data ${CONFIG_DIR}/test
type=$($UCI get test.section)
assertEquals 'type' "$type"
}

View file

@ -1,30 +0,0 @@
test_set_parsing()
{
cp ${REF_DIR}/set_parsing.data ${CONFIG_DIR}/test
assertFailWithNoReturn "${UCI_Q} set test.=val"
assertFailWithNoReturn "${UCI_Q} set test.section.=val"
assertFailWithNoReturn "${UCI_Q} set test.section.opt.=val"
assertFailWithNoReturn "${UCI_Q} set test.section.opt.zflk=val"
}
test_set_named_section()
{
touch ${CONFIG_DIR}/set
${UCI} set set.section=named
assertSameFile ${REF_DIR}/set_named_section.result ${CHANGES_DIR}/set
}
test_set_nonexisting_option()
{
cp ${REF_DIR}/set_nonexisting_option.data ${CONFIG_DIR}/set
${UCI} set set.section.opt=val
assertSameFile ${REF_DIR}/set_nonexisting_option.result ${CHANGES_DIR}/set
}
test_set_existing_option()
{
cp ${REF_DIR}/set_existing_option.data ${CONFIG_DIR}/set
${UCI} set set.section.opt=val
assertSameFile ${REF_DIR}/set_existing_option.result ${CHANGES_DIR}/set
}

View file

@ -1,8 +0,0 @@
test_add_section()
{
touch ${CONFIG_DIR}/add
section_name=$(${UCI} add add type)
assertNotNull "uci add does not return a section name." $section_name
sed 's/section/'$section_name'/' ${REF_DIR}/add_section.result > ${TMP_DIR}/add_section.result
assertSameFile ${TMP_DIR}/add_section.result ${CHANGES_DIR}/add
}

View file

@ -1,11 +0,0 @@
test_get_parsing()
{
cp ${REF_DIR}/show_parsing.data ${CONFIG_DIR}/test
assertFailWithNoReturn "${UCI_Q} show test."
assertFailWithNoReturn "${UCI_Q} show test.section."
assertFailWithNoReturn "${UCI_Q} show test.section.opt."
assertFailWithNoReturn "${UCI_Q} show test.section.opt.val."
assertFailWithNoReturn "${UCI_Q} show test.section.opt.val.qsdf.qsd"
assertFailWithNoReturn "${UCI_Q} show test.section.opt.valqsqsd"
}

View file

@ -1,9 +0,0 @@
test_ucimap_example()
{
rm -rf ./save
( cd ..; ./ucimap-example -s ) > "${TMP_DIR}/ucimap_example.result"
assertSameFile "${TMP_DIR}/ucimap_example.result" "${REF_DIR}/ucimap_example_1.result"
( cd ..; ./ucimap-example -s ) > "${TMP_DIR}/ucimap_example.result"
assertSameFile "${TMP_DIR}/ucimap_example.result" "${REF_DIR}/ucimap_example_2.result"
rm -rf ./save
}

View file

@ -1,78 +0,0 @@
#!/bin/sh
TESTS_DIR="./tests"
CONFIG_DIR=${TESTS_DIR}"/config"
CHANGES_DIR="/tmp/.uci"
TMP_DIR=${TESTS_DIR}"/tmp"
FULL_SUITE=${TESTS_DIR}"/full_suite.sh"
UCI_STATIC="../uci-static"
[ -x $UCI_STATIC ] || {
echo "uci-static is not present."
return 1
}
UCI="${UCI_STATIC} -c ${CONFIG_DIR} -p ${CHANGES_DIR}"
UCI_Q="${UCI_STATIC} -c ${CONFIG_DIR} -p ${CHANGES_DIR} -q"
REF_DIR="./references"
SCRIPTS_DIR="./tests.d"
DO_TEST="./shunit2/shunit2"
rm -rf ${TESTS_DIR}
mkdir -p ${TESTS_DIR}
cat << 'EOF' > ${FULL_SUITE}
setUp() {
mkdir -p ${CONFIG_DIR} ${CHANGES_DIR} ${TMP_DIR}
}
tearDown() {
rm -rf ${CONFIG_DIR} ${CHANGES_DIR} ${TMP_DIR}
}
assertSameFile() {
local ref=$1
local test=$2
diff -qr $ref $test
assertTrue $? || {
echo "REF:"
cat $ref
echo "----"
echo "TEST:"
cat $test
echo "----"
}
}
assertNotSegFault()
{
[ $1 -eq 139 ] && fail "Returned with 139: segmentation fault (SIGSEGV)!!!"
}
assertNotIllegal()
{
[ $1 -eq 132 ] && fail "Returned with 132: Illegal instruction (SIGILL)!!!"
}
assertFailWithNoReturn() {
local test="$1"
value=$( $test )
rv=$?
assertFalse "'$test' does not fail" $rv
assertNotSegFault $rv
assertNotIllegal $rv
assertNull "'$test' returns '$value'" "$value"
}
EOF
for suite in $(ls ${SCRIPTS_DIR}/*)
do
cat ${suite} >> ${FULL_SUITE}
done
echo ". ${DO_TEST}" >> ${FULL_SUITE}
REF_DIR="${REF_DIR}" \
CONFIG_DIR="${CONFIG_DIR}" \
CHANGES_DIR="${CHANGES_DIR}" \
TMP_DIR="${TMP_DIR}" \
UCI="${UCI}" \
UCI_Q="${UCI_Q}" \
/bin/sh ${FULL_SUITE}
rm -rf ${TESTS_DIR}

View file

@ -1,631 +0,0 @@
/*
* libuci - Library for the Unified Configuration Interface
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
#ifndef __LIBUCI_H
#define __LIBUCI_H
#include "uci_config.h"
/*
* you can use these defines to enable debugging behavior for
* apps compiled against libuci:
*
* #define UCI_DEBUG_TYPECAST:
* enable uci_element typecast checking at run time
*
*/
#include <stdbool.h>
#include <setjmp.h>
#include <stdio.h>
#define UCI_CONFDIR "/etc/config"
#define UCI_SAVEDIR "/tmp/.uci"
#define UCI_DIRMODE 0700
#define UCI_FILEMODE 0600
enum
{
UCI_OK = 0,
UCI_ERR_MEM,
UCI_ERR_INVAL,
UCI_ERR_NOTFOUND,
UCI_ERR_IO,
UCI_ERR_PARSE,
UCI_ERR_DUPLICATE,
UCI_ERR_UNKNOWN,
UCI_ERR_LAST
};
struct uci_list;
struct uci_list
{
struct uci_list *next;
struct uci_list *prev;
};
struct uci_ptr;
struct uci_plugin;
struct uci_hook_ops;
struct uci_element;
struct uci_package;
struct uci_section;
struct uci_option;
struct uci_history;
struct uci_context;
struct uci_backend;
struct uci_parse_context;
/**
* uci_alloc_context: Allocate a new uci context
*/
extern struct uci_context *uci_alloc_context(void);
/**
* uci_free_context: Free the uci context including all of its data
*/
extern void uci_free_context(struct uci_context *ctx);
/**
* uci_perror: Print the last uci error that occured
* @ctx: uci context
* @str: string to print before the error message
*/
extern void uci_perror(struct uci_context *ctx, const char *str);
/**
* uci_geterror: Get an error string for the last uci error
* @ctx: uci context
* @dest: target pointer for the string
* @str: prefix for the error message
*
* Note: string must be freed by the caller
*/
extern void uci_get_errorstr(struct uci_context *ctx, char **dest, const char *str);
/**
* uci_import: Import uci config data from a stream
* @ctx: uci context
* @stream: file stream to import from
* @name: (optional) assume the config has the given name
* @package: (optional) store the last parsed config package in this variable
* @single: ignore the 'package' keyword and parse everything into a single package
*
* the name parameter is for config files that don't explicitly use the 'package <...>' keyword
* if 'package' points to a non-null struct pointer, enable history tracking and merge
*/
extern int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct uci_package **package, bool single);
/**
* uci_export: Export one or all uci config packages
* @ctx: uci context
* @stream: output stream
* @package: (optional) uci config package to export
* @header: include the package header
*/
extern int uci_export(struct uci_context *ctx, FILE *stream, struct uci_package *package, bool header);
/**
* uci_load: Parse an uci config file and store it in the uci context
*
* @ctx: uci context
* @name: name of the config file (relative to the config directory)
* @package: store the loaded config package in this variable
*/
extern int uci_load(struct uci_context *ctx, const char *name, struct uci_package **package);
/**
* uci_unload: Unload a config file from the uci context
*
* @ctx: uci context
* @package: pointer to the uci_package struct
*/
extern int uci_unload(struct uci_context *ctx, struct uci_package *p);
/**
* uci_lookup_ptr: Split an uci tuple string and look up an element tree
* @ctx: uci context
* @ptr: lookup result struct
* @str: uci tuple string to look up
* @extended: allow extended syntax lookup
*
* if extended is set to true, uci_lookup_ptr supports the following
* extended syntax:
*
* Examples:
* network.@interface[0].ifname ('ifname' option of the first interface section)
* network.@interface[-1] (last interface section)
* Note: uci_lookup_ext will automatically load a config package if necessary
*/
extern int uci_lookup_ptr(struct uci_context *ctx, struct uci_ptr *ptr, char *str, bool extended);
/**
* uci_add_section: Add an unnamed section
* @ctx: uci context
* @p: package to add the section to
* @type: section type
* @res: pointer to store a reference to the new section in
*/
extern int uci_add_section(struct uci_context *ctx, struct uci_package *p, const char *type, struct uci_section **res);
/**
* uci_set: Set an element's value; create the element if necessary
* @ctx: uci context
* @ptr: uci pointer
*
* The updated/created element is stored in ptr->last
*/
extern int uci_set(struct uci_context *ctx, struct uci_ptr *ptr);
/**
* uci_add_list: Append a string to an element list
* @ctx: uci context
* @ptr: uci pointer (with value)
*
* Note: if the given option already contains a string value,
* it will be converted to an 1-element-list before appending the next element
*/
extern int uci_add_list(struct uci_context *ctx, struct uci_ptr *ptr);
/**
* uci_reorder: Reposition a section
* @ctx: uci context
* @s: uci section to reposition
* @pos: new position in the section list
*/
extern int uci_reorder_section(struct uci_context *ctx, struct uci_section *s, int pos);
/**
* uci_rename: Rename an element
* @ctx: uci context
* @ptr: uci pointer (with value)
*/
extern int uci_rename(struct uci_context *ctx, struct uci_ptr *ptr);
/**
* uci_delete: Delete a section or option
* @ctx: uci context
* @ptr: uci pointer
*/
extern int uci_delete(struct uci_context *ctx, struct uci_ptr *ptr);
/**
* uci_save: save change history for a package
* @ctx: uci context
* @p: uci_package struct
*/
extern int uci_save(struct uci_context *ctx, struct uci_package *p);
/**
* uci_commit: commit changes to a package
* @ctx: uci context
* @p: uci_package struct pointer
* @overwrite: overwrite existing config data and flush history
*
* committing may reload the whole uci_package data,
* the supplied pointer is updated accordingly
*/
extern int uci_commit(struct uci_context *ctx, struct uci_package **p, bool overwrite);
/**
* uci_list_configs: List available uci config files
* @ctx: uci context
*
* caller is responsible for freeing the allocated memory behind list
*/
extern int uci_list_configs(struct uci_context *ctx, char ***list);
/**
* uci_set_savedir: override the default history save directory
* @ctx: uci context
* @dir: directory name
*/
extern int uci_set_savedir(struct uci_context *ctx, const char *dir);
/**
* uci_set_savedir: override the default config storage directory
* @ctx: uci context
* @dir: directory name
*/
extern int uci_set_confdir(struct uci_context *ctx, const char *dir);
/**
* uci_add_history_path: add a directory to the search path for change history files
* @ctx: uci context
* @dir: directory name
*
* This function allows you to add directories, which contain 'overlays'
* for the active config, that will never be committed.
*/
extern int uci_add_history_path(struct uci_context *ctx, const char *dir);
/**
* uci_revert: revert all changes to a config item
* @ctx: uci context
* @ptr: uci pointer
*/
extern int uci_revert(struct uci_context *ctx, struct uci_ptr *ptr);
/**
* uci_parse_argument: parse a shell-style argument, with an arbitrary quoting style
* @ctx: uci context
* @stream: input stream
* @str: pointer to the current line (use NULL for parsing the next line)
* @result: pointer for the result
*/
extern int uci_parse_argument(struct uci_context *ctx, FILE *stream, char **str, char **result);
/**
* uci_set_backend: change the default backend
* @ctx: uci context
* @name: name of the backend
*
* The default backend is "file", which uses /etc/config for config storage
*/
extern int uci_set_backend(struct uci_context *ctx, const char *name);
/**
* uci_validate_text: validate a value string for uci options
* @str: value
*
* this function checks whether a given string is acceptable as value
* for uci options
*/
extern bool uci_validate_text(const char *str);
/**
* uci_add_hook: add a uci hook
* @ctx: uci context
* @ops: uci hook ops
*
* NB: allocated and freed by the caller
*/
extern int uci_add_hook(struct uci_context *ctx, const struct uci_hook_ops *ops);
/**
* uci_remove_hook: remove a uci hook
* @ctx: uci context
* @ops: uci hook ops
*/
extern int uci_remove_hook(struct uci_context *ctx, const struct uci_hook_ops *ops);
/**
* uci_load_plugin: load an uci plugin
* @ctx: uci context
* @filename: path to the uci plugin
*
* NB: plugin will be unloaded automatically when the context is freed
*/
int uci_load_plugin(struct uci_context *ctx, const char *filename);
/**
* uci_load_plugins: load all uci plugins from a directory
* @ctx: uci context
* @pattern: pattern of uci plugin files (optional)
*
* if pattern is NULL, then uci_load_plugins will call uci_load_plugin
* for uci_*.so in <prefix>/lib/
*/
int uci_load_plugins(struct uci_context *ctx, const char *pattern);
/* UCI data structures */
enum uci_type {
UCI_TYPE_UNSPEC = 0,
UCI_TYPE_HISTORY = 1,
UCI_TYPE_PACKAGE = 2,
UCI_TYPE_SECTION = 3,
UCI_TYPE_OPTION = 4,
UCI_TYPE_PATH = 5,
UCI_TYPE_BACKEND = 6,
UCI_TYPE_ITEM = 7,
UCI_TYPE_HOOK = 8,
UCI_TYPE_PLUGIN = 9,
};
enum uci_option_type {
UCI_TYPE_STRING = 0,
UCI_TYPE_LIST = 1,
};
enum uci_flags {
UCI_FLAG_STRICT = (1 << 0), /* strict mode for the parser */
UCI_FLAG_PERROR = (1 << 1), /* print parser error messages */
UCI_FLAG_EXPORT_NAME = (1 << 2), /* when exporting, name unnamed sections */
UCI_FLAG_SAVED_HISTORY = (1 << 3), /* store the saved history in memory as well */
};
struct uci_element
{
struct uci_list list;
enum uci_type type;
char *name;
};
struct uci_backend
{
struct uci_element e;
char **(*list_configs)(struct uci_context *ctx);
struct uci_package *(*load)(struct uci_context *ctx, const char *name);
void (*commit)(struct uci_context *ctx, struct uci_package **p, bool overwrite);
/* private: */
const void *ptr;
void *priv;
};
struct uci_context
{
/* list of config packages */
struct uci_list root;
/* parser context, use for error handling only */
struct uci_parse_context *pctx;
/* backend for import and export */
struct uci_backend *backend;
struct uci_list backends;
/* uci runtime flags */
enum uci_flags flags;
char *confdir;
char *savedir;
/* search path for history files */
struct uci_list history_path;
/* private: */
int err;
const char *func;
jmp_buf trap;
bool internal, nested;
char *buf;
int bufsz;
struct uci_list hooks;
struct uci_list plugins;
};
struct uci_package
{
struct uci_element e;
struct uci_list sections;
struct uci_context *ctx;
bool has_history;
char *path;
/* private: */
struct uci_backend *backend;
void *priv;
int n_section;
struct uci_list history;
struct uci_list saved_history;
};
struct uci_section
{
struct uci_element e;
struct uci_list options;
struct uci_package *package;
bool anonymous;
char *type;
};
struct uci_option
{
struct uci_element e;
struct uci_section *section;
enum uci_option_type type;
union {
struct uci_list list;
char *string;
} v;
};
enum uci_command {
UCI_CMD_ADD,
UCI_CMD_REMOVE,
UCI_CMD_CHANGE,
UCI_CMD_RENAME,
UCI_CMD_REORDER,
UCI_CMD_LIST_ADD,
};
struct uci_history
{
struct uci_element e;
enum uci_command cmd;
char *section;
char *value;
};
struct uci_ptr
{
enum uci_type target;
enum {
UCI_LOOKUP_DONE = (1 << 0),
UCI_LOOKUP_COMPLETE = (1 << 1),
UCI_LOOKUP_EXTENDED = (1 << 2),
} flags;
struct uci_package *p;
struct uci_section *s;
struct uci_option *o;
struct uci_element *last;
const char *package;
const char *section;
const char *option;
const char *value;
};
struct uci_hook_ops
{
void (*load)(const struct uci_hook_ops *ops, struct uci_package *p);
void (*set)(const struct uci_hook_ops *ops, struct uci_package *p, struct uci_history *e);
};
struct uci_hook
{
struct uci_element e;
const struct uci_hook_ops *ops;
};
struct uci_plugin_ops
{
int (*attach)(struct uci_context *ctx);
void (*detach)(struct uci_context *ctx);
};
struct uci_plugin
{
struct uci_element e;
const struct uci_plugin_ops *ops;
void *dlh;
};
/* linked list handling */
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*/
#ifndef container_of
#define container_of(ptr, type, member) \
((type *) ((char *)ptr - offsetof(type,member)))
#endif
/**
* uci_list_entry: casts an uci_list pointer to the containing struct.
* @_type: config, section or option
* @_ptr: pointer to the uci_list struct
*/
#define list_to_element(ptr) \
container_of(ptr, struct uci_element, list)
/**
* uci_foreach_entry: loop through a list of uci elements
* @_list: pointer to the uci_list struct
* @_ptr: iteration variable, struct uci_element
*
* use like a for loop, e.g:
* uci_foreach(&list, p) {
* ...
* }
*/
#define uci_foreach_element(_list, _ptr) \
for(_ptr = list_to_element((_list)->next); \
&_ptr->list != (_list); \
_ptr = list_to_element(_ptr->list.next))
/**
* uci_foreach_entry_safe: like uci_foreach_safe, but safe for deletion
* @_list: pointer to the uci_list struct
* @_tmp: temporary variable, struct uci_element *
* @_ptr: iteration variable, struct uci_element *
*
* use like a for loop, e.g:
* uci_foreach(&list, p) {
* ...
* }
*/
#define uci_foreach_element_safe(_list, _tmp, _ptr) \
for(_ptr = list_to_element((_list)->next), \
_tmp = list_to_element(_ptr->list.next); \
&_ptr->list != (_list); \
_ptr = _tmp, _tmp = list_to_element(_ptr->list.next))
/**
* uci_list_empty: returns true if a list is empty
* @list: list head
*/
#define uci_list_empty(list) ((list)->next == (list))
/* wrappers for dynamic type handling */
#define uci_type_backend UCI_TYPE_BACKEND
#define uci_type_history UCI_TYPE_HISTORY
#define uci_type_package UCI_TYPE_PACKAGE
#define uci_type_section UCI_TYPE_SECTION
#define uci_type_option UCI_TYPE_OPTION
#define uci_type_hook UCI_TYPE_HOOK
#define uci_type_plugin UCI_TYPE_PLUGIN
/* element typecasting */
#ifdef UCI_DEBUG_TYPECAST
static const char *uci_typestr[] = {
[uci_type_backend] = "backend",
[uci_type_history] = "history",
[uci_type_package] = "package",
[uci_type_section] = "section",
[uci_type_option] = "option",
[uci_type_hook] = "hook",
[uci_type_plugin] = "plugin",
};
static void uci_typecast_error(int from, int to)
{
fprintf(stderr, "Invalid typecast from '%s' to '%s'\n", uci_typestr[from], uci_typestr[to]);
}
#define BUILD_CAST(_type) \
static inline struct uci_ ## _type *uci_to_ ## _type (struct uci_element *e) \
{ \
if (e->type != uci_type_ ## _type) { \
uci_typecast_error(e->type, uci_type_ ## _type); \
} \
return (struct uci_ ## _type *) e; \
}
BUILD_CAST(backend)
BUILD_CAST(history)
BUILD_CAST(package)
BUILD_CAST(section)
BUILD_CAST(option)
BUILD_CAST(hook)
BUILD_CAST(plugin)
#else
#define uci_to_backend(ptr) container_of(ptr, struct uci_backend, e)
#define uci_to_history(ptr) container_of(ptr, struct uci_history, e)
#define uci_to_package(ptr) container_of(ptr, struct uci_package, e)
#define uci_to_section(ptr) container_of(ptr, struct uci_section, e)
#define uci_to_option(ptr) container_of(ptr, struct uci_option, e)
#define uci_to_hook(ptr) container_of(ptr, struct uci_hook, e)
#define uci_to_plugin(ptr) container_of(ptr, struct uci_plugin, e)
#endif
/**
* uci_alloc_element: allocate a generic uci_element, reserve a buffer and typecast
* @ctx: uci context
* @type: {package,section,option}
* @name: string containing the name of the element
* @datasize: additional buffer size to reserve at the end of the struct
*/
#define uci_alloc_element(ctx, type, name, datasize) \
uci_to_ ## type (uci_alloc_generic(ctx, uci_type_ ## type, name, sizeof(struct uci_ ## type) + datasize))
#define uci_dataptr(ptr) \
(((char *) ptr) + sizeof(*ptr))
#endif

View file

@ -1,182 +0,0 @@
/*
* libuci - Library for the Unified Configuration Interface
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
#ifndef __UCI_INTERNAL_H
#define __UCI_INTERNAL_H
#define __public
#ifdef UCI_PLUGIN_SUPPORT
#define __plugin extern
#else
#define __plugin static
#endif
struct uci_parse_context
{
/* error context */
const char *reason;
int line;
int byte;
/* private: */
struct uci_package *package;
struct uci_section *section;
bool merge;
FILE *file;
const char *name;
char *buf;
int bufsz;
};
__plugin void *uci_malloc(struct uci_context *ctx, size_t size);
__plugin void *uci_realloc(struct uci_context *ctx, void *ptr, size_t size);
__plugin char *uci_strdup(struct uci_context *ctx, const char *str);
__plugin bool uci_validate_str(const char *str, bool name);
__plugin void uci_add_history(struct uci_context *ctx, struct uci_list *list, int cmd, const char *section, const char *option, const char *value);
__plugin void uci_free_history(struct uci_history *h);
__plugin struct uci_package *uci_alloc_package(struct uci_context *ctx, const char *name);
#ifdef UCI_PLUGIN_SUPPORT
/**
* uci_add_backend: add an extra backend
* @ctx: uci context
* @name: name of the backend
*
* The default backend is "file", which uses /etc/config for config storage
*/
__plugin int uci_add_backend(struct uci_context *ctx, struct uci_backend *b);
/**
* uci_add_backend: add an extra backend
* @ctx: uci context
* @name: name of the backend
*
* The default backend is "file", which uses /etc/config for config storage
*/
__plugin int uci_del_backend(struct uci_context *ctx, struct uci_backend *b);
#endif
#define UCI_BACKEND(_var, _name, ...) \
struct uci_backend _var = { \
.e.list = { \
.next = &_var.e.list, \
.prev = &_var.e.list, \
}, \
.e.name = _name, \
.e.type = UCI_TYPE_BACKEND, \
.ptr = &_var, \
__VA_ARGS__ \
}
/*
* functions for debug and error handling, for internal use only
*/
#ifdef UCI_DEBUG
#define DPRINTF(...) fprintf(stderr, __VA_ARGS__)
#else
#define DPRINTF(...)
#endif
/*
* throw an uci exception and store the error number
* in the context.
*/
#define UCI_THROW(ctx, err) do { \
DPRINTF("Exception: %s in %s, %s:%d\n", #err, __func__, __FILE__, __LINE__); \
longjmp(ctx->trap, err); \
} while (0)
/*
* store the return address for handling exceptions
* needs to be called in every externally visible library function
*
* NB: this does not handle recursion at all. Calling externally visible
* functions from other uci functions is only allowed at the end of the
* calling function, or by wrapping the function call in UCI_TRAP_SAVE
* and UCI_TRAP_RESTORE.
*/
#define UCI_HANDLE_ERR(ctx) do { \
DPRINTF("ENTER: %s\n", __func__); \
int __val = 0; \
ctx->err = 0; \
if (!ctx) \
return UCI_ERR_INVAL; \
if (!ctx->internal && !ctx->nested) \
__val = setjmp(ctx->trap); \
ctx->internal = false; \
ctx->nested = false; \
if (__val) { \
DPRINTF("LEAVE: %s, ret=%d\n", __func__, __val); \
ctx->err = __val; \
return __val; \
} \
} while (0)
/*
* In a block enclosed by UCI_TRAP_SAVE and UCI_TRAP_RESTORE, all exceptions
* are intercepted and redirected to the label specified in 'handler'
* after UCI_TRAP_RESTORE, or when reaching the 'handler' label, the old
* exception handler is restored
*/
#define UCI_TRAP_SAVE(ctx, handler) do { \
jmp_buf __old_trap; \
int __val; \
memcpy(__old_trap, ctx->trap, sizeof(ctx->trap)); \
__val = setjmp(ctx->trap); \
if (__val) { \
ctx->err = __val; \
memcpy(ctx->trap, __old_trap, sizeof(ctx->trap)); \
goto handler; \
}
#define UCI_TRAP_RESTORE(ctx) \
memcpy(ctx->trap, __old_trap, sizeof(ctx->trap)); \
} while(0)
/**
* UCI_INTERNAL: Do an internal call of a public API function
*
* Sets Exception handling to passthrough mode.
* Allows API functions to change behavior compared to public use
*/
#define UCI_INTERNAL(func, ctx, ...) do { \
ctx->internal = true; \
func(ctx, __VA_ARGS__); \
} while (0)
/**
* UCI_NESTED: Do an normal nested call of a public API function
*
* Sets Exception handling to passthrough mode.
* Allows API functions to change behavior compared to public use
*/
#define UCI_NESTED(func, ctx, ...) do { \
ctx->nested = true; \
func(ctx, __VA_ARGS__); \
} while (0)
/*
* check the specified condition.
* throw an invalid argument exception if it's false
*/
#define UCI_ASSERT(ctx, expr) do { \
if (!(expr)) { \
DPRINTF("[%s:%d] Assertion failed\n", __FILE__, __LINE__); \
UCI_THROW(ctx, UCI_ERR_INVAL); \
} \
} while (0)
#endif

View file

@ -1,318 +0,0 @@
/*
* ucimap-example - sample code for the ucimap library
* Copyright (C) 2008-2009 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation
*
* This program 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.
*/
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucimap.h>
#include "list.h"
struct list_head ifs;
struct uci_network {
struct ucimap_section_data map;
struct list_head list;
struct list_head alias;
const char *name;
const char *proto;
const char *ifname;
unsigned char *ipaddr;
int test;
bool enabled;
struct ucimap_list *aliases;
};
struct uci_alias {
struct ucimap_section_data map;
struct list_head list;
const char *name;
struct uci_network *interface;
};
static int
network_parse_ip(void *section, struct uci_optmap *om, union ucimap_data *data, const char *str)
{
unsigned char *target;
int tmp[4];
int i;
if (sscanf(str, "%d.%d.%d.%d", &tmp[0], &tmp[1], &tmp[2], &tmp[3]) != 4)
return -1;
target = malloc(4);
if (!target)
return -1;
data->ptr = target;
for (i = 0; i < 4; i++)
target[i] = (char) tmp[i];
return 0;
}
static int
network_format_ip(void *section, struct uci_optmap *om, union ucimap_data *data, char **str)
{
static char buf[16];
unsigned char *ip = (unsigned char *) data->ptr;
if (ip) {
sprintf(buf, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
*str = buf;
} else {
*str = NULL;
}
return 0;
}
static void
network_free_ip(void *section, struct uci_optmap *om, void *ptr)
{
free(ptr);
}
static int
network_init_interface(struct uci_map *map, void *section, struct uci_section *s)
{
struct uci_network *net = section;
INIT_LIST_HEAD(&net->list);
INIT_LIST_HEAD(&net->alias);
net->name = s->e.name;
net->test = -1;
return 0;
}
static int
network_init_alias(struct uci_map *map, void *section, struct uci_section *s)
{
struct uci_alias *alias = section;
INIT_LIST_HEAD(&alias->list);
alias->name = s->e.name;
return 0;
}
static int
network_add_interface(struct uci_map *map, void *section)
{
struct uci_network *net = section;
list_add_tail(&net->list, &ifs);
return 0;
}
static int
network_add_alias(struct uci_map *map, void *section)
{
struct uci_alias *a = section;
if (a->interface)
list_add_tail(&a->list, &a->interface->alias);
return 0;
}
static struct ucimap_section_data *
network_allocate(struct uci_map *map, struct uci_sectionmap *sm, struct uci_section *s)
{
struct uci_network *p = malloc(sizeof(struct uci_network));
memset(p, 0, sizeof(struct uci_network));
return &p->map;
}
struct my_optmap {
struct uci_optmap map;
int test;
};
static struct uci_sectionmap network_interface;
static struct uci_sectionmap network_alias;
static struct my_optmap network_interface_options[] = {
{
.map = {
UCIMAP_OPTION(struct uci_network, proto),
.type = UCIMAP_STRING,
.name = "proto",
.data.s.maxlen = 32,
}
},
{
.map = {
UCIMAP_OPTION(struct uci_network, ifname),
.type = UCIMAP_STRING,
.name = "ifname"
}
},
{
.map = {
UCIMAP_OPTION(struct uci_network, ipaddr),
.type = UCIMAP_CUSTOM,
.name = "ipaddr",
.parse = network_parse_ip,
.format = network_format_ip,
.free = network_free_ip,
}
},
{
.map = {
UCIMAP_OPTION(struct uci_network, enabled),
.type = UCIMAP_BOOL,
.name = "enabled",
}
},
{
.map = {
UCIMAP_OPTION(struct uci_network, test),
.type = UCIMAP_INT,
.name = "test"
}
},
{
.map = {
UCIMAP_OPTION(struct uci_network, aliases),
.type = UCIMAP_LIST | UCIMAP_SECTION | UCIMAP_LIST_AUTO,
.data.sm = &network_alias
}
}
};
static struct uci_sectionmap network_interface = {
UCIMAP_SECTION(struct uci_network, map),
.type = "interface",
.alloc = network_allocate,
.init = network_init_interface,
.add = network_add_interface,
.options = &network_interface_options[0].map,
.n_options = ARRAY_SIZE(network_interface_options),
.options_size = sizeof(struct my_optmap)
};
static struct uci_optmap network_alias_options[] = {
{
UCIMAP_OPTION(struct uci_alias, interface),
.type = UCIMAP_SECTION,
.data.sm = &network_interface
}
};
static struct uci_sectionmap network_alias = {
UCIMAP_SECTION(struct uci_alias, map),
.type = "alias",
.options = network_alias_options,
.init = network_init_alias,
.add = network_add_alias,
.n_options = ARRAY_SIZE(network_alias_options),
};
static struct uci_sectionmap *network_smap[] = {
&network_interface,
&network_alias,
};
static struct uci_map network_map = {
.sections = network_smap,
.n_sections = ARRAY_SIZE(network_smap),
};
int main(int argc, char **argv)
{
struct uci_context *ctx;
struct uci_package *pkg;
struct list_head *p;
struct uci_network *net;
struct uci_alias *alias;
bool set = false;
int i;
INIT_LIST_HEAD(&ifs);
ctx = uci_alloc_context();
ucimap_init(&network_map);
if ((argc >= 2) && !strcmp(argv[1], "-s")) {
uci_set_savedir(ctx, "./test/save");
set = true;
}
uci_set_confdir(ctx, "./test/config");
uci_load(ctx, "network", &pkg);
ucimap_parse(&network_map, pkg);
list_for_each(p, &ifs) {
const unsigned char *ipaddr;
int n_aliases = 0;
net = list_entry(p, struct uci_network, list);
ipaddr = net->ipaddr;
if (!ipaddr)
ipaddr = (const unsigned char *) "\x00\x00\x00\x00";
printf("New network section '%s'\n"
" type: %s\n"
" ifname: %s\n"
" ipaddr: %d.%d.%d.%d\n"
" test: %d\n"
" enabled: %s\n",
net->name,
net->proto,
net->ifname,
ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3],
net->test,
(net->enabled ? "on" : "off"));
if (net->aliases->n_items > 0) {
printf("Configured aliases:");
for (i = 0; i < net->aliases->n_items; i++) {
alias = net->aliases->item[i].ptr;
printf(" %s", alias->name);
}
printf("\n");
}
list_for_each_entry(alias, &net->alias, list) {
n_aliases++;
for (i = 0; i < net->aliases->n_items; i++) {
if (alias == net->aliases->item[i].ptr)
goto next_alias;
}
printf("New alias: %s\n", alias->name);
next_alias:
continue;
}
if (set && !strcmp(net->name, "lan")) {
ucimap_free_item(&net->map, &net->ipaddr);
ucimap_set_changed(&net->map, &net->ipaddr);
ucimap_resize_list(&net->map, &net->aliases, n_aliases);
net->aliases->n_items = 0;
list_for_each_entry(alias, &net->alias, list) {
net->aliases->item[net->aliases->n_items++].ptr = alias;
}
ucimap_set_changed(&net->map, &net->aliases);
ucimap_store_section(&network_map, pkg, &net->map);
uci_save(ctx, pkg);
}
}
ucimap_cleanup(&network_map);
uci_free_context(ctx);
return 0;
}

View file

@ -1,926 +0,0 @@
/*
* ucimap.c - Library for the Unified Configuration Interface
* Copyright (C) 2008-2009 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
/*
* This file contains ucimap, an API for mapping UCI to C data structures
*/
#include <strings.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include "ucimap.h"
#include "uci_internal.h"
struct ucimap_alloc {
void *ptr;
};
struct ucimap_alloc_custom {
void *section;
struct uci_optmap *om;
void *ptr;
};
struct ucimap_fixup {
struct ucimap_fixup *next;
struct uci_sectionmap *sm;
const char *name;
enum ucimap_type type;
union ucimap_data *data;
};
#define ucimap_foreach_option(_sm, _o) \
if (!(_sm)->options_size) \
(_sm)->options_size = sizeof(struct uci_optmap); \
for (_o = &(_sm)->options[0]; \
((char *)(_o)) < ((char *) &(_sm)->options[0] + \
(_sm)->options_size * (_sm)->n_options); \
_o = (struct uci_optmap *) ((char *)(_o) + \
(_sm)->options_size))
static inline bool
ucimap_is_alloc(enum ucimap_type type)
{
switch(type & UCIMAP_SUBTYPE) {
case UCIMAP_STRING:
return true;
default:
return false;
}
}
static inline bool
ucimap_is_fixup(enum ucimap_type type)
{
switch(type & UCIMAP_SUBTYPE) {
case UCIMAP_SECTION:
return true;
default:
return false;
}
}
static inline bool
ucimap_is_simple(enum ucimap_type type)
{
return ((type & UCIMAP_TYPE) == UCIMAP_SIMPLE);
}
static inline bool
ucimap_is_list(enum ucimap_type type)
{
return ((type & UCIMAP_TYPE) == UCIMAP_LIST);
}
static inline bool
ucimap_is_list_auto(enum ucimap_type type)
{
return ucimap_is_list(type) && !!(type & UCIMAP_LIST_AUTO);
}
static inline bool
ucimap_is_custom(enum ucimap_type type)
{
return ((type & UCIMAP_SUBTYPE) == UCIMAP_CUSTOM);
}
static inline void *
ucimap_section_ptr(struct ucimap_section_data *sd)
{
return ((char *) sd - sd->sm->smap_offset);
}
static inline struct ucimap_section_data *
ucimap_ptr_section(struct uci_sectionmap *sm, void *ptr) {
ptr = (char *) ptr + sm->smap_offset;
return ptr;
}
static inline union ucimap_data *
ucimap_get_data(struct ucimap_section_data *sd, struct uci_optmap *om)
{
void *data;
data = (char *) ucimap_section_ptr(sd) + om->offset;
return data;
}
int
ucimap_init(struct uci_map *map)
{
map->fixup = NULL;
map->sdata = NULL;
map->fixup_tail = &map->fixup;
map->sdata_tail = &map->sdata;
return 0;
}
static void
ucimap_add_alloc(struct ucimap_section_data *sd, void *ptr)
{
struct ucimap_alloc *a = &sd->allocmap[sd->allocmap_len++];
a->ptr = ptr;
}
void
ucimap_free_section(struct uci_map *map, struct ucimap_section_data *sd)
{
void *section;
int i;
section = ucimap_section_ptr(sd);
if (sd->ref)
*sd->ref = sd->next;
if (sd->sm->free)
sd->sm->free(map, section);
for (i = 0; i < sd->allocmap_len; i++) {
free(sd->allocmap[i].ptr);
}
if (sd->alloc_custom) {
for (i = 0; i < sd->alloc_custom_len; i++) {
struct ucimap_alloc_custom *a = &sd->alloc_custom[i];
a->om->free(a->section, a->om, a->ptr);
}
free(sd->alloc_custom);
}
free(sd->allocmap);
free(sd);
}
void
ucimap_cleanup(struct uci_map *map)
{
struct ucimap_section_data *sd;
for (sd = map->sdata; sd; sd = sd->next) {
ucimap_free_section(map, sd);
}
}
static void *
ucimap_find_section(struct uci_map *map, struct ucimap_fixup *f)
{
struct ucimap_section_data *sd;
for (sd = map->sdata; sd; sd = sd->next) {
if (sd->sm != f->sm)
continue;
if (strcmp(f->name, sd->section_name) != 0)
continue;
return ucimap_section_ptr(sd);
}
for (sd = map->pending; sd; sd = sd->next) {
if (sd->sm != f->sm)
continue;
if (strcmp(f->name, sd->section_name) != 0)
continue;
return ucimap_section_ptr(sd);
}
return NULL;
}
static union ucimap_data *
ucimap_list_append(struct ucimap_list *list)
{
if (unlikely(list->size <= list->n_items)) {
/* should not happen */
DPRINTF("ERROR: overflow while filling a list (size=%d)\n", list->size);
return NULL;
}
return &list->item[list->n_items++];
}
static bool
ucimap_handle_fixup(struct uci_map *map, struct ucimap_fixup *f)
{
void *ptr = ucimap_find_section(map, f);
struct ucimap_list *list;
union ucimap_data *data;
if (!ptr)
return false;
switch(f->type & UCIMAP_TYPE) {
case UCIMAP_SIMPLE:
f->data->ptr = ptr;
break;
case UCIMAP_LIST:
list = f->data->list;
data = ucimap_list_append(f->data->list);
if (!data)
return false;
data->ptr = ptr;
break;
}
return true;
}
void
ucimap_free_item(struct ucimap_section_data *sd, void *item)
{
struct ucimap_alloc_custom *ac;
struct ucimap_alloc *a;
void *ptr = *((void **) item);
int i;
if (!ptr)
return;
*((void **)item) = NULL;
for (i = 0, a = sd->allocmap; i < sd->allocmap_len; i++, a++) {
if (a->ptr != ptr)
continue;
if (i != sd->allocmap_len - 1)
a->ptr = sd->allocmap[sd->allocmap_len - 1].ptr;
sd->allocmap_len--;
return;
}
for (i = 0, ac = sd->alloc_custom; i < sd->alloc_custom_len; i++, ac++) {
if (ac->ptr != ptr)
continue;
if (i != sd->alloc_custom_len - 1)
memcpy(ac, &sd->alloc_custom[sd->alloc_custom_len - 1],
sizeof(struct ucimap_alloc_custom));
ac->om->free(ac->section, ac->om, ac->ptr);
sd->alloc_custom_len--;
return;
}
}
int
ucimap_resize_list(struct ucimap_section_data *sd, struct ucimap_list **list, int items)
{
struct ucimap_list *new;
struct ucimap_alloc *a;
int i, offset = 0;
int size = sizeof(struct ucimap_list) + items * sizeof(union ucimap_data);
if (!*list) {
new = calloc(1, size);
ucimap_add_alloc(sd, new);
goto set;
}
for (i = 0, a = sd->allocmap; i < sd->allocmap_len; i++, a++) {
if (a->ptr != *list)
continue;
goto realloc;
}
return -ENOENT;
realloc:
if (items > (*list)->size)
offset = (items - (*list)->size) * sizeof(union ucimap_data);
a->ptr = realloc(a->ptr, size);
if (offset)
memset((char *) a->ptr + offset, 0, size - offset);
new = a->ptr;
set:
new->size = items;
*list = new;
return 0;
}
static void
ucimap_add_fixup(struct ucimap_section_data *sd, union ucimap_data *data, struct uci_optmap *om, const char *str)
{
struct ucimap_fixup *f, tmp;
struct uci_map *map = sd->map;
tmp.sm = om->data.sm;
tmp.name = str;
tmp.type = om->type;
tmp.data = data;
if (ucimap_handle_fixup(map, &tmp))
return;
f = malloc(sizeof(struct ucimap_fixup));
if (!f)
return;
memcpy(f, &tmp, sizeof(tmp));
f->next = NULL;
*map->fixup_tail = f;
map->fixup_tail = &f->next;
}
static void
ucimap_add_custom_alloc(struct ucimap_section_data *sd, struct uci_optmap *om, void *ptr)
{
struct ucimap_alloc_custom *a = &sd->alloc_custom[sd->alloc_custom_len++];
a->section = ucimap_section_ptr(sd);
a->om = om;
a->ptr = ptr;
}
static void
ucimap_add_value(union ucimap_data *data, struct uci_optmap *om, struct ucimap_section_data *sd, const char *str)
{
union ucimap_data tdata = *data;
char *eptr = NULL;
long lval;
char *s;
int val;
if (ucimap_is_list(om->type) && !ucimap_is_fixup(om->type)) {
data = ucimap_list_append(data->list);
if (!data)
return;
}
switch(om->type & UCIMAP_SUBTYPE) {
case UCIMAP_STRING:
if ((om->data.s.maxlen > 0) &&
(strlen(str) > om->data.s.maxlen))
return;
s = strdup(str);
tdata.s = s;
ucimap_add_alloc(sd, s);
break;
case UCIMAP_BOOL:
if (!strcmp(str, "on"))
val = true;
else if (!strcmp(str, "1"))
val = true;
else if (!strcmp(str, "enabled"))
val = true;
else if (!strcmp(str, "off"))
val = false;
else if (!strcmp(str, "0"))
val = false;
else if (!strcmp(str, "disabled"))
val = false;
else
return;
tdata.b = val;
break;
case UCIMAP_INT:
lval = strtol(str, &eptr, om->data.i.base);
if (lval < INT_MIN || lval > INT_MAX)
return;
if (!eptr || *eptr == '\0')
tdata.i = (int) lval;
else
return;
break;
case UCIMAP_SECTION:
ucimap_add_fixup(sd, data, om, str);
return;
case UCIMAP_CUSTOM:
break;
}
if (om->parse) {
if (om->parse(ucimap_section_ptr(sd), om, data, str) < 0)
return;
if (ucimap_is_custom(om->type) && om->free) {
if (tdata.ptr != data->ptr)
ucimap_add_custom_alloc(sd, om, data->ptr);
}
}
if (ucimap_is_custom(om->type))
return;
memcpy(data, &tdata, sizeof(union ucimap_data));
}
static void
ucimap_convert_list(union ucimap_data *data, struct uci_optmap *om, struct ucimap_section_data *sd, const char *str)
{
char *s, *p;
s = strdup(str);
if (!s)
return;
ucimap_add_alloc(sd, s);
do {
while (isspace(*s))
s++;
if (!*s)
break;
p = s;
while (*s && !isspace(*s))
s++;
if (isspace(*s)) {
*s = 0;
s++;
}
ucimap_add_value(data, om, sd, p);
} while (*s);
}
static int
ucimap_parse_options(struct uci_map *map, struct uci_sectionmap *sm, struct ucimap_section_data *sd, struct uci_section *s)
{
struct uci_element *e, *l;
struct uci_option *o;
union ucimap_data *data;
uci_foreach_element(&s->options, e) {
struct uci_optmap *om = NULL, *tmp;
ucimap_foreach_option(sm, tmp) {
if (strcmp(e->name, tmp->name) == 0) {
om = tmp;
break;
}
}
if (!om)
continue;
data = ucimap_get_data(sd, om);
o = uci_to_option(e);
if ((o->type == UCI_TYPE_STRING) && ucimap_is_simple(om->type)) {
ucimap_add_value(data, om, sd, o->v.string);
} else if ((o->type == UCI_TYPE_LIST) && ucimap_is_list(om->type)) {
uci_foreach_element(&o->v.list, l) {
ucimap_add_value(data, om, sd, l->name);
}
} else if ((o->type == UCI_TYPE_STRING) && ucimap_is_list_auto(om->type)) {
ucimap_convert_list(data, om, sd, o->v.string);
}
}
return 0;
}
static void
ucimap_add_section_list(struct uci_map *map, struct ucimap_section_data *sd)
{
sd->ref = map->sdata_tail;
*sd->ref = sd;
map->sdata_tail = &sd->next;
}
static void
ucimap_add_section(struct ucimap_section_data *sd)
{
struct uci_map *map = sd->map;
sd->next = NULL;
if (sd->sm->add(map, ucimap_section_ptr(sd)) < 0)
ucimap_free_section(map, sd);
else
ucimap_add_section_list(map, sd);
}
#ifdef UCI_DEBUG
static const char *ucimap_type_names[] = {
[UCIMAP_STRING] = "string",
[UCIMAP_INT] = "integer",
[UCIMAP_BOOL] = "boolean",
[UCIMAP_SECTION] = "section",
[UCIMAP_LIST] = "list",
};
static const char *
ucimap_get_type_name(int type)
{
static char buf[32];
const char *name;
if (ucimap_is_list(type))
return ucimap_type_names[UCIMAP_LIST];
name = ucimap_type_names[type & UCIMAP_SUBTYPE];
if (!name) {
sprintf(buf, "Unknown (%d)", type & UCIMAP_SUBTYPE);
name = buf;
}
return name;
}
#endif
static bool
ucimap_check_optmap_type(struct uci_sectionmap *sm, struct uci_optmap *om)
{
unsigned int type;
if (unlikely(sm->type_name != om->type_name) &&
unlikely(strcmp(sm->type_name, om->type_name) != 0)) {
DPRINTF("Option '%s' of section type '%s' refereces unknown "
"section type '%s', should be '%s'.\n",
om->name, sm->type, om->type_name, sm->type_name);
return false;
}
if (om->detected_type < 0)
return true;
if (ucimap_is_custom(om->type))
return true;
if (ucimap_is_list(om->type) !=
ucimap_is_list(om->detected_type))
goto failed;
if (ucimap_is_list(om->type))
return true;
type = om->type & UCIMAP_SUBTYPE;
switch(type) {
case UCIMAP_STRING:
case UCIMAP_INT:
case UCIMAP_BOOL:
if (type != om->detected_type)
goto failed;
break;
case UCIMAP_SECTION:
goto failed;
default:
break;
}
return true;
failed:
DPRINTF("Invalid type in option '%s' of section type '%s', "
"declared type is %s, detected type is %s\n",
om->name, sm->type,
ucimap_get_type_name(om->type),
ucimap_get_type_name(om->detected_type));
return false;
}
static void
ucimap_count_alloc(struct uci_optmap *om, int *n_alloc, int *n_custom)
{
if (ucimap_is_alloc(om->type))
(*n_alloc)++;
else if (ucimap_is_custom(om->type) && om->free)
(*n_custom)++;
}
int
ucimap_parse_section(struct uci_map *map, struct uci_sectionmap *sm, struct ucimap_section_data *sd, struct uci_section *s)
{
struct uci_optmap *om;
char *section_name;
void *section;
int n_alloc = 2;
int n_alloc_custom = 0;
int err;
sd->map = map;
sd->sm = sm;
ucimap_foreach_option(sm, om) {
if (!ucimap_check_optmap_type(sm, om))
continue;
if (ucimap_is_list(om->type)) {
union ucimap_data *data;
struct uci_element *e;
int n_elements = 0;
int n_elements_alloc = 0;
int n_elements_custom = 0;
int size;
data = ucimap_get_data(sd, om);
uci_foreach_element(&s->options, e) {
struct uci_option *o = uci_to_option(e);
struct uci_element *tmp;
if (strcmp(e->name, om->name) != 0)
continue;
if (o->type == UCI_TYPE_LIST) {
uci_foreach_element(&o->v.list, tmp) {
ucimap_count_alloc(om, &n_elements_alloc, &n_elements_custom);
n_elements++;
}
} else if ((o->type == UCI_TYPE_STRING) &&
ucimap_is_list_auto(om->type)) {
const char *data = o->v.string;
do {
while (isspace(*data))
data++;
if (!*data)
break;
n_elements++;
ucimap_count_alloc(om, &n_elements_alloc, &n_elements_custom);
while (*data && !isspace(*data))
data++;
} while (*data);
/* for the duplicated data string */
if (n_elements)
n_alloc++;
}
break;
}
/* add one more for the ucimap_list */
n_alloc += n_elements_alloc + 1;
n_alloc_custom += n_elements_custom;
size = sizeof(struct ucimap_list) +
n_elements * sizeof(union ucimap_data);
data->list = malloc(size);
if (!data->list)
goto error_mem;
memset(data->list, 0, size);
data->list->size = n_elements;
} else {
ucimap_count_alloc(om, &n_alloc, &n_alloc_custom);
}
}
sd->allocmap = calloc(n_alloc, sizeof(struct ucimap_alloc));
if (!sd->allocmap)
goto error_mem;
if (n_alloc_custom > 0) {
sd->alloc_custom = calloc(n_alloc_custom, sizeof(struct ucimap_alloc_custom));
if (!sd->alloc_custom)
goto error_mem;
}
section_name = strdup(s->e.name);
if (!section_name)
goto error_mem;
sd->section_name = section_name;
sd->cmap = calloc(1, BITFIELD_SIZE(sm->n_options));
if (!sd->cmap)
goto error_mem;
ucimap_add_alloc(sd, (void *)section_name);
ucimap_add_alloc(sd, (void *)sd->cmap);
ucimap_foreach_option(sm, om) {
if (!ucimap_is_list(om->type))
continue;
ucimap_add_alloc(sd, ucimap_get_data(sd, om)->list);
}
section = ucimap_section_ptr(sd);
err = sm->init(map, section, s);
if (err)
goto error;
if (map->parsed) {
ucimap_add_section(sd);
} else {
ucimap_add_section_list(map, sd);
}
err = ucimap_parse_options(map, sm, sd, s);
if (err)
goto error;
return 0;
error_mem:
if (sd->allocmap)
free(sd->allocmap);
free(sd);
return UCI_ERR_MEM;
error:
ucimap_free_section(map, sd);
return err;
}
static int
ucimap_fill_ptr(struct uci_ptr *ptr, struct uci_section *s, const char *option)
{
struct uci_package *p = s->package;
memset(ptr, 0, sizeof(struct uci_ptr));
ptr->package = p->e.name;
ptr->p = p;
ptr->section = s->e.name;
ptr->s = s;
ptr->option = option;
return uci_lookup_ptr(p->ctx, ptr, NULL, false);
}
void
ucimap_set_changed(struct ucimap_section_data *sd, void *field)
{
void *section = ucimap_section_ptr(sd);
struct uci_sectionmap *sm = sd->sm;
struct uci_optmap *om;
int ofs = (char *)field - (char *)section;
int i = 0;
ucimap_foreach_option(sm, om) {
if (om->offset == ofs) {
SET_BIT(sd->cmap, i);
break;
}
i++;
}
}
static char *
ucimap_data_to_string(struct ucimap_section_data *sd, struct uci_optmap *om, union ucimap_data *data)
{
static char buf[32];
char *str = NULL;
switch(om->type & UCIMAP_SUBTYPE) {
case UCIMAP_STRING:
str = data->s;
break;
case UCIMAP_INT:
sprintf(buf, "%d", data->i);
str = buf;
break;
case UCIMAP_BOOL:
sprintf(buf, "%d", !!data->b);
str = buf;
break;
case UCIMAP_SECTION:
if (data->ptr)
str = (char *) ucimap_ptr_section(om->data.sm, data->ptr)->section_name;
else
str = "";
break;
case UCIMAP_CUSTOM:
break;
default:
return NULL;
}
if (om->format) {
if (om->format(ucimap_section_ptr(sd), om, data, &str) < 0)
return NULL;
if (!str)
str = "";
}
return str;
}
int
ucimap_store_section(struct uci_map *map, struct uci_package *p, struct ucimap_section_data *sd)
{
struct uci_sectionmap *sm = sd->sm;
struct uci_section *s = NULL;
struct uci_optmap *om;
struct uci_element *e;
struct uci_ptr ptr;
int i = 0;
int ret;
uci_foreach_element(&p->sections, e) {
if (!strcmp(e->name, sd->section_name)) {
s = uci_to_section(e);
break;
}
}
if (!s)
return UCI_ERR_NOTFOUND;
ucimap_foreach_option(sm, om) {
union ucimap_data *data;
i++;
data = ucimap_get_data(sd, om);
if (!TEST_BIT(sd->cmap, i - 1))
continue;
ucimap_fill_ptr(&ptr, s, om->name);
if (ucimap_is_list(om->type)) {
struct ucimap_list *list = data->list;
bool first = true;
int j;
for (j = 0; j < list->n_items; j++) {
ptr.value = ucimap_data_to_string(sd, om, &list->item[j]);
if (!ptr.value)
continue;
if (first) {
ret = uci_set(s->package->ctx, &ptr);
first = false;
} else {
ret = uci_add_list(s->package->ctx, &ptr);
}
if (ret)
return ret;
}
} else {
ptr.value = ucimap_data_to_string(sd, om, data);
if (!ptr.value)
continue;
ret = uci_set(s->package->ctx, &ptr);
if (ret)
return ret;
}
CLR_BIT(sd->cmap, i - 1);
}
return 0;
}
void
ucimap_parse(struct uci_map *map, struct uci_package *pkg)
{
struct uci_element *e;
struct ucimap_section_data *sd, **sd_tail;
struct ucimap_fixup *f;
int i;
sd_tail = map->sdata_tail;
map->parsed = false;
map->sdata_tail = &map->pending;
uci_foreach_element(&pkg->sections, e) {
struct uci_section *s = uci_to_section(e);
for (i = 0; i < map->n_sections; i++) {
struct uci_sectionmap *sm = map->sections[i];
struct ucimap_section_data *sd;
if (strcmp(s->type, map->sections[i]->type) != 0)
continue;
if (sm->alloc) {
sd = sm->alloc(map, sm, s);
memset(sd, 0, sizeof(struct ucimap_section_data));
} else {
sd = malloc(sm->alloc_len);
memset(sd, 0, sm->alloc_len);
}
if (!sd)
continue;
ucimap_parse_section(map, sm, sd, s);
}
}
if (!map->parsed) {
map->parsed = true;
map->sdata_tail = sd_tail;
}
f = map->fixup;
while (f) {
struct ucimap_fixup *next = f->next;
ucimap_handle_fixup(map, f);
free(f);
f = next;
}
map->fixup_tail = &map->fixup;
map->fixup = NULL;
sd = map->pending;
while (sd) {
struct ucimap_section_data *next = sd->next;
ucimap_add_section(sd);
sd = next;
}
map->pending = NULL;
}

View file

@ -1,324 +0,0 @@
/*
* ucimap.h - Library for the Unified Configuration Interface
* Copyright (C) 2008-2009 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
/*
* This file contains ucimap, an API for mapping UCI to C data structures
*/
#ifndef __UCIMAP_H
#define __UCIMAP_H
#include <stdbool.h>
#include "uci.h"
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
#define BITFIELD_SIZE(_fields) (((_fields) / 8) + 1)
#define CLR_BIT(_name, _bit) do { \
_name[(_bit) / 8] &= ~(1 << ((_bit) % 8)); \
} while (0)
#define SET_BIT(_name, _bit) do { \
_name[(_bit) / 8] |= (1 << ((_bit) % 8)); \
} while (0)
#define TEST_BIT(_name, _bit) \
(_name[(_bit) / 8] & (1 << ((_bit) % 8)))
#ifndef __GNUC__
#define __optmap_gen_type(_type, _field) -1
#ifndef likely
#define likely(_expr) !!(_expr)
#endif
#ifndef unlikely
#define unlikely(_expr) !!(_expr)
#endif
#else /* __GNUC__ */
#define __compatible(_type, _field, _newtype) \
__builtin_types_compatible_p(typeof(&(((_type *)0)->_field)), _newtype *)
#define __list_compatible(_type, _field, __val, __else) \
__builtin_choose_expr(__compatible(_type, _field, struct ucimap_list *), __val, __else)
#define __int_compatible(_type, _field, __val, __else) \
__builtin_choose_expr(__compatible(_type, _field, int), __val, \
__builtin_choose_expr(__compatible(_type, _field, unsigned int), __val, \
__else))
#define __string_compatible(_type, _field, __val, __else) \
__builtin_choose_expr(__compatible(_type, _field, char *), __val, \
__builtin_choose_expr(__compatible(_type, _field, unsigned char *), __val, \
__builtin_choose_expr(__compatible(_type, _field, const char *), __val, \
__builtin_choose_expr(__compatible(_type, _field, const unsigned char *), __val, \
__else))))
#define __bool_compatible(_type, _field, __val, __else) \
__builtin_choose_expr(__compatible(_type, _field, bool), __val, __else)
#define __optmap_gen_type(_type, _field) \
__list_compatible(_type, _field, UCIMAP_LIST, \
__int_compatible(_type, _field, UCIMAP_INT, \
__string_compatible(_type, _field, UCIMAP_STRING, \
__bool_compatible(_type, _field, UCIMAP_BOOL, \
-1))))
#ifndef likely
#define likely(x) __builtin_expect(!!(x), 1)
#endif
#ifndef unlikely
#define unlikely(x) __builtin_expect(!!(x), 0)
#endif
#endif /* __GNUC__ */
#define UCIMAP_OPTION(_type, _field) \
.type = UCIMAP_CUSTOM, \
.name = #_field, \
.offset = offsetof(_type, _field), \
.detected_type = __optmap_gen_type(_type, _field), \
.type_name = #_type
#define UCIMAP_SECTION(_name, _field) \
.alloc_len = sizeof(_name), \
.smap_offset = offsetof(_name, _field), \
.type_name = #_name
struct uci_sectionmap;
struct uci_optmap;
struct ucimap_list;
struct ucimap_fixup;
struct ucimap_alloc;
struct ucimap_alloc_custom;
struct ucimap_section_data;
struct uci_map {
struct uci_sectionmap **sections;
unsigned int n_sections;
bool parsed;
void *priv;
/* private */
struct ucimap_fixup *fixup;
struct ucimap_fixup **fixup_tail;
struct ucimap_section_data *sdata;
struct ucimap_section_data *pending;
struct ucimap_section_data **sdata_tail;
};
enum ucimap_type {
/* types */
UCIMAP_SIMPLE = 0x00,
UCIMAP_LIST = 0x10,
UCIMAP_TYPE = 0xf0, /* type mask */
/* subtypes */
UCIMAP_STRING = 0x0,
UCIMAP_BOOL = 0x1,
UCIMAP_INT = 0x2,
UCIMAP_SECTION = 0x3,
UCIMAP_CUSTOM = 0x4,
UCIMAP_SUBTYPE = 0xf, /* subtype mask */
/* automatically create lists from
* options with space-separated items */
UCIMAP_LIST_AUTO = 0x0100,
UCIMAP_FLAGS = 0xff00, /* flags mask */
};
union ucimap_data {
int i;
bool b;
char *s;
void *ptr;
void **data;
struct ucimap_list *list;
};
struct ucimap_section_data {
struct uci_map *map;
struct uci_sectionmap *sm;
const char *section_name;
/* map for changed fields */
unsigned char *cmap;
bool done;
/* internal */
struct ucimap_section_data *next, **ref;
struct ucimap_alloc *allocmap;
struct ucimap_alloc_custom *alloc_custom;
unsigned int allocmap_len;
unsigned int alloc_custom_len;
};
struct uci_sectionmap {
/* type string for the uci section */
const char *type;
/* length of the struct to map into, filled in by macro */
unsigned int alloc_len;
/* sectionmap offset, filled in by macro */
unsigned int smap_offset;
/* return a pointer to the section map data (allocate if necessary) */
struct ucimap_section_data *(*alloc)(struct uci_map *map,
struct uci_sectionmap *sm, struct uci_section *s);
/* give the caller time to initialize the preallocated struct */
int (*init)(struct uci_map *map, void *section, struct uci_section *s);
/* pass the fully processed struct to the callback after the section end */
int (*add)(struct uci_map *map, void *section);
/* let the callback clean up its own stuff in the section */
int (*free)(struct uci_map *map, void *section);
/* list of option mappings for this section */
struct uci_optmap *options;
unsigned int n_options;
unsigned int options_size;
/* internal */
const char *type_name;
};
struct uci_optmap {
unsigned int offset;
const char *name;
enum ucimap_type type;
int (*parse)(void *section, struct uci_optmap *om, union ucimap_data *data, const char *string);
int (*format)(void *section, struct uci_optmap *om, union ucimap_data *data, char **string);
void (*free)(void *section, struct uci_optmap *om, void *ptr);
union {
struct {
int base;
int min;
int max;
} i;
struct {
int maxlen;
} s;
struct uci_sectionmap *sm;
} data;
/* internal */
int detected_type;
const char *type_name;
};
struct ucimap_list {
int n_items;
int size;
union ucimap_data item[];
};
/**
* ucimap_init: initialize the ucimap data structure
* @map: ucimap data structure
*
* you must call this function before doing any other ucimap operation
* on the data structure
*/
extern int ucimap_init(struct uci_map *map);
/**
* ucimap_cleanup: clean up all allocated data from ucimap
* @map: ucimap data structure
*/
extern void ucimap_cleanup(struct uci_map *map);
/**
* ucimap_parse: parse all sections in an uci package using ucimap
* @map: ucimap data structure
* @pkg: uci package
*/
extern void ucimap_parse(struct uci_map *map, struct uci_package *pkg);
/**
* ucimap_set_changed: mark a field in a custom data structure as changed
* @sd: pointer to the ucimap section data
* @field: pointer to the field inside the custom data structure
*
* @sd must be set to the section data inside the data structure that contains @field
*/
extern void ucimap_set_changed(struct ucimap_section_data *sd, void *field);
/**
* ucimap_store_section: copy all changed data from the converted data structure to uci
* @map: ucimap data structure
* @p: uci package to store the changes in
* @sd: pointer to the ucimap section data
*
* changes are not saved or committed automatically
*/
extern int ucimap_store_section(struct uci_map *map, struct uci_package *p, struct ucimap_section_data *sd);
/**
* ucimap_parse_section: parse a single section
* @map: ucimap data structure
* @sm: uci section map
* @sd: pointer to the ucimap section data
* @s: pointer to the uci section
*
* this function overwrites the ucimap section data, do not use on a section
* that has been parsed already
*/
extern int ucimap_parse_section(struct uci_map *map, struct uci_sectionmap *sm, struct ucimap_section_data *sd, struct uci_section *s);
/**
* ucimap_free_section: free a data structure for a converted section
* @map: ucimap data structure
* @sd: pointer to the ucimap section data
*
* this function will clean up all data that was allocated by ucimap for this section.
* all references to the data structure become invalid
*/
extern void ucimap_free_section(struct uci_map *map, struct ucimap_section_data *sd);
/**
* ucimap_resize_list: allocate or resize a uci list
* @sd: pointer to the ucimap section data
* @list: pointer to the list field
* @items: new size
*
* @sd must point to the data structure that contains @list.
* @list must point to the field containing a pointer to the list, not the list directly
* the memory allocated for this list is tracked for the section and freed automatically
*/
extern int ucimap_resize_list(struct ucimap_section_data *sd, struct ucimap_list **list, int items);
/**
* ucimap_free_item: free the allocated memory for a data structure member
* @sd: pointer to the ucimap section data
* @item: pointer to the field inside the data structure
*
* @sd must point to the data structure that contains @item.
* @item must point to the field containing a pointer to the allocated item
*/
extern void ucimap_free_item(struct ucimap_section_data *sd, void *item);
#endif

View file

@ -1,489 +0,0 @@
/*
* libuci - Library for the Unified Configuration Interface
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program 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.
*/
/*
* This file contains misc utility functions and wrappers to standard
* functions, which throw exceptions upon failure.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <stdbool.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#define LINEBUF 32
#define LINEBUF_MAX 4096
__plugin void *uci_malloc(struct uci_context *ctx, size_t size)
{
void *ptr;
ptr = malloc(size);
if (!ptr)
UCI_THROW(ctx, UCI_ERR_MEM);
memset(ptr, 0, size);
return ptr;
}
__plugin void *uci_realloc(struct uci_context *ctx, void *ptr, size_t size)
{
ptr = realloc(ptr, size);
if (!ptr)
UCI_THROW(ctx, UCI_ERR_MEM);
return ptr;
}
__plugin char *uci_strdup(struct uci_context *ctx, const char *str)
{
char *ptr;
ptr = strdup(str);
if (!ptr)
UCI_THROW(ctx, UCI_ERR_MEM);
return ptr;
}
/* Based on an efficient hash function published by D. J. Bernstein */
static unsigned int djbhash(unsigned int hash, char *str)
{
int len = strlen(str);
int i;
/* initial value */
if (hash == ~0)
hash = 5381;
for(i = 0; i < len; i++) {
hash = ((hash << 5) + hash) + str[i];
}
return (hash & 0x7FFFFFFF);
}
/*
* validate strings for names and types, reject special characters
* for names, only alphanum and _ is allowed (shell compatibility)
* for types, we allow more characters
*/
__plugin bool uci_validate_str(const char *str, bool name)
{
if (!*str)
return false;
while (*str) {
unsigned char c = *str;
if (!isalnum(c) && c != '_') {
if (name || (c < 33) || (c > 126))
return false;
}
str++;
}
return true;
}
static inline bool uci_validate_package(const char *str)
{
return uci_validate_str(str, false);
}
static inline bool uci_validate_type(const char *str)
{
return uci_validate_str(str, false);
}
static inline bool uci_validate_name(const char *str)
{
return uci_validate_str(str, true);
}
bool uci_validate_text(const char *str)
{
while (*str) {
unsigned char c = *str;
if ((c == '\r') || (c == '\n') ||
((c < 32) && (c != '\t')))
return false;
str++;
}
return true;
}
static void uci_alloc_parse_context(struct uci_context *ctx)
{
ctx->pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
}
int uci_parse_ptr(struct uci_context *ctx, struct uci_ptr *ptr, char *str)
{
char *last = NULL;
char *tmp;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, str);
UCI_ASSERT(ctx, ptr);
memset(ptr, 0, sizeof(struct uci_ptr));
/* value */
last = strchr(str, '=');
if (last) {
*last = 0;
last++;
ptr->value = last;
}
ptr->package = strsep(&str, ".");
if (!ptr->package)
goto error;
ptr->section = strsep(&str, ".");
if (!ptr->section) {
ptr->target = UCI_TYPE_PACKAGE;
goto lastval;
}
ptr->option = strsep(&str, ".");
if (!ptr->option) {
ptr->target = UCI_TYPE_SECTION;
goto lastval;
} else {
ptr->target = UCI_TYPE_OPTION;
}
tmp = strsep(&str, ".");
if (tmp)
goto error;
lastval:
if (ptr->package && !uci_validate_package(ptr->package))
goto error;
if (ptr->section && !uci_validate_name(ptr->section))
ptr->flags |= UCI_LOOKUP_EXTENDED;
if (ptr->option && !uci_validate_name(ptr->option))
goto error;
if (ptr->value && !uci_validate_text(ptr->value))
goto error;
return 0;
error:
memset(ptr, 0, sizeof(struct uci_ptr));
UCI_THROW(ctx, UCI_ERR_PARSE);
}
static void uci_parse_error(struct uci_context *ctx, char *pos, char *reason)
{
struct uci_parse_context *pctx = ctx->pctx;
pctx->reason = reason;
pctx->byte = pos - pctx->buf;
UCI_THROW(ctx, UCI_ERR_PARSE);
}
/*
* Fetch a new line from the input stream and resize buffer if necessary
*/
static void uci_getln(struct uci_context *ctx, int offset)
{
struct uci_parse_context *pctx = ctx->pctx;
char *p;
int ofs;
if (pctx->buf == NULL) {
pctx->buf = uci_malloc(ctx, LINEBUF);
pctx->bufsz = LINEBUF;
}
ofs = offset;
do {
p = &pctx->buf[ofs];
p[ofs] = 0;
p = fgets(p, pctx->bufsz - ofs, pctx->file);
if (!p || !*p)
return;
ofs += strlen(p);
if (pctx->buf[ofs - 1] == '\n') {
pctx->line++;
pctx->buf[ofs - 1] = 0;
return;
}
if (pctx->bufsz > LINEBUF_MAX/2)
uci_parse_error(ctx, p, "line too long");
pctx->bufsz *= 2;
pctx->buf = uci_realloc(ctx, pctx->buf, pctx->bufsz);
} while (1);
}
/*
* parse a character escaped by '\'
* returns true if the escaped character is to be parsed
* returns false if the escaped character is to be ignored
*/
static inline bool parse_backslash(struct uci_context *ctx, char **str)
{
/* skip backslash */
*str += 1;
/* undecoded backslash at the end of line, fetch the next line */
if (!**str) {
*str += 1;
uci_getln(ctx, *str - ctx->pctx->buf);
return false;
}
/* FIXME: decode escaped char, necessary? */
return true;
}
/*
* move the string pointer forward until a non-whitespace character or
* EOL is reached
*/
static void skip_whitespace(struct uci_context *ctx, char **str)
{
restart:
while (**str && isspace(**str))
*str += 1;
if (**str == '\\') {
if (!parse_backslash(ctx, str))
goto restart;
}
}
static inline void addc(char **dest, char **src)
{
**dest = **src;
*dest += 1;
*src += 1;
}
/*
* parse a double quoted string argument from the command line
*/
static void parse_double_quote(struct uci_context *ctx, char **str, char **target)
{
char c;
/* skip quote character */
*str += 1;
while ((c = **str)) {
switch(c) {
case '"':
**target = 0;
*str += 1;
return;
case '\\':
if (!parse_backslash(ctx, str))
continue;
/* fall through */
default:
addc(target, str);
break;
}
}
uci_parse_error(ctx, *str, "unterminated \"");
}
/*
* parse a single quoted string argument from the command line
*/
static void parse_single_quote(struct uci_context *ctx, char **str, char **target)
{
char c;
/* skip quote character */
*str += 1;
while ((c = **str)) {
switch(c) {
case '\'':
**target = 0;
*str += 1;
return;
default:
addc(target, str);
}
}
uci_parse_error(ctx, *str, "unterminated '");
}
/*
* parse a string from the command line and detect the quoting style
*/
static void parse_str(struct uci_context *ctx, char **str, char **target)
{
bool next = true;
do {
switch(**str) {
case '\'':
parse_single_quote(ctx, str, target);
break;
case '"':
parse_double_quote(ctx, str, target);
break;
case '#':
**str = 0;
/* fall through */
case 0:
goto done;
case ';':
next = false;
goto done;
case '\\':
if (!parse_backslash(ctx, str))
continue;
/* fall through */
default:
addc(target, str);
break;
}
} while (**str && !isspace(**str));
done:
/*
* if the string was unquoted and we've stopped at a whitespace
* character, skip to the next one, because the whitespace will
* be overwritten by a null byte here
*/
if (**str && next)
*str += 1;
/* terminate the parsed string */
**target = 0;
}
/*
* extract the next argument from the command line
*/
static char *next_arg(struct uci_context *ctx, char **str, bool required, bool name)
{
char *val;
char *ptr;
val = ptr = *str;
skip_whitespace(ctx, str);
if(*str[0] == ';') {
*str[0] = 0;
*str += 1;
} else {
parse_str(ctx, str, &ptr);
}
if (!*val) {
if (required)
uci_parse_error(ctx, *str, "insufficient arguments");
goto done;
}
if (name && !uci_validate_name(val))
uci_parse_error(ctx, val, "invalid character in field");
done:
return val;
}
int uci_parse_argument(struct uci_context *ctx, FILE *stream, char **str, char **result)
{
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, str != NULL);
UCI_ASSERT(ctx, result != NULL);
if (ctx->pctx && (ctx->pctx->file != stream))
uci_cleanup(ctx);
if (!ctx->pctx)
uci_alloc_parse_context(ctx);
ctx->pctx->file = stream;
if (!*str) {
uci_getln(ctx, 0);
*str = ctx->pctx->buf;
}
*result = next_arg(ctx, str, false, false);
return 0;
}
/*
* open a stream and go to the right position
*
* note: when opening for write and seeking to the beginning of
* the stream, truncate the file
*/
static FILE *uci_open_stream(struct uci_context *ctx, const char *filename, int pos, bool write, bool create)
{
struct stat statbuf;
FILE *file = NULL;
int fd, ret;
int mode = (write ? O_RDWR : O_RDONLY);
if (create)
mode |= O_CREAT;
if (!write && ((stat(filename, &statbuf) < 0) ||
((statbuf.st_mode & S_IFMT) != S_IFREG))) {
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
}
fd = open(filename, mode, UCI_FILEMODE);
if (fd < 0)
goto error;
ret = flock(fd, (write ? LOCK_EX : LOCK_SH));
if ((ret < 0) && (errno != ENOSYS))
goto error;
ret = lseek(fd, 0, pos);
if (ret < 0)
goto error;
file = fdopen(fd, (write ? "w+" : "r"));
if (file)
goto done;
error:
UCI_THROW(ctx, UCI_ERR_IO);
done:
return file;
}
static void uci_close_stream(FILE *stream)
{
int fd;
if (!stream)
return;
fflush(stream);
fd = fileno(stream);
flock(fd, LOCK_UN);
fclose(stream);
}