MEDIUM: sample: handle comma-delimited converter list
We now support having a comma-delimited converter list, which can start
right after the fetch keyword. The immediate benefit is that it allows
to use converters in log-format expressions, for example :
set-header source-net %[src,ipmask(24)]
The parser is also slightly improved and should be more resilient against
configuration errors. Also, optional arguments in converters were mistakenly
not allowed till now, so this was fixed.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 5a0269e..eab6827 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -8699,8 +8699,10 @@
support sample fetch rules which allow a list of transformations to be applied
on top of the fetched sample, and the finaly result is automatically converted
to the type of the table. These transformations are enumerated as a series
-of specific keywords after the sample fetch method. These keywords can also
-support some arguments (eg: a netmask) which must be passed in parenthesis.
+of specific keywords after the sample fetch method. These keywords may equally
+be appended immediately after the fetch keyword's argument, delimited by a
+comma. These keywords can also support some arguments (eg: a netmask) which
+must be passed in parenthesis.
The currently available list of transformation keywords include :
diff --git a/src/sample.c b/src/sample.c
index fd18934..433a7ad 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -534,55 +534,59 @@
*/
struct sample_expr *sample_parse_expr(char **str, int *idx, char *err, int err_size, struct arg_list *al)
{
- const char *endw;
- const char *end;
+ const char *begw; /* beginning of word */
+ const char *endw; /* end of word */
+ const char *endt; /* end of term */
struct sample_expr *expr;
struct sample_fetch *fetch;
struct sample_conv *conv;
unsigned long prev_type;
- char *p;
+ char *fkw = NULL;
+ char *ckw = NULL;
+ /* prepare a generic message if any further snprintf() fails */
snprintf(err, err_size, "memory error.");
- if (!str[*idx]) {
+ begw = str[*idx];
+ for (endw = begw; *endw && *endw != '(' && *endw != ','; endw++);
+
+ if (endw == begw) {
snprintf(err, err_size, "missing fetch method.");
goto out_error;
}
- end = str[*idx] + strlen(str[*idx]);
- endw = strchr(str[*idx], '(');
+ /* keep a copy of the current fetch keyword for error reporting */
+ fkw = my_strndup(begw, endw - begw);
- if (!endw)
- endw = end;
- else if ((end-1)[0] != ')') {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "syntax error: missing ')' after keyword '%s'.", p);
- free(p);
- }
+ fetch = find_sample_fetch(begw, endw - begw);
+ if (!fetch) {
+ snprintf(err, err_size, "unknown fetch method '%s'.", fkw);
goto out_error;
}
- fetch = find_sample_fetch(str[*idx], endw - str[*idx]);
- if (!fetch) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "unknown fetch method '%s'.", p);
- free(p);
+ endt = endw;
+ if (*endt == '(') {
+ /* look for the end of this term */
+ while (*endt && *endt != ')')
+ endt++;
+ if (*endt != ')') {
+ snprintf(err, err_size, "syntax error: missing ')' after fetch keyword '%s'.", fkw);
+ goto out_error;
}
- goto out_error;
}
- if (fetch->out_type >= SMP_TYPES) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "returns type of fetch method '%s' is unknown.", p);
- free(p);
- }
+ /* At this point, we have :
+ * - begw : beginning of the keyword
+ * - endw : end of the keyword (points to next delimiter or '(')
+ * - endt : end of the term (=endw or last parenthesis if args are present)
+ */
+
+ if (fetch->out_type >= SMP_TYPES) {
+ snprintf(err, err_size, "returns type of fetch method '%s' is unknown.", fkw);
goto out_error;
}
-
prev_type = fetch->out_type;
+
expr = calloc(1, sizeof(struct sample_expr));
if (!expr)
goto out_error;
@@ -591,27 +595,19 @@
expr->fetch = fetch;
expr->arg_p = empty_arg_list;
- if (end != endw) {
+ if (endt != endw) {
char *err_msg = NULL;
int err_arg;
if (!fetch->arg_mask) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "fetch method '%s' does not support any args.", p);
- free(p);
- }
+ snprintf(err, err_size, "fetch method '%s' does not support any args.", fkw);
goto out_error;
}
al->kw = expr->fetch->kw;
al->conv = NULL;
- if (make_arg_list(endw + 1, end - endw - 2, fetch->arg_mask, &expr->arg_p, &err_msg, NULL, &err_arg, al) < 0) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "invalid arg %d in fetch method '%s' : %s.", err_arg+1, p, err_msg);
- free(p);
- }
+ if (make_arg_list(endw + 1, endt - endw - 1, fetch->arg_mask, &expr->arg_p, &err_msg, NULL, &err_arg, al) < 0) {
+ snprintf(err, err_size, "invalid arg %d in fetch method '%s' : %s.", err_arg+1, fkw, err_msg);
free(err_msg);
goto out_error;
}
@@ -620,62 +616,88 @@
expr->arg_p = empty_arg_list;
if (fetch->val_args && !fetch->val_args(expr->arg_p, &err_msg)) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "invalid args in fetch method '%s' : %s.", p, err_msg);
- free(p);
- }
+ snprintf(err, err_size, "invalid args in fetch method '%s' : %s.", fkw, err_msg);
free(err_msg);
goto out_error;
}
}
else if (ARGM(fetch->arg_mask)) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "missing args for fetch method '%s'.", p);
- free(p);
- }
+ snprintf(err, err_size, "missing args for fetch method '%s'.", fkw);
goto out_error;
}
- for (*idx += 1; *(str[*idx]); (*idx)++) {
+ /* Now process the converters if any. We have two supported syntaxes
+ * for the converters, which can be combined :
+ * - comma-delimited list of converters just after the keyword and args ;
+ * - one converter per keyword
+ * The combination allows to have each keyword being a comma-delimited
+ * series of converters.
+ *
+ * We want to process the former first, then the latter. For this we start
+ * from the beginning of the supposed place in the exiting conv chain, which
+ * starts at the last comma (endt).
+ */
+
+ while (1) {
struct sample_conv_expr *conv_expr;
- end = str[*idx] + strlen(str[*idx]);
- endw = strchr(str[*idx], '(');
+ if (*endt == ')') /* skip last closing parenthesis */
+ endt++;
- if (!endw)
- endw = end;
- else if ((end-1)[0] != ')') {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "syntax error, missing ')' after keyword '%s'.", p);
- free(p);
- }
+ if (*endt && *endt != ',') {
+ if (ckw)
+ snprintf(err, err_size, "missing comma after conv keyword '%s'.", ckw);
+ else
+ snprintf(err, err_size, "missing comma after fetch keyword '%s'.", fkw);
goto out_error;
}
+ while (*endt == ',') /* then trailing commas */
+ endt++;
+
+ begw = endt; /* start of conv keyword */
+
+ if (!*begw) {
+ /* none ? skip to next string */
+ (*idx)++;
+ begw = str[*idx];
+ if (!begw || !*begw)
+ break;
+ }
+
+ for (endw = begw; *endw && *endw != '(' && *endw != ','; endw++);
+
- conv = find_sample_conv(str[*idx], endw - str[*idx]);
- if (!conv)
- break;
+ free(ckw);
+ ckw = my_strndup(begw, endw - begw);
+
+ conv = find_sample_conv(begw, endw - begw);
+ if (!conv) {
+ /* we found an isolated keyword that we don't know, it's not ours */
+ if (begw == str[*idx])
+ break;
+ snprintf(err, err_size, "unknown conv method '%s'.", ckw);
+ goto out_error;
+ }
- if (conv->in_type >= SMP_TYPES ||
- conv->out_type >= SMP_TYPES) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "returns type of conv method '%s' is unknown.", p);
- free(p);
+ endt = endw;
+ if (*endt == '(') {
+ /* look for the end of this term */
+ while (*endt && *endt != ')')
+ endt++;
+ if (*endt != ')') {
+ snprintf(err, err_size, "syntax error: missing ')' after conv keyword '%s'.", ckw);
+ goto out_error;
}
+ }
+
+ if (conv->in_type >= SMP_TYPES || conv->out_type >= SMP_TYPES) {
+ snprintf(err, err_size, "returns type of conv method '%s' is unknown.", ckw);
goto out_error;
}
/* If impossible type conversion */
if (!sample_casts[prev_type][conv->in_type]) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "conv method '%s' cannot be applied.", p);
- free(p);
- }
+ snprintf(err, err_size, "conv method '%s' cannot be applied.", ckw);
goto out_error;
}
@@ -687,28 +709,19 @@
LIST_ADDQ(&(expr->conv_exprs), &(conv_expr->list));
conv_expr->conv = conv;
- if (end != endw) {
+ if (endt != endw) {
char *err_msg = NULL;
int err_arg;
if (!conv->arg_mask) {
- p = my_strndup(str[*idx], endw - str[*idx]);
-
- if (p) {
- snprintf(err, err_size, "conv method '%s' does not support any args.", p);
- free(p);
- }
+ snprintf(err, err_size, "conv method '%s' does not support any args.", ckw);
goto out_error;
}
al->kw = expr->fetch->kw;
al->conv = conv_expr->conv->kw;
- if (make_arg_list(endw + 1, end - endw - 2, conv->arg_mask, &conv_expr->arg_p, &err_msg, NULL, &err_arg, al) < 0) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "invalid arg %d in conv method '%s' : %s.", err_arg+1, p, err_msg);
- free(p);
- }
+ if (make_arg_list(endw + 1, endt - endw - 1, conv->arg_mask, &conv_expr->arg_p, &err_msg, NULL, &err_arg, al) < 0) {
+ snprintf(err, err_size, "invalid arg %d in conv method '%s' : %s.", err_arg+1, ckw, err_msg);
free(err_msg);
goto out_error;
}
@@ -717,31 +730,26 @@
conv_expr->arg_p = empty_arg_list;
if (conv->val_args && !conv->val_args(conv_expr->arg_p, &err_msg)) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "invalid args in conv method '%s' : %s.", p, err_msg);
- free(p);
- }
+ snprintf(err, err_size, "invalid args in conv method '%s' : %s.", ckw, err_msg);
free(err_msg);
goto out_error;
}
}
- else if (conv->arg_mask) {
- p = my_strndup(str[*idx], endw - str[*idx]);
- if (p) {
- snprintf(err, err_size, "missing args for conv method '%s'.", p);
- free(p);
- }
+ else if (ARGM(conv->arg_mask)) {
+ snprintf(err, err_size, "missing args for conv method '%s'.", ckw);
goto out_error;
}
-
}
+ out:
+ free(fkw);
+ free(ckw);
return expr;
out_error:
/* TODO: prune_sample_expr(expr); */
- return NULL;
+ expr = NULL;
+ goto out;
}
/*