Commit 723690f9 authored by Geoff Simmons's avatar Geoff Simmons

Bugfix and extend .setenv().

Support the task-scoped invocation in vcl_deliver.

Use a VSTAILQ for the setenv list. VSLIST pushes only to the head,
so setenv() calls were in reverse order compared to the VCL calls.
We want later invocations of .setenv() overwrite the value of previous
invocations for the same variable (if overwrite is true). So use
a tail queue and push to the tail.
parent 7b2a7420
......@@ -2,6 +2,8 @@
varnishtest "environment variables in the invoked process"
# The tests rely on $HOME always being set.
server s1 {
rxreq
txresp -body {foo bar baz quux}
......@@ -34,6 +36,8 @@ varnish v1 -vcl+backend {
env.setenv("FOO", "bar");
env.setenv("BAZ", "quux");
env.setenv("EMPTY", "");
env.setenv("HOME", "sweet home", overwrite=false);
env.setenv("FOO", "foo");
}
sub vcl_deliver {
......@@ -45,11 +49,31 @@ client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.body ~ {(?m)^\s*FOO\s*=\s*bar\s*$}
expect resp.body ~ {(?m)^\s*FOO\s*=\s*foo\s*$}
expect resp.body ~ {(?m)^\s*BAZ\s*=\s*quux\s*$}
expect resp.body ~ {(?m)^\s*EMPTY\s*=\s*$}
expect resp.body !~ {(?m)^\s*HOME\s*=\s*sweet home\s*$}
} -run
varnish v1 -vcl+backend {
import ${vmod_pipe};
sub vcl_init {
new env = pipe.vdp("${env}");
}
sub vcl_deliver {
env.setenv("FOO", "bar");
env.setenv("BAZ", "quux");
env.setenv("EMPTY", "");
env.setenv("HOME", "sweet home", overwrite=false);
env.setenv("FOO", "foo");
set resp.filters = "env";
}
}
client c1 -run
varnish v1 -errvcl {vdp pipe failure: env.setenv(): var is empty} {
import ${vmod_pipe};
backend b None;
......@@ -70,10 +94,97 @@ varnish v1 -errvcl {vdp pipe failure: env.setenv(): var may not contain '=': FOO
}
}
varnish v1 -vcl+backend {
import ${vmod_pipe};
sub vcl_init {
new env = pipe.vdp("${env}");
}
sub vcl_recv {
if (req.http.Test == "Illegal-Sub") {
env.setenv("FOO", "bar");
}
}
sub vcl_deliver {
if (req.http.Test == "Empty-Var") {
env.setenv("", "foo");
}
elsif (req.http.Test == "Null-Var") {
env.setenv(req.http.Unset, "foo");
}
elsif (req.http.Test == "Equal-Var") {
env.setenv("FOO=", "");
}
elsif (req.http.Test == "Null-Value") {
env.setenv("FOO", req.http.Unset);
}
}
}
client c1 {
txreq -hdr "Test: Empty-Var"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
} -run
client c1 {
txreq -hdr "Test: Null-Var"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
} -run
client c1 {
txreq -hdr "Test: Equal-Var"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
} -run
client c1 {
txreq -hdr "Test: Null-Value"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
} -run
client c1 {
txreq -hdr "Test: Illegal-Sub"
rxresp
expect resp.status == 503
expect resp.reason == "VCL failed"
} -run
logexpect l1 -v v1 -g vxid -d 1 -q VCL_Error {
expect 0 * Begin {^req \d+ rxreq$}
expect * = VCL_Error {^vdp pipe failure: env\.setenv\(\): var is empty$}
expect * = End
expect 0 * Begin {^req \d+ rxreq$}
expect * = VCL_Error {^vdp pipe failure: env\.setenv\(\): var is empty$}
expect * = End
expect 0 * Begin {^req \d+ rxreq$}
expect * = VCL_Error {^vdp pipe failure: env\.setenv\(\): var may not contain '=': FOO=$}
expect * = End
expect 0 * Begin {^req \d+ rxreq$}
expect * = VCL_Error {^vdp pipe failure: env\.setenv\(\): value is NULL$}
expect * = End
expect 0 * Begin {^req \d+ rxreq$}
expect * = VCL_Error {^vdp pipe failure: env\.setenv\(\): may only be called in vcl_init or vcl_deliver$}
expect * = End
} -run
varnish v1 -vcl { backend b None; }
# Tests the object finalization with cleanup of env lists.
varnish v1 -cli "vcl.discard vcl1"
varnish v1 -cli "vcl.discard vcl2"
varnish v1 -cli "vcl.discard vcl3"
delay 1
varnish v1 -cli "vcl.list"
......@@ -65,13 +65,13 @@ extern char **environ;
struct setenv_entry {
unsigned magic;
#define PIPE_SETENV_MAGIC 0x1167db2e
VSLIST_ENTRY(setenv_entry) list;
VSTAILQ_ENTRY(setenv_entry) list;
char *var;
char *value;
VCL_BOOL overwrite;
};
VSLIST_HEAD(setenv_head, setenv_entry);
VSTAILQ_HEAD(setenv_head, setenv_entry);
struct VPFX(pipe_vdp) {
unsigned magic;
......@@ -132,10 +132,11 @@ static const char *stream_name[] = {
};
struct task_cfg {
unsigned magic;
unsigned magic;
#define PIPE_TASK_MAGIC 0x43294c37
char **argv;
int argc;
char **argv;
struct setenv_head *setenv_head;
int argc;
};
/* VDP */
......@@ -231,6 +232,8 @@ vdp_init(struct req *req, void **priv)
CAST_OBJ_NOTNULL(task, task_priv->priv, PIPE_TASK_MAGIC);
if (task->argv != NULL)
argv = task->argv;
if (task->setenv_head != NULL)
setenv_head = task->setenv_head;
}
if (mk_pipe(in, obj->name, req->vsl) != 0)
......@@ -265,7 +268,7 @@ vdp_init(struct req *req, void **priv)
exit(EXIT_FAILURE);
if (setenv_head != NULL)
VSLIST_FOREACH(setenv_entry, setenv_head, list) {
VSTAILQ_FOREACH(setenv_entry, setenv_head, list) {
CHECK_OBJ_NOTNULL(setenv_entry,
PIPE_SETENV_MAGIC);
errno = 0;
......@@ -699,14 +702,14 @@ vmod_vdp__fini(struct VPFX(pipe_vdp) **vdpp)
free(vdp_obj->argv);
}
if (vdp_obj->setenv_head != NULL)
while (!VSLIST_EMPTY(vdp_obj->setenv_head)) {
setenv_entry = VSLIST_FIRST(vdp_obj->setenv_head);
while (!VSTAILQ_EMPTY(vdp_obj->setenv_head)) {
setenv_entry = VSTAILQ_FIRST(vdp_obj->setenv_head);
CHECK_OBJ_NOTNULL(setenv_entry, PIPE_SETENV_MAGIC);
if (setenv_entry->var != NULL)
free(setenv_entry->var);
if (setenv_entry->value != NULL)
free(setenv_entry->value);
VSLIST_REMOVE_HEAD(vdp_obj->setenv_head, list);
VSTAILQ_REMOVE_HEAD(vdp_obj->setenv_head, list);
FREE_OBJ(setenv_entry);
}
FREE_OBJ(vdp_obj);
......@@ -722,10 +725,35 @@ task_free(void *p)
free(task->argv);
}
static struct task_cfg *
get_task(VRT_CTX, struct VPFX(pipe_vdp) *obj, const char *method)
{
struct vmod_priv *priv;
struct task_cfg *task;
priv = VRT_priv_task(ctx, obj);
AN(priv);
if (priv->priv == NULL) {
if ((priv->priv = WS_Alloc(ctx->ws, sizeof(*task))) == NULL) {
VDPFAIL(ctx, "%s.%s(): insufficient workspace for "
"task config", obj->name, method);
return (NULL);
}
priv->len = sizeof(*task);
priv->free = task_free;
task = (struct task_cfg *)priv->priv;
INIT_OBJ(task, PIPE_TASK_MAGIC);
}
else {
WS_Assert_Allocated(ctx->ws, priv->priv, sizeof(*task));
CAST_OBJ_NOTNULL(task, priv->priv, PIPE_TASK_MAGIC);
}
return (task);
}
VCL_VOID
vmod_vdp_arg(VRT_CTX, struct VPFX(pipe_vdp) *obj, VCL_STRING arg)
{
struct vmod_priv *priv;
struct task_cfg *task;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
......@@ -759,15 +787,9 @@ vmod_vdp_arg(VRT_CTX, struct VPFX(pipe_vdp) *obj, VCL_STRING arg)
}
AN(ctx->method & VCL_MET_DELIVER);
priv = VRT_priv_task(ctx, obj);
AN(priv);
if (priv->priv == NULL) {
if ((priv->priv = WS_Alloc(ctx->ws, sizeof(*task))) == NULL) {
VDPFAIL(ctx, "%s.arg(): insufficient workspace for "
"task config", obj->name);
return;
}
task = (struct task_cfg *)priv->priv;
if ((task = get_task(ctx, obj, "arg")) == NULL)
return;
if (task->argv == NULL) {
errno = 0;
task->argv = malloc(3 * sizeof(*task->argv));
if (task->argv == NULL) {
......@@ -783,16 +805,9 @@ vmod_vdp_arg(VRT_CTX, struct VPFX(pipe_vdp) *obj, VCL_STRING arg)
task->argv[0] = obj->path;
task->argv[2] = NULL;
task->argc = 2;
task->magic = PIPE_TASK_MAGIC;
priv->len = sizeof(*task);
priv->free = task_free;
return;
}
WS_Assert_Allocated(ctx->ws, priv->priv, sizeof(*task));
CAST_OBJ_NOTNULL(task, priv->priv, PIPE_TASK_MAGIC);
errno = 0;
task->argv = realloc(task->argv,
(task->argc + 2) * sizeof(*task->argv));
......@@ -817,10 +832,7 @@ vmod_vdp_setenv(VRT_CTX, struct VPFX(pipe_vdp) *obj, VCL_STRING var,
VCL_STRING value, VCL_BOOL overwrite)
{
struct setenv_entry *entry;
#if 0
struct vmod_priv *priv;
struct task_cfg *task;
#endif
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(ctx->ws, WS_MAGIC);
......@@ -846,32 +858,67 @@ vmod_vdp_setenv(VRT_CTX, struct VPFX(pipe_vdp) *obj, VCL_STRING var,
return;
}
/* XXX only vcl_init */
if (obj->setenv_head == NULL) {
errno = 0;
obj->setenv_head = malloc(sizeof(*obj->setenv_head));
if (ctx->method & VCL_MET_INIT) {
if (obj->setenv_head == NULL) {
errno = 0;
obj->setenv_head = malloc(sizeof(*obj->setenv_head));
if (obj->setenv_head == NULL) {
VDPFAIL(ctx, "%s.setenv(): cannot allocate "
"list head: %s", obj->name,
vstrerror(errno));
return;
}
VSTAILQ_INIT(obj->setenv_head);
}
errno = 0;
ALLOC_OBJ(entry, PIPE_SETENV_MAGIC);
if (entry == NULL) {
VDPFAIL(ctx,
"%s.setenv(): cannot allocate list head: %s",
"%s.setenv(): cannot allocate list entry: %s",
obj->name, vstrerror(errno));
return;
}
VSLIST_INIT(obj->setenv_head);
entry->var = strdup(var);
entry->value = strdup(value);
entry->overwrite = overwrite;
VSTAILQ_INSERT_TAIL(obj->setenv_head, entry, list);
return;
}
errno = 0;
ALLOC_OBJ(entry, PIPE_SETENV_MAGIC);
if (entry == NULL) {
VDPFAIL(ctx, "%s.setenv(): cannot allocate list entry: %s",
obj->name, vstrerror(errno));
AN(ctx->method & VCL_MET_DELIVER);
if ((task = get_task(ctx, obj, "setenv")) == NULL)
return;
if (task->setenv_head == NULL) {
task->setenv_head = WS_Alloc(ctx->ws,
sizeof(*task->setenv_head));
if (task->setenv_head == NULL) {
VDPFAIL(ctx, "%s.setenv(): insufficient workspace for "
"list head", obj->name);
return;
}
VSTAILQ_INIT(task->setenv_head);
}
entry->var = strdup(var);
entry->value = strdup(value);
if ((entry = WS_Alloc(ctx->ws, sizeof(*entry))) == NULL) {
VDPFAIL(ctx, "%s.setenv(): insufficient workspace for list "
"entry", obj->name);
return;
}
INIT_OBJ(entry, PIPE_SETENV_MAGIC);
if ((entry->var = WS_Copy(ctx->ws, var, -1)) == NULL) {
VDPFAIL(ctx, "%s.setenv(): insufficient workspace for var",
obj->name);
return;
}
if ((entry->value = WS_Copy(ctx->ws, value, -1)) == NULL) {
VDPFAIL(ctx, "%s.setenv(): insufficient workspace for value",
obj->name);
return;
}
entry->overwrite = overwrite;
VSLIST_INSERT_HEAD(obj->setenv_head, entry, list);
return;
VSTAILQ_INSERT_TAIL(task->setenv_head, entry, list);
}
VCL_STRING
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment