bump uci version 2016-07-04
This commit is contained in:
13
src/3P/uci/.gitignore
vendored
Normal file
13
src/3P/uci/.gitignore
vendored
Normal 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
47
src/3P/uci/CMakeLists.txt
Normal 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
238
src/3P/uci/blob.c
Normal 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
777
src/3P/uci/cli.c
Normal 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
513
src/3P/uci/delta.c
Normal 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
932
src/3P/uci/file.c
Normal 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
234
src/3P/uci/libuci.c
Normal 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
733
src/3P/uci/list.c
Normal 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;
|
||||
}
|
||||
|
||||
51
src/3P/uci/lua/CMakeLists.txt
Normal file
51
src/3P/uci/lua/CMakeLists.txt
Normal 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
999
src/3P/uci/lua/uci.c
Normal 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
130
src/3P/uci/parse.c
Normal 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
178
src/3P/uci/sh/uci.sh
Executable 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
34
src/3P/uci/test/README
Normal 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.
|
||||
25
src/3P/uci/test/config/network
Normal file
25
src/3P/uci/test/config/network
Normal 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'
|
||||
|
||||
4
src/3P/uci/test/references/add_list_changes.result
Normal file
4
src/3P/uci/test/references/add_list_changes.result
Normal file
@@ -0,0 +1,4 @@
|
||||
list_test_config.SEC0='section'
|
||||
list_test_config.SEC0.list0+='value0'
|
||||
list_test_config.SEC0.list0+='"Hello
|
||||
, world"'
|
||||
6
src/3P/uci/test/references/add_list_config.result
Normal file
6
src/3P/uci/test/references/add_list_config.result
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
config section 'SEC0'
|
||||
list list0 'value0'
|
||||
list list0 '"Hello
|
||||
, world"'
|
||||
|
||||
3
src/3P/uci/test/references/add_list_show.result
Normal file
3
src/3P/uci/test/references/add_list_show.result
Normal file
@@ -0,0 +1,3 @@
|
||||
list_test_config.SEC0=section
|
||||
list_test_config.SEC0.list0='value0' '"Hello
|
||||
, world"'
|
||||
1
src/3P/uci/test/references/add_section.result
Normal file
1
src/3P/uci/test/references/add_section.result
Normal file
@@ -0,0 +1 @@
|
||||
+add.section='type'
|
||||
9
src/3P/uci/test/references/batch_comments.result
Normal file
9
src/3P/uci/test/references/batch_comments.result
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
config section 'SEC0'
|
||||
option option0 'value0'
|
||||
option option1 '"Hello,
|
||||
World"'
|
||||
|
||||
config section 'SEC1'
|
||||
option option0 'value1'
|
||||
|
||||
9
src/3P/uci/test/references/batch_set.result
Normal file
9
src/3P/uci/test/references/batch_set.result
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
config section 'SEC0'
|
||||
option option0 'value0'
|
||||
option option1 '"Hello,
|
||||
World"'
|
||||
|
||||
config section 'SEC1'
|
||||
option option0 'value1'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
config sectype 'sec0'
|
||||
list li0 '1'
|
||||
list li0 '0'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package delta
|
||||
|
||||
config sectype 'sec0'
|
||||
list li0 '0'
|
||||
list li0 '1'
|
||||
5
src/3P/uci/test/references/del_list_config.result
Normal file
5
src/3P/uci/test/references/del_list_config.result
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
config section 'SEC0'
|
||||
list list0 '"Hello
|
||||
, world"'
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
config section 'SEC0'
|
||||
list list0 'value0'
|
||||
|
||||
11
src/3P/uci/test/references/export.data
Normal file
11
src/3P/uci/test/references/export.data
Normal 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.\'
|
||||
"
|
||||
12
src/3P/uci/test/references/export.result
Normal file
12
src/3P/uci/test/references/export.result
Normal 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.'\''
|
||||
'
|
||||
|
||||
2
src/3P/uci/test/references/get.data
Normal file
2
src/3P/uci/test/references/get.data
Normal file
@@ -0,0 +1,2 @@
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
5
src/3P/uci/test/references/get_multiline.data
Normal file
5
src/3P/uci/test/references/get_multiline.data
Normal file
@@ -0,0 +1,5 @@
|
||||
config 'type' 'section'
|
||||
# Cannot preserve trailling whitespace with assertEquals.
|
||||
option opt "\"Hello, \
|
||||
World.
|
||||
\'"
|
||||
5
src/3P/uci/test/references/get_parsing.data
Normal file
5
src/3P/uci/test/references/get_parsing.data
Normal file
@@ -0,0 +1,5 @@
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
|
||||
config 'unnamed'
|
||||
option 'opt1' 'val1'
|
||||
12
src/3P/uci/test/references/import.data
Normal file
12
src/3P/uci/test/references/import.data
Normal 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.\'
|
||||
"
|
||||
11
src/3P/uci/test/references/import.result
Normal file
11
src/3P/uci/test/references/import.result
Normal 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.'\''
|
||||
'
|
||||
|
||||
3
src/3P/uci/test/references/revert_option.result
Normal file
3
src/3P/uci/test/references/revert_option.result
Normal file
@@ -0,0 +1,3 @@
|
||||
revert.SEC0='section'
|
||||
revert.SEC0.option1='"Hello,
|
||||
World"'
|
||||
@@ -0,0 +1,2 @@
|
||||
revert.SEC0='section'
|
||||
revert.SEC0.option0='value0'
|
||||
0
src/3P/uci/test/references/revert_section.result
Normal file
0
src/3P/uci/test/references/revert_section.result
Normal file
2
src/3P/uci/test/references/set_existing_option.data
Normal file
2
src/3P/uci/test/references/set_existing_option.data
Normal file
@@ -0,0 +1,2 @@
|
||||
config 'named' 'section'
|
||||
option 'opt' 'err'
|
||||
1
src/3P/uci/test/references/set_existing_option.result
Normal file
1
src/3P/uci/test/references/set_existing_option.result
Normal file
@@ -0,0 +1 @@
|
||||
set.section.opt='val'
|
||||
@@ -0,0 +1,2 @@
|
||||
set.section.opt='Hello,\'\''
|
||||
World"'
|
||||
1
src/3P/uci/test/references/set_named_section.result
Normal file
1
src/3P/uci/test/references/set_named_section.result
Normal file
@@ -0,0 +1 @@
|
||||
set.section='named'
|
||||
1
src/3P/uci/test/references/set_nonexisting_option.data
Normal file
1
src/3P/uci/test/references/set_nonexisting_option.data
Normal file
@@ -0,0 +1 @@
|
||||
config 'named' 'section'
|
||||
1
src/3P/uci/test/references/set_nonexisting_option.result
Normal file
1
src/3P/uci/test/references/set_nonexisting_option.result
Normal file
@@ -0,0 +1 @@
|
||||
set.section.opt='val'
|
||||
@@ -0,0 +1,2 @@
|
||||
set.section.opt='Hello,\'\''
|
||||
World"'
|
||||
2
src/3P/uci/test/references/set_parsing.data
Normal file
2
src/3P/uci/test/references/set_parsing.data
Normal file
@@ -0,0 +1,2 @@
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
2
src/3P/uci/test/references/set_parsing_multiline.data
Normal file
2
src/3P/uci/test/references/set_parsing_multiline.data
Normal file
@@ -0,0 +1,2 @@
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
2
src/3P/uci/test/references/show_parsing.data
Normal file
2
src/3P/uci/test/references/show_parsing.data
Normal file
@@ -0,0 +1,2 @@
|
||||
config 'type' 'section'
|
||||
option 'opt' 'val'
|
||||
20
src/3P/uci/test/references/show_parsing_multiline.data
Normal file
20
src/3P/uci/test/references/show_parsing_multiline.data
Normal 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
|
||||
} '
|
||||
@@ -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
|
||||
} '
|
||||
@@ -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
|
||||
} '
|
||||
@@ -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
|
||||
} '
|
||||
15
src/3P/uci/test/references/ucimap_example_1.result
Normal file
15
src/3P/uci/test/references/ucimap_example_1.result
Normal 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
|
||||
14
src/3P/uci/test/references/ucimap_example_2.result
Normal file
14
src/3P/uci/test/references/ucimap_example_2.result
Normal 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
|
||||
1040
src/3P/uci/test/shunit2/shunit2
Normal file
1040
src/3P/uci/test/shunit2/shunit2
Normal file
File diff suppressed because it is too large
Load Diff
5
src/3P/uci/test/tests.d/000_import
Normal file
5
src/3P/uci/test/tests.d/000_import
Normal file
@@ -0,0 +1,5 @@
|
||||
test_import ()
|
||||
{
|
||||
${UCI} import < ${REF_DIR}/import.data
|
||||
assertSameFile ${REF_DIR}/import.result ${CONFIG_DIR}/import-test
|
||||
}
|
||||
14
src/3P/uci/test/tests.d/010_export
Normal file
14
src/3P/uci/test/tests.d/010_export
Normal 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
|
||||
}
|
||||
47
src/3P/uci/test/tests.d/020_get
Normal file
47
src/3P/uci/test/tests.d/020_get
Normal 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"
|
||||
}
|
||||
46
src/3P/uci/test/tests.d/030_set
Normal file
46
src/3P/uci/test/tests.d/030_set
Normal 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
|
||||
}
|
||||
8
src/3P/uci/test/tests.d/040_add
Normal file
8
src/3P/uci/test/tests.d/040_add
Normal 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
|
||||
}
|
||||
45
src/3P/uci/test/tests.d/050_show
Normal file
45
src/3P/uci/test/tests.d/050_show
Normal 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"
|
||||
}
|
||||
43
src/3P/uci/test/tests.d/060_batch
Normal file
43
src/3P/uci/test/tests.d/060_batch
Normal 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"
|
||||
}
|
||||
47
src/3P/uci/test/tests.d/070_revert
Normal file
47
src/3P/uci/test/tests.d/070_revert
Normal 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"
|
||||
}
|
||||
52
src/3P/uci/test/tests.d/080_list
Normal file
52
src/3P/uci/test/tests.d/080_list
Normal 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"
|
||||
}
|
||||
46
src/3P/uci/test/tests.d/090_cli_options
Normal file
46
src/3P/uci/test/tests.d/090_cli_options
Normal 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
78
src/3P/uci/test/tests.sh
Normal 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
696
src/3P/uci/uci.h
Normal 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
42
src/3P/uci/uci_blob.h
Normal 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
|
||||
2
src/3P/uci/uci_config.h.in
Normal file
2
src/3P/uci/uci_config.h.in
Normal file
@@ -0,0 +1,2 @@
|
||||
#cmakedefine UCI_DEBUG 1
|
||||
#cmakedefine UCI_DEBUG_TYPECAST 1
|
||||
258
src/3P/uci/uci_internal.h
Normal file
258
src/3P/uci/uci_internal.h
Normal 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
922
src/3P/uci/ucimap.c
Normal 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
323
src/3P/uci/ucimap.h
Normal 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
254
src/3P/uci/util.c
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user