From 76de80e7d41bf20dea667596ed0dc251bc8f28e7 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Tue, 29 Dec 2015 05:05:26 +0100 Subject: Use ylog + add chroot option + started on backend framework --- Makefile.am | 7 +++- init-from-git.sh | 2 + src/app.c | 51 +++++++++++++++++++++--- src/app.h | 5 ++- src/back_fastcgi.c | 66 +++++++++++++++++++++++++++++++ src/back_fastcgi.h | 53 +++++++++++++++++++++++++ src/fcgy.h | 2 + src/fconn_fastcgi.c | 15 +++---- src/main.c | 27 ++++++++++--- src/main.h | 16 +++++--- src/req.c | 2 +- src/util.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.h | 13 ++++++ test/parse_argv.c | 74 ++++++++++++++++++++++++++++++++++ 14 files changed, 418 insertions(+), 27 deletions(-) create mode 100644 src/back_fastcgi.c create mode 100644 src/back_fastcgi.h create mode 100644 test/parse_argv.c diff --git a/Makefile.am b/Makefile.am index c2a038e..42fb52e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,9 +29,11 @@ EXTRA_DIST+=\ deps/ev/ev_wrap.h # ylib +libdeps_a_SOURCES+=deps/ylib/ylog.c EXTRA_DIST+=\ deps/ylib/list.h\ deps/ylib/vec.h\ + deps/ylib/ylog.h\ deps/ylib/yopt.h # fcgy @@ -39,6 +41,7 @@ bin_PROGRAMS=fcgy fcgy_LDADD=libdeps.a -lm -lpthread $(EV_LIBS) fcgy_SOURCES=\ src/app.c\ + src/back_fastcgi.c\ src/fastcgi.c\ src/fconn_fastcgi.c\ src/main.c\ @@ -49,6 +52,7 @@ nodist_fcgy_SOURCES=version.c noinst_HEADERS=\ src/app.h\ + src/back_fastcgi.h\ src/fastcgi.h\ src/fcgy.h\ src/fconn_fastcgi.h\ @@ -60,10 +64,11 @@ MOSTLYCLEANFILES+=version.c # Tests -TESTS=test/fastcgi_param_parse +TESTS=test/fastcgi_param_parse test/parse_argv check_PROGRAMS=$(TESTS) test_fastcgi_param_parse_SOURCES=test/fastcgi_param_parse.c src/fastcgi.c test_fastcgi_param_parse_LDADD=libdeps.a -lm -lpthread $(EV_LIBS) +test_parse_argv_SOURCES=test/parse_argv.c src/util.c if USE_GIT_VERSION diff --git a/init-from-git.sh b/init-from-git.sh index 029b2d3..7f6bd30 100755 --- a/init-from-git.sh +++ b/init-from-git.sh @@ -28,6 +28,8 @@ ylib() { mkdir -p ylib\ && wget -q $U/list.h -O ylib/list.h\ && wget -q $U/vec.h -O ylib/vec.h\ + && wget -q $U/ylog.c -O ylib/ylog.c\ + && wget -q $U/ylog.h -O ylib/ylog.h\ && wget -q $U/yopt.h -O ylib/yopt.h } diff --git a/src/app.c b/src/app.c index 84673aa..68742e2 100644 --- a/src/app.c +++ b/src/app.c @@ -44,18 +44,18 @@ static void front_accept_cb(EV_P_ ev_io *w, int revents) { fd = -1; } - /* TODO: Honor FCGI_WEB_SERVER_ADDRS, if set and this is an stdio frontend */ - if(fd < 0) { - fprintf(stderr, "Error accept()'ing: %s.\n", strerror(errno)); + ywarn("Error accept()'ing: %s. Retrying in %.1f seconds.", strerror(errno), LISTEN_BACKOFF_TIME); ev_io_stop(EV_A_ w); ev_timer_set(f->backoff, LISTEN_BACKOFF_TIME, 0.); ev_timer_start(EV_A_ f->backoff); return; } + /* TODO: Honor FCGI_WEB_SERVER_ADDRS, if set and this is an stdio frontend */ + fconn_fastcgi_create(f->app, fd, fd); - fprintf(stderr, "HI!\n"); + ydebug("Incoming connection."); } @@ -81,6 +81,10 @@ static fcgy_front *front_create(fcgy_app *app, const char *addr) { static int front_bind(fcgy_front *f, char *err, size_t errlen) { if(f->addr.generic.sa_family == 0) { f->fd = 0; + if(util_fd_flags(f->fd, FCGY_NONBLOCK|FCGY_CLOEXEC) < 0) { + snprintf(err, errlen, "can't setup socket: %s", strerror(errno)); + return -1; + } return 0; } @@ -125,6 +129,12 @@ fcgy_app *app_create() { int app_config(fcgy_app *app, fcgy_config_name name, const char *val, char *err, size_t errlen) { fcgy_front *f; switch(name) { + + case FCGY_CONFIG_APP_CHROOT: + free(app->chroot); + app->chroot = strcmp(val, "no") == 0 ? NULL : strdup(val); + return 0; + case FCGY_CONFIG_APP_LISTEN: f = front_create(app, val); if(!f) { @@ -133,6 +143,28 @@ int app_config(fcgy_app *app, fcgy_config_name name, const char *val, char *err, } hlist_prepend(app->fronts, f); return 0; + + case FCGY_CONFIG_APP_FASTCGI: + if(app->back) { + snprintf(err, errlen, "multiple backends specified, only a single backend is supported"); + return -1; + } + app->back = back_fastcgi_create(app, val); + if(!app->back) { + snprintf(err, errlen, "invalid backend command '%s'", val); + return -1; + } + return 0; + + case FCGY_CONFIG_APP_FASTCGI_CHDIR: + if(!app->back) { + snprintf(err, errlen, "no FastCGI backend configured"); + return -1; + } + free(app->back->chdir); + app->back->chdir = strcmp(val, "no") == 0 ? NULL : strdup(val); + return 0; + default: assert(0 && "app_config() called on invalid option"); } @@ -153,10 +185,17 @@ int app_bind(fcgy_app *app, char *err, size_t errlen) { } -void app_run(fcgy_app *app) { +int app_run(fcgy_app *app) { + if(app->chroot && chroot(app->chroot) < 0) { + yerr("Could not chroot to '%s': %s", app->chroot, strerror(errno)); + return -1; + } + fcgy_front *f; for(f=app->fronts; f; f=f->next) front_start(f); + + return back_fastcgi_run(app->back); } @@ -169,5 +208,7 @@ void app_destroy(fcgy_app *app) { } while(app->fconns) fconn_fastcgi_destroy(app->fconns); + if(app->back) + back_fastcgi_destroy(app->back); free(app); } diff --git a/src/app.h b/src/app.h index 1963d7b..54d669a 100644 --- a/src/app.h +++ b/src/app.h @@ -43,13 +43,14 @@ struct fcgy_app { fcgy_front *fronts; /* Linked list of frontends */ fconn_fastcgi *fconns; /* Linked list of (alive) frontend connections */ /* TODO: uid / gid */ - /* TODO: backends & options */ + back_fastcgi *back; /* Only a single (FastCGI) backend supported for now */ + char *chroot; }; fcgy_app *app_create(); int app_config(fcgy_app *, fcgy_config_name, const char *, char *, size_t); int app_bind(fcgy_app *, char *, size_t); -void app_run(fcgy_app *); +int app_run(fcgy_app *); void app_destroy(fcgy_app *); #endif diff --git a/src/back_fastcgi.c b/src/back_fastcgi.c new file mode 100644 index 0000000..a13f277 --- /dev/null +++ b/src/back_fastcgi.c @@ -0,0 +1,66 @@ +/* Copyright (c) 2015-2016 Yoran Heling + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "fcgy.h" + +back_fastcgi *back_fastcgi_create(fcgy_app *app, const char *cmd) { + util_argv arg; + if(util_parse_argv(cmd, &arg) < 0 || arg.n < 2) + return NULL; + + back_fastcgi *b = calloc(1, sizeof(back_fastcgi)); + b->app = app; + b->argv = arg; + return b; +} + + +static void set_chdir(back_fastcgi *b) { + if(b->chdir) + return; + char *file = strrchr(b->argv.a[0], '/'); + if(file) { + char *oldcmd = b->argv.a[0]; + *file = 0; + b->argv.a[0] = strdup(file+1); + b->chdir = *oldcmd ? strdup(oldcmd) : NULL; + free(oldcmd); + } +} + + +int back_fastcgi_run(back_fastcgi *b) { + set_chdir(b); + if(b->chdir && chdir(b->chdir) < 0) { + yerr("Could not chdir to '%s': %s", b->chdir, strerror(errno)); + return -1; + } + return 0; +} + + +/* XXX: Can't instantly destroy when we have processes running. Need async shutdown. */ +void back_fastcgi_destroy(back_fastcgi *b) { + free(b->chdir); + util_free_argv(&b->argv); + free(b); +} diff --git a/src/back_fastcgi.h b/src/back_fastcgi.h new file mode 100644 index 0000000..e1b9fdf --- /dev/null +++ b/src/back_fastcgi.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2015-2016 Yoran Heling + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef FCGY_BACK_FASTCGI_H +#define FCGY_BACK_FASTCGI_H + +/* +struct bconn_fastcgi { + int fd; + fcgy_req *req; +}; + +struct back_process { + util_sockaddr addr; + struct bconn_fastcgi *conns; + int pid; + int errfd, outfd; +}; +*/ + +typedef struct back_fastcgi back_fastcgi; + +struct back_fastcgi { + fcgy_app *app; + util_argv argv; + char *chdir; +}; + + +back_fastcgi *back_fastcgi_create(fcgy_app *, const char *); +int back_fastcgi_run(back_fastcgi *); +void back_fastcgi_destroy(back_fastcgi *); + +#endif diff --git a/src/fcgy.h b/src/fcgy.h index dcacb1c..6f3137b 100644 --- a/src/fcgy.h +++ b/src/fcgy.h @@ -50,6 +50,7 @@ #include #include #include +#include /* version.c */ extern char *fcgy_version; @@ -64,6 +65,7 @@ typedef struct fconn_fastcgi fconn_fastcgi; #include "util.h" #include "fastcgi.h" #include "main.h" +#include "back_fastcgi.h" #include "app.h" #include "fconn_fastcgi.h" #include "req.h" diff --git a/src/fconn_fastcgi.c b/src/fconn_fastcgi.c index e0f4132..2e37371 100644 --- a/src/fconn_fastcgi.c +++ b/src/fconn_fastcgi.c @@ -33,7 +33,7 @@ static void handle_get_values(fconn_fastcgi *c, fcgy_req *req, fastcgi_header h, static void handle_begin_request(fconn_fastcgi *c, fcgy_req *req, fastcgi_header h, const char *buf) { if(h.requestId == 0 || h.contentLength < 8 || *buf != 0 || buf[1] < 1 || buf[1] > 3) { - fprintf(stderr, "Invalid BEGIN_REQUEST record\n"); + ywarn("Invalid BEGIN_REQUEST record, disconnecting front-end."); fconn_fastcgi_destroy(c); return; } @@ -41,7 +41,7 @@ static void handle_begin_request(fconn_fastcgi *c, fcgy_req *req, fastcgi_header size_t n; vec_search_insert(c->reqs, n, req_cmp(c, h.requestId)); if(n < c->reqs.n && c->reqs.a[n]->fconn_id == h.requestId) { - fprintf(stderr, "BEGIN_REQUEST record for existing request ID\n"); + ywarn("BEGIN_REQUEST record for existing request ID, disconnecting front-end."); fconn_fastcgi_destroy(c); return; } @@ -64,7 +64,7 @@ static void handle_params(fconn_fastcgi *c, fcgy_req *req, fastcgi_header h, con while(len > 0) { n = fastcgi_param_parse(c->pp, buf, len); if(n < 0) { - fprintf(stderr, "Invalid parameter\n"); + ywarn("Invalid parameter, disconnecting front-end."); fconn_fastcgi_destroy(c); return; } @@ -88,12 +88,13 @@ static void read_cb(fastcgi_reader *r, ssize_t len, fastcgi_header h, const char fconn_fastcgi *c = r->data; if(len < 0) { - fprintf(stderr, "Read error: %s\n", strerror(errno)); + if(errno != ECONNRESET || c->reqs.n) + ywarn("Unexpected connection error: %s", strerror(errno)); fconn_fastcgi_destroy(c); return; } - fprintf(stderr, "Got FastCGI record: len = %4u, version = %u, type = %2u, requestId = %2u, contentLength = %4u\n", + ytrace("Got FastCGI record: len = %4u, version = %u, type = %2u, requestId = %2u, contentLength = %4u\n", (unsigned)len, (unsigned)h.version, (unsigned)h.type, (unsigned)h.requestId, (unsigned)h.contentLength); static const struct { @@ -117,7 +118,7 @@ static void read_cb(fastcgi_reader *r, ssize_t len, fastcgi_header h, const char } if(!handler) { - fprintf(stderr, "Unknown record type %u\n", (unsigned)h.type); + ydebug("Received record with unknown type %u\n", (unsigned)h.type); /* TODO: Reply with FCGI_UNKNOWN_TYPE */ return; } @@ -126,7 +127,7 @@ static void read_cb(fastcgi_reader *r, ssize_t len, fastcgi_header h, const char if(handler->req) { vec_search(c->reqs, req_cmp(c, h.requestId), req = c->reqs.a[i]); if(!req) { - fprintf(stderr, "Received record for unknown request id (type = %u, id = %u)\n", (unsigned)h.type, (unsigned)h.requestId); + ywarn("Received record for unknown request id (type = %u, id = %u), disconnecting front-end.\n", (unsigned)h.type, (unsigned)h.requestId); fconn_fastcgi_destroy(c); return; } diff --git a/src/main.c b/src/main.c index cdcec31..ad2d7ca 100644 --- a/src/main.c +++ b/src/main.c @@ -55,8 +55,10 @@ static int parse_args(int argc, char **argv) { } continue; } - /* TODO: Handle global options */ switch(v) { + case FCGY_CONFIG_GLOBAL_LOG_LEVEL: + ylog_set_level(2, val); + break; case FCGY_CONFIG_CLI_VERSION: printf("fcgy %s\n", fcgy_version); exit(0); @@ -74,7 +76,14 @@ static int parse_args(int argc, char **argv) { return -1; } - /* TODO: Some sanity checking (e.g. do we have an app front and backend?) */ + if(!app->fronts) { + fprintf(stderr, "No front-ends configured.\n"); + return -1; + } + if(!app->back) { + fprintf(stderr, "No backend configured.\n"); + return -1; + } return 0; } @@ -87,6 +96,9 @@ static void shutdown_sig(EV_P_ ev_signal *w, int revents) { int main(int argc, char **argv) { + ylog_set_level(2, "2"); + /* TODO: Set a custom ylog handler (writing to stderr while we have a console, syslog/file otherwise) */ + app = app_create(); if(parse_args(argc, argv) < 0) return 1; @@ -95,17 +107,22 @@ int main(int argc, char **argv) { char ebuff[256]; if(app_bind(app, ebuff, sizeof(ebuff)) < 0) { - fprintf(stderr, "fcgy: %s\n", ebuff); + fprintf(stderr, "fcgy: %s\n", ebuff); /* XXX: Probably better to let app_bind() call yerr() instead of this indirection */ + app_destroy(app); + return 1; + } + if(app_run(app) < 0) { + app_destroy(app); return 1; } - app_run(app); ev_signal_init(&termsig, shutdown_sig, SIGTERM); ev_signal_init(&intsig, shutdown_sig, SIGINT); ev_signal_start(EV_DEFAULT_UC_ &termsig); ev_signal_start(EV_DEFAULT_UC_ &intsig); + yinfo("%s %s started", PACKAGE_NAME, fcgy_version); ev_run(EV_DEFAULT_UC_ 0); - fprintf(stderr, "Clean shutdown.\n"); + yinfo("Shutting down"); return 0; } diff --git a/src/main.h b/src/main.h index 6962670..7ba13dc 100644 --- a/src/main.h +++ b/src/main.h @@ -25,29 +25,33 @@ /* Global options. * X(NAME, "name") */ -#define FCGY_CONFIG_GLOBAL +#define FCGY_CONFIG_GLOBAL\ + X(LOG_LEVEL, "log-level") /* Application options. * X(NAME, "name") */ #define FCGY_CONFIG_APP \ - X(LISTEN, "listen") + X(CHROOT, "chroot")\ + X(FASTCGI, "fastcgi")\ + X(FASTCGI_CHDIR, "fastcgi-chdir")\ + X(LISTEN, "listen") /* Command-line options. * X(NAME, "yopt-style-name", needarg) */ #define FCGY_CONFIG_CLI \ X(VERSION, "-V,--version", 0)\ - X(HELP, "-h,--help", 0) + X(HELP, "-h,--help", 0) typedef enum { - FCGY_CONFIG_GLOBAL_MARKER = 0x001000, + FCGY_CONFIG_GLOBAL_MARKER = 0x1000, #define X(N, n) FCGY_CONFIG_GLOBAL_##N, FCGY_CONFIG_GLOBAL #undef X - FCGY_CONFIG_APP_MARKER = 0x010000, + FCGY_CONFIG_APP_MARKER = 0x2000, #define X(N, n) FCGY_CONFIG_APP_##N, FCGY_CONFIG_APP #undef X - FCGY_CONFIG_CLI_MARKER = 0x100000, + FCGY_CONFIG_CLI_MARKER = 0x4000, #define X(N, n, a) FCGY_CONFIG_CLI_##N, FCGY_CONFIG_CLI #undef X diff --git a/src/req.c b/src/req.c index 12a0267..cddcb82 100644 --- a/src/req.c +++ b/src/req.c @@ -48,5 +48,5 @@ void req_set_param(fcgy_req *r, uint32_t namelen, uint32_t valuelen, char *buf) p->namelen = namelen; p->valuelen = valuelen; p->buf = buf; - fprintf(stderr, "Parameter: %s=%s\n", buf, buf+namelen+1); + ytrace("Parameter: %s=%s\n", buf, buf+namelen+1); } diff --git a/src/util.c b/src/util.c index a2ab156..0e3485c 100644 --- a/src/util.c +++ b/src/util.c @@ -128,3 +128,115 @@ void util_format_addr(const util_sockaddr *s, char *dst) { break; } } + + + +typedef vec_t(char) util_buf; /* Abusing vec as a string library */ + +static int unquote_double(const char *orig, util_buf *buf) { + const char *str = orig; + str++; + while(*str) { + switch(*str) { + case '"': return str-orig+1; + case '\\': + str++; + switch(*str) { + case '\0': + return -1; + case '"': + case '\\': + case '`': + case '$': + case '\n': + vec_append(*buf, *str); + break; + default: + vec_append(*buf, '\\'); + str--; /* Didn't mean to escape this char */ + } + break; + default: + vec_append(*buf, *str); + } + str++; + } + return -1; +} + + +static int unquote_single(const char *orig, util_buf *buf) { + const char *str = orig; + str++; + while(*str) { + if(*str == '\'') + return str-orig+1; + vec_append(*buf, *str); + str++; + } + return -1; +} + + +void util_free_argv(util_argv *arg) { + size_t i; + for(i=0; in; i++) + free(arg->a[i]); + vec_clear(*arg); +} + + +int util_parse_argv(const char *str, util_argv *arg) { + memset(arg, 0, sizeof(util_argv)); + util_buf buf = {0}; + int n; + + while(*str) { + switch(*str) { + case ' ': + case '\t': + case '\n': + if(buf.a) { + vec_append(buf, 0); + vec_append(*arg, buf.a); + memset(&buf, 0, sizeof(buf)); + } else + vec_clear(buf); + break; + case '\\': + str++; + switch(*str) { + case '\n': break; + case '\0': + free(buf.a); + util_free_argv(arg); + return -1; + default: + vec_append(buf, *str); + } + break; + case '"': + case '\'': + if(!buf.a) /* Make sure we create a token, even if it's an empty string */ + vec_grow(buf); + n = (*str == '"' ? unquote_double : unquote_single)(str, &buf); + if(n < 0) { + free(buf.a); + util_free_argv(arg); + return -1; + } + str += n-1; + break; + default: + vec_append(buf, *str); + } + str++; + } + + if(buf.a) { + vec_append(buf, 0); + vec_append(*arg, buf.a); + } + vec_append(*arg, NULL); + return 0; +} diff --git a/src/util.h b/src/util.h index fb2ba27..eb6e22e 100644 --- a/src/util.h +++ b/src/util.h @@ -70,5 +70,18 @@ static inline socklen_t util_sockaddr_len(int type) { return 0; } + +/* Parse a shell command line into an argv list. Splits the string into tokens + * and parses shell escapes and quoting. This function is limited only to + * \-escapes and single/double quotes. Comments, variable expansions, globs, + * etc, etc are not interpreted and are passed through as-is. + * Returns -1 if the string ends with an escape char or unmatched quote. + * Returns 0 on success, and the resulting vector is written to the second + * argument. The argument list can be freed with util_free_argv(). + */ +typedef vec_t(char *) util_argv; +void util_free_argv(util_argv *); +int util_parse_argv(const char *, util_argv *); + #endif diff --git a/test/parse_argv.c b/test/parse_argv.c new file mode 100644 index 0000000..34bedd6 --- /dev/null +++ b/test/parse_argv.c @@ -0,0 +1,74 @@ +/* Copyright (c) 2015-2016 Yoran Heling + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "fcgy.h" + +#undef NDEBUG +#include + +#define T(in, ...) do {\ + char *res[] = {__VA_ARGS__};\ + util_argv arg;\ + assert(util_parse_argv(in, &arg) == 0);\ + assert(arg.n-1 == sizeof(res)/sizeof(*res));\ + size_t i;\ + for(i=0; i