more cleanups
This commit is contained in:
parent
e588d54990
commit
2e6d469170
45 changed files with 0 additions and 8975 deletions
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
698
lib/uci/cli.c
698
lib/uci/cli.c
|
|
@ -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;
|
||||
}
|
||||
581
lib/uci/file.c
581
lib/uci/file.c
|
|
@ -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,
|
||||
);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
391
lib/uci/libuci.c
391
lib/uci/libuci.c
|
|
@ -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;
|
||||
}
|
||||
719
lib/uci/list.c
719
lib/uci/list.c
|
|
@ -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;
|
||||
}
|
||||
|
||||
601
lib/uci/list.h
601
lib/uci/list.h
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
all:
|
||||
sh ./tests.sh
|
||||
|
|
@ -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.
|
||||
|
|
@ -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'
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
+add.section=type
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
list 'list_opt' 'val0'
|
||||
list 'list_opt' 'val1'
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package 'export'
|
||||
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
list 'list_opt' 'val0'
|
||||
list 'list_opt' 'val1'
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
|
||||
config 'unnamed'
|
||||
option 'opt1' 'val1'
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package 'import'
|
||||
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
list 'list_opt' 'val0'
|
||||
list 'list_opt' 'val1'
|
||||
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
list 'list_opt' 'val0'
|
||||
list 'list_opt' 'val1'
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
config 'named' 'section'
|
||||
option 'opt' 'err'
|
||||
|
|
@ -1 +0,0 @@
|
|||
set.section.opt=val
|
||||
|
|
@ -1 +0,0 @@
|
|||
set.section=named
|
||||
|
|
@ -1 +0,0 @@
|
|||
config 'named' 'section'
|
||||
|
|
@ -1 +0,0 @@
|
|||
set.section.opt=val
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -1,5 +0,0 @@
|
|||
test_import ()
|
||||
{
|
||||
${UCI} import < ${REF_DIR}/import.data
|
||||
assertSameFile ${REF_DIR}/import.result ${CONFIG_DIR}/import
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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}
|
||||
631
lib/uci/uci.h
631
lib/uci/uci.h
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
926
lib/uci/ucimap.c
926
lib/uci/ucimap.c
|
|
@ -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;
|
||||
}
|
||||
324
lib/uci/ucimap.h
324
lib/uci/ucimap.h
|
|
@ -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
|
||||
489
lib/uci/util.c
489
lib/uci/util.c
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue