/* -*- mode:c; c-file-style:"k&r"; c-basic-offset: 4; tab-width:4; indent-tabs-mode:nil; mode:auto-fill; fill-column:78; -*- */ /* vim: set ts=4 sw=4 et tw=78 fo=cqt wm=0: */ /* Copyright (C) 2014 Stony Brook University This file is part of Graphene Library OS. Graphene Library OS 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 3 of the License, or (at your option) any later version. Graphene Library OS 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 program. If not, see . */ /* * config.c * * This file contains functions to read app config (manifest) file and create * a tree to lookup / access config values. */ #include #include #include DEFINE_LIST(config); struct config { const char * key, * val; size_t klen, vlen; /* for leaf nodes, vlen stores the size of config values; for branch nodes, vlen stores the sum of config value lengths plus one of all the immediate children. */ char * buf; LIST_TYPE(config) list; LISTP_TYPE(config) children; LIST_TYPE(config) siblings; }; static int __add_config (struct config_store * store, const char * key, int klen, const char * val, int vlen, struct config ** entry) { LISTP_TYPE(config) * list = &store->root; struct config * e = NULL; struct config * parent = NULL; while (klen) { if (e && e->val) return -PAL_ERROR_INVAL; const char * token = key; size_t len = 0; for ( ; len < klen ; len++) if (token[len] == '.') break; listp_for_each_entry(e, list, siblings) if (e->klen == len && !memcmp(e->key, token, len)) goto next; e = store->malloc(sizeof(struct config)); if (!e) return -PAL_ERROR_NOMEM; e->key = token; e->klen = len; e->val = NULL; e->vlen = 0; e->buf = NULL; INIT_LIST_HEAD(e, list); listp_add_tail(e, &store->entries, list); INIT_LISTP(&e->children); INIT_LIST_HEAD(e, siblings); listp_add_tail(e, list, siblings); if (parent) parent->vlen += (len + 1); next: if (len < klen) len++; key += len; klen -= len; list = &e->children; parent = e; } if (!e || e->val || !listp_empty(&e->children)) return -PAL_ERROR_INVAL; e->val = val; e->vlen = vlen; if (entry) *entry = e; return 0; } static struct config * __get_config (struct config_store * store, const char * key) { LISTP_TYPE(config) * list = &store->root; struct config * e = NULL; while (*key) { const char * token = key; int len = 0; for ( ; token[len] ; len++) if (token[len] == '.') break; listp_for_each_entry(e, list, siblings) if (e->klen == len && !memcmp(e->key, token, len)) goto next; return NULL; next: if (token[len]) len++; key += len; list = &e->children; } return e; } ssize_t get_config (struct config_store * store, const char * key, char * val_buf, size_t size) { struct config * e = __get_config(store, key); if (!e || !e->val) return -PAL_ERROR_INVAL; if (e->vlen >= size) return -PAL_ERROR_TOOLONG; memcpy(val_buf, e->val, e->vlen); val_buf[e->vlen] = 0; return e->vlen; } int get_config_entries (struct config_store * store, const char * key, char * key_buf, size_t key_bufsize) { struct config * e = __get_config(store, key); if (!e || e->val) return -PAL_ERROR_INVAL; LISTP_TYPE(config) * children = &e->children; int nentries = 0; listp_for_each_entry(e, children, siblings) { if (e->klen + 1 > key_bufsize) return -PAL_ERROR_TOOLONG; memcpy(key_buf, e->key, e->klen); key_buf[e->klen] = 0; key_buf += e->klen + 1; key_bufsize -= e->klen + 1; nentries++; } return nentries; } ssize_t get_config_entries_size (struct config_store * store, const char * key) { struct config * e = __get_config(store, key); if (!e || e->val) return -PAL_ERROR_INVAL; return e->vlen; } static int __del_config (struct config_store * store, LISTP_TYPE(config) * root, struct config * p, const char * key) { struct config * e, * found = NULL; int len = 0; for ( ; key[len] ; len++) if (key[len] == '.') break; listp_for_each_entry(e, root, siblings) if (e->klen == len && !memcmp(e->key, key, len)) { found = e; break; } if (!found) return -PAL_ERROR_INVAL; if (key[len]) { if (found->val) return -PAL_ERROR_INVAL; int ret = __del_config(store, &found->children, found, key + len + 1); if (ret < 0) return ret; if (!listp_empty(&found->children)) return 0; } else { if (!found->val) return -PAL_ERROR_INVAL; } if (p) p->vlen -= (found->klen + 1); listp_del(found, root, siblings); listp_del(found, &store->entries, list); if (found->buf) store->free(found->buf); store->free(found); return 0; } int set_config (struct config_store * store, const char * key, const char * val) { if (!key) return -PAL_ERROR_INVAL; if (!val) { /* deletion */ return __del_config(store, &store->root, 0, key); } int klen = strlen(key); int vlen = strlen(val); char * buf = store->malloc(klen + vlen + 2); if (!buf) return -PAL_ERROR_NOMEM; memcpy(buf, key, klen + 1); memcpy(buf + klen + 1, val, vlen + 1); struct config * e = __get_config(store, key); if (e) { e->val = buf + klen + 1; e->vlen = vlen; e->buf = buf; } else { int ret = __add_config(store, buf, klen, buf + klen + 1, vlen, &e); if (ret < 0) { store->free(buf); return ret; } e->buf = buf; } return 0; } int read_config (struct config_store * store, int (*filter) (const char * key, int ken), const char ** errstring) { INIT_LISTP(&store->root); INIT_LISTP(&store->entries); char * ptr = store->raw_data; char * ptr_end = store->raw_data + store->raw_size; const char * err = "unknown error"; #define IS_SPACE(c) ((c) == ' ' || (c) == '\t') #define IS_BREAK(c) ((c) == '\r' || (c) == '\n') #define IS_VALID(c) \ (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z') || \ ((c) >= '0' && (c) <= '9') || (c) == '_') register int skipping = 0; #define IS_SKIP(p) \ (skipping ? ({ if (IS_BREAK(*(p))) skipping = 0; 1; }) \ : ((*(p)) == '#' ? ({ skipping = 1; 1; }) : IS_BREAK(*(p)))) #define RANGE (ptr < ptr_end) #define GOTO_INVAL(msg) ({ err = msg; goto inval; }) #define CHECK_PTR(msg) if (!RANGE) GOTO_INVAL(msg) while (RANGE) { for ( ; RANGE && (IS_SKIP(ptr) || IS_SPACE(*ptr)) ; ptr++); if (!(RANGE)) break; if (!IS_VALID(*ptr)) GOTO_INVAL("invalid start of key"); char * key = ptr; for ( ; RANGE ; ptr++) { char * pptr = ptr; for ( ; RANGE && IS_VALID(*ptr) ; ptr++); CHECK_PTR("stream ended at key"); if (pptr == ptr) GOTO_INVAL("key token with zero length"); if (*ptr != '.') break; } int klen = ptr - key; for ( ; RANGE && IS_SPACE(*ptr) ; ptr++) CHECK_PTR("stream ended at key"); if (*ptr != '=') GOTO_INVAL("equal mark expected"); ptr++; for ( ; RANGE && IS_SPACE(*ptr) ; ptr++); CHECK_PTR("stream ended at equal mark"); char * val = NULL; int vlen; if (*ptr == '"') { int shift = 0; val = (++ptr); for ( ; RANGE && *ptr != '"' ; ptr++) { if (*ptr == '\\') { shift++; ptr++; } if (shift) *(ptr - shift) = *ptr; } CHECK_PTR("stream ended without closing quote"); vlen = (ptr - shift) - val; } else { val = ptr; for ( ; RANGE && !IS_SKIP(ptr) ; ptr++); vlen = ptr - val; } ptr++; if (!filter || !filter(key, klen)) { int ret = __add_config(store, key, klen, val, vlen, NULL); if (ret < 0) { if (ret == -PAL_ERROR_TOOLONG) GOTO_INVAL("key too long"); if (ret == -PAL_ERROR_INVAL) GOTO_INVAL("key format invalid"); GOTO_INVAL("unknown error"); } } } return 0; inval: if (errstring) *errstring = err; return -PAL_ERROR_INVAL; } int free_config (struct config_store * store) { struct config * e, * n; listp_for_each_entry_safe(e, n, &store->entries, list) { if (e->buf) store->free(e->buf); store->free(e); } INIT_LISTP(&store->root); INIT_LISTP(&store->entries); return 0; } static int __dup_config (const struct config_store * ss, const LISTP_TYPE(config) * sr, struct config_store * ts, LISTP_TYPE(config) * tr, void ** data, int * size) { struct config * e, * new; listp_for_each_entry(e, sr, siblings) { char * key = NULL, * val = NULL, * buf = NULL; int need = 0; if (e->key) { if (*size > e->klen) { key = *data; *data += e->klen; *size -= e->klen; memcpy(key, e->key, e->klen); } else need += e->klen; } if (e->val) { if (*size > e->vlen) { val = *data; *data += e->vlen; *size -= e->vlen; memcpy(val, e->val, e->vlen); } else need += e->vlen; } if (need) { buf = ts->malloc(need); if (!buf) return -PAL_ERROR_NOMEM; } if (e->key && !key) { key = buf; memcpy(key, e->key, e->klen); } if (e->val && !val) { val = buf + (key == buf ? e->klen : 0); memcpy(val, e->val, e->vlen); } new = ts->malloc(sizeof(struct config)); if (!new) return -PAL_ERROR_NOMEM; new->key = key; new->klen = e->klen; new->val = val; new->vlen = e->vlen; new->buf = buf; INIT_LIST_HEAD(new, list); listp_add_tail(new, &ts->entries, list); INIT_LISTP(&new->children); INIT_LIST_HEAD(new, siblings); listp_add_tail(new, tr, siblings); if (!listp_empty(&e->children)) { int ret = __dup_config(ss, &e->children, ts, &new->children, data, size); if (ret < 0) return ret; } } return 0; } int copy_config (struct config_store * store, struct config_store * new_store) { INIT_LISTP(&new_store->root); INIT_LISTP(&new_store->entries); struct config * e; int size = 0; listp_for_each_entry(e, &store->entries, list) { if (e->key) size += e->klen; if (e->val) size += e->vlen; } void * data = new_store->malloc(size); if (!data) return -PAL_ERROR_NOMEM; void * dataptr = data; int datasz = size; new_store->raw_data = data; new_store->raw_size = size; return __dup_config(store, &store->root, new_store, &new_store->root, &dataptr, &datasz); } static int __write_config (void * f, int (*write) (void *, void *, int), struct config_store * store, LISTP_TYPE(config) * root, char * keybuf, int klen, unsigned long * offset) { struct config * e; int ret; char * buf = NULL; int bufsz = 0; listp_for_each_entry(e, root, siblings) if (e->val) { int total = klen + e->klen + e->vlen + 2; while (total > bufsz) { bufsz += CONFIG_MAX; buf = __alloca(CONFIG_MAX); } memcpy(buf, keybuf, klen); memcpy(buf + klen, e->key, e->klen); buf[klen + e->klen] = '='; memcpy(buf + total - e->vlen - 1, e->val, e->vlen); buf[total - 1] = '\n'; ret = write(f, buf, total); if (ret < 0) return ret; *offset += total; } else { if (klen + e->klen + 1 > CONFIG_MAX) return -PAL_ERROR_TOOLONG; memcpy(keybuf + klen, e->key, e->klen); keybuf[klen + e->klen] = '.'; if ((ret = __write_config(f, write, store, &e->children, keybuf, klen + e->klen + 1, offset)) < 0) return ret; } return 0; } int write_config (void * f, int (*write) (void *, void *, int), struct config_store * store) { char buf[CONFIG_MAX]; unsigned long offset = 0; return __write_config(f, write, store, &store->root, buf, 0, &offset); }