diff options
Diffstat (limited to 'src/config')
-rw-r--r-- | src/config/PARSING-config.txt | 26 | ||||
-rw-r--r-- | src/config/conftree.c | 51 | ||||
-rw-r--r-- | src/config/defaults.c | 70 | ||||
-rw-r--r-- | src/config/deps-exe/shibari-cache-config | 8 | ||||
-rw-r--r-- | src/config/lexparse.c | 230 | ||||
-rw-r--r-- | src/config/node.c | 34 | ||||
-rw-r--r-- | src/config/repo.c | 46 | ||||
-rw-r--r-- | src/config/shibari-cache-config-internal.h | 91 | ||||
-rw-r--r-- | src/config/shibari-cache-config.c | 101 | ||||
-rw-r--r-- | src/config/util.c | 15 |
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) ; +} |