/* 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 #include #include #include #include #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); }