summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2015-12-29 05:05:26 +0100
committerYorhel <git@yorhel.nl>2015-12-29 05:05:26 +0100
commit76de80e7d41bf20dea667596ed0dc251bc8f28e7 (patch)
tree466b6d36c2f2c3e56b5cc4b89a5273e12adba876
parentfe28e88caea2a7826fddb43181ad5263faf55034 (diff)
Use ylog + add chroot option + started on backend frameworkHEADmaster
-rw-r--r--Makefile.am7
-rwxr-xr-xinit-from-git.sh2
-rw-r--r--src/app.c51
-rw-r--r--src/app.h5
-rw-r--r--src/back_fastcgi.c66
-rw-r--r--src/back_fastcgi.h53
-rw-r--r--src/fcgy.h2
-rw-r--r--src/fconn_fastcgi.c15
-rw-r--r--src/main.c27
-rw-r--r--src/main.h16
-rw-r--r--src/req.c2
-rw-r--r--src/util.c112
-rw-r--r--src/util.h13
-rw-r--r--test/parse_argv.c74
14 files changed, 418 insertions, 27 deletions
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 <ev.h>
#include <vec.h>
#include <list.h>
+#include <ylog.h>
/* 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; i<arg->n; 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 <assert.h>
+
+#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<arg.n-1; i++)\
+ assert(strcmp(arg.a[i], res[i]) == 0);\
+ assert(arg.a[i] == NULL);\
+ util_free_argv(&arg);\
+ assert(arg.n == 0);\
+ assert(arg.m == 0);\
+ assert(arg.a == NULL);\
+ } while(0)
+
+#define F(in) do {\
+ util_argv arg;\
+ assert(util_parse_argv(in, &arg) == -1);\
+ assert(arg.n == 0);\
+ assert(arg.m == 0);\
+ assert(arg.a == NULL);\
+ } while(0)
+
+int main(int argc, char **argv) {
+ T("");
+ T(" \t \n ");
+ T("cmd arg1 arg2 \targ3\narg\\ 4", "cmd", "arg1", "arg2", "arg3", "arg 4");
+ T("'' \"\"", "", "");
+ T("'x'\\'y\\\"\"z\"", "x'y\"z");
+ T(" cmd \"arg1\" 'arg2' ", "cmd", "arg1", "arg2");
+ T("\"a'r$g\"", "a'r$g");
+ T("\\\"a\\'r$g\\\"", "\"a'r$g\"");
+ T("'x\"y\\'", "x\"y\\");
+ T("x \\\ny", "x", "y");
+ T("' x y '\" z\n\"", " x y z\n");
+ T("\\ \\ ", " ");
+ T("\"\\x\"", "\\x");
+
+ F("'");
+ F(" '' \"");
+ F("\\");
+ F("' xyz");
+ F("\" xyz");
+ F("xyz\\");
+ F("xyz\"");
+ return 0;
+}