diff options
author | Laurent Bercot <ska-skaware@skarnet.org> | 2023-08-05 11:51:25 +0000 |
---|---|---|
committer | Laurent Bercot <ska@appnovation.com> | 2023-08-05 11:51:25 +0000 |
commit | 17c382d1c9d7236c101418060758d2296cc5e17e (patch) | |
tree | fd00e58df0d9d3c70ddd1accfec9e819249c672a /src/config | |
download | tipidee-17c382d1c9d7236c101418060758d2296cc5e17e.tar.gz tipidee-17c382d1c9d7236c101418060758d2296cc5e17e.tar.xz tipidee-17c382d1c9d7236c101418060758d2296cc5e17e.zip |
Initial commit
Signed-off-by: Laurent Bercot <ska@appnovation.com>
Diffstat (limited to 'src/config')
-rw-r--r-- | src/config/PARSING-config.txt | 56 | ||||
-rw-r--r-- | src/config/PARSING-preprocess.txt | 38 | ||||
-rw-r--r-- | src/config/PROTOCOL.txt | 43 | ||||
-rw-r--r-- | src/config/confnode.c | 35 | ||||
-rw-r--r-- | src/config/conftree.c | 82 | ||||
-rw-r--r-- | src/config/defaults.c | 106 | ||||
-rw-r--r-- | src/config/deps-exe/tipidee-config | 6 | ||||
-rw-r--r-- | src/config/deps-exe/tipidee-config-preprocess | 1 | ||||
-rw-r--r-- | src/config/lexparse.c | 443 | ||||
-rw-r--r-- | src/config/tipidee-config-internal.h | 59 | ||||
-rw-r--r-- | src/config/tipidee-config-preprocess.c | 270 | ||||
-rw-r--r-- | src/config/tipidee-config.c | 135 |
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 ; +} |