summaryrefslogtreecommitdiff
path: root/src/config
diff options
context:
space:
mode:
Diffstat (limited to 'src/config')
-rw-r--r--src/config/PARSING-config.txt26
-rw-r--r--src/config/conftree.c51
-rw-r--r--src/config/defaults.c70
-rw-r--r--src/config/deps-exe/shibari-cache-config8
-rw-r--r--src/config/lexparse.c230
-rw-r--r--src/config/node.c34
-rw-r--r--src/config/repo.c46
-rw-r--r--src/config/shibari-cache-config-internal.h91
-rw-r--r--src/config/shibari-cache-config.c101
-rw-r--r--src/config/util.c15
10 files changed, 672 insertions, 0 deletions
diff --git a/src/config/PARSING-config.txt b/src/config/PARSING-config.txt
new file mode 100644
index 0000000..072a1fa
--- /dev/null
+++ b/src/config/PARSING-config.txt
@@ -0,0 +1,26 @@
+
+class | 0 1 2 3 4
+st\ev | \0 space # \n other
+
+START | P np
+00 | END SPACE COMMENT START WORD
+
+COMMENT | P
+01 | END COMMENT COMMENT START COMMENT
+
+SPACE | P P np
+02 | END SPACE COMMENT START WORD
+
+WORD | 0P 0 p 0P p
+03 | END SPACE WORD START WORD
+
+END: 04
+X: 05
+
+states: 3 bits
+actions: 4 bits
+
+0x10 n new word
+0x20 p push cur
+0x40 0 push \0
+0x80 P process line
diff --git a/src/config/conftree.c b/src/config/conftree.c
new file mode 100644
index 0000000..573646a
--- /dev/null
+++ b/src/config/conftree.c
@@ -0,0 +1,51 @@
+/* ISC license. */
+
+#include <skalibs/genalloc.h>
+#include <skalibs/avltree.h>
+#include <skalibs/cdbmake.h>
+
+#include "shibari-cache-config-internal.h"
+
+static repo conftree = \
+{ \
+ .ga = GENALLOC_ZERO, \
+ .tree = AVLTREE_INIT(8, 3, 8, &node_dtok, &node_cmp, &conftree.ga), \
+ .storage = &g.storage \
+} ;
+
+void confnode_start (node *node, char const *key, size_t filepos, uint32_t line)
+{
+ return node_start(&g.storage, node, key, filepos, line) ;
+}
+
+void confnode_add (node *node, char const *s, size_t len)
+{
+ return node_add(&g.storage, node, s, len) ;
+}
+
+node const *conftree_search (char const *key)
+{
+ return repo_search(&conftree, key) ;
+}
+
+void conftree_add (node const *node)
+{
+ return repo_add(&conftree, node) ;
+}
+
+void conftree_update (node const *node)
+{
+ return repo_update(&conftree, node) ;
+}
+
+static int confnode_write (uint32_t d, unsigned int h, void *data)
+{
+ node *nod = genalloc_s(node, &conftree.ga) + d ;
+ (void)h ;
+ return cdbmake_add((cdbmaker *)data, conftree.storage->s + nod->key, nod->keylen, conftree.storage->s + nod->data, nod->datalen) ;
+}
+
+int conftree_write (cdbmaker *cm)
+{
+ return avltree_iter(&conftree.tree, &confnode_write, cm) ;
+}
diff --git a/src/config/defaults.c b/src/config/defaults.c
new file mode 100644
index 0000000..14fce76
--- /dev/null
+++ b/src/config/defaults.c
@@ -0,0 +1,70 @@
+/* ISC license. */
+
+#include <stddef.h>
+
+#include "shibari-cache-config-internal.h"
+
+struct defaults_s
+{
+ char const *key ;
+ char const *value ;
+ size_t vlen ;
+} ;
+
+#define REC(k, v, n) { .key = (k), .value = (v), .vlen = (n) }
+#define RECS(k, v) REC(k, v, sizeof(v))
+#define RECU32(k, u) { .key = (k), .value = (char const [4]){ (u) >> 24 & 0xffu, (u) >> 16 & 0xffu, (u) >> 8 & 0xffu, (u) & 0xffu }, .vlen = 4 }
+
+static struct defaults_s const defaults[] =
+{
+ RECU32("G:logv", 1),
+ REC("G:listen4", "\0\0\0\0\0\35", 6),
+ REC("G:listen6", "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\35", 18),
+
+ REC("R4:",
+ "\0\306\51\0\4"
+ "\0\252\367\252\2"
+ "\0\300\41\4\14"
+ "\0\307\7\133\15"
+ "\0\300\313\346\12"
+ "\0\300\5\5\361"
+ "\0\300\160\44\4"
+ "\0\306\141\276\65"
+ "\0\300\44\224\21"
+ "\0\300\72\200\36"
+ "\0\301\0\16\201"
+ "\0\307\7\123\52"
+ "\0\312\14\33\41"
+ , 65),
+
+ REC("R6:",
+ "\0\40\1\5\3\272\76\0\0\0\0\0\0\0\2\0\60"
+ "\0\50\1\1\270\0\20\0\0\0\0\0\0\0\0\0\13"
+ "\0\40\1\5\0\0\2\0\0\0\0\0\0\0\0\0\14"
+ "\0\40\1\5\0\0\55\0\0\0\0\0\0\0\0\0\15"
+ "\0\40\1\5\0\0\250\0\0\0\0\0\0\0\0\0\16"
+ "\0\40\1\5\0\0\57\0\0\0\0\0\0\0\0\0\17"
+ "\0\40\1\5\0\0\22\0\0\0\0\0\0\0\0\15\15"
+ "\0\40\1\5\0\0\1\0\0\0\0\0\0\0\0\0\123"
+ "\0\40\1\7\376\0\0\0\0\0\0\0\0\0\0\0\123"
+ "\0\40\1\5\3\14\47\0\0\0\0\0\0\0\2\0\60"
+ "\0\40\1\7\375\0\0\0\0\0\0\0\0\0\0\0\1"
+ "\0\40\1\5\0\0\237\0\0\0\0\0\0\0\0\0\102"
+ "\0\40\1\15\303\0\0\0\0\0\0\0\0\0\0\0\65"
+ , 221),
+ REC(0, 0, 0)
+} ;
+
+void conf_defaults (void)
+{
+ for (struct defaults_s const *p = defaults ; p->key ; p++)
+ {
+ if (!conftree_search(p->key))
+ {
+ node node ;
+ confnode_start(&node, p->key, 0, 0) ;
+ confnode_add(&node, p->value, p->vlen) ;
+ conftree_add(&node) ;
+ }
+ }
+}
diff --git a/src/config/deps-exe/shibari-cache-config b/src/config/deps-exe/shibari-cache-config
new file mode 100644
index 0000000..1685f25
--- /dev/null
+++ b/src/config/deps-exe/shibari-cache-config
@@ -0,0 +1,8 @@
+util.o
+node.o
+repo.o
+conftree.o
+defaults.o
+lexparse.o
+-ls6dns
+-lskarnet
diff --git a/src/config/lexparse.c b/src/config/lexparse.c
new file mode 100644
index 0000000..9987fbd
--- /dev/null
+++ b/src/config/lexparse.c
@@ -0,0 +1,230 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/bitarray.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/skamisc.h>
+
+#include <s6-dns/s6dns-domain.h>
+
+#include <shibari/config.h>
+#include "shibari-cache-config-internal.h"
+
+#define dietoobig() strerr_diefu1sys(100, "read configuration")
+
+typedef struct mdt_s mdt, *mdt_ref ;
+struct mdt_s
+{
+ size_t filepos ;
+ uint32_t line ;
+ char linefmt[UINT32_FMT] ;
+} ;
+#define MDT_ZERO { .filepos = 0, .line = 0, .linefmt = "0" }
+
+struct namevalue_s
+{
+ char const *name ;
+ uint32_t value ;
+} ;
+
+enum directivevalue_e
+{
+ T_VERBOSITY,
+ T_LISTEN,
+ T_SERVER,
+ T_FORWARD,
+} ;
+
+static void conftree_checkunique (char const *key, mdt const *md)
+{
+ node const *node = conftree_search(key) ;
+ if (node)
+ {
+ char fmt[UINT32_FMT] ;
+ fmt[uint32_fmt(fmt, node->line)] = 0 ;
+ strerr_diefn(1, 12, "duplicate ", "key ", key, " in file ", g.storage.s + md->filepos, " line ", md->linefmt, ", previously defined", " in file ", g.storage.s + node->filepos, " line ", fmt) ;
+ }
+}
+
+static void add_unique (char const *key, char const *value, size_t valuelen, mdt const *md)
+{
+ node node ;
+ conftree_checkunique(key, md) ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, value, valuelen) ;
+ conftree_add(&node) ;
+}
+
+static int ip40_scan (char const *s, char *ip)
+{
+ size_t len = ip4_scan(s, ip) ;
+ return len ? !s[len] : 0 ;
+}
+
+static int ip60_scan (char const *s, char *ip)
+{
+ size_t len = ip6_scan(s, ip) ;
+ return len ? !s[len] : 0 ;
+}
+
+static inline void parse_verbosity (char const *s, size_t const *word, size_t n, mdt const *md)
+{
+ uint32_t v ;
+ char pack[4] ;
+ if (n != 1)
+ strerr_dief8x(1, "too ", n ? "many" : "few", " arguments to directive ", "verbosity", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!uint320_scan(s + word[0], &v))
+ strerr_dief7x(1, " argument to directive ", "verbosity", " must be an integer ", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ uint32_pack_big(pack, v) ;
+ add_unique("G:logv", pack, 4, md) ;
+}
+
+static inline void parse_listen (char const *s, size_t const *word, size_t n, mdt const *md)
+{
+ if (!n)
+ strerr_dief6x(1, "too few arguments to directive ", "listen", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ size_t n4 = 0, n6 = 0 ;
+ char ip6[n << 4] ;
+ char ip4[n << 2] ;
+ for (size_t i = 0 ; i < n ; i++)
+ {
+ if (ip60_scan(s + word[i], ip6 + (n6 << 4))) n6++ ;
+ else if (ip40_scan(s + word[i], ip4 + (n4 << 2))) n4++ ;
+ else strerr_dief6x(1, "arguments to directive ", "listen", " must be IPs in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ }
+ add_unique("G:listen4", ip4, n4 << 2, md) ;
+ add_unique("G:listen6", ip6, n6 << 4, md) ;
+ }
+}
+
+static inline void parse_server (char const *s, size_t const *word, size_t n, mdt const *md, int forward)
+{
+ char const *x = forward ? "forward" : "server" ;
+ s6dns_domain_t domain ;
+ if (n-- < 2)
+ strerr_dief8x(1, "too ", "few", " arguments to directive ", x, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!s6dns_domain_fromstring(&domain, s + word[0], strlen(s + word[0]))
+ || !s6dns_domain_noqualify(&domain))
+ strerr_dief7x(1, "first argument to directive ", x, " must be a zone ", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ word++ ;
+ {
+ size_t n4 = 0, n6 = 0 ;
+ char ip6[n * 17] ;
+ char ip4[n * 5] ;
+ char key[3 + domain.len] ;
+ for (size_t i = 0 ; i < n ; i++)
+ {
+ if (ip60_scan(s + word[i], ip6 + (n6 * 17) + 1)) ip6[n6++ * 17] = !!forward ;
+ else if (ip40_scan(s + word[i], ip4 + (n4 * 5) + 1)) ip4[n4++ * 5] = !!forward ;
+ else strerr_dief6x(1, "subsequent arguments to directive ", x, " must be IPs in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ }
+ memcpy(key, "R4:", 3) ;
+ memcpy(key + 3, domain.s + 1, domain.len - 1) ;
+ key[2 + domain.len] = 0 ;
+ add_unique(key, ip4, n4 * 5, md) ;
+ key[1] = '6' ;
+ add_unique(key, ip6, n6 * 17, md) ;
+ }
+}
+
+static inline void process_line (char const *s, size_t const *word, size_t n, mdt *md)
+{
+ static struct namevalue_s const directives[] =
+ {
+ { .name = "forward", .value = T_FORWARD },
+ { .name = "listen", .value = T_LISTEN },
+ { .name = "server", .value = T_SERVER },
+ { .name = "verbosity", .value = T_VERBOSITY },
+ } ;
+ struct namevalue_s const *directive ;
+ char const *word0 ;
+ if (!n--) return ;
+ word0 = s + *word++ ;
+ directive = BSEARCH(struct namevalue_s, word0, directives) ;
+ if (!directive)
+ strerr_dief6x(1, "unrecognized word ", word0, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ switch (directive->value)
+ {
+ case T_VERBOSITY :
+ parse_verbosity(s, word, n, md) ;
+ break ;
+ case T_LISTEN :
+ parse_listen(s, word, n, md) ;
+ break ;
+ case T_SERVER :
+ parse_server(s, word, n, md, 0) ;
+ break ;
+ case T_FORWARD :
+ parse_server(s, word, n, md, 1) ;
+ break ;
+ }
+}
+
+static inline uint8_t cclass (char c)
+{
+ switch (c)
+ {
+ case 0 : return 0 ;
+ case ' ' :
+ case '\t' :
+ case '\f' :
+ case '\r' : return 1 ;
+ case '#' : return 2 ;
+ case '\n' : return 3 ;
+ default : return 4 ;
+ }
+}
+
+static inline char next (buffer *b, mdt const *md)
+{
+ char c ;
+ ssize_t r = buffer_get(b, &c, 1) ;
+ if (r == -1) strerr_diefu1sys(111, "read from preprocessor") ;
+ if (!r) return 0 ;
+ if (!c) strerr_dief5x(1, "null character", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ return c ;
+}
+
+void conf_lexparse (buffer *b, char const *ifile)
+{
+ static uint8_t const table[4][5] = /* see PARSING-config.txt */
+ {
+ { 0x04, 0x02, 0x01, 0x80, 0x33 },
+ { 0x04, 0x01, 0x01, 0x80, 0x01 },
+ { 0x84, 0x02, 0x01, 0x80, 0x33 },
+ { 0xc4, 0x42, 0x23, 0xc0, 0x23 }
+ } ;
+ stralloc sa = STRALLOC_ZERO ;
+ genalloc words = GENALLOC_ZERO ; /* size_t */
+ mdt md = MDT_ZERO ;
+ uint8_t state = 0 ;
+ if (!stralloc_catb(&g.storage, ifile, strlen(ifile) + 1)) dienomem() ;
+ while (state < 0x04)
+ {
+ char c = next(b, &md) ;
+ uint8_t what = table[state][cclass(c)] ;
+ state = what & 0x07 ;
+ if (what & 0x10) if (!genalloc_catb(size_t, &words, &sa.len, 1)) dienomem() ;
+ if (what & 0x20) if (!stralloc_catb(&sa, &c, 1)) dienomem() ;
+ if (what & 0x40) if (!stralloc_0(&sa)) dienomem() ;
+ if (what & 0x80)
+ {
+ process_line(sa.s, genalloc_s(size_t, &words), genalloc_len(size_t, &words), &md) ;
+ genalloc_setlen(size_t, &words, 0) ;
+ sa.len = 0 ;
+ md.line++ ;
+ md.linefmt[uint32_fmt(md.linefmt, md.line)] = 0 ;
+ }
+ }
+ genalloc_free(size_t, &words) ;
+ stralloc_free(&sa) ;
+}
diff --git a/src/config/node.c b/src/config/node.c
new file mode 100644
index 0000000..7e6cd4b
--- /dev/null
+++ b/src/config/node.c
@@ -0,0 +1,34 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/stralloc.h>
+#include <skalibs/strerr.h>
+
+#include "shibari-cache-config-internal.h"
+
+#define diestorage() strerr_diefu2x(100, "add node to configuration tree", ": too much data")
+#define diefilepos() strerr_diefu2x(100, "add node to configuration tree", ": file too large")
+
+void node_start (stralloc *storage, node *node, char const *key, size_t filepos, uint32_t line)
+{
+ size_t l = strlen(key) ;
+ size_t k = storage->len ;
+ if (!stralloc_catb(storage, key, l + 1)) dienomem() ;
+ if (storage->len >= UINT32_MAX) diestorage() ;
+ if (filepos > UINT32_MAX) diefilepos() ;
+ node->key = k ;
+ node->keylen = l ;
+ node->data = storage->len ;
+ node->datalen = 0 ;
+ node->filepos = filepos ;
+ node->line = line ;
+}
+
+void node_add (stralloc *storage, node *node, char const *s, size_t len)
+{
+ if (!stralloc_catb(storage, s, len)) dienomem() ;
+ if (storage->len >= UINT32_MAX) diestorage() ;
+ node->datalen += len ;
+}
diff --git a/src/config/repo.c b/src/config/repo.c
new file mode 100644
index 0000000..8ed8c51
--- /dev/null
+++ b/src/config/repo.c
@@ -0,0 +1,46 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/genalloc.h>
+#include <skalibs/avltree.h>
+
+#include "shibari-cache-config-internal.h"
+
+void *node_dtok (uint32_t d, void *data)
+{
+ repo *r = data ;
+ return r->storage->s + genalloc_s(node, &r->ga)[d].key ;
+}
+
+int node_cmp (void const *a, void const *b, void *data)
+{
+ (void)data ;
+ return strcmp((char const *)a, (char const *)b) ;
+}
+
+node const *repo_search (repo const *r, char const *key)
+{
+ uint32_t i ;
+ return avltree_search(&r->tree, key, &i) ? genalloc_s(node const, &r->ga) + i : 0 ;
+}
+
+void repo_add (repo *r, node const *nod)
+{
+ uint32_t i = genalloc_len(node, &r->ga) ;
+ if (!genalloc_append(node, &r->ga, nod)) dienomem() ;
+ if (!avltree_insert(&r->tree, i)) dienomem() ;
+}
+
+void repo_update (repo *r, node const *nod)
+{
+ uint32_t i ;
+ if (avltree_search(&r->tree, r->storage->s + nod->key, &i))
+ {
+ if (!avltree_delete(&r->tree, r->storage->s + nod->key)) dienomem() ;
+ genalloc_s(node, &r->ga)[i] = *nod ;
+ if (!avltree_insert(&r->tree, i)) dienomem() ;
+ }
+ else repo_add(r, nod) ;
+}
diff --git a/src/config/shibari-cache-config-internal.h b/src/config/shibari-cache-config-internal.h
new file mode 100644
index 0000000..c7b0197
--- /dev/null
+++ b/src/config/shibari-cache-config-internal.h
@@ -0,0 +1,91 @@
+/* ISC license. */
+
+#ifndef SHIBARI_CACHE_CONFIG_INTERNAL_H
+#define SHIBARI_CACHE_CONFIG_INTERNAL_H
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/cdbmake.h>
+#include <skalibs/avltree.h>
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+typedef struct node_s node, *node_ref ;
+struct node_s
+{
+ uint32_t key ;
+ uint32_t keylen ;
+ uint32_t data ;
+ uint32_t datalen ;
+ uint32_t filepos ;
+ uint32_t line ;
+} ;
+#define NODE_ZERO { .key = 0, .keylen = 0, .data = 0, .datalen = 0 }
+
+typedef struct repo_s repo, *repo_ref ;
+struct repo_s
+{
+ genalloc ga ;
+ avltree tree ;
+ stralloc *storage ;
+} ;
+#define REPO_ZERO { .ga = GENALLOC_ZERO, .tree = AVLTREE_ZERO, .storage = 0 }
+
+struct global_s
+{
+ stralloc storage ;
+} ;
+#define GLOBAL_ZERO { .storage = STRALLOC_ZERO }
+
+extern struct global_s g ;
+
+
+ /* util */
+
+extern int keycmp (void const *, void const *) ; /* for any struct starting with a string key */
+#define BSEARCH(type, key, array) bsearch(key, (array), sizeof(array)/sizeof(type), sizeof(type), &keycmp)
+
+
+ /* node */
+
+extern void node_start (stralloc *, node *, char const *, size_t, uint32_t) ;
+extern void node_add (stralloc *, node *, char const *, size_t) ;
+
+
+ /* repo */
+
+extern void *node_dtok (uint32_t, void *) ;
+extern int node_cmp (void const *, void const *, void *) ;
+extern node const *repo_search (repo const *, char const *) ;
+extern void repo_add (repo *, node const *) ;
+extern void repo_update (repo *, node const *) ;
+
+
+ /* conftree */
+
+extern void confnode_start (node *, char const *, size_t, uint32_t) ;
+extern void confnode_add (node *, char const *, size_t) ;
+
+extern node const *conftree_search (char const *) ;
+extern void conftree_add (node const *) ;
+extern void conftree_update (node const *) ;
+
+extern int conftree_write (cdbmaker *) ;
+
+
+ /* lexparse */
+
+extern void conf_lexparse (buffer *, char const *) ;
+
+
+ /* defaults */
+
+extern void conf_defaults (void) ;
+
+#endif
diff --git a/src/config/shibari-cache-config.c b/src/config/shibari-cache-config.c
new file mode 100644
index 0000000..0aef2ce
--- /dev/null
+++ b/src/config/shibari-cache-config.c
@@ -0,0 +1,101 @@
+/* ISC license. */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h> /* rename() */
+#include <errno.h>
+#include <signal.h>
+
+#include <skalibs/posixplz.h>
+#include <skalibs/types.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/djbunix.h>
+
+#include <shibari/config.h>
+#include "shibari-cache-config-internal.h"
+
+#define USAGE "shibari-cache-config [ -i textfile ] [ -o cdbfile ] [ -m mode ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+struct global_s g = GLOBAL_ZERO ;
+
+static inline void conf_output (char const *ofile, unsigned int omode)
+{
+ int fdw ;
+ cdbmaker cm = CDBMAKER_ZERO ;
+ size_t olen = strlen(ofile) ;
+ char otmp[olen + 8] ;
+ memcpy(otmp, ofile, olen) ;
+ memcpy(otmp + olen, ":XXXXXX", 8) ;
+ fdw = mkstemp(otmp) ;
+ if (fdw == -1) strerr_diefu3sys(111, "open ", otmp, " for writing") ;
+ if (!cdbmake_start(&cm, fdw))
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "cdmake_start ", otmp) ;
+ }
+ if (!conftree_write(&cm))
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "write config tree into ", otmp) ;
+ }
+ if (!cdbmake_finish(&cm))
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "cdbmake_finish ", otmp) ;
+ }
+ if (fsync(fdw) == -1)
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "fsync ", otmp) ;
+ }
+ if (fchmod(fdw, omode & 0777) == -1)
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "fchmod ", otmp) ;
+ }
+ if (rename(otmp, ofile) == -1)
+ {
+ unlink_void(otmp) ;
+ strerr_diefu4sys(111, "rename ", otmp, " to ", ofile) ;
+ }
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+ char const *ifile = SHIBARI_SYSCONFPREFIX "shibari-cache.conf" ;
+ char const *ofile = SHIBARI_SYSCONFPREFIX "shibari-cache.conf.cdb" ;
+ unsigned int omode = 0644 ;
+
+ PROG = "shibari-cache-config" ;
+ {
+ subgetopt l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "i:o:m:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'i' : ifile = l.arg ; break ;
+ case 'o' : ofile = l.arg ; break ;
+ case 'm' : if (!uint0_oscan(l.arg, &omode)) dieusage() ; break ;
+ default : strerr_dieusage(100, USAGE) ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ }
+
+ {
+ int fdr = openc_readb(ifile) ;
+ char buf[4096] ;
+ buffer b = BUFFER_INIT(&buffer_read, fdr, buf, 4096) ;
+ if (fdr == -1) strerr_diefu2sys(111, "open ", ifile) ;
+ conf_lexparse(&b, ifile) ;
+ }
+ conf_defaults() ;
+ conf_output(ofile, omode) ;
+ return 0 ;
+}
diff --git a/src/config/util.c b/src/config/util.c
new file mode 100644
index 0000000..f3da287
--- /dev/null
+++ b/src/config/util.c
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#include <string.h>
+
+#include "shibari-cache-config-internal.h"
+
+struct starts_with_a_string_key_s
+{
+ char const *s ;
+} ;
+
+int keycmp (void const *a, void const *b)
+{
+ return strcmp((char const *)a, ((struct starts_with_a_string_key_s const *)b)->s) ;
+}