summaryrefslogtreecommitdiff
path: root/src/config
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2023-08-05 11:51:25 +0000
committerLaurent Bercot <ska@appnovation.com>2023-08-05 11:51:25 +0000
commit17c382d1c9d7236c101418060758d2296cc5e17e (patch)
treefd00e58df0d9d3c70ddd1accfec9e819249c672a /src/config
downloadtipidee-17c382d1c9d7236c101418060758d2296cc5e17e.tar.xz
Initial commit
Signed-off-by: Laurent Bercot <ska@appnovation.com>
Diffstat (limited to 'src/config')
-rw-r--r--src/config/PARSING-config.txt56
-rw-r--r--src/config/PARSING-preprocess.txt38
-rw-r--r--src/config/PROTOCOL.txt43
-rw-r--r--src/config/confnode.c35
-rw-r--r--src/config/conftree.c82
-rw-r--r--src/config/defaults.c106
-rw-r--r--src/config/deps-exe/tipidee-config6
-rw-r--r--src/config/deps-exe/tipidee-config-preprocess1
-rw-r--r--src/config/lexparse.c443
-rw-r--r--src/config/tipidee-config-internal.h59
-rw-r--r--src/config/tipidee-config-preprocess.c270
-rw-r--r--src/config/tipidee-config.c135
12 files changed, 1274 insertions, 0 deletions
diff --git a/src/config/PARSING-config.txt b/src/config/PARSING-config.txt
new file mode 100644
index 0000000..b1be2df
--- /dev/null
+++ b/src/config/PARSING-config.txt
@@ -0,0 +1,56 @@
+global verbosity 1
+global read_timeout 60000
+global index index.cgi index.html
+
+content-type application/pdf .pdf
+content-type text/plain .c .h .txt
+
+
+domain example.com
+
+nph-prefix nph-
+
+redirect rickroll.html 307 https://www.youtube.com/watch?v=dQw4w9WgXcQ
+redirect community/ 308 https://example.org/
+
+cgi /cgi-bin/
+nph /cgi-bin/nph/
+
+cgi /some/script
+nph /some/otherscript
+
+noncgi /cgi-bin/file.html
+noncgi /cgi-bin/directory
+
+file-type /source/ text/plain
+file-type /source/file.html text/html
+
+basic-auth /protected.html
+basic-auth /protected/
+
+
+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/PARSING-preprocess.txt b/src/config/PARSING-preprocess.txt
new file mode 100644
index 0000000..e81c86e
--- /dev/null
+++ b/src/config/PARSING-preprocess.txt
@@ -0,0 +1,38 @@
+
+ Automaton for the preprocessor:
+
+
+class | 0 1 2 3 4
+st\ev | \0 \n ! space other
+
+START | print print print
+0 | END START CMD NORMAL NORMAL
+
+NORMAL | print print print print
+1 | END START NORMAL NORMAL NORMAL
+
+CMD | add
+2 | END START IGNORE CMD1 CMD2
+
+IGNORE |
+3 | END START IGNORE IGNORE IGNORE
+
+CMD1 | add
+4 | X X X CMD1 CMD2
+
+CMD2 | idcmd add
+5 | X X X ARG CMD2
+
+ARG | add
+6 | X X ARG1 ARG ARG1
+
+ARG1 | proc proc add add add
+7 | END START ARG1 ARG1 ARG1
+
+states: 0-7 plus END and X -> 4 bits
+actions: 4. -> 8 bits total, fits in a char.
+
+print 0x10 copies the character to stdout
+add 0x20 adds the character to the processing string
+idcmd 0x40 ids the processing string for an !include cmd
+proc 0x80 gets the filename and procs the include
diff --git a/src/config/PROTOCOL.txt b/src/config/PROTOCOL.txt
new file mode 100644
index 0000000..ffeb72f
--- /dev/null
+++ b/src/config/PROTOCOL.txt
@@ -0,0 +1,43 @@
+* Globals
+
+G:verbosity -> 4 bytes
+G:read_timeout -> 4 bytes exit if waiting on client input for read_timeout ms
+G:write_timeout -> 4 bytes die if waiting on flush to client for write_timeout ms
+G:cgi_timeout -> 4 bytes 504 if CGI hasn't finished answering / die if NPH hasn't finished reading
+G:max_request_body_length -> 4 bytes
+G:max_cgi_body_length -> 4 bytes
+G:index_file -> file1\0file2\0file3\0 list of files to attempt when URL is a directory
+
+They all need to exist, tipidee-config creates defaults.
+
+
+* Content-Type
+
+T:extension -> string T:pdf -> application/pdf
+or individual Content-Types in resource attributes
+
+tipidee-config hardcodes a number of default content-types, they
+can be overridden.
+
+
+* Individual redirection
+
+R:vres -> Xurl R:skarnet.org/rickroll.html -> Xhttps://www.youtube.com/watch?v=dQw4w9WgXcQ
+ X = '@' | redir_type (307=0, 308=1, 302=2, 301=3)
+
+
+* Prefix redirection
+
+r:prefix -> Xurlprefix r:s6.org -> Xhttps://skarnet.org/software/s6
+ X = '@' | redir_type (307=0, 308=1, 302=2, 301=3)
+
+* Resource attributes
+
+A:file -> Xstring X = '@' | 1 (iscgi)
+ if string nonempty: it's the content-type for the resource. If empty, default ctype
+
+a:prefix -> Xstring same, but for prefixes
+
+* NPH prefixes
+
+N:domain -> nphprefix N:skarnet.org -> nph- any CGI under skarnet.org starting with nph
diff --git a/src/config/confnode.c b/src/config/confnode.c
new file mode 100644
index 0000000..758e79d
--- /dev/null
+++ b/src/config/confnode.c
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/stralloc.h>
+#include <skalibs/strerr.h>
+
+#include "tipidee-config-internal.h"
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+#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 confnode_start (confnode *node, char const *key, size_t filepos, uint32_t line)
+{
+ size_t l = strlen(key) ;
+ size_t k = g.storage.len ;
+ if (!stralloc_catb(&g.storage, key, l + 1)) dienomem() ;
+ if (g.storage.len >= UINT32_MAX) diestorage() ;
+ if (filepos > UINT32_MAX) diefilepos() ;
+ node->key = k ;
+ node->keylen = l ;
+ node->data = g.storage.len ;
+ node->datalen = 0 ;
+ node->filepos = filepos ;
+ node->line = line ;
+}
+
+void confnode_add (confnode *node, char const *s, size_t len)
+{
+ if (!stralloc_catb(&g.storage, s, len)) dienomem() ;
+ if (g.storage.len >= UINT32_MAX) strerr_diefu1x(100, "add node to configuration tree: too much data") ;
+ node->datalen += len ;
+}
diff --git a/src/config/conftree.c b/src/config/conftree.c
new file mode 100644
index 0000000..fc0b5bc
--- /dev/null
+++ b/src/config/conftree.c
@@ -0,0 +1,82 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include <skalibs/gensetdyn.h>
+#include <skalibs/avltree.h>
+#include <skalibs/cdbmake.h>
+#include <skalibs/strerr.h>
+
+#include "tipidee-config-internal.h"
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+static void *confnode_dtok (uint32_t d, void *data)
+{
+ return g.storage.s + GENSETDYN_P(confnode, (gensetdyn *)data, d)->key ;
+}
+
+static int confnode_cmp (void const *a, void const *b, void *data)
+{
+ (void)data ;
+ return strcmp((char const *)a, (char const *)b) ;
+}
+
+struct nodestore_s
+{
+ gensetdyn set ;
+ avltree tree ;
+} ;
+
+static struct nodestore_s nodestore = \
+{ \
+ .set = GENSETDYN_INIT(confnode, 8, 3, 8), \
+ .tree = AVLTREE_INIT(8, 3, 8, &confnode_dtok, &confnode_cmp, &nodestore.set) \
+} ;
+
+
+confnode const *conftree_search (char const *key)
+{
+ uint32_t i ;
+ return avltree_search(&nodestore.tree, key, &i) ? GENSETDYN_P(confnode const, &nodestore.set, i) : 0 ;
+}
+
+void conftree_add (confnode const *node)
+{
+ uint32_t i ;
+ if (!gensetdyn_new(&nodestore.set, &i)) dienomem() ;
+ *GENSETDYN_P(confnode, &nodestore.set, i) = *node ;
+ if (!avltree_insert(&nodestore.tree, i)) dienomem() ;
+}
+
+void conftree_update (confnode const *node)
+{
+ uint32_t i ;
+ if (avltree_search(&nodestore.tree, g.storage.s + node->key, &i))
+ {
+ if (!avltree_delete(&nodestore.tree, g.storage.s + node->key)) dienomem() ;
+ *GENSETDYN_P(confnode, &nodestore.set, i) = *node ;
+ if (!avltree_insert(&nodestore.tree, i)) dienomem() ;
+ }
+ else return conftree_add(node) ;
+}
+
+static int confnode_write (uint32_t d, unsigned int h, void *data)
+{
+ confnode *node = GENSETDYN_P(confnode, &nodestore.set, d) ;
+ cdbmaker *cm = data ;
+ (void)h ;
+ if ((g.storage.s[node->key] & ~0x20) == 'A')
+ {
+ g.storage.s[++node->data] |= '@' ;
+ node->datalen-- ;
+ }
+ return cdbmake_add(cm, g.storage.s + node->key, node->keylen, g.storage.s + node->data, node->datalen) ;
+}
+
+int conftree_write (cdbmaker *cm)
+{
+ return avltree_iter(&nodestore.tree, &confnode_write, cm) ;
+}
diff --git a/src/config/defaults.c b/src/config/defaults.c
new file mode 100644
index 0000000..5913796
--- /dev/null
+++ b/src/config/defaults.c
@@ -0,0 +1,106 @@
+/* ISC license. */
+
+#include <stddef.h>
+
+#include "tipidee-config-internal.h"
+
+struct defaults_s
+{
+ char const *key ;
+ char const *value ;
+ size_t vlen ;
+} ;
+
+#define RECB(k, v, n) { .key = k, .value = v, .vlen = n }
+#define REC(k, v) RECB(k, v, sizeof(v))
+
+struct defaults_s const defaults[] =
+{
+ RECB("G:verbosity", "\0\0\0\001", 4),
+ RECB("G:read_timeout", "\0\0\0", 4),
+ RECB("G:write_timeout", "\0\0\0", 4),
+ RECB("G:cgi_timeout", "\0\0\0", 4),
+ RECB("G:max_request_body_length", "\0\0 ", 4),
+ RECB("G:max_cgi_body_length", "\0@\0", 4),
+ REC("G:index_file", "index.html"),
+
+ REC("T:html", "text/html"),
+ REC("T:htm", "text/html"),
+ REC("T:txt", "text/plain"),
+ REC("T:h", "text/plain"),
+ REC("T:c", "text/plain"),
+ REC("T:cc", "text/plain"),
+ REC("T:cpp", "text/plain"),
+ REC("T:java", "text/plain"),
+ REC("T:mjs", "text/javascript"),
+ REC("T:css", "text/css"),
+ REC("T:csv", "text/csv"),
+ REC("T:doc", "application/msword"),
+ REC("T:docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
+ REC("T:js", "application/javascript"),
+ REC("T:jpg", "image/jpeg"),
+ REC("T:jpeg", "image/jpeg"),
+ REC("T:gif", "image/gif"),
+ REC("T:png", "image/png"),
+ REC("T:bmp", "image/bmp"),
+ REC("T:svg", "image/svg+xml"),
+ REC("T:tif", "image/tiff"),
+ REC("T:tiff", "image/tiff"),
+ REC("T:ico", "image/vnd.microsoft.icon"),
+ REC("T:au", "audio/basic"),
+ REC("T:aac", "audio/aac"),
+ REC("T:wav", "audio/wav"),
+ REC("T:mid", "audio/midi"),
+ REC("T:midi", "audio/midi"),
+ REC("T:mp3", "audio/mpeg"),
+ REC("T:ogg", "audio/ogg"),
+ REC("T:oga", "audio/ogg"),
+ REC("T:ogv", "video/ogg"),
+ REC("T:avi", "video/x-msvideo"),
+ REC("T:wmv", "video/x-ms-wmv"),
+ REC("T:qt", "video/quicktime"),
+ REC("T:mov", "video/quicktime"),
+ REC("T:mpe", "video/mpeg"),
+ REC("T:mpeg", "video/mpeg"),
+ REC("T:mp4", "video/mp4"),
+ REC("T:otf", "font/otf"),
+ REC("T:ttf", "font/ttf"),
+ REC("T:epub", "application/epub+zip"),
+ REC("T:jar", "application/java-archive"),
+ REC("T:json", "application/json"),
+ REC("T:jsonld", "application/ld+json"),
+ REC("T:pdf", "application/pdf"),
+ REC("T:ppt", "application/vnd.ms-powerpoint"),
+ REC("T:pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"),
+ REC("T:odp", "application/vnd.oasis.opendocument.presentation"),
+ REC("T:ods", "application/vnd.oasis.opendocument.spreadsheet"),
+ REC("T:odt", "application/vnd.oasis.opendocument.text"),
+ REC("T:oggx", "application/ogg"),
+ REC("T:rar", "application/vnd.rar"),
+ REC("T:rtf", "application/rtf"),
+ REC("T:sh", "application/x-sh"),
+ REC("T:tar", "application/x-tar"),
+ REC("T:xhtml", "application/xhtml+xml"),
+ REC("T:xls", "application/vnd.ms-excel"),
+ REC("T:xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
+ REC("T:xml", "application/xml"),
+ REC("T:xul", "application/vnd.mozilla.xul+xml"),
+ REC("T:zip", "application/zip"),
+ REC("T:7z", "application/x-7z-compressed"),
+
+ RECB(0, 0, 0)
+} ;
+
+void conf_defaults (void)
+{
+ for (struct defaults_s const *p = defaults ; p->key ; p++)
+ {
+ if (!conftree_search(p->key))
+ {
+ confnode 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/tipidee-config b/src/config/deps-exe/tipidee-config
new file mode 100644
index 0000000..85a9645
--- /dev/null
+++ b/src/config/deps-exe/tipidee-config
@@ -0,0 +1,6 @@
+confnode.o
+conftree.o
+defaults.o
+lexparse.o
+-lskarnet
+${SPAWN_LIB}
diff --git a/src/config/deps-exe/tipidee-config-preprocess b/src/config/deps-exe/tipidee-config-preprocess
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/config/deps-exe/tipidee-config-preprocess
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/config/lexparse.c b/src/config/lexparse.c
new file mode 100644
index 0000000..f843f97
--- /dev/null
+++ b/src/config/lexparse.c
@@ -0,0 +1,443 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/skamisc.h>
+
+#include <tipidee/config.h>
+#include "tipidee-config-internal.h"
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+#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 globalkey_s
+{
+ char const *name ;
+ char const *key ;
+ uint32_t type ;
+} ;
+
+static int globalkey_cmp (void const *a, void const *b)
+{
+ return strcmp((char const *)a, ((struct globalkey_s const *)b)->name) ;
+}
+
+enum token_e
+{
+ T_BANG,
+ T_GLOBAL,
+ T_CONTENTTYPE,
+ T_DOMAIN,
+ T_NPHPREFIX,
+ T_REDIRECT,
+ T_CGI,
+ T_NONCGI,
+ T_NPH,
+ T_NONNPH,
+ T_BASICAUTH,
+ T_NOAUTH,
+ T_FILETYPE
+} ;
+
+struct directive_s
+{
+ char const *s ;
+ enum token_e token ;
+} ;
+
+static int directive_cmp (void const *a, void const *b)
+{
+ return strcmp((char const *)a, ((struct directive_s const *)b)->s) ;
+}
+
+static void check_unique (char const *key, mdt const *md)
+{
+ confnode const *node = conftree_search(key) ;
+ if (node)
+ {
+ char fmt[UINT32_FMT] ;
+ fmt[uint32_fmt(fmt, node->line)] = 0 ;
+ strerr_diefn(1, 11, "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)
+{
+ confnode node ;
+ check_unique(key, md) ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, value, valuelen) ;
+ conftree_add(&node) ;
+}
+
+static inline void parse_global (char const *s, size_t const *word, size_t n, mdt const *md)
+{
+ static struct globalkey_s const globalkeys[] =
+ {
+ { .name = "cgi_timeout", .key = "G:cgi_timeout", .type = 0 },
+ { .name = "index_file", .key = "G:index_file", .type = 1 },
+ { .name = "max_cgi_body_length", .key = "G:max_cgi_body_length", .type = 0 },
+ { .name = "max_request_body_length", .key = "G:max_request_body_length", .type = 0 },
+ { .name = "read_timeout", .key = "G:read_timeout", .type = 0 },
+ { .name = "verbosity", .key = "G:verbosity", .type = 0 },
+ { .name = "write_timeout", .key = "G:write_timeout", .type = 0 }
+ } ;
+ struct globalkey_s *gl ;
+ if (n != 2)
+ strerr_dief8x(1, "too ", n > 2 ? "many" : "few", " arguments to directive ", "global", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ gl = bsearch(s + word[0], globalkeys, sizeof(globalkeys)/sizeof(struct globalkey_s), sizeof(struct globalkey_s), &globalkey_cmp) ;
+ if (!gl) strerr_dief6x(1, "unrecognized global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ switch (gl->type)
+ {
+ case 0 : /* 4 bytes */
+ {
+ char pack[4] ;
+ uint32_t u ;
+ if (n != 2) strerr_dief7x(1, "too many", " arguments to global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!uint320_scan(s + word[1], &u))
+ strerr_dief6x(1, "invalid (non-numeric) value for global setting ", s + word[0], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ uint32_pack_big(pack, u) ;
+ add_unique(gl->key, pack, 4, md) ;
+ break ;
+ }
+ case 1 : /* argv */
+ {
+ confnode node ;
+ check_unique(gl->key, md) ;
+ confnode_start(&node, gl->key, md->filepos, md->line) ;
+ for (size_t i = 1 ; i < n ; i++)
+ confnode_add(&node, s + word[i], strlen(s + word[i]) + 1) ;
+ conftree_add(&node) ;
+ break ;
+ }
+ }
+}
+
+static inline void parse_contenttype (char const *s, size_t const *word, size_t n, mdt const *md)
+{
+ char const *ct ;
+ if (n != 3)
+ strerr_dief8x(1, "too ", n > 3 ? "many" : "few", " arguments to directive ", "redirect", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ ct = s + *word++ ;
+ if (!strchr(ct, '/'))
+ strerr_dief6x(1, "Content-Type must include a slash, ", "check directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ n-- ;
+ for (size_t i = 0 ; i < n ; i++)
+ {
+ size_t len = strlen(s + word[i]) ;
+ char key[len + 2] ;
+ if (s[word[i]] != '.')
+ strerr_dief6x(1, "file extensions must start with a dot, ", "check directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ key[0] = 'T' ;
+ key[1] = ':' ;
+ memcpy(key + 2, s + word[i] + 1, len - 1) ;
+ key[len + 1] = 0 ;
+ add_unique(key, ct, strlen(ct) + 1, md) ;
+ }
+}
+
+static inline void parse_redirect (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md)
+{
+ static uint32_t const codes[4] = { 307, 308, 302, 301 } ;
+ uint32_t code ;
+ uint32_t i = 0 ;
+ if (n != 3)
+ strerr_dief8x(1, "too ", n > 3 ? "many" : "few", " arguments to directive ", "redirect", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!domain)
+ strerr_dief6x(1, "redirection", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (s[word[0]] != '/')
+ strerr_dief5x(1, "redirected resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!uint320_scan(s + word[1], &code))
+ strerr_dief6x(1, "invalid redirection code ", s + word[1], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ for (; i < 4 ; i++) if (code == codes[i]) break ;
+ if (i >= 4)
+ strerr_dief6x(1, "redirection code ", "must be 301, 302, 307 or 308", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (strncmp(s + word[2], "http://", 7) && strncmp(s + word[2], "https://", 8))
+ strerr_dief5x(1, "redirection target must be a full http:// or https:// target", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ confnode node ;
+ size_t urlen = strlen(s + word[0]) ;
+ char key[3 + domainlen + urlen] ;
+ if (s[word[0] + urlen - 1] == '/') { key[0] = 'r' ; urlen-- ; } else key[0] = 'R' ;
+ key[1] = ':' ;
+ memcpy(key + 2, domain, domainlen) ;
+ memcpy(key + 2 + domainlen, s + word[0], urlen) ;
+ key[2 + domainlen + urlen] = 0 ;
+ check_unique(key, md) ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ key[0] = '@' | i ;
+ confnode_add(&node, &key[0], 1) ;
+ confnode_add(&node, s + word[2], strlen(s + word[2]) + 1) ;
+ conftree_add(&node) ;
+ }
+}
+
+static void parse_bitattr (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md, unsigned int bit, int set)
+{
+ static char const *attr[3][2] = { { "noncgi", "cgi" }, { "nonnph", "nph", }, { "noauth", "basic-auth" } } ;
+ uint8_t mask = (uint8_t)0x01 << bit ;
+ if (n != 1)
+ strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", attr[bit][set], " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!domain)
+ strerr_dief7x(1, "resource attribute ", "definition", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (s[*word] != '/')
+ strerr_dief5x(1, "resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ confnode const *oldnode ;
+ size_t arglen = strlen(s + *word) ;
+ char key[3 + domainlen + arglen] ;
+ if (s[*word + arglen - 1] == '/') { key[0] = 'a' ; arglen-- ; } else key[0] = 'A' ;
+ key[1] = ':' ;
+ memcpy(key + 2, domain, domainlen) ;
+ memcpy(key + 2 + domainlen, s + *word, arglen) ;
+ key[2 + domainlen + arglen] = 0 ;
+ oldnode = conftree_search(key) ;
+ if (oldnode)
+ if (g.storage.s[oldnode->data] & mask)
+ {
+ char fmt[UINT32_FMT] ;
+ fmt[uint32_fmt(fmt, oldnode->line)] = 0 ;
+ strerr_diefn(1, 13, "resource attribute ", attr[bit][set], " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ;
+ }
+ else
+ {
+ g.storage.s[oldnode->data] |= mask ;
+ if (set) g.storage.s[oldnode->data + 1] |= mask ;
+ else g.storage.s[oldnode->data + 1] &= ~mask ;
+ }
+ else
+ {
+ confnode node ;
+ char val[3] = { mask, set ? mask : 0, 0 } ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, val, 3) ;
+ conftree_add(&node) ;
+ }
+ }
+}
+
+static inline void parse_filetype (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md)
+{
+ if (n != 2)
+ strerr_dief8x(1, "too ", n > 2 ? "many" : "few", " arguments to directive ", "file-type", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!domain)
+ strerr_dief7x(1, "file-type", " definition", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (s[word[0]] != '/')
+ strerr_dief5x(1, "resource must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ confnode const *oldnode ;
+ size_t arglen = strlen(s + word[0]) ;
+ char key[3 + domainlen + arglen] ;
+ if (s[word[0] + arglen - 1] == '/') { key[0] = 'a' ; arglen-- ; } else key[0] = 'A' ;
+ key[1] = ':' ;
+ memcpy(key + 2, domain, domainlen) ;
+ memcpy(key + 2 + domainlen, s + *word, arglen) ;
+ key[2 + domainlen + arglen] = 0 ;
+ oldnode = conftree_search(key) ;
+ if (oldnode)
+ {
+ if (g.storage.s[oldnode->data] & 0x80)
+ {
+ char fmt[UINT32_FMT] ;
+ fmt[uint32_fmt(fmt, oldnode->line)] = 0 ;
+ strerr_diefn(1, 12, "file-type", " redefinition", " in file ", g.storage.s + md->filepos, " line ", md->linefmt, "; previous definition", " in file ", g.storage.s + oldnode->filepos, " line ", fmt, " or later") ;
+ }
+
+ else
+ {
+ confnode node ;
+ char val[2] = { g.storage.s[oldnode->data] | 0x80, g.storage.s[oldnode->data + 1] } ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, val, 2) ;
+ confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ;
+ conftree_update(&node) ;
+ }
+ }
+ else
+ {
+ confnode node ;
+ char val[2] = { 0x80, 0x00 } ;
+ confnode_start(&node, key, md->filepos, md->line) ;
+ confnode_add(&node, val, 2) ;
+ confnode_add(&node, s + word[1], strlen(s + word[1]) + 1) ;
+ conftree_add(&node) ;
+ }
+ }
+}
+
+static inline void process_line (char const *s, size_t const *word, size_t n, stralloc *domain, mdt *md)
+{
+ static struct directive_s const directives[] =
+ {
+ { .s = "!", .token = T_BANG },
+ { .s = "basic-auth", .token = T_BASICAUTH },
+ { .s = "cgi", .token = T_CGI },
+ { .s = "content-type", .token = T_CONTENTTYPE },
+ { .s = "domain", .token = T_DOMAIN },
+ { .s = "file-type", .token = T_FILETYPE },
+ { .s = "global", .token = T_GLOBAL },
+ { .s = "no-auth", .token = T_NOAUTH },
+ { .s = "noncgi", .token = T_NONCGI },
+ { .s = "nonnph", .token = T_NONNPH },
+ { .s = "nph", .token = T_NPH },
+ { .s = "nph-prefix", .token = T_NPHPREFIX },
+ { .s = "redirect", .token = T_REDIRECT },
+ } ;
+ struct directive_s const *directive ;
+ char const *word0 ;
+ if (!n--) return ;
+ word0 = s + *word++ ;
+ directive = bsearch(word0, directives, sizeof(directives)/sizeof(struct directive_s), sizeof(struct directive_s), &directive_cmp) ;
+ if (!directive)
+ strerr_dief6x(1, "unrecognized word ", word0, " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ switch (directive->token)
+ {
+ case T_BANG :
+ {
+ size_t len, r, w ;
+ if (n != 2 || !uint320_scan(s + word[0], &md->line))
+ strerr_dief5x(101, "can't happen: invalid ! control directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ len = strlen(s + word[1]) ;
+ if (!stralloc_readyplus(&g.storage, len + 1)) dienomem() ;
+ if (!string_unquote(g.storage.s + g.storage.len, &w, s + word[1], len, &r))
+ strerr_dief7sys(101, "can't happen: unable to unquote ", s + word[1], " in file ", g.storage.s + md->filepos, " line ", md->linefmt, ", error reported is") ;
+ g.storage.s[g.storage.len + w++] = 0 ;
+ md->filepos = g.storage.len ;
+ g.storage.len += w ;
+ break ;
+ }
+ case T_GLOBAL :
+ parse_global(s, word, n, md) ;
+ break ;
+ case T_CONTENTTYPE :
+ parse_contenttype(s, word, n, md) ;
+ break ;
+ case T_DOMAIN :
+ if (n != 1)
+ strerr_dief8x(1, "too", n > 1 ? "many" : "few", " arguments to directive ", "domain", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ domain->len = 0 ;
+ if (!stralloc_cats(domain, s + *word)) dienomem() ;
+ while (domain->len && (domain->s[domain->len - 1] == '/' || domain->s[domain->len - 1] == '.')) domain->len-- ;
+ break ;
+ case T_NPHPREFIX :
+ if (n != 1)
+ strerr_dief8x(1, "too ", n > 1 ? "many" : "few", " arguments to directive ", "nph-prefix", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (!domain->s)
+ strerr_dief6x(1, "nph prefix definition", "without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ if (strchr(s + *word, '/')) strerr_dief5x(1, "invalid / in nph-prefix argument", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ;
+ {
+ char key[3 + domain->len] ;
+ key[0] = 'N' ;
+ key[1] = ':' ;
+ memcpy(key + 2, domain->s, domain->len) ;
+ key[2 + domain->len] = 0 ;
+ add_unique(key, s + *word, strlen(s + *word) + 1, md) ;
+ }
+ break ;
+ case T_REDIRECT :
+ parse_redirect(s, word, n, domain->s, domain->len, md) ;
+ break ;
+ case T_CGI :
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 0, 1) ;
+ break ;
+ case T_NONCGI :
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 0, 0) ;
+ break ;
+ case T_NPH :
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 1, 1) ;
+ break ;
+ case T_NONNPH :
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 1, 0) ;
+ break ;
+ case T_BASICAUTH :
+ strerr_warnw5x("file ", g.storage.s + md->filepos, " line ", md->linefmt, ": directive basic-auth not implemented in tipidee-" TIPIDEE_VERSION) ;
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 2, 1) ;
+ break ;
+ case T_NOAUTH :
+ strerr_warnw5x("file ", g.storage.s + md->filepos, " line ", md->linefmt, ": directive basic-auth not implemented in tipidee-" TIPIDEE_VERSION) ;
+ parse_bitattr(s, word, n, domain->s, domain->len, md, 2, 0) ;
+ break ;
+ case T_FILETYPE :
+ parse_filetype(s, word, n, domain->s, domain->len, md) ;
+ 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.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 */
+ stralloc domain = STRALLOC_ZERO ;
+ 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), &domain, &md) ;
+ genalloc_setlen(size_t, &words, 0) ;
+ sa.len = 0 ;
+ md.line++ ;
+ md.linefmt[uint32_fmt(md.linefmt, md.line)] = 0 ;
+ }
+ }
+ stralloc_free(&domain) ;
+ genalloc_free(size_t, &words) ;
+ stralloc_free(&sa) ;
+}
diff --git a/src/config/tipidee-config-internal.h b/src/config/tipidee-config-internal.h
new file mode 100644
index 0000000..e274f94
--- /dev/null
+++ b/src/config/tipidee-config-internal.h
@@ -0,0 +1,59 @@
+/* ISC license. */
+
+#ifndef TIPIDEE_CONFIG_INTERNAL_H
+#define TIPIDEE_CONFIG_INTERNAL_H
+
+#include <stdint.h>
+#include <string.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/cdbmake.h>
+
+typedef struct confnode_s confnode, *confnode_ref ;
+struct confnode_s
+{
+ uint32_t key ;
+ uint32_t keylen ;
+ uint32_t data ;
+ uint32_t datalen ;
+ uint32_t filepos ;
+ uint32_t line ;
+} ;
+#define CONFNODE_ZERO { .key = 0, .keylen = 0, .data = 0, .datalen = 0 }
+
+struct global_s
+{
+ stralloc storage ;
+} ;
+#define GLOBAL_ZERO { .storage = STRALLOC_ZERO }
+
+extern struct global_s g ;
+
+
+ /* confnode */
+
+extern void confnode_start (confnode *, char const *, size_t, uint32_t) ;
+extern void confnode_add (confnode *, char const *, size_t) ;
+#define confnode_adds(node, s) confnode_add(node, (s), strlen(s))
+#define confnode_add0(node) confnode_add((node), "", 1)
+
+
+ /* conftree */
+
+extern confnode const *conftree_search (char const *) ;
+extern void conftree_add (confnode const *) ;
+extern void conftree_update (confnode 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/tipidee-config-preprocess.c b/src/config/tipidee-config-preprocess.c
new file mode 100644
index 0000000..6ac4812
--- /dev/null
+++ b/src/config/tipidee-config-preprocess.c
@@ -0,0 +1,270 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/direntry.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+
+#define USAGE "tipidee-config-preprocess file"
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb") ;
+
+static stralloc sa = STRALLOC_ZERO ;
+static genalloc ga = GENALLOC_ZERO ; /* size_t */
+
+
+ /* Name storage */
+
+static stralloc namesa = STRALLOC_ZERO ;
+
+static void *name_dtok (uint32_t pos, void *aux)
+{
+ return ((stralloc *)aux)->s + pos + 1 ;
+}
+
+static int name_cmp (void const *a, void const *b, void *aux)
+{
+ (void)aux ;
+ return strcmp((char const *)a, (char const *)b) ;
+}
+
+static avltree namemap = AVLTREE_INIT(8, 3, 8, &name_dtok, &name_cmp, &namesa) ;
+
+
+ /* Directory sorting */
+
+static char const *dname_cmp_base ;
+static int dname_cmp (void const *a, void const *b)
+{
+ return strcmp(dname_cmp_base + *(size_t *)a, dname_cmp_base + *(size_t *)b) ;
+}
+
+
+ /* Recursive inclusion functions */
+
+static void includefromhere (char const *) ;
+
+static inline void includecwd (void)
+{
+ DIR *dir ;
+ size_t sabase = sa.len ;
+ size_t gabase = genalloc_len(size_t, &ga) ;
+ if (sagetcwd(&sa) < 0 || !stralloc_0(&sa)) dienomem() ;
+ dir = opendir(".") ;
+ if (!dir) strerr_diefu2sys(111, "opendir ", sa.s + sabase) ;
+
+ for (;;)
+ {
+ direntry *d ;
+ errno = 0 ;
+ d = readdir(dir) ;
+ if (!d) break ;
+ if (d->d_name[0] == '.') continue ;
+ if (!genalloc_catb(size_t, &ga, &sa.len, 1)) dienomem() ;
+ if (!stralloc_catb(&sa, d->d_name, strlen(d->d_name)+1)) dienomem() ;
+ }
+ dir_close(dir) ;
+ if (errno) strerr_diefu2sys(111, "readdir ", sa.s + sabase) ;
+
+ dname_cmp_base = sa.s ;
+ qsort(genalloc_s(size_t, &ga) + gabase, genalloc_len(size_t, &ga) - gabase, sizeof(size_t), &dname_cmp) ;
+
+ for (size_t i = 0 ; i < genalloc_len(size_t, &ga) ; i++)
+ includefromhere(sa.s + genalloc_s(size_t, &ga)[gabase + i]) ;
+
+ genalloc_setlen(size_t, &ga, gabase) ;
+ sa.len = sabase ;
+}
+
+static void include (char const *file)
+{
+ size_t sabase = sa.len ;
+ size_t filelen = strlen(file) ;
+ if (!sadirname(&sa, file, filelen) || !stralloc_0(&sa)) dienomem() ;
+ if (chdir(sa.s + sabase) < 0) strerr_diefu2sys(111, "chdir to ", sa.s + sabase) ;
+ sa.len = sabase ;
+ if (!sabasename(&sa, file, filelen)) dienomem() ;
+ {
+ char fn[sa.len + 1 - sabase] ;
+ memcpy(fn, sa.s + sabase, sa.len - sabase) ;
+ fn[sa.len - sabase] = 0 ;
+ sa.len = sabase ;
+ includefromhere(fn) ;
+ }
+}
+
+static inline int idcmd (char const *s)
+{
+ static char const *commands[] =
+ {
+ "include",
+ "includedir",
+ "included:",
+ 0
+ } ;
+ for (char const **p = commands ; *p ; p++)
+ if (!strcmp(s, *p)) return p - commands ;
+ return -1 ;
+}
+
+static inline unsigned char cclass (char c)
+{
+ static unsigned char const classtable[34] = "0444444443144344444444444444444432" ;
+ return (unsigned char)c < 34 ? classtable[(unsigned char)c] - '0' : 4 ;
+}
+
+static void includefromhere (char const *file)
+{
+ static unsigned char const table[8][5] =
+ {
+ { 0x08, 0x10, 0x02, 0x11, 0x11 },
+ { 0x08, 0x10, 0x11, 0x11, 0x11 },
+ { 0x08, 0x00, 0x03, 0x04, 0x25 },
+ { 0x08, 0x00, 0x03, 0x03, 0x03 },
+ { 0x09, 0x09, 0x09, 0x04, 0x25 },
+ { 0x09, 0x09, 0x09, 0x46, 0x25 },
+ { 0x0a, 0x0a, 0x07, 0x06, 0x27 },
+ { 0x88, 0x80, 0x27, 0x27, 0x27 }
+ } ;
+ size_t sabase = sa.len ;
+ size_t namesabase = namesa.len ;
+ size_t sastart ;
+ int cmd = -1 ;
+ int fd ;
+ buffer b ;
+ uint32_t d ;
+ uint32_t line = 1 ;
+ char buf[4096] ;
+ char linefmt[UINT32_FMT] = "1" ;
+ unsigned char state = 0 ;
+
+ if (!stralloc_catb(&namesa, "\004", 1) || sarealpath(&namesa, file) < 0 || !stralloc_0(&namesa)) dienomem() ;
+ if (avltree_search(&namemap, namesa.s + namesabase + 1, &d))
+ {
+ if (namesa.s[d] & 0x04)
+ strerr_dief3x(3, "file ", namesa.s + namesabase + 1, " is included in a cycle") ;
+ if (!(namesa.s[d] & 0x02))
+ strerr_dief3x(3, "file ", namesa.s + namesabase + 1, " is included twice but does not declare !included: unique or !included: multiple") ;
+ namesa.len = namesabase ;
+ if (namesa.s[d] & 0x01) return ;
+ }
+ else
+ {
+ if (namesabase > UINT32_MAX)
+ strerr_dief3x(3, "in ", namesa.s + d + 1, ": too many, too long filenames") ;
+ d = namesabase ;
+ if (!avltree_insert(&namemap, d)) dienomem() ;
+ }
+
+ if (!string_quote(&sa, namesa.s + d + 1, strlen(namesa.s + d + 1))) dienomem() ;
+ if (!stralloc_0(&sa)) dienomem() ;
+
+ sastart = sa.len ;
+ fd = open_readb(file) ;
+ if (fd < 0) strerr_diefu2sys(111, "open ", namesa.s + d + 1) ;
+ buffer_init(&b, &buffer_read, fd, buf, 4096) ;
+
+ if (buffer_put(buffer_1, "! 0 ", 4) < 4
+ || buffer_puts(buffer_1, sa.s + sabase) < 0
+ || buffer_put(buffer_1, "\n", 1) < 1)
+ strerr_diefu1sys(111, "write to stdout") ;
+
+ while (state < 8)
+ {
+ uint16_t what ;
+ char c = 0 ;
+ if (buffer_get(&b, &c, 1) < 0) strerr_diefu2sys(111, "read from ", namesa.s + d + 1) ;
+ what = table[state][cclass((unsigned char)c)] ;
+ state = what & 0x000f ;
+ if (what & 0x0010) if (buffer_put(buffer_1, &c, 1) < 1) strerr_diefu1sys(111, "write to stdout") ;
+ if (what & 0x0020) if (!stralloc_catb(&sa, &c, 1)) dienomem() ;
+ if (what & 0x0040)
+ {
+ if (!stralloc_0(&sa)) dienomem() ;
+ cmd = idcmd(sa.s + sastart) ;
+ if (cmd == -1)
+ strerr_dief6x(2, "in ", namesa.s + d + 1, " line ", linefmt, ": unrecognized directive: ", sa.s + sastart) ;
+ sa.len = sastart ;
+ }
+ if (what & 0x0080)
+ {
+ if (!stralloc_0(&sa)) dienomem() ;
+ switch (cmd)
+ {
+ case 2 :
+ if (!strcmp(sa.s + sastart, "unique")) namesa.s[d] |= 3 ;
+ else if (!strcmp(sa.s + sastart, "multiple")) namesa.s[d] |= 2 ;
+ else strerr_dief6x(3, "in ", namesa.s + d + 1, " line ", linefmt, "invalid !included: argument: ", sa.s + sastart) ;
+ break ;
+ case 1 :
+ case 0 :
+ {
+ int fdhere = open2(".", O_RDONLY | O_DIRECTORY) ;
+ if (fdhere == -1)
+ strerr_dief3sys(111, "in ", namesa.s + d + 1, ": unable to open base directory: ") ;
+ if (cmd & 1)
+ {
+ if (chdir(sa.s + sastart) == -1)
+ strerr_dief6sys(111, "in ", namesa.s + d + 1, " line ", linefmt, ": unable to chdir to ", sa.s + sastart) ;
+ includecwd() ;
+ }
+ else include(sa.s + sastart) ;
+ if (fchdir(fdhere) == -1)
+ strerr_dief4sys(111, "in ", namesa.s + d + 1, ": unable to fchdir back after including ", sa.s + sastart) ;
+ fd_close(fdhere) ;
+ if (buffer_put(buffer_1, "! ", 2) < 2
+ || buffer_puts(buffer_1, linefmt) < 0
+ || buffer_put(buffer_1, " ", 1) < 1
+ || buffer_puts(buffer_1, sa.s + sabase) < 0
+ || buffer_put(buffer_1, "\n", 1) < 1)
+ strerr_diefu1sys(111, "write to stdout") ;
+ break ;
+ }
+ }
+ sa.len = sastart ;
+ }
+ if (c == '\n' && state <= 8) linefmt[uint32_fmt(linefmt, ++line)] = 0 ;
+ }
+ if (state > 8) strerr_dief5x(2, "in ", namesa.s + d + 1, " line ", linefmt, ": syntax error: invalid ! line") ;
+ fd_close(fd) ;
+ sa.len = sabase ;
+ namesa.s[d] &= ~0x04 ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+ PROG = "tipidee-config-preprocess" ;
+ {
+ subgetopt l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ }
+ if (!argc) dieusage() ;
+
+ include(argv[0]) ;
+ if (!buffer_flush(buffer_1))
+ strerr_diefu1sys(111, "write to stdout") ;
+ return 0 ;
+}
diff --git a/src/config/tipidee-config.c b/src/config/tipidee-config.c
new file mode 100644
index 0000000..be13e39
--- /dev/null
+++ b/src/config/tipidee-config.c
@@ -0,0 +1,135 @@
+/* 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/sig.h>
+#include <skalibs/djbunix.h>
+
+#include <tipidee/config.h>
+#include "tipidee-config-internal.h"
+
+#define USAGE "tipidee-config [ -i textfile ] [ -o cdbfile ] [ -m mode ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+struct global_s g = GLOBAL_ZERO ;
+
+static pid_t pid = 0 ;
+
+static void sigchld_handler (int sig)
+{
+ (void)sig ;
+ for (;;)
+ {
+ int wstat ;
+ pid_t r = wait_nohang(&wstat) ;
+ if (r == -1 && errno != ECHILD) strerr_diefu1sys(111, "wait") ;
+ else if (r <= 0) break ;
+ else if (r == pid)
+ {
+ if (WIFEXITED(wstat) && !WEXITSTATUS(wstat)) pid = 0 ;
+ else _exit(wait_estatus(wstat)) ;
+ }
+ }
+}
+
+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 (coe(fdw) == -1)
+ {
+ unlink_void(otmp) ;
+ strerr_diefu2sys(111, "coe ", otmp) ;
+ }
+ 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 & 0755) == -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 = "/etc/tipidee.conf" ;
+ char const *ofile = "/etc/tipidee.conf.cdb" ;
+ unsigned int omode = 0644 ;
+
+ PROG = "tipidee-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 ;
+ buffer b ;
+ char buf[4096] ;
+ sig_block(SIGCHLD) ;
+ if (!sig_catch(SIGCHLD, &sigchld_handler))
+ strerr_diefu1sys(111, "install SIGCHLD handler") ;
+ {
+ char const *ppargv[3] = { TIPIDEE_LIBEXECPREFIX "tipidee-config-preprocess", ifile, 0 } ;
+ pid = child_spawn1_pipe(ppargv[0], ppargv, envp, &fdr, 1) ;
+ if (!pid) strerr_diefu2sys(errno == ENOENT ? 127 : 126, "spawn ", ppargv[0]) ;
+ }
+ sig_unblock(SIGCHLD) ;
+ buffer_init(&b, &buffer_read, fdr, buf, 4096) ;
+ conf_lexparse(&b, ifile) ;
+ }
+ conf_defaults() ;
+ conf_output(ofile, omode) ;
+ return 0 ;
+}