bump uci version 2016-07-04

This commit is contained in:
2016-12-12 20:35:57 +01:00
parent c874c662ca
commit b5a0cbc488
69 changed files with 9097 additions and 0 deletions

13
src/3P/uci/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
Makefile
CMakeCache.txt
CMakeFiles
*.cmake
*.o
*.a
*.so
*.dylib
install_manifest.txt
uci
uci_config.h
test/save

47
src/3P/uci/CMakeLists.txt Normal file
View File

@@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 2.6)
PROJECT(uci C)
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -I. -DUCI_PREFIX="${CMAKE_INSTALL_PREFIX}")
OPTION(UCI_DEBUG "debugging support" OFF)
OPTION(UCI_DEBUG_TYPECAST "typecast debugging support" OFF)
OPTION(BUILD_LUA "build Lua binding" ON)
IF(BUILD_STATIC)
FIND_LIBRARY(ubox_library NAMES ubox.a)
ELSE(BUILD_STATIC)
FIND_LIBRARY(ubox_library NAMES ubox)
ENDIF(BUILD_STATIC)
FIND_PATH(ubox_include_dir libubox/usock.h)
CONFIGURE_FILE( ${CMAKE_SOURCE_DIR}/uci_config.h.in ${CMAKE_SOURCE_DIR}/uci_config.h )
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR} ${ubox_include_dir})
SET(LIB_SOURCES libuci.c file.c util.c delta.c parse.c blob.c)
ADD_LIBRARY(uci SHARED ${LIB_SOURCES})
TARGET_LINK_LIBRARIES(uci ${ubox_library})
SET_TARGET_PROPERTIES(uci PROPERTIES OUTPUT_NAME uci)
ADD_EXECUTABLE(cli cli.c)
SET_TARGET_PROPERTIES(cli PROPERTIES OUTPUT_NAME uci)
TARGET_LINK_LIBRARIES(cli uci)
ADD_LIBRARY(ucimap STATIC ucimap.c)
ADD_SUBDIRECTORY(lua)
INSTALL(FILES uci.h uci_config.h uci_blob.h ucimap.h
DESTINATION include
)
INSTALL(TARGETS uci cli
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)

238
src/3P/uci/blob.c Normal file
View File

@@ -0,0 +1,238 @@
/*
* blob.c - uci <-> blobmsg conversion layer
* Copyright (C) 2012-2013 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 Lesser General Public License for more details.
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <libubox/blobmsg.h>
#include "uci.h"
#include "uci_blob.h"
static bool
uci_attr_to_blob(struct blob_buf *b, const char *str,
const char *name, enum blobmsg_type type)
{
char *err;
int intval;
long long llval;
switch (type) {
case BLOBMSG_TYPE_STRING:
blobmsg_add_string(b, name, str);
break;
case BLOBMSG_TYPE_BOOL:
if (!strcmp(str, "true") || !strcmp(str, "1"))
intval = 1;
else if (!strcmp(str, "false") || !strcmp(str, "0"))
intval = 0;
else
return false;
blobmsg_add_u8(b, name, intval);
break;
case BLOBMSG_TYPE_INT32:
intval = strtol(str, &err, 0);
if (*err)
return false;
blobmsg_add_u32(b, name, intval);
break;
case BLOBMSG_TYPE_INT64:
llval = strtoll(str, &err, 0);
if (*err)
return false;
blobmsg_add_u64(b, name, llval);
break;
default:
return false;
}
return true;
}
static void
uci_array_to_blob(struct blob_buf *b, struct uci_option *o,
enum blobmsg_type type)
{
struct uci_element *e;
char *str, *next, *word;
if (o->type == UCI_TYPE_LIST) {
uci_foreach_element(&o->v.list, e) {
uci_attr_to_blob(b, e->name, NULL, type);
}
return;
}
str = strdup(o->v.string);
next = str;
while ((word = strsep(&next, " \t")) != NULL) {
if (!*word)
continue;
uci_attr_to_blob(b, word, NULL, type);
}
free(str);
}
static int
__uci_element_to_blob(struct blob_buf *b, struct uci_element *e,
const struct uci_blob_param_list *p)
{
const struct blobmsg_policy *attr = NULL;
struct uci_option *o = uci_to_option(e);
unsigned int types = 0;
void *array;
int i, ret = 0;
for (i = 0; i < p->n_params; i++) {
attr = &p->params[i];
if (strcmp(attr->name, e->name) != 0)
continue;
if (attr->type > BLOBMSG_TYPE_LAST)
continue;
if (types & (1 << attr->type))
continue;
types |= 1 << attr->type;
if (attr->type == BLOBMSG_TYPE_ARRAY) {
int element_type = 0;
if (p->info)
element_type = p->info[i].type;
if (!element_type)
element_type = BLOBMSG_TYPE_STRING;
array = blobmsg_open_array(b, attr->name);
uci_array_to_blob(b, o, element_type);
blobmsg_close_array(b, array);
ret++;
continue;
}
if (o->type == UCI_TYPE_LIST)
continue;
ret += uci_attr_to_blob(b, o->v.string, attr->name, attr->type);
}
return ret;
}
static int
__uci_to_blob(struct blob_buf *b, struct uci_section *s,
const struct uci_blob_param_list *p)
{
struct uci_element *e;
int ret = 0;
uci_foreach_element(&s->options, e)
ret += __uci_element_to_blob(b, e, p);
return ret;
}
int
uci_to_blob(struct blob_buf *b, struct uci_section *s,
const struct uci_blob_param_list *p)
{
int ret = 0;
int i;
ret += __uci_to_blob(b, s, p);
for (i = 0; i < p->n_next; i++)
ret += uci_to_blob(b, s, p->next[i]);
return ret;
}
bool
uci_blob_diff(struct blob_attr **tb1, struct blob_attr **tb2,
const struct uci_blob_param_list *config, unsigned long *diff)
{
bool ret = false;
int i;
for (i = 0; i < config->n_params; i++) {
if (!tb1[i] && !tb2[i])
continue;
if (!!tb1[i] != !!tb2[i])
goto mark;
if (blob_len(tb1[i]) != blob_len(tb2[i]))
goto mark;
if (memcmp(tb1[i], tb2[i], blob_raw_len(tb1[i])) != 0)
goto mark;
continue;
mark:
ret = true;
if (diff)
bitfield_set(diff, i);
else
return ret;
}
return ret;
}
static bool
__uci_blob_check_equal(struct blob_attr *c1, struct blob_attr *c2,
const struct uci_blob_param_list *config)
{
struct blob_attr **tb1, **tb2;
if (!!c1 ^ !!c2)
return false;
if (!c1 && !c2)
return true;
tb1 = alloca(config->n_params * sizeof(struct blob_attr *));
blobmsg_parse(config->params, config->n_params, tb1,
blob_data(c1), blob_len(c1));
tb2 = alloca(config->n_params * sizeof(struct blob_attr *));
blobmsg_parse(config->params, config->n_params, tb2,
blob_data(c2), blob_len(c2));
return !uci_blob_diff(tb1, tb2, config, NULL);
}
bool
uci_blob_check_equal(struct blob_attr *c1, struct blob_attr *c2,
const struct uci_blob_param_list *config)
{
int i;
if (!__uci_blob_check_equal(c1, c2, config))
return false;
for (i = 0; i < config->n_next; i++) {
if (!__uci_blob_check_equal(c1, c2, config->next[i]))
return false;
}
return true;
}

777
src/3P/uci/cli.c Normal file
View File

@@ -0,0 +1,777 @@
/*
* 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 <stdarg.h>
#include <errno.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),
} flags;
static FILE *input;
static struct uci_context *ctx;
enum {
/* section cmds */
CMD_GET,
CMD_SET,
CMD_ADD_LIST,
CMD_DEL_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;
char *ret;
int maxlen;
if (!(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));
if (!ti)
return NULL;
memset(ti, 0, sizeof(struct uci_type_list));
ti->next = type_list;
type_list = ti;
ti->name = s->type;
}
if (s->anonymous) {
maxlen = strlen(s->type) + 1 + 2 + 10;
if (!typestr) {
typestr = malloc(maxlen);
} else {
typestr = realloc(typestr, maxlen);
}
if (typestr)
sprintf(typestr, "@%s[%d]", ti->name, ti->idx);
ret = typestr;
} else {
ret = s->e.name;
}
ti->idx++;
return ret;
}
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"
"\tdel_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>][=<id>]]]\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-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 cli_error(const char *fmt, ...)
{
va_list ap;
if (flags & CLI_FLAG_QUIET)
return;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
static void uci_print_value(FILE *f, const char *v)
{
fprintf(f, "'");
while (*v) {
if (*v != '\'')
fputc(*v, f);
else
fprintf(f, "'\\''");
v++;
}
fprintf(f, "'");
}
static void uci_show_value(struct uci_option *o, bool quote)
{
struct uci_element *e;
bool sep = false;
char *space;
switch(o->type) {
case UCI_TYPE_STRING:
if (quote)
uci_print_value(stdout, o->v.string);
else
printf("%s", o->v.string);
printf("\n");
break;
case UCI_TYPE_LIST:
uci_foreach_element(&o->v.list, e) {
printf("%s", (sep ? delimiter : ""));
space = strpbrk(e->name, " \t\r\n");
if (!space && !quote)
printf("%s", e->name);
else
uci_print_value(stdout, e->name);
sep = true;
}
printf("\n");
break;
default:
printf("<unknown>\n");
break;
}
}
static void uci_show_option(struct uci_option *o, bool quote)
{
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, quote);
}
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), true);
}
}
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_delta, e) {
struct uci_delta *h = uci_to_delta(e);
char *prefix = "";
char *op = "=";
switch(h->cmd) {
case UCI_CMD_REMOVE:
prefix = "-";
break;
case UCI_CMD_LIST_ADD:
op = "+=";
break;
case UCI_CMD_LIST_DEL:
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", op);
uci_print_value(stdout, h->value);
}
printf("\n");
}
}
static int package_cmd(int cmd, char *tuple)
{
struct uci_element *e = NULL;
struct uci_ptr ptr;
int ret = 1;
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) {
ret = 0;
goto out;
}
if (uci_commit(ctx, &ptr.p, false) != UCI_OK) {
cli_perror();
goto out;
}
break;
case CMD_EXPORT:
if (uci_export(ctx, stdout, ptr.p, true) != UCI_OK) {
goto out;
}
break;
case CMD_SHOW:
if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) {
ctx->err = UCI_ERR_NOTFOUND;
cli_perror();
goto out;
}
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, true);
break;
default:
/* should not happen */
goto out;
}
break;
}
ret = 0;
out:
if (ptr.p)
uci_unload(ctx, ptr.p);
return ret;
}
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;
int ret = 1;
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();
goto out;
}
for (p = configs; *p; p++) {
package_cmd(cmd, *p);
}
ret = 0;
out:
free(configs);
return ret;
}
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;
int dummy;
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_DEL) &&
(cmd != CMD_ADD_LIST) && (cmd != CMD_DEL_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, false);
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_DEL_LIST:
ret = uci_del_list(ctx, &ptr);
break;
case CMD_REORDER:
if (!ptr.s || !ptr.value) {
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:
if (ptr.value && !sscanf(ptr.value, "%d", &dummy))
return 1;
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) {
cli_error("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]) {
cli_error("uci: %s", strerror(errno));
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++) {
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)
cli_error("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 if (!strcasecmp(argv[0], "del_list"))
cmd = CMD_DEL_LIST;
else
cmd = -1;
switch(cmd) {
case CMD_ADD_LIST:
case CMD_DEL_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) {
cli_error("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':
if (input != stdin) {
fclose(input);
cli_error("Too many input files.\n");
return 1;
}
input = fopen(optarg, "r");
if (!input) {
cli_error("uci: %s", strerror(errno));
return 1;
}
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_delta_path(ctx, optarg);
break;
case 'P':
uci_add_delta_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;
}
ret = uci_cmd(argc - 1, argv + 1);
if (input != stdin)
fclose(input);
if (ret == 255)
uci_usage();
uci_free_context(ctx);
return ret;
}

513
src/3P/uci/delta.c Normal file
View File

@@ -0,0 +1,513 @@
/*
* 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 Lesser General Public License for more details.
*/
/*
* This file contains the code for handling uci config delta 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>
#include <string.h>
#include <stdlib.h>
#include "uci.h"
#include "uci_internal.h"
/* record a change that was done to a package */
void
uci_add_delta(struct uci_context *ctx, struct uci_list *list, int cmd, const char *section, const char *option, const char *value)
{
struct uci_delta *h;
int size = strlen(section) + 1;
char *ptr;
if (value)
size += strlen(value) + 1;
h = uci_alloc_element(ctx, delta, 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_delta(struct uci_delta *h)
{
if (!h)
return;
if ((h->section != NULL) &&
(h->section != uci_dataptr(h))) {
free(h->section);
free(h->value);
}
uci_free_element(&h->e);
}
static void uci_delta_save(struct uci_context *ctx, FILE *f,
const char *name, const struct uci_delta *h)
{
const struct uci_element *e = &h->e;
char prefix[2] = {0, 0};
if (h->cmd <= __UCI_CMD_LAST)
prefix[0] = uci_command_char[h->cmd];
fprintf(f, "%s%s.%s", prefix, name, h->section);
if (e->name)
fprintf(f, ".%s", e->name);
if (h->cmd == UCI_CMD_REMOVE && !h->value)
fprintf(f, "\n");
else {
int i;
fprintf(f, "='");
for (i = 0; h->value[i]; i++) {
unsigned char c = h->value[i];
if (c != '\'')
fputc(c, f);
else
fprintf(f, "'\\''");
}
fprintf(f, "'\n");
}
}
int uci_set_savedir(struct uci_context *ctx, const char *dir)
{
char *sdir;
struct uci_element *e, *tmp;
bool exists = false;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, dir != NULL);
/* Move dir to the end of ctx->delta_path */
uci_foreach_element_safe(&ctx->delta_path, tmp, e) {
if (!strcmp(e->name, dir)) {
exists = true;
uci_list_del(&e->list);
break;
}
}
if (!exists)
e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element));
uci_list_add(&ctx->delta_path, &e->list);
sdir = uci_strdup(ctx, dir);
if (ctx->savedir != uci_savedir)
free(ctx->savedir);
ctx->savedir = sdir;
return 0;
}
int uci_add_delta_path(struct uci_context *ctx, const char *dir)
{
struct uci_element *e;
struct uci_list *savedir;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, dir != NULL);
/* Duplicate delta path is not allowed */
uci_foreach_element(&ctx->delta_path, e) {
if (!strcmp(e->name, dir))
UCI_THROW(ctx, UCI_ERR_DUPLICATE);
}
e = uci_alloc_generic(ctx, UCI_TYPE_PATH, dir, sizeof(struct uci_element));
/* Keep savedir at the end of ctx->delta_path list */
savedir = ctx->delta_path.prev;
uci_list_insert(savedir->prev, &e->list);
return 0;
}
char const uci_command_char[] = {
[UCI_CMD_ADD] = '+',
[UCI_CMD_REMOVE] = '-',
[UCI_CMD_CHANGE] = 0,
[UCI_CMD_RENAME] = '@',
[UCI_CMD_REORDER] = '^',
[UCI_CMD_LIST_ADD] = '|',
[UCI_CMD_LIST_DEL] = '~'
};
static inline int uci_parse_delta_tuple(struct uci_context *ctx, struct uci_ptr *ptr)
{
struct uci_parse_context *pctx = ctx->pctx;
char *str = pctx_cur_str(pctx), *arg;
int c;
UCI_INTERNAL(uci_parse_argument, ctx, ctx->pctx->file, &str, &arg);
for (c = 0; c <= __UCI_CMD_LAST; c++) {
if (uci_command_char[c] == *arg)
break;
}
if (c > __UCI_CMD_LAST)
c = UCI_CMD_CHANGE;
if (c != UCI_CMD_CHANGE)
arg += 1;
UCI_INTERNAL(uci_parse_ptr, ctx, ptr, arg);
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;
case UCI_CMD_LIST_DEL:
if (!ptr->option)
goto error;
}
return c;
error:
UCI_THROW(ctx, UCI_ERR_INVAL);
return 0;
}
static void uci_parse_delta_line(struct uci_context *ctx, struct uci_package *p)
{
struct uci_element *e = NULL;
struct uci_ptr ptr;
int cmd;
cmd = uci_parse_delta_tuple(ctx, &ptr);
if (strcmp(ptr.package, p->e.name) != 0)
goto error;
if (ctx->flags & UCI_FLAG_SAVED_DELTA)
uci_add_delta(ctx, &p->saved_delta, cmd, ptr.section, ptr.option, ptr.value);
switch(cmd) {
case UCI_CMD_REORDER:
uci_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_LIST_DEL:
UCI_INTERNAL(uci_del_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_delta(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)) {
pctx->pos = 0;
uci_getln(ctx, 0);
if (!pctx->buf[0])
continue;
/*
* ignore parse errors in single lines, we want to preserve as much
* delta as possible
*/
UCI_TRAP_SAVE(ctx, error);
uci_parse_delta_line(ctx, p);
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_delta_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, NULL, SEEK_SET, flush, false);
UCI_TRAP_RESTORE(ctx);
if (p)
changes = uci_parse_delta(ctx, stream, p);
done:
if (f)
*f = stream;
else
uci_close_stream(stream);
return changes;
}
/* returns the number of changes that were successfully parsed */
__private int uci_load_delta(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_delta)
return 0;
uci_foreach_element(&ctx->delta_path, e) {
if ((asprintf(&filename, "%s/%s", e->name, p->e.name) < 0) || !filename)
UCI_THROW(ctx, UCI_ERR_MEM);
changes += uci_load_delta_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);
UCI_TRAP_SAVE(ctx, done);
f = uci_open_stream(ctx, filename, NULL, SEEK_SET, flush, false);
UCI_TRAP_RESTORE(ctx);
if (flush && f && (changes > 0)) {
if (ftruncate(fileno(f), 0) < 0) {
free(filename);
uci_close_stream(f);
UCI_THROW(ctx, UCI_ERR_IO);
}
}
done:
free(filename);
uci_close_stream(f);
ctx->err = 0;
return changes;
}
static void uci_filter_delta(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, NULL, SEEK_SET, true, false);
pctx->file = f;
while (!feof(f)) {
enum uci_command c;
bool match;
pctx->pos = 0;
uci_getln(ctx, 0);
if (!pctx->buf[0])
continue;
c = uci_parse_delta_tuple(ctx, &ptr);
match = true;
if (section) {
if (!ptr.section || (strcmp(section, ptr.section) != 0))
match = false;
}
if (match && option) {
if (!ptr.option || (strcmp(option, ptr.option) != 0))
match = false;
}
if (!match) {
uci_add_delta(ctx, &list, c,
ptr.section, ptr.option, ptr.value);
}
}
/* rebuild the delta file */
rewind(f);
if (ftruncate(fileno(f), 0) < 0)
UCI_THROW(ctx, UCI_ERR_IO);
uci_foreach_element_safe(&list, tmp, e) {
struct uci_delta *h = uci_to_delta(e);
uci_delta_save(ctx, f, name, h);
uci_free_delta(h);
}
UCI_TRAP_RESTORE(ctx);
done:
free(filename);
uci_close_stream(pctx->file);
uci_foreach_element_safe(&list, tmp, e) {
uci_free_delta(uci_to_delta(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);
uci_expand_ptr(ctx, ptr, false);
UCI_ASSERT(ctx, ptr->p->has_delta);
/*
* - flush unwritten changes
* - save the package name
* - unload the package
* - filter the delta
* - 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_delta(ctx, package, section, option);
UCI_INTERNAL(uci_load, ctx, package, &ptr->p);
UCI_TRAP_RESTORE(ctx);
ctx->err = 0;
error:
free(package);
free(section);
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 delta to a file, update the real file
* directly.
* does not modify the uci_package pointer
*/
if (!p->has_delta)
return uci_commit(ctx, &p, false);
if (uci_list_empty(&p->delta))
return 0;
if (stat(ctx->savedir, &statbuf) < 0) {
if (stat(ctx->confdir, &statbuf) == 0) {
mkdir(ctx->savedir, statbuf.st_mode);
} else {
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);
ctx->err = 0;
UCI_TRAP_SAVE(ctx, done);
f = uci_open_stream(ctx, filename, NULL, SEEK_END, true, true);
UCI_TRAP_RESTORE(ctx);
uci_foreach_element_safe(&p->delta, tmp, e) {
struct uci_delta *h = uci_to_delta(e);
uci_delta_save(ctx, f, p->e.name, h);
uci_free_delta(h);
}
done:
uci_close_stream(f);
free(filename);
if (ctx->err)
UCI_THROW(ctx, ctx->err);
return 0;
}

932
src/3P/uci/file.c Normal file
View File

@@ -0,0 +1,932 @@
/*
* 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 Lesser 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/stat.h>
#include <sys/file.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
#include <glob.h>
#include <string.h>
#include <stdlib.h>
#include "uci.h"
#include "uci_internal.h"
#define LINEBUF 32
/*
* Fetch a new line from the input stream and resize buffer if necessary
*/
__private 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;
}
/* It takes 2 slots for fgets to read 1 char. */
if (offset >= pctx->bufsz - 1) {
pctx->bufsz *= 2;
pctx->buf = uci_realloc(ctx, pctx->buf, pctx->bufsz);
}
ofs = offset;
do {
p = &pctx->buf[ofs];
p[0] = 0;
p = fgets(p, pctx->bufsz - ofs, pctx->file);
if (!p || !*p)
return;
ofs += strlen(p);
if (pctx->buf[ofs - 1] == '\n') {
pctx->line++;
return;
}
pctx->bufsz *= 2;
pctx->buf = uci_realloc(ctx, pctx->buf, pctx->bufsz);
if (!pctx->buf)
UCI_THROW(ctx, UCI_ERR_MEM);
} 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 bool parse_backslash(struct uci_context *ctx)
{
struct uci_parse_context *pctx = ctx->pctx;
/* skip backslash */
pctx->pos += 1;
/* undecoded backslash at the end of line, fetch the next line */
if (!pctx_cur_char(pctx) ||
pctx_cur_char(pctx) == '\n' ||
(pctx_cur_char(pctx) == '\r' &&
pctx_char(pctx, pctx_pos(pctx) + 1) == '\n' &&
!pctx_char(pctx, pctx_pos(pctx) + 2))) {
uci_getln(ctx, pctx->pos);
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)
{
struct uci_parse_context *pctx = ctx->pctx;
while (pctx_cur_char(pctx) && isspace(pctx_cur_char(pctx)))
pctx->pos += 1;
}
static inline void addc(struct uci_context *ctx, int *pos_dest, int *pos_src)
{
struct uci_parse_context *pctx = ctx->pctx;
pctx_char(pctx, *pos_dest) = pctx_char(pctx, *pos_src);
*pos_dest += 1;
*pos_src += 1;
}
/*
* parse a double quoted string argument from the command line
*/
static void parse_double_quote(struct uci_context *ctx, int *target)
{
struct uci_parse_context *pctx = ctx->pctx;
char c;
/* skip quote character */
pctx->pos += 1;
while (1) {
c = pctx_cur_char(pctx);
switch(c) {
case '"':
pctx->pos += 1;
return;
case 0:
/* Multi-line str value */
uci_getln(ctx, pctx->pos);
if (!pctx_cur_char(pctx)) {
uci_parse_error(ctx, "EOF with unterminated \"");
}
break;
case '\\':
if (!parse_backslash(ctx))
continue;
/* fall through */
default:
addc(ctx, target, &pctx->pos);
break;
}
}
}
/*
* parse a single quoted string argument from the command line
*/
static void parse_single_quote(struct uci_context *ctx, int *target)
{
struct uci_parse_context *pctx = ctx->pctx;
char c;
/* skip quote character */
pctx->pos += 1;
while (1) {
c = pctx_cur_char(pctx);
switch(c) {
case '\'':
pctx->pos += 1;
return;
case 0:
/* Multi-line str value */
uci_getln(ctx, pctx->pos);
if (!pctx_cur_char(pctx))
uci_parse_error(ctx, "EOF with unterminated '");
break;
default:
addc(ctx, target, &pctx->pos);
}
}
}
/*
* parse a string from the command line and detect the quoting style
*/
static void parse_str(struct uci_context *ctx, int *target)
{
struct uci_parse_context *pctx = ctx->pctx;
bool next = true;
do {
switch(pctx_cur_char(pctx)) {
case '\'':
parse_single_quote(ctx, target);
break;
case '"':
parse_double_quote(ctx, target);
break;
case '#':
pctx_cur_char(pctx) = 0;
/* fall through */
case 0:
goto done;
case ';':
next = false;
goto done;
case '\\':
if (!parse_backslash(ctx))
continue;
/* fall through */
default:
addc(ctx, target, &pctx->pos);
break;
}
} while (pctx_cur_char(pctx) && !isspace(pctx_cur_char(pctx)));
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 (pctx_cur_char(pctx) && next)
pctx->pos += 1;
/* terminate the parsed string */
pctx_char(pctx, *target) = 0;
}
/*
* extract the next argument from the command line
*/
static int next_arg(struct uci_context *ctx, bool required, bool name, bool package)
{
struct uci_parse_context *pctx = ctx->pctx;
int val, ptr;
skip_whitespace(ctx);
val = ptr = pctx_pos(pctx);
if (pctx_cur_char(pctx) == ';') {
pctx_cur_char(pctx) = 0;
pctx->pos += 1;
} else {
parse_str(ctx, &ptr);
}
if (!pctx_char(pctx, val)) {
if (required)
uci_parse_error(ctx, "insufficient arguments");
goto done;
}
if (name && !uci_validate_str(pctx_str(pctx, val), name, package))
uci_parse_error(ctx, "invalid character in name field");
done:
return val;
}
int uci_parse_argument(struct uci_context *ctx, FILE *stream, char **str, char **result)
{
int ofs_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) {
ctx->pctx->pos = 0;
uci_getln(ctx, 0);
}
ofs_result = next_arg(ctx, false, false, false);
*result = pctx_str(ctx->pctx, ofs_result);
*str = pctx_cur_str(ctx->pctx);
return 0;
}
static int
uci_fill_ptr(struct uci_context *ctx, struct uci_ptr *ptr, struct uci_element *e)
{
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;
return 0;
}
/*
* 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 *tmp;
int ofs_tmp;
skip_whitespace(ctx);
ofs_tmp = next_arg(ctx, false, false, false);
tmp = pctx_str(ctx->pctx, ofs_tmp);
if (*tmp && (ctx->flags & UCI_FLAG_STRICT))
uci_parse_error(ctx, "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, bool single)
{
struct uci_parse_context *pctx = ctx->pctx;
int ofs_name;
char *name;
/* command string null-terminated by strtok */
pctx->pos += strlen(pctx_cur_str(pctx)) + 1;
ofs_name = next_arg(ctx, true, true, true);
name = pctx_str(pctx, ofs_name);
assert_eol(ctx);
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)
{
struct uci_parse_context *pctx = ctx->pctx;
struct uci_element *e;
struct uci_ptr ptr;
int ofs_name, ofs_type;
char *name;
char *type;
uci_fixup_section(ctx, ctx->pctx->section);
if (!ctx->pctx->package) {
if (!ctx->pctx->name)
uci_parse_error(ctx, "attempting to import a file without a package name");
uci_switch_config(ctx);
}
/* command string null-terminated by strtok */
pctx->pos += strlen(pctx_cur_str(pctx)) + 1;
ofs_type = next_arg(ctx, true, false, false);
type = pctx_str(pctx, ofs_type);
if (!uci_validate_type(type))
uci_parse_error(ctx, "invalid character in type field");
ofs_name = next_arg(ctx, false, true, false);
type = pctx_str(pctx, ofs_type);
name = pctx_str(pctx, ofs_name);
assert_eol(ctx);
if (!name || !name[0]) {
ctx->internal = !pctx->merge;
UCI_NESTED(uci_add_section, ctx, pctx->package, type, &pctx->section);
} else {
uci_fill_ptr(ctx, &ptr, &pctx->package->e);
e = uci_lookup_list(&pctx->package->sections, name);
if (e) {
ptr.s = uci_to_section(e);
if ((ctx->flags & UCI_FLAG_STRICT) && strcmp(ptr.s->type, type))
uci_parse_error(ctx, "section of different type overwrites prior section with same name");
}
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, bool list)
{
struct uci_parse_context *pctx = ctx->pctx;
struct uci_element *e;
struct uci_ptr ptr;
int ofs_name, ofs_value;
char *name = NULL;
char *value = NULL;
if (!pctx->section)
uci_parse_error(ctx, "option/list command found before the first section");
/* command string null-terminated by strtok */
pctx->pos += strlen(pctx_cur_str(pctx)) + 1;
ofs_name = next_arg(ctx, true, true, false);
ofs_value = next_arg(ctx, false, false, false);
name = pctx_str(pctx, ofs_name);
value = pctx_str(pctx, ofs_value);
assert_eol(ctx);
uci_fill_ptr(ctx, &ptr, &pctx->section->e);
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;
/* Skip whitespace characters at the start of line */
skip_whitespace(ctx);
do {
word = strtok(pctx_cur_str(pctx), " \t");
if (!word)
return;
switch(word[0]) {
case 0:
case '#':
return;
case 'p':
if ((word[1] == 0) || !strcmp(word + 1, "ackage"))
uci_parse_package(ctx, single);
else
goto invalid;
break;
case 'c':
if ((word[1] == 0) || !strcmp(word + 1, "onfig"))
uci_parse_config(ctx);
else
goto invalid;
break;
case 'o':
if ((word[1] == 0) || !strcmp(word + 1, "ption"))
uci_parse_option(ctx, false);
else
goto invalid;
break;
case 'l':
if ((word[1] == 0) || !strcmp(word + 1, "ist"))
uci_parse_option(ctx, true);
else
goto invalid;
break;
default:
goto invalid;
}
continue;
invalid:
uci_parse_error(ctx, "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 const 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);
if (!ctx->buf)
return str;
}
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 && *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)) {
pctx->pos = 0;
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;
}
static void uci_file_commit(struct uci_context *ctx, struct uci_package **package, bool overwrite)
{
struct uci_package *p = *package;
FILE *f1, *f2 = NULL;
char *name = NULL;
char *path = NULL;
char *filename = NULL;
struct stat statbuf;
bool do_rename = false;
if (!p->path) {
if (overwrite)
p->path = uci_config_path(ctx, p->e.name);
else
UCI_THROW(ctx, UCI_ERR_INVAL);
}
if ((asprintf(&filename, "%s/.%s.uci-XXXXXX", ctx->confdir, p->e.name) < 0) || !filename)
UCI_THROW(ctx, UCI_ERR_MEM);
/* open the config file for writing now, so that it is locked */
f1 = uci_open_stream(ctx, p->path, NULL, SEEK_SET, true, true);
/* flush unsaved changes and reload from delta file */
UCI_TRAP_SAVE(ctx, done);
if (p->has_delta) {
if (!overwrite) {
name = uci_strdup(ctx, p->e.name);
path = uci_strdup(ctx, p->path);
/* dump our own changes to the delta file */
if (!uci_list_empty(&p->delta))
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, f1, name, &p, true);
p->path = path;
p->has_delta = true;
*package = p;
/* freed together with the uci_package */
path = NULL;
}
/* flush delta */
if (!uci_load_delta(ctx, p, true))
goto done;
}
if (!mktemp(filename))
*filename = 0;
if (!*filename) {
free(filename);
UCI_THROW(ctx, UCI_ERR_IO);
}
if ((stat(filename, &statbuf) == 0) && ((statbuf.st_mode & S_IFMT) != S_IFREG))
UCI_THROW(ctx, UCI_ERR_IO);
f2 = uci_open_stream(ctx, filename, p->path, SEEK_SET, true, true);
uci_export(ctx, f2, p, false);
fflush(f2);
fsync(fileno(f2));
uci_close_stream(f2);
do_rename = true;
UCI_TRAP_RESTORE(ctx);
done:
free(name);
free(path);
uci_close_stream(f1);
if (do_rename) {
path = realpath(p->path, NULL);
if (!path || rename(filename, path)) {
unlink(filename);
UCI_THROW(ctx, UCI_ERR_IO);
}
free(path);
}
free(filename);
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) {
free(dir);
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;
}
UCI_TRAP_SAVE(ctx, done);
file = uci_open_stream(ctx, filename, NULL, SEEK_SET, false, false);
ctx->err = 0;
UCI_INTERNAL(uci_import, ctx, file, name, &package, true);
UCI_TRAP_RESTORE(ctx);
if (package) {
package->path = filename;
package->has_delta = confdir;
uci_load_delta(ctx, package, false);
}
done:
uci_close_stream(file);
if (ctx->err) {
free(filename);
UCI_THROW(ctx, ctx->err);
}
return package;
}
__private UCI_BACKEND(uci_file_backend, "file",
.load = uci_file_load,
.commit = uci_file_commit,
.list_configs = uci_list_config_files,
);

234
src/3P/uci/libuci.c Normal file
View File

@@ -0,0 +1,234 @@
/*
* 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 Lesser 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_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",
};
#include "uci_internal.h"
#include "list.c"
__private const char *uci_confdir = UCI_CONFDIR;
__private const char *uci_savedir = UCI_SAVEDIR;
/* 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->delta_path);
uci_list_init(&ctx->backends);
ctx->flags = UCI_FLAG_STRICT | UCI_FLAG_SAVED_DELTA;
ctx->confdir = (char *) uci_confdir;
ctx->savedir = (char *) uci_savedir;
uci_add_delta_path(ctx, 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->delta_path, tmp, e) {
uci_free_element(e);
}
UCI_TRAP_RESTORE(ctx);
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;
}
__private 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 && ctx->func ? ctx->func : ""), (ctx && ctx->func ? ": " : ""),
uci_errstr[err],
error_info);
if (err < 0)
*dest = NULL;
} else {
strcat(error_info, "\n");
fprintf(stderr, format,
(prefix ? prefix : ""), (prefix ? ": " : ""),
(ctx && ctx->func ? ctx->func : ""), (ctx && 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;
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, ctx->backend && ctx->backend->load);
p = ctx->backend->load(ctx, name);
if (package)
*package = p;
return 0;
}
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;
}

733
src/3P/uci/list.c Normal file
View File

@@ -0,0 +1,733 @@
/*
* 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 Lesser General Public License for more details.
*/
static 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) {
if (pos-- <= 0)
break;
new_head = &p->list;
}
uci_list_add(new_head->next, 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
*/
__private 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;
}
__private void
uci_free_element(struct uci_element *e)
{
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;
}
/* 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);
}
/* fix up an unnamed section, e.g. after adding options to it */
__private 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);
}
__private 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->delta);
uci_list_init(&p->saved_delta);
return p;
}
__private void
uci_free_package(struct uci_package **package)
{
struct uci_element *e, *tmp;
struct uci_package *p = *package;
if(!p)
return;
free(p->path);
uci_foreach_element_safe(&p->sections, tmp, e) {
uci_free_section(uci_to_section(e));
}
uci_foreach_element_safe(&p->delta, tmp, e) {
uci_free_delta(uci_to_delta(e));
}
uci_foreach_element_safe(&p->saved_delta, tmp, e) {
uci_free_delta(uci_to_delta(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;
}
__private 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:
free(section);
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_next(struct uci_context *ctx, struct uci_element **e, struct uci_list *list, const char *name)
{
UCI_HANDLE_ERR(ctx);
*e = uci_lookup_list(list, name);
if (!*e)
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
return 0;
}
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 */
if (ptr->p)
e = &ptr->p->e;
else
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 && !ptr->s)
goto complete;
/* if the section name validates as a regular name, pass through
* to the regular uci_lookup function call */
if (ptr->s) {
e = &ptr->s->e;
} else if (ptr->flags & UCI_LOOKUP_EXTENDED) {
if (extended)
e = uci_lookup_ext_section(ctx, ptr);
else
UCI_THROW(ctx, UCI_ERR_INVAL);
} 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 UCI_OK;
notfound:
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
/* not a chance here */
return UCI_ERR_NOTFOUND;
}
__private struct uci_element *
uci_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_delta)
uci_add_delta(ctx, &p->delta, 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 delta tracking */
bool internal = ctx && ctx->internal;
struct uci_element *e;
struct uci_package *p;
char *n;
UCI_HANDLE_ERR(ctx);
e = uci_expand_ptr(ctx, ptr, true);
p = ptr->p;
UCI_ASSERT(ctx, ptr->s);
UCI_ASSERT(ctx, ptr->value);
if (!internal && p->has_delta)
uci_add_delta(ctx, &p->delta, UCI_CMD_RENAME, ptr->section, ptr->option, ptr->value);
n = uci_strdup(ctx, ptr->value);
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;
bool internal = ctx && ctx->internal;
char order[32];
UCI_HANDLE_ERR(ctx);
uci_list_set_pos(&s->package->sections, &s->e.list, pos);
if (!internal && p->has_delta) {
sprintf(order, "%d", pos);
uci_add_delta(ctx, &p->delta, 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 && 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_delta)
uci_add_delta(ctx, &p->delta, 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 && ctx->internal;
struct uci_package *p;
struct uci_element *e1, *e2, *tmp;
int index;
UCI_HANDLE_ERR(ctx);
e1 = uci_expand_ptr(ctx, ptr, true);
p = ptr->p;
UCI_ASSERT(ctx, ptr->s);
if (ptr->o && ptr->o->type == UCI_TYPE_LIST && ptr->value && *ptr->value) {
if (!sscanf(ptr->value, "%d", &index))
return 1;
uci_foreach_element_safe(&ptr->o->v.list, tmp, e2) {
if (index == 0) {
if (!internal && p->has_delta)
uci_add_delta(ctx, &p->delta, UCI_CMD_REMOVE, ptr->section, ptr->option, ptr->value);
uci_free_option(uci_to_option(e2));
return 0;
}
index--;
}
return 0;
}
if (!internal && p->has_delta)
uci_add_delta(ctx, &p->delta, UCI_CMD_REMOVE, ptr->section, ptr->option, NULL);
uci_free_any(&e1);
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 delta tracking */
bool internal = ctx && ctx->internal;
struct uci_option *prev = NULL;
const char *value2 = NULL;
UCI_HANDLE_ERR(ctx);
uci_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_del_list(struct uci_context *ctx, struct uci_ptr *ptr)
{
/* NB: pass on internal flag to uci_del_element */
bool internal = ctx && ctx->internal;
struct uci_element *e, *tmp;
struct uci_package *p;
UCI_HANDLE_ERR(ctx);
uci_expand_ptr(ctx, ptr, false);
UCI_ASSERT(ctx, ptr->s);
UCI_ASSERT(ctx, ptr->value);
if (!(ptr->o && ptr->option))
return 0;
if ((ptr->o->type != UCI_TYPE_LIST))
return 0;
p = ptr->p;
if (!internal && p->has_delta)
uci_add_delta(ctx, &p->delta, UCI_CMD_LIST_DEL, ptr->section, ptr->option, ptr->value);
uci_foreach_element_safe(&ptr->o->v.list, tmp, e) {
if (!strcmp(ptr->value, uci_to_option(e)->e.name)) {
uci_free_option(uci_to_option(e));
}
}
return 0;
}
int uci_set(struct uci_context *ctx, struct uci_ptr *ptr)
{
/* NB: UCI_INTERNAL use means without delta tracking */
bool internal = ctx && ctx->internal;
UCI_HANDLE_ERR(ctx);
uci_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_delta)
uci_add_delta(ctx, &ptr->p->delta, UCI_CMD_CHANGE, ptr->section, ptr->option, ptr->value);
return 0;
}
int uci_unload(struct uci_context *ctx, struct uci_package *p)
{
UCI_HANDLE_ERR(ctx);
UCI_ASSERT(ctx, p != NULL);
uci_free_package(&p);
return 0;
}

View File

@@ -0,0 +1,51 @@
cmake_minimum_required(VERSION 2.6)
PROJECT(uci C)
SET(CMAKE_INSTALL_PREFIX /)
IF(NOT LUA_CFLAGS)
INCLUDE(FindPkgConfig)
pkg_search_module(LUA lua5.1 lua-5.1)
ENDIF()
ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -I.. ${LUA_CFLAGS})
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/..)
IF(APPLE)
INCLUDE_DIRECTORIES(/opt/local/include)
LINK_DIRECTORIES(/opt/local/lib)
ENDIF()
IF(APPLE)
SET(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -undefined dynamic_lookup")
ENDIF(APPLE)
IF(NOT LUAPATH)
EXECUTE_PROCESS(
COMMAND lua -e "for k in string.gmatch(package.cpath .. \";\", \"([^;]+)/..so;\") do if k:sub(1,1) == \"/\" then print(k) break end end"
OUTPUT_VARIABLE LUAPATH
RESULT_VARIABLE LUA_CHECK_RES
OUTPUT_STRIP_TRAILING_WHITESPACE
)
IF(BUILD_LUA)
IF(NOT ${LUA_CHECK_RES} EQUAL 0 OR "${LUAPATH}" EQUAL "")
MESSAGE(SEND_ERROR "Lua was not found on your system")
ENDIF()
ENDIF()
ENDIF()
IF(BUILD_LUA)
ADD_LIBRARY(uci_lua MODULE uci.c)
SET_TARGET_PROPERTIES(uci_lua PROPERTIES
OUTPUT_NAME uci
PREFIX ""
)
TARGET_LINK_LIBRARIES(uci_lua uci dl)
INSTALL(TARGETS uci_lua
LIBRARY DESTINATION ${LUAPATH}
)
ENDIF()

999
src/3P/uci/lua/uci.c Normal file
View File

@@ -0,0 +1,999 @@
/*
* 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
#if !defined LUA_VERSION_NUM || LUA_VERSION_NUM==501
/*
* ** Adapted from Lua 5.2.0
* */
static void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
luaL_checkstack(L, nup+1, "too many upvalues");
for (; l->name != NULL; l++) { /* fill the table with given functions */
int i;
lua_pushstring(L, l->name);
for (i = 0; i < nup; i++) /* copy upvalues to the top */
lua_pushvalue(L, -(nup+1));
lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */
lua_settable(L, -(nup + 3));
}
lua_pop(L, nup); /* remove upvalues */
}
#define lua_rawlen(L, i) lua_objlen(L, i)
#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");
return NULL;
}
}
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 NULL;
}
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");
return NULL;
}
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)
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, true) != UCI_OK)
goto error;
break;
case 1:
if (uci_lookup_ptr(ctx, ptr, s, true) != 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, *tmp;
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 + offset))
type = NULL;
else
type = luaL_checkstring(L, 2 + offset);
if (!lua_isfunction(L, 3 + offset) || !package)
return luaL_error(L, "Invalid argument");
p = find_package(L, ctx, package, true);
if (!p)
goto done;
uci_foreach_element_safe(&p->sections, tmp, 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;
}
else
{
lua_error(L);
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, true);
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 (s)
free(s);
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;
ctx = find_context(L, &offset);
if (lookup_args(L, ctx, offset, &ptr, &s))
goto error;
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, true);
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:
if (s)
free(s);
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, true);
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:
if (s)
free(s);
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;
const char *v;
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_rawlen(L, nargs) < 1)
return 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, true);
if (err)
goto error;
if (((ptr.s == NULL) && (ptr.option != NULL)) || (ptr.value == NULL)) {
err = UCI_ERR_INVAL;
goto error;
}
if (istable) {
if (lua_rawlen(L, nargs) == 1) {
i = 1;
if (ptr.o) {
v = ptr.value;
ptr.value = NULL;
err = uci_delete(ctx, &ptr);
if (err)
goto error;
ptr.value = v;
}
} else {
i = 2;
err = uci_set(ctx, &ptr);
if (err)
goto error;
}
for (; i <= lua_rawlen(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:
if (s)
free(s);
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 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, true);
uci_foreach_element_safe(&ctx->root, tmp, e) {
struct uci_package *p = uci_to_package(e);
if (ptr.p && (ptr.p != p))
continue;
ptr.p = p;
switch(cmd) {
case CMD_COMMIT:
uci_commit(ctx, &p, false);
break;
case CMD_SAVE:
uci_save(ctx, p);
break;
case CMD_REVERT:
uci_revert(ctx, &ptr);
break;
}
}
err:
if (s)
free(s);
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_delta *h;
const char *name;
const char *value;
h = uci_to_delta(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;
value = h->value ? h->value : "";
if (name) {
lua_getfield(L, -1, name);
/* this delta is a list add operation */
if (h->cmd == UCI_CMD_LIST_ADD) {
/* there seems to be no table yet */
if (!lua_istable(L, -1)) {
lua_newtable(L);
/* if there is a value on the stack already, add */
if (!lua_isnil(L, -2)) {
lua_pushvalue(L, -2);
lua_rawseti(L, -2, 1);
lua_pushstring(L, value);
lua_rawseti(L, -2, 2);
/* this is the first table item */
} else {
lua_pushstring(L, value);
lua_rawseti(L, -2, 1);
}
lua_setfield(L, -3, name);
/* a table is on the top of the stack and this is a subsequent,
* list_add, append this value to table */
} else {
lua_pushstring(L, value);
lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
}
/* non-list change, simply set/replace field */
} else {
lua_pushstring(L, value);
lua_setfield(L, -3, name);
}
lua_pop(L, 1);
} else {
lua_pushstring(L, value);
lua_setfield(L, -2, ".type");
}
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->delta) && uci_list_empty(&p->saved_delta))
goto done;
lua_newtable(L);
uci_foreach_element(&p->saved_delta, e) {
uci_lua_add_change(L, e);
}
uci_foreach_element(&p->delta, 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:
return 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 offset = 0;
ctx = find_context(L, &offset);
luaL_checkstring(L, 1 + offset);
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_delta(lua_State *L)
{
struct uci_context *ctx;
int offset = 0;
ctx = find_context(L, &offset);
luaL_checkstring(L, 1 + offset);
uci_add_delta_path(ctx, lua_tostring(L, -1));
return uci_push_status(L, ctx, false);
}
static int
uci_lua_set_savedir(lua_State *L)
{
struct uci_context *ctx;
int offset = 0;
ctx = find_context(L, &offset);
luaL_checkstring(L, 1 + offset);
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)
return 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))
return 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))
return 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_delta },
{ "add_delta", uci_lua_add_delta },
{ "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_setfuncs(L, uci, 0);
lua_pop(L, 1);
/* create module */
lua_newtable(L);
lua_pushvalue(L, -1);
luaL_setfuncs(L, uci, 0);
lua_setglobal(L, MODNAME);
return 1;
}

130
src/3P/uci/parse.c Normal file
View File

@@ -0,0 +1,130 @@
/*
* 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 Lesser General Public License for more details.
*/
#include <string.h>
#include <stdint.h>
#include "uci.h"
void uci_parse_section(struct uci_section *s, const struct uci_parse_option *opts,
int n_opts, struct uci_option **tb)
{
struct uci_element *e;
memset(tb, 0, n_opts * sizeof(*tb));
uci_foreach_element(&s->options, e) {
struct uci_option *o = uci_to_option(e);
int i;
for (i = 0; i < n_opts; i++) {
if (tb[i])
continue;
if (strcmp(opts[i].name, o->e.name) != 0)
continue;
if (opts[i].type != o->type)
continue;
/* match found */
tb[i] = o;
break;
}
}
}
//-----------------------------------------------------------------------------
// MurmurHashNeutral2, by Austin Appleby
// Same as MurmurHash2, but endian- and alignment-neutral.
static uint32_t hash_murmur2(uint32_t h, const void * key, int len)
{
const unsigned char * data = key;
const uint32_t m = 0x5bd1e995;
const int r = 24;
while(len >= 4)
{
unsigned int k;
k = data[0];
k |= data[1] << 8;
k |= data[2] << 16;
k |= data[3] << 24;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
switch(len)
{
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0];
h *= m;
};
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}
static uint32_t uci_hash_list(uint32_t h, const struct uci_list *list)
{
const struct uci_element *e;
uci_foreach_element(list, e) {
h = hash_murmur2(h, e->name, strlen(e->name) + 1);
}
return h;
}
uint32_t uci_hash_options(struct uci_option **tb, int n_opts)
{
uint32_t h = 0xdeadc0de;
int i;
for (i = 0; i < n_opts; i++) {
const struct uci_option *o = tb[i];
if (!tb[i])
continue;
h = hash_murmur2(h, o->e.name, strlen(o->e.name) + 1);
h = hash_murmur2(h, &o->type, sizeof(o->type));
switch (tb[i]->type) {
case UCI_TYPE_STRING:
h = hash_murmur2(h, o->v.string, strlen(o->v.string) + 1);
break;
case UCI_TYPE_LIST:
h = uci_hash_list(h, &o->v.list);
break;
}
}
return h;
}

178
src/3P/uci/sh/uci.sh Executable file
View File

@@ -0,0 +1,178 @@
#!/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
}

34
src/3P/uci/test/README Normal file
View File

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

View File

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

View File

@@ -0,0 +1,4 @@
list_test_config.SEC0='section'
list_test_config.SEC0.list0+='value0'
list_test_config.SEC0.list0+='"Hello
, world"'

View File

@@ -0,0 +1,6 @@
config section 'SEC0'
list list0 'value0'
list list0 '"Hello
, world"'

View File

@@ -0,0 +1,3 @@
list_test_config.SEC0=section
list_test_config.SEC0.list0='value0' '"Hello
, world"'

View File

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

View File

@@ -0,0 +1,9 @@
config section 'SEC0'
option option0 'value0'
option option1 '"Hello,
World"'
config section 'SEC1'
option option0 'value1'

View File

@@ -0,0 +1,9 @@
config section 'SEC0'
option option0 'value0'
option option1 '"Hello,
World"'
config section 'SEC1'
option option0 'value1'

View File

@@ -0,0 +1,5 @@
config sectype 'sec0'
list li0 '1'
list li0 '0'

View File

@@ -0,0 +1,5 @@
package delta
config sectype 'sec0'
list li0 '0'
list li0 '1'

View File

@@ -0,0 +1,5 @@
config section 'SEC0'
list list0 '"Hello
, world"'

View File

@@ -0,0 +1,4 @@
config section 'SEC0'
list list0 'value0'

View File

@@ -0,0 +1,11 @@
config 'type' 'section'
option 'opt' 'val'
list 'list_opt' 'val0'
list 'list_opt' 'val1'
option mul_line_opt_sq 'line 1
line 2 \
line 3'
option mul_line_opt_dq "\"Hello\, \
World.\'
"

View File

@@ -0,0 +1,12 @@
package export
config type 'section'
option opt 'val'
list list_opt 'val0'
list list_opt 'val1'
option mul_line_opt_sq 'line 1
line 2 \
line 3'
option mul_line_opt_dq '"Hello, World.'\''
'

View File

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

View File

@@ -0,0 +1,5 @@
config 'type' 'section'
# Cannot preserve trailling whitespace with assertEquals.
option opt "\"Hello, \
World.
\'"

View File

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

View File

@@ -0,0 +1,12 @@
package 'import-test'
config 'type' 'section'
option 'opt' 'val'
list 'list_opt' 'val0'
list 'list_opt' 'val1'
option mul_line_opt_sq \''line 1
line 2 \
line 3'\'
option mul_line_opt_dq "\"Hello, \
World.\'
"

View File

@@ -0,0 +1,11 @@
config type 'section'
option opt 'val'
list list_opt 'val0'
list list_opt 'val1'
option mul_line_opt_sq ''\''line 1
line 2 \
line 3'\'''
option mul_line_opt_dq '"Hello, World.'\''
'

View File

@@ -0,0 +1,3 @@
revert.SEC0='section'
revert.SEC0.option1='"Hello,
World"'

View File

@@ -0,0 +1,2 @@
revert.SEC0='section'
revert.SEC0.option0='value0'

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
set.section.opt='Hello,\'\''
World"'

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
set.section.opt='Hello,\'\''
World"'

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
config main
option version 1.4.1
config sockd 'instance0'
option enabled 1
list internal_network vpn
list external_network wan
option extra_config '
user.unprivileged: nobody
client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
session.max: 64
log: error
}
socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect
} '

View File

@@ -0,0 +1,12 @@
sockd.instance0.extra_config='
user.unprivileged: nobody
client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
session.max: 64
log: error
}
socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect
} '

View File

@@ -0,0 +1,18 @@
sockd.@main[0]=main
sockd.@main[0].version='1.4.1'
sockd.instance0=sockd
sockd.instance0.enabled='1'
sockd.instance0.internal_network='vpn'
sockd.instance0.external_network='wan'
sockd.instance0.extra_config='
user.unprivileged: nobody
client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
session.max: 64
log: error
}
socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect
} '

View File

@@ -0,0 +1,16 @@
sockd.instance0=sockd
sockd.instance0.enabled='1'
sockd.instance0.internal_network='vpn'
sockd.instance0.external_network='wan'
sockd.instance0.extra_config='
user.unprivileged: nobody
client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
session.max: 64
log: error
}
socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect
} '

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,14 @@
test_export ()
{
cp ${REF_DIR}/export.data ${CONFIG_DIR}/export
${UCI_Q} export nilpackage
assertFalse $?
${UCI_Q} export export 1>/dev/null 2>&1
assertTrue $?
${UCI} export > ${TMP_DIR}/export.result
assertTrue $?
assertSameFile ${REF_DIR}/export.result ${TMP_DIR}/export.result
}

View File

@@ -0,0 +1,47 @@
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_option_multiline()
{
cp ${REF_DIR}/get_multiline.data ${CONFIG_DIR}/test
value="$($UCI get test.section.opt)"
assertEquals '"Hello, World.
'\''' "$value"
}
test_get_section()
{
cp ${REF_DIR}/get.data ${CONFIG_DIR}/test
type=$($UCI get test.section)
assertEquals 'type' "$type"
}

View File

@@ -0,0 +1,46 @@
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_nonexisting_option_multiline()
{
cp ${REF_DIR}/set_nonexisting_option.data ${CONFIG_DIR}/set
${UCI} set set.section.opt="Hello,\'
World\""
assertSameFile ${REF_DIR}/set_nonexisting_option_multiline.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
}
test_set_existing_option_multiline()
{
cp ${REF_DIR}/set_existing_option.data ${CONFIG_DIR}/set
${UCI} set set.section.opt="Hello,\'
World\""
assertSameFile ${REF_DIR}/set_existing_option_multiline.result ${CHANGES_DIR}/set
}

View File

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

View File

@@ -0,0 +1,45 @@
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"
assertFailWithNoReturn "${UCI_Q} show test.nilsection"
assertFailWithNoReturn "${UCI_Q} show test.nilsection.nilopt"
assertFailWithNoReturn "${UCI_Q} show test.section.nilopt"
}
prepare_get_parsing_multiline() {
cp ${REF_DIR}/show_parsing_multiline.data ${CONFIG_DIR}/sockd
}
test_get_parsing_multiline_package()
{
prepare_get_parsing_multiline
value=$(${UCI_Q} show sockd)
value_ref=$(cat ${REF_DIR}/show_parsing_multiline_package.result)
assertEquals "$value_ref" "$value"
}
test_get_parsing_multiline_section()
{
prepare_get_parsing_multiline
value=$(${UCI_Q} show sockd.instance0)
value_ref=$(cat ${REF_DIR}/show_parsing_multiline_section.result)
assertEquals "$value_ref" "$value"
}
test_get_parsing_multiline_option()
{
prepare_get_parsing_multiline
value=$(${UCI_Q} show sockd.instance0.extra_config)
value_ref=$(cat ${REF_DIR}/show_parsing_multiline_option.result)
assertEquals "$value_ref" "$value"
}

View File

@@ -0,0 +1,43 @@
test_batch_set()
{
touch ${CONFIG_DIR}/batch_set
${UCI} batch <<EOF
set batch_set.SEC0='section'
set batch_set.SEC0.option0='value0'
set batch_set.SEC0.option1='"Hello,
'" World\""
set batch_set.SEC1='section'
set batch_set.SEC1.option0="value1"
EOF
${UCI} commit
assertSameFile "${REF_DIR}/batch_set.result" "${CONFIG_DIR}/batch_set"
}
test_batch_comments()
{
touch ${CONFIG_DIR}/batch_comments
${UCI} batch <<EOF
# first line comment
set batch_comments.SEC0='section'
set batch_comments.SEC0.option0='value0'
# two consecutive blank lines
# two consecutive blank lines
set batch_comments.SEC0.option1='"Hello,
'" World\""
set batch_comments.SEC1='section'
set batch_comments.SEC1.option0="value1"
# comment line starts with spaces.
commit
# last line comment
EOF
assertSameFile "${REF_DIR}/batch_comments.result" "${CONFIG_DIR}/batch_comments"
}

View File

@@ -0,0 +1,47 @@
revert_test_prepare() {
touch ${CONFIG_DIR}/revert
${UCI} set revert.SEC0=section
${UCI} set revert.SEC0.option0=value0
${UCI} set revert.SEC0.option1='"Hello,
'" World\""
}
test_revert_section()
{
revert_test_prepare
${UCI} revert revert.SEC0
assertSameFile "${REF_DIR}/revert_section.result" "$CHANGES_DIR/revert"
}
test_revert_option()
{
revert_test_prepare
${UCI} revert revert.SEC0.option0
assertSameFile "${REF_DIR}/revert_option.result" "$CHANGES_DIR/revert"
}
test_revert_option_multiline()
{
revert_test_prepare
${UCI} revert revert.SEC0.option1
assertSameFile "${REF_DIR}/revert_option_multiline.result" "$CHANGES_DIR/revert"
}
test_revert_option_long()
{
local val="$(head -c 8192 < /dev/zero | tr '\0' 'a')"
local res
touch ${CONFIG_DIR}/p
${UCI} set p.s=sec
${UCI} set p.s.o="$val"
res="$(${UCI} changes)"
assertEquals "p.s='sec'
p.s.o='$val'" "$res"
${UCI} revert p
res="$(${UCI} changes)"
assertEquals "" "$res"
}

View File

@@ -0,0 +1,52 @@
prepare_list_test() {
touch ${CONFIG_DIR}/list_test_config
${UCI} set list_test_config.SEC0=section
${UCI} add_list list_test_config.SEC0.list0=value0
${UCI} add_list list_test_config.SEC0.list0='"Hello
,'" world\""
}
test_add_list_config() {
prepare_list_test
${UCI} commit
assertSameFile "${REF_DIR}/add_list_config.result" "$CONFIG_DIR/list_test_config"
}
test_add_list_get() {
# To maintain compatibility with current code, do not quote
# list values that do not contain blank spaces ("\x20\t\r\n") within it.
prepare_list_test
value_list_get=$(${UCI} get list_test_config.SEC0.list0)
assertEquals "$value_list_get" "value0 '\"Hello
, world\"'"
}
test_add_list_show() {
prepare_list_test
value_list_show=$(${UCI} show list_test_config)
value_list_show_ref=$(cat "$REF_DIR/add_list_show.result")
assertEquals "$value_list_show" "$value_list_show_ref"
}
test_add_list_changes() {
prepare_list_test
value_list_changes=$(${UCI} changes)
value_list_changes_ref=$(cat "$REF_DIR/add_list_changes.result")
assertEquals "$value_list_changes" "$value_list_changes_ref"
}
test_del_list() {
prepare_list_test
${UCI} commit
${UCI} del_list list_test_config.SEC0.list0=value0
${UCI} commit
assertSameFile "${REF_DIR}/del_list_config.result" "$CONFIG_DIR/list_test_config"
}
test_del_list_multiline() {
prepare_list_test
${UCI} commit
${UCI} del_list list_test_config.SEC0.list0='"Hello
,'' world"'
${UCI} commit
assertSameFile "${REF_DIR}/del_list_multiline_config.result" "$CONFIG_DIR/list_test_config"
}

View File

@@ -0,0 +1,46 @@
test_add_delta() {
local new_savedir="$TMP_DIR/new_savedir"
local config_delta="$CONFIG_DIR/delta"
local cmdoutput
# add normal changes
touch "$config_delta"
$UCI set delta.sec0=sectype
$UCI add_list delta.sec0.li0=0
# save new changes in "$new_savedir"
mkdir -p "$new_savedir"
touch "$new_savedir/delta"
$UCI -P "$new_savedir" set delta.sec0=sectype
$UCI -P "$new_savedir" add_list delta.sec0.li0=1
assertEquals "delta.sec0='sectype'
delta.sec0.li0+='0'" "$($UCI changes)"
# check combined changes. Order matters here.
cmdoutput="$($UCI -P "$new_savedir" changes)"
assertTrue "$?"
assertEquals "delta.sec0='sectype'
delta.sec0.li0+='0'
delta.sec0='sectype'
delta.sec0.li0+='1'" "$cmdoutput"
# check combined export. Order matters here.
cmdoutput="$($UCI -P "$new_savedir" export)"
assertTrue "$?"
assertEquals "$(cat $REF_DIR/cli.options.delta.export.result)" "$cmdoutput"
# check CLI_FLAG_NOCOMMIT with -P option.
$UCI -P "$new_savedir" commit
assertTrue "$?"
assertEquals "" "$(cat $config_delta)"
# check normal commit.
$UCI -p "$new_savedir" commit
assertTrue "$?"
assertSameFile "$REF_DIR/cli.options.delta.commit.result" "$config_delta"
rm -rf "$new_savedir"
rm -f "$config_delta"
}

78
src/3P/uci/test/tests.sh Normal file
View File

@@ -0,0 +1,78 @@
#!/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_BIN="../uci"
[ -x $UCI_BIN ] || {
echo "uci is not present." >&2
return 1
}
UCI="${UCI_BIN} -c ${CONFIG_DIR} -p ${CHANGES_DIR}"
UCI_Q="${UCI_BIN} -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}

696
src/3P/uci/uci.h Normal file
View File

@@ -0,0 +1,696 @@
/*
* 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 Lesser General Public License for more details.
*/
#ifndef __LIBUCI_H
#define __LIBUCI_H
#ifdef __cplusplus
extern "C" {
#endif
#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>
#include <stdint.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_element;
struct uci_package;
struct uci_section;
struct uci_option;
struct uci_delta;
struct uci_context;
struct uci_backend;
struct uci_parse_option;
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 delta 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_ptr will automatically load a config package if necessary
* @str must not be constant, as it will be modified and used for the strings inside @ptr,
* thus it must also be available as long as @ptr is in use.
*
* This function returns UCI_ERR_NOTFOUND if the package specified in the tuple
* string cannot be found. Otherwise it will return UCI_OK.
*
* Note that failures in looking up other parts, if they are also specfied,
* including section and option, will also have a return value UCI_OK but with
* ptr->flags * UCI_LOOKUP_COMPLETE not set.
*/
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_del_list: Remove a string from an element list
* @ctx: uci context
* @ptr: uci pointer (with value)
*
*/
extern int uci_del_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 delta 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 delta
*
* 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 delta save directory
* @ctx: uci context
* @dir: directory name
*
* This will also try adding the specified dir to the end of delta pathes.
*/
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_delta_path: add a directory to the search path for change delta 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.
*
* Adding a duplicate directory will cause UCI_ERR_DUPLICATE be returned.
*/
extern int uci_add_delta_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_parse_ptr: parse a uci string into a uci_ptr
* @ctx: uci context
* @ptr: target data structure
* @str: string to parse
*
* str is modified by this function
*/
int uci_parse_ptr(struct uci_context *ctx, struct uci_ptr *ptr, char *str);
/**
* uci_lookup_next: lookup a child element
* @ctx: uci context
* @e: target element pointer
* @list: list of elements
* @name: name of the child element
*
* if parent is NULL, the function looks up the package with the given name
*/
int uci_lookup_next(struct uci_context *ctx, struct uci_element **e, struct uci_list *list, const char *name);
/**
* uci_parse_section: look up a set of options
* @s: uci section
* @opts: list of options to look up
* @n_opts: number of options to look up
* @tb: array of pointers to found options
*/
void uci_parse_section(struct uci_section *s, const struct uci_parse_option *opts,
int n_opts, struct uci_option **tb);
/**
* uci_hash_options: build a hash over a list of options
* @tb: list of option pointers
* @n_opts: number of options
*/
uint32_t uci_hash_options(struct uci_option **tb, int n_opts);
/* UCI data structures */
enum uci_type {
UCI_TYPE_UNSPEC = 0,
UCI_TYPE_DELTA = 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,
};
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_DELTA = (1 << 3), /* store the saved delta 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 delta files */
struct uci_list delta_path;
/* private: */
int err;
const char *func;
jmp_buf trap;
bool internal, nested;
char *buf;
int bufsz;
};
struct uci_package
{
struct uci_element e;
struct uci_list sections;
struct uci_context *ctx;
bool has_delta;
char *path;
/* private: */
struct uci_backend *backend;
void *priv;
int n_section;
struct uci_list delta;
struct uci_list saved_delta;
};
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;
};
/*
* UCI_CMD_ADD is used for anonymous sections or list values
*/
enum uci_command {
UCI_CMD_ADD,
UCI_CMD_REMOVE,
UCI_CMD_CHANGE,
UCI_CMD_RENAME,
UCI_CMD_REORDER,
UCI_CMD_LIST_ADD,
UCI_CMD_LIST_DEL,
__UCI_CMD_MAX,
__UCI_CMD_LAST = __UCI_CMD_MAX - 1
};
extern char const uci_command_char[];
struct uci_delta
{
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_parse_option {
const char *name;
enum uci_option_type type;
};
/* 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_delta UCI_TYPE_DELTA
#define uci_type_package UCI_TYPE_PACKAGE
#define uci_type_section UCI_TYPE_SECTION
#define uci_type_option UCI_TYPE_OPTION
/* element typecasting */
#ifdef UCI_DEBUG_TYPECAST
static const char *uci_typestr[] = {
[uci_type_backend] = "backend",
[uci_type_delta] = "delta",
[uci_type_package] = "package",
[uci_type_section] = "section",
[uci_type_option] = "option",
};
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(delta)
BUILD_CAST(package)
BUILD_CAST(section)
BUILD_CAST(option)
#else
#define uci_to_backend(ptr) container_of(ptr, struct uci_backend, e)
#define uci_to_delta(ptr) container_of(ptr, struct uci_delta, 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)
#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))
/**
* uci_lookup_package: look up a package
* @ctx: uci context
* @name: name of the package
*/
static inline struct uci_package *
uci_lookup_package(struct uci_context *ctx, const char *name)
{
struct uci_element *e = NULL;
if (uci_lookup_next(ctx, &e, &ctx->root, name) == 0)
return uci_to_package(e);
else
return NULL;
}
/**
* uci_lookup_section: look up a section
* @ctx: uci context
* @p: package that the section belongs to
* @name: name of the section
*/
static inline struct uci_section *
uci_lookup_section(struct uci_context *ctx, struct uci_package *p, const char *name)
{
struct uci_element *e = NULL;
if (uci_lookup_next(ctx, &e, &p->sections, name) == 0)
return uci_to_section(e);
else
return NULL;
}
/**
* uci_lookup_option: look up an option
* @ctx: uci context
* @section: section that the option belongs to
* @name: name of the option
*/
static inline struct uci_option *
uci_lookup_option(struct uci_context *ctx, struct uci_section *s, const char *name)
{
struct uci_element *e = NULL;
if (uci_lookup_next(ctx, &e, &s->options, name) == 0)
return uci_to_option(e);
else
return NULL;
}
static inline const char *
uci_lookup_option_string(struct uci_context *ctx, struct uci_section *s, const char *name)
{
struct uci_option *o;
o = uci_lookup_option(ctx, s, name);
if (!o || o->type != UCI_TYPE_STRING)
return NULL;
return o->v.string;
}
#ifdef __cplusplus
}
#endif
#endif

42
src/3P/uci/uci_blob.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* blob.c - uci <-> blobmsg conversion layer
* Copyright (C) 2012-2013 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 Lesser General Public License for more details.
*/
#ifndef __UCI_BLOB_H
#define __UCI_BLOB_H
#include <libubox/blobmsg.h>
#include "uci.h"
struct uci_blob_param_info {
enum blobmsg_type type;
};
struct uci_blob_param_list {
int n_params;
const struct blobmsg_policy *params;
const struct uci_blob_param_info *info;
const char * const *validate;
int n_next;
const struct uci_blob_param_list *next[];
};
int uci_to_blob(struct blob_buf *b, struct uci_section *s,
const struct uci_blob_param_list *p);
bool uci_blob_check_equal(struct blob_attr *c1, struct blob_attr *c2,
const struct uci_blob_param_list *config);
bool uci_blob_diff(struct blob_attr **tb1, struct blob_attr **tb2,
const struct uci_blob_param_list *config,
unsigned long *diff_bits);
#endif

View File

@@ -0,0 +1,2 @@
#cmakedefine UCI_DEBUG 1
#cmakedefine UCI_DEBUG_TYPECAST 1

258
src/3P/uci/uci_internal.h Normal file
View File

@@ -0,0 +1,258 @@
/*
* 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 Lesser General Public License for more details.
*/
#ifndef __UCI_INTERNAL_H
#define __UCI_INTERNAL_H
#define __private __attribute__((visibility("hidden")))
#define __public
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;
int pos;
};
#define pctx_pos(pctx) ((pctx)->pos)
#define pctx_str(pctx, i) (&(pctx)->buf[(i)])
#define pctx_cur_str(pctx) pctx_str(pctx, pctx_pos(pctx))
#define pctx_char(pctx, i) ((pctx)->buf[(i)])
#define pctx_cur_char(pctx) pctx_char(pctx, pctx_pos(pctx))
extern const char *uci_confdir;
extern const char *uci_savedir;
__private void *uci_malloc(struct uci_context *ctx, size_t size);
__private void *uci_realloc(struct uci_context *ctx, void *ptr, size_t size);
__private char *uci_strdup(struct uci_context *ctx, const char *str);
__private bool uci_validate_str(const char *str, bool name, bool package);
__private void uci_add_delta(struct uci_context *ctx, struct uci_list *list, int cmd, const char *section, const char *option, const char *value);
__private void uci_free_delta(struct uci_delta *h);
__private struct uci_package *uci_alloc_package(struct uci_context *ctx, const char *name);
__private FILE *uci_open_stream(struct uci_context *ctx, const char *filename, const char *origfilename, int pos, bool write, bool create);
__private void uci_close_stream(FILE *stream);
__private void uci_getln(struct uci_context *ctx, int offset);
__private void uci_parse_error(struct uci_context *ctx, char *reason);
__private void uci_alloc_parse_context(struct uci_context *ctx);
__private void uci_cleanup(struct uci_context *ctx);
__private struct uci_element *uci_lookup_list(struct uci_list *list, const char *name);
__private void uci_fixup_section(struct uci_context *ctx, struct uci_section *s);
__private void uci_free_package(struct uci_package **package);
__private struct uci_element *uci_alloc_generic(struct uci_context *ctx, int type, const char *name, int size);
__private void uci_free_element(struct uci_element *e);
__private struct uci_element *uci_expand_ptr(struct uci_context *ctx, struct uci_ptr *ptr, bool complete);
__private int uci_load_delta(struct uci_context *ctx, struct uci_package *p, bool flush);
static inline bool uci_validate_package(const char *str)
{
return uci_validate_str(str, false, true);
}
static inline bool uci_validate_type(const char *str)
{
return uci_validate_str(str, false, false);
}
static inline bool uci_validate_name(const char *str)
{
return uci_validate_str(str, true, false);
}
/* 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);
}
extern struct uci_backend uci_file_backend;
#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
*/
__private 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
*/
__private 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; \
if (!ctx) \
return UCI_ERR_INVAL; \
ctx->err = 0; \
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

922
src/3P/uci/ucimap.c Normal file
View File

@@ -0,0 +1,922 @@
/*
* 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 Lesser 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)
{
return (type & UCIMAP_SUBTYPE) == UCIMAP_STRING;
}
static inline bool
ucimap_is_fixup(enum ucimap_type type)
{
return (type & UCIMAP_SUBTYPE) == UCIMAP_SECTION;
}
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, *sd_next;
for (sd = map->sdata; sd; sd = sd_next) {
sd_next = 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);
union ucimap_data *data;
if (!ptr)
return false;
switch(f->type & UCIMAP_TYPE) {
case UCIMAP_SIMPLE:
f->data->ptr = ptr;
break;
case UCIMAP_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);
if (!new)
return -ENOMEM;
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 (!a->ptr)
return -ENOMEM;
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.next = NULL;
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:
free(sd->alloc_custom);
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);
sd = ucimap_ptr_section(sm, sd);
}
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;
}

323
src/3P/uci/ucimap.h Normal file
View File

@@ -0,0 +1,323 @@
/*
* 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 Lesser 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) \
.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_CUSTOM = 0x0,
UCIMAP_STRING = 0x1,
UCIMAP_BOOL = 0x2,
UCIMAP_INT = 0x3,
UCIMAP_SECTION = 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

254
src/3P/uci/util.c Normal file
View File

@@ -0,0 +1,254 @@
/*
* 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 Lesser General Public License for more details.
*/
/*
* This file contains misc utility functions and wrappers to standard
* functions, which throw exceptions upon failure.
*/
#define _GNU_SOURCE
#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>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include "uci.h"
#include "uci_internal.h"
__private 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;
}
__private 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;
}
__private 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;
}
/*
* validate strings for names and types, reject special characters
* for names, only alphanum and _ is allowed (shell compatibility)
* for types, we allow more characters
*/
__private bool uci_validate_str(const char *str, bool name, bool package)
{
if (!*str)
return false;
for (; *str; str++) {
unsigned char c = *str;
if (isalnum(c) || c == '_')
continue;
if (c == '-' && package)
continue;
if (name || (c < 33) || (c > 126))
return false;
}
return true;
}
bool uci_validate_text(const char *str)
{
while (*str) {
unsigned char c = *str;
if (c < 32 && c != '\t' && c != '\n' && c != '\r')
return false;
str++;
}
return true;
}
__private 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);
}
__private void uci_parse_error(struct uci_context *ctx, char *reason)
{
struct uci_parse_context *pctx = ctx->pctx;
pctx->reason = reason;
pctx->byte = pctx_pos(pctx);
UCI_THROW(ctx, UCI_ERR_PARSE);
}
/*
* 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
*/
__private FILE *uci_open_stream(struct uci_context *ctx, const char *filename, const char *origfilename, int pos, bool write, bool create)
{
struct stat statbuf;
FILE *file = NULL;
int fd, ret;
int flags = (write ? O_RDWR : O_RDONLY);
mode_t mode = UCI_FILEMODE;
char *name = NULL;
char *filename2 = NULL;
if (create) {
flags |= O_CREAT;
if (origfilename) {
name = basename((char *) origfilename);
} else {
name = basename((char *) filename);
}
if ((asprintf(&filename2, "%s/%s", ctx->confdir, name) < 0) || !filename2) {
UCI_THROW(ctx, UCI_ERR_MEM);
} else {
if (stat(filename2, &statbuf) == 0)
mode = statbuf.st_mode;
free(filename2);
}
}
if (!write && ((stat(filename, &statbuf) < 0) ||
((statbuf.st_mode & S_IFMT) != S_IFREG))) {
UCI_THROW(ctx, UCI_ERR_NOTFOUND);
}
fd = open(filename, flags, mode);
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;
}
__private void uci_close_stream(FILE *stream)
{
int fd;
if (!stream)
return;
fflush(stream);
fd = fileno(stream);
flock(fd, LOCK_UN);
fclose(stream);
}