MEDIUM: vars: add support for a "set-var" global directive

While we do support process-wide variables ("proc.<name>"), there was
no way to preset them from the configuration. This was particularly
limiting their usefulness since configs involving them always had to
first check if the variable was set prior to performing an operation.

This patch adds a new "set-var" directive in the global section that
supports setting the proc.<name> variables from an expression, like
other set-var actions do. The syntax however follows what is already
being done for setenv, which consists in having one argument for the
variable name and another one for the expression.

Only "constant" expressions are allowed here, such as "int", "str"
etc, combined with arithmetic or string converters, and variable
lookups. A few extra sample fetch keywords like "date", "rand" and
"uuid" are also part of the constant expressions and may make sense
to allow to create a random key or differentiate processes.

The way it was done consists in parsing a dummy rule an executing the
expression in the CFG_PARSE context, then releasing the expression.
This is safe because the sample that variables store does not hold a
back pointer to expression that created them.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index aef9b45..0f9e31a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -923,6 +923,7 @@
    - ulimit-n
    - user
    - set-dumpable
+   - set-var
    - setenv
    - stats
    - ssl-default-bind-ciphers
@@ -1598,6 +1599,23 @@
   configuration. See also "server-state-base" and "show servers state",
   "load-server-state-from-file" and "server-state-file-name"
 
+set-var <var-name> <expr>
+  Sets the process-wide variable '<var-name>' to the result of the evaluation
+  of the sample expression <expr>. The variable '<var-name>' may only be a
+  process-wide variable (using the 'proc.' prefix). It works exactly like the
+  'set-var' action in TCP or HTTP rules except that the expression is evaluated
+  at configuration parsing time and that the variable is instantly set. The
+  sample fetch functions and converters permitted in the expression are only
+  those using internal data, typically 'int(value)' or 'str(value)'. It's is
+  possible to reference previously allocated variables as well. These variables
+  will then be readable (and modifiable) from the regular rule sets.
+
+  Example:
+      global
+          set-var proc.current_state str(primary)
+          set-var proc.prio int(100)
+          set-var proc.threshold int(200),sub(proc.prio)
+
 setenv <name> <value>
   Sets environment variable <name> to value <value>. If the variable exists, it
   is overwritten. The changes immediately take effect so that the next line in
diff --git a/src/vars.c b/src/vars.c
index 1077a79..5c8d45e 100644
--- a/src/vars.c
+++ b/src/vars.c
@@ -718,7 +718,8 @@
  *
  * It returns ACT_RET_PRS_ERR if fails and <err> is filled with an error
  * message. Otherwise, it returns ACT_RET_PRS_OK and the variable <expr>
- * is filled with the pointer to the expression to execute.
+ * is filled with the pointer to the expression to execute. The proxy is
+ * only used to retrieve the ->conf entries.
  */
 static enum act_parse_ret parse_store(const char **args, int *arg, struct proxy *px,
                                       struct act_rule *rule, char **err)
@@ -799,6 +800,71 @@
 	return ACT_RET_PRS_OK;
 }
 
+
+/* parses a global "set-var" directive. It will create a temporary rule and
+ * expression that are parsed, processed, and released on the fly so that we
+ * respect the real set-var syntax. These directives take the following format:
+ *    set-var <name> <expression>
+ * Note that parse_store() expects "set-var(name) <expression>" so we have to
+ * temporarily replace the keyword here.
+ */
+static int vars_parse_global_set_var(char **args, int section_type, struct proxy *curpx,
+                                     const struct proxy *defpx, const char *file, int line,
+                                     char **err)
+{
+	struct proxy px = {
+		.id = "CLI",
+		.conf.args.file = file,
+		.conf.args.line = line,
+	};
+	struct act_rule rule = {
+		.arg.vars.scope = SCOPE_PROC,
+		.from = ACT_F_CFG_PARSER,
+	};
+	enum act_parse_ret p_ret;
+	char *old_arg1;
+	char *tmp_arg1;
+	int arg = 2; // variable name
+	int ret = -1;
+
+	LIST_INIT(&px.conf.args.list);
+
+	if (!*args[1] || !*args[2]) {
+		memprintf(err, "'%s' requires a process-wide variable name ('proc.<name>') and a sample expression.", args[0]);
+		goto end;
+	}
+
+	tmp_arg1 = NULL;
+	if (!memprintf(&tmp_arg1, "set-var(%s)", args[1]))
+		goto end;
+
+	/* parse_store() will always return a message in <err> on error */
+	old_arg1 = args[1]; args[1] = tmp_arg1;
+	p_ret = parse_store((const char **)args, &arg, &px, &rule, err);
+	free(args[1]); args[1] = old_arg1;
+
+	if (p_ret != ACT_RET_PRS_OK)
+		goto end;
+
+	if (rule.arg.vars.scope != SCOPE_PROC) {
+		memprintf(err, "'%s': cannot set variable '%s', only scope 'proc' is permitted in the global section.", args[0], args[1]);
+		goto end;
+	}
+
+	if (smp_resolve_args(&px, err) != 0) {
+		release_sample_expr(rule.arg.vars.expr);
+		indent_msg(err, 2);
+		goto end;
+	}
+
+	action_store(&rule, &px, NULL, NULL, 0);
+	release_sample_expr(rule.arg.vars.expr);
+
+	ret = 0;
+ end:
+	return ret;
+}
+
 static int vars_max_size(char **args, int section_type, struct proxy *curpx,
                          const struct proxy *defpx, const char *file, int line,
                          char **err, unsigned int *limit)
@@ -937,6 +1003,7 @@
 INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_kws);
 
 static struct cfg_kw_list cfg_kws = {{ },{
+	{ CFG_GLOBAL, "set-var",              vars_parse_global_set_var },
 	{ CFG_GLOBAL, "tune.vars.global-max-size", vars_max_size_global },
 	{ CFG_GLOBAL, "tune.vars.proc-max-size",   vars_max_size_proc   },
 	{ CFG_GLOBAL, "tune.vars.sess-max-size",   vars_max_size_sess   },