444 lines
9.0 KiB
C
444 lines
9.0 KiB
C
|
|
/*
|
||
|
|
scconfig - database
|
||
|
|
Copyright (C) 2009..2012 Tibor Palinkas
|
||
|
|
|
||
|
|
This library is free software; you can redistribute it and/or
|
||
|
|
modify it under the terms of the GNU Lesser General Public
|
||
|
|
License as published by the Free Software Foundation; either
|
||
|
|
version 2.1 of the License, or (at your option) any later version.
|
||
|
|
|
||
|
|
This library 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.
|
||
|
|
|
||
|
|
You should have received a copy of the GNU Lesser General Public
|
||
|
|
License along with this library; if not, write to the Free Software
|
||
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
|
|
||
|
|
Project page: http://repo.hu/projects/scconfig
|
||
|
|
Contact via email: scconfig [at] igor2.repo.hu
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <assert.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <stdarg.h>
|
||
|
|
#include "db.h"
|
||
|
|
#include "log.h"
|
||
|
|
#include "libs.h"
|
||
|
|
|
||
|
|
ht_t *DBs = NULL;
|
||
|
|
char *db_cwd = NULL;
|
||
|
|
|
||
|
|
void append(const char *key, const char *value)
|
||
|
|
{
|
||
|
|
const char *orig;
|
||
|
|
char *new;
|
||
|
|
int l1, l2;
|
||
|
|
|
||
|
|
assert(key != NULL);
|
||
|
|
assert(value != NULL);
|
||
|
|
if (*value == '\0')
|
||
|
|
return;
|
||
|
|
|
||
|
|
orig = get(key);
|
||
|
|
if (orig == NULL) {
|
||
|
|
put(key, value);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
l1 = strlen(orig);
|
||
|
|
l2 = strlen(value);
|
||
|
|
new = malloc(l1 + l2 + 1);
|
||
|
|
memcpy(new, orig, l1);
|
||
|
|
memcpy(new + l1, value, l2);
|
||
|
|
new[l1+l2] = '\0';
|
||
|
|
put(key, new);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static const char *db_split_path(const char *key, ht_t **ht, char **fld)
|
||
|
|
{
|
||
|
|
size_t fld_len;
|
||
|
|
const char *path;
|
||
|
|
char first_level_dir[32];
|
||
|
|
|
||
|
|
path = str_chr((char *)(key+1), '/');
|
||
|
|
assert(path != NULL);
|
||
|
|
fld_len = path - key;
|
||
|
|
path++;
|
||
|
|
if (*path == '\0') {
|
||
|
|
*ht = NULL;
|
||
|
|
if (fld != NULL)
|
||
|
|
*fld = NULL;
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
assert(fld_len < sizeof(first_level_dir));
|
||
|
|
strncpy(first_level_dir, key, fld_len);
|
||
|
|
first_level_dir[fld_len] = '\0';
|
||
|
|
*ht = ht_get(DBs, first_level_dir+1);
|
||
|
|
if (fld != NULL)
|
||
|
|
*fld = first_level_dir;
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void export_qs(FILE *f, const char *s)
|
||
|
|
{
|
||
|
|
fputc('"', f);
|
||
|
|
for(;*s != '\0';s++) {
|
||
|
|
switch(*s) {
|
||
|
|
case '"': fputc('\\', f); fputc('"', f); break;
|
||
|
|
case '\n': fputc('\\', f); fputc('n', f); break;
|
||
|
|
case '\r': fputc('\\', f); fputc('r', f); break;
|
||
|
|
case '\t': fputc('\\', f); fputc('t', f); break;
|
||
|
|
default: fputc(*s, f);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fputc('"', f);
|
||
|
|
fputc('\n', f);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int needs_quote(const char *s) {
|
||
|
|
for(; *s != '\0'; s++)
|
||
|
|
if ((*s < 32) || (*s > 126) || (*s == '"')) return 1;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int export_(FILE *f, int export_empty, ht_t *table, const char *fld)
|
||
|
|
{
|
||
|
|
ht_entry_t *h;
|
||
|
|
|
||
|
|
for(h = ht_first(table); h != NULL; h = ht_next(table, h))
|
||
|
|
if (export_empty || ((h->value != NULL) && (*(char *)h->value != '\0'))) {
|
||
|
|
fprintf(f, "/%s/%s=", fld, h->key);
|
||
|
|
if (h->value != NULL) {
|
||
|
|
if (needs_quote((char *)h->value))
|
||
|
|
export_qs(f, (const char *)h->value);
|
||
|
|
else
|
||
|
|
fprintf(f, "%s\n", (char *)h->value);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
fprintf(f, "\n");
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int export(const char *fn, int export_empty, const char *root)
|
||
|
|
{
|
||
|
|
FILE *f;
|
||
|
|
int ret = 0;
|
||
|
|
/* ht_t *table; */
|
||
|
|
ht_entry_t *h;
|
||
|
|
|
||
|
|
if (fn != NULL) {
|
||
|
|
f = fopen(fn, "w");
|
||
|
|
if (f == NULL)
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
f = stdout;
|
||
|
|
|
||
|
|
if ((root == NULL) || ((root[0] == '/') && (root[1] == '\0'))) {
|
||
|
|
/* export all directories */
|
||
|
|
for(h = ht_first(DBs); h != NULL; h = ht_next(DBs, h))
|
||
|
|
ret += export_(f, export_empty, h->value, h->key);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
error("not yet implemented\n");
|
||
|
|
abort();
|
||
|
|
/* db_split_path(); */
|
||
|
|
}
|
||
|
|
|
||
|
|
if (f != stdout)
|
||
|
|
fclose(f);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* append a single character, grow the buffer as needed */
|
||
|
|
#define qappend(chr) \
|
||
|
|
do { \
|
||
|
|
if (used >= alloced) { \
|
||
|
|
alloced += 256; \
|
||
|
|
res = realloc(res, alloced); \
|
||
|
|
} \
|
||
|
|
res[used] = chr; \
|
||
|
|
used++; \
|
||
|
|
} while(0)
|
||
|
|
|
||
|
|
/* read until end of quote and interpret backslash sequences if do_esc is non-zero */
|
||
|
|
static char *readq(FILE *f, char *str, long strmax, int quote, int do_esc, int *num_lines, const char *fn)
|
||
|
|
{
|
||
|
|
int bs = 0;
|
||
|
|
long used = 0, alloced = 0;
|
||
|
|
char *res = NULL, *s;
|
||
|
|
|
||
|
|
for(;;) {
|
||
|
|
for(s = str; *s != '\0'; s++) {
|
||
|
|
if (*s == '\n') (*num_lines)++;
|
||
|
|
|
||
|
|
if (bs) { /* character escaped by backslash */
|
||
|
|
switch(*s) {
|
||
|
|
case '\\': qappend('\\'); break;
|
||
|
|
case 'n': qappend('\n'); break;
|
||
|
|
case 'r': qappend('\r'); break;
|
||
|
|
case 't': qappend('\t'); break;
|
||
|
|
default: qappend(*s); break;
|
||
|
|
}
|
||
|
|
bs = 0;
|
||
|
|
}
|
||
|
|
else if (*s == quote) { /* end */
|
||
|
|
qappend('\0');
|
||
|
|
if ((s[1] != '\r') && (s[1] != '\n') && (s[1] != '\0'))
|
||
|
|
fprintf(stderr, "Warning: trailing text after quote ignored in %s:%d\n", fn, (*num_lines)+1);
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
else if (do_esc && (*s == '\\')) bs = 1; /* backslash start */
|
||
|
|
else qappend(*s); /* plain character */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* get the next chunk */
|
||
|
|
fgets(str, strmax, f);
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL; /* can't get here */
|
||
|
|
}
|
||
|
|
|
||
|
|
int import(const char *fn)
|
||
|
|
{
|
||
|
|
char line[1024];
|
||
|
|
char *key, *value, *nl, *slash;
|
||
|
|
int num_records, num_lines;
|
||
|
|
FILE *f;
|
||
|
|
|
||
|
|
f = fopen(fn, "r");
|
||
|
|
if (f == NULL)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
for(num_records = 0, num_lines = 0; !feof(f); num_lines++) {
|
||
|
|
*line = '\0';
|
||
|
|
fgets(line, sizeof(line) - 1, f);
|
||
|
|
if ((*line != '#') && (*line != '\n') && (*line != '\r') && (*line != '\0')) {
|
||
|
|
int quote, do_esc=0;
|
||
|
|
key = line;
|
||
|
|
value = str_chr(key, '=');
|
||
|
|
if (value == NULL) {
|
||
|
|
error("Error importing: missing '=' in line %d in file %s.\n", num_lines, fn);
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
num_records++;
|
||
|
|
*value = '\0';
|
||
|
|
value++;
|
||
|
|
if (*value == '"') {
|
||
|
|
quote=*value;
|
||
|
|
value++;
|
||
|
|
do_esc=1;
|
||
|
|
}
|
||
|
|
else if (*value == '\'') {
|
||
|
|
quote=*value;
|
||
|
|
value++;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
quote=0;
|
||
|
|
|
||
|
|
if (!quote) {
|
||
|
|
nl = str_chr(value, '\n');
|
||
|
|
if (nl != NULL)
|
||
|
|
*nl = '\0';
|
||
|
|
}
|
||
|
|
else
|
||
|
|
value = readq(f, value, sizeof(line) - (value - line) - 4, quote, do_esc, &num_lines, fn);
|
||
|
|
|
||
|
|
slash = str_chr(key+1, '/');
|
||
|
|
if (slash == NULL) {
|
||
|
|
error("Error importing: no directory name for %s.\n", key);
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
*slash = '\0';
|
||
|
|
db_mkdir(key);
|
||
|
|
*slash = '/';
|
||
|
|
put(key, value);
|
||
|
|
logprintf(0, "(Import from '%s': '%s'='%s')\n", fn, key, value);
|
||
|
|
if (quote)
|
||
|
|
free(value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fclose(f);
|
||
|
|
return num_records;
|
||
|
|
}
|
||
|
|
|
||
|
|
int import_args(const char *key, const char *fn)
|
||
|
|
{
|
||
|
|
(void) key; /* suppress compiler warnings for unused key; needed because function pointers to this function from arg.c */
|
||
|
|
db_mkdir("/target");
|
||
|
|
db_mkdir("/host");
|
||
|
|
|
||
|
|
return import(fn) < 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static const char *db_get(const char *key)
|
||
|
|
{
|
||
|
|
const char *path;
|
||
|
|
ht_t *ht;
|
||
|
|
|
||
|
|
path = db_split_path(key, &ht, NULL);
|
||
|
|
if (ht == NULL)
|
||
|
|
return NULL;
|
||
|
|
return ht_get(ht, path);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const char *db_put(const char *key, const char *value)
|
||
|
|
{
|
||
|
|
const char *path;
|
||
|
|
ht_t *ht;
|
||
|
|
|
||
|
|
path = db_split_path(key, &ht, NULL);
|
||
|
|
if (ht == NULL) {
|
||
|
|
error("db_put: can't find top level hash for '%s'\n", key);
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
return ht_set(ht, path, (void *)value);
|
||
|
|
}
|
||
|
|
|
||
|
|
#define assamble_path \
|
||
|
|
assert(strlen(key) + strlen(db_cwd) < sizeof(tmp)-1); \
|
||
|
|
sprintf(tmp, "%s/%s", db_cwd, key);
|
||
|
|
|
||
|
|
const char *get(const char *key)
|
||
|
|
{
|
||
|
|
char tmp[256];
|
||
|
|
|
||
|
|
if (*key == '/')
|
||
|
|
return db_get(key);
|
||
|
|
assamble_path;
|
||
|
|
return db_get(tmp);
|
||
|
|
}
|
||
|
|
|
||
|
|
const char *put(const char *key, const char *value)
|
||
|
|
{
|
||
|
|
char tmp[256];
|
||
|
|
|
||
|
|
if (*key == '/')
|
||
|
|
return db_put(key, value);
|
||
|
|
assamble_path;
|
||
|
|
return db_put(tmp, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
void db_init(void)
|
||
|
|
{
|
||
|
|
DBs = ht_resize(ht_alloc(0), 16);
|
||
|
|
}
|
||
|
|
|
||
|
|
void db_uninit(void)
|
||
|
|
{
|
||
|
|
ht_entry_t *h;
|
||
|
|
ht_t *dir;
|
||
|
|
|
||
|
|
for(h = ht_first(DBs); h != NULL; h = ht_next(DBs, h)) {
|
||
|
|
dir = h->value;
|
||
|
|
dir->refcount--;
|
||
|
|
if (dir->refcount == 0)
|
||
|
|
ht_free(dir);
|
||
|
|
}
|
||
|
|
ht_free(DBs);
|
||
|
|
if (db_cwd != NULL)
|
||
|
|
free(db_cwd);
|
||
|
|
/* Just in case someone calls db_init again... */
|
||
|
|
db_cwd = NULL;
|
||
|
|
DBs = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
void db_cd(const char *path)
|
||
|
|
{
|
||
|
|
assert(*path == '/');
|
||
|
|
if (db_cwd != NULL)
|
||
|
|
free(db_cwd);
|
||
|
|
db_cwd = strclone(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
void db_mkdir(const char *path)
|
||
|
|
{
|
||
|
|
ht_t *ht, *target;
|
||
|
|
assert(*path == '/');
|
||
|
|
target = ht_get(DBs, path+1);
|
||
|
|
if (target == NULL) {
|
||
|
|
ht = ht_resize(ht_alloc(1), 256);
|
||
|
|
ht_set(DBs, path+1, ht);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void db_rmdir(const char *path)
|
||
|
|
{
|
||
|
|
ht_t *ht;
|
||
|
|
assert(*path == '/');
|
||
|
|
ht = ht_get(DBs, path+1);
|
||
|
|
if (ht == NULL)
|
||
|
|
return;
|
||
|
|
ht_del(DBs, path+1);
|
||
|
|
/* ht_free(ht); */
|
||
|
|
}
|
||
|
|
|
||
|
|
void db_link(const char *existing, const char *new)
|
||
|
|
{
|
||
|
|
ht_t *ht;
|
||
|
|
|
||
|
|
assert(*new == '/');
|
||
|
|
ht = ht_get(DBs, existing+1);
|
||
|
|
assert(ht != NULL);
|
||
|
|
ht_set(DBs, new+1, ht);
|
||
|
|
ht->refcount++;
|
||
|
|
}
|
||
|
|
|
||
|
|
char *concat_nodes(const char *prefix, ...)
|
||
|
|
{
|
||
|
|
char *buff;
|
||
|
|
const char *node, *value;
|
||
|
|
int allocated = 256, len, totallen;
|
||
|
|
|
||
|
|
va_list ap;
|
||
|
|
va_start(ap, prefix);
|
||
|
|
buff = malloc(allocated);
|
||
|
|
if (prefix != NULL) {
|
||
|
|
strcpy(buff, prefix);
|
||
|
|
totallen = strlen(prefix);
|
||
|
|
buff[totallen] = ' ';
|
||
|
|
totallen++;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
totallen = 0;
|
||
|
|
|
||
|
|
while((node = va_arg(ap, const char *)) != NULL) {
|
||
|
|
value = get(node);
|
||
|
|
if (value != NULL) {
|
||
|
|
len = strlen(value);
|
||
|
|
if (totallen + len >= allocated) {
|
||
|
|
allocated = totallen + len + 256;
|
||
|
|
buff = realloc(buff, allocated);
|
||
|
|
}
|
||
|
|
memcpy(buff + totallen, value, len);
|
||
|
|
totallen += len;
|
||
|
|
buff[totallen] = ' ';
|
||
|
|
totallen++;
|
||
|
|
|
||
|
|
buff[totallen] = '\0';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
buff[totallen - 1] = '\0';
|
||
|
|
va_end(ap);
|
||
|
|
return buff;
|
||
|
|
}
|
||
|
|
|
||
|
|
int node_istrue(const char *key)
|
||
|
|
{
|
||
|
|
const char *s = get(key);
|
||
|
|
if (s == NULL)
|
||
|
|
return 0;
|
||
|
|
return istrue(s);
|
||
|
|
}
|