]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-var-expand: Add ability to export and import programs
authorAki Tuomi <aki.tuomi@open-xchange.com>
Sat, 21 Dec 2024 21:35:47 +0000 (23:35 +0200)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 12 Feb 2025 10:34:16 +0000 (12:34 +0200)
This adds functions for exporting and importing var-expand programs
into strings.

src/lib-var-expand/expansion-program.c
src/lib-var-expand/test-var-expand.c
src/lib-var-expand/var-expand.h

index 3f1c36e5d12a2df1a17182b64d20c67003848127..0b761e541a6f5c14a26e42ae356f4c911d6e9289 100644 (file)
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "array.h"
 #include "str.h"
+#include "strescape.h"
 #include "hex-binary.h"
 #include "var-expand-private.h"
 #include "var-expand-parser-private.h"
@@ -179,3 +180,455 @@ void var_expand_program_free(struct var_expand_program **_program)
 
        pool_unref(&program->pool);
 }
+
+/* Export code */
+
+/* Encodes numbers in 7-bit bytes, using 8th to indicate
+   that the number continues. Uses little endian encoding
+   to allow this. */
+static void export_number(string_t *dest, intmax_t number)
+{
+       unsigned char b;
+
+       /* fast path - store any non-negative number that's smaller than
+          127 as number + 1, including 0. */
+       if (number >= 0 && number < 0x7f) {
+               b = number + 1;
+               str_append_c(dest, b);
+               return;
+       }
+
+       /* Store sign with 0x80, so we can differentiate
+          from fast path. */
+       if (number < 0) {
+               str_append_c(dest, 0x80 | '-');
+               number = -number;
+       } else
+               str_append_c(dest, 0x80 | '+');
+
+       /* Store the number in 7 byte chunks
+          so we can use the 8th bit for indicating
+          whether the number continues. */
+       while (number > 0) {
+               if (number > 0x7f)
+                       b = 0x80;
+               else
+                       b = 0x0;
+               b |= number & 0x7f;
+               number >>= 7;
+               str_append_c(dest, b);
+       }
+}
+
+static void var_expand_program_export_one(const struct var_expand_program *program,
+                                         string_t *dest)
+{
+       const struct var_expand_statement *stmt = program->first;
+       while (stmt != NULL) {
+               str_append(dest, stmt->function);
+               str_append_c(dest, '\1');
+               const struct var_expand_parameter *param = stmt->params;
+               param = stmt->params;
+               while (param != NULL) {
+                       if (param->key != NULL)
+                               str_append(dest, param->key);
+                       str_append_c(dest, '\1');
+                       switch (param->value_type) {
+                       case VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING:
+                               str_append_c(dest, 's');
+                               str_append_tabescaped(dest, param->value.str);
+                               str_append_c(dest, '\r');
+                               break;
+                       case VAR_EXPAND_PARAMETER_VALUE_TYPE_INT:
+                               str_append_c(dest, 'i');
+                               export_number(dest, param->value.num);
+                               break;
+                       case VAR_EXPAND_PARAMETER_VALUE_TYPE_VARIABLE:
+                               str_append_c(dest, 'v');
+                               str_append_tabescaped(dest, param->value.str);
+                               str_append_c(dest, '\r');
+                               break;
+                       default:
+                               i_unreached();
+                       }
+                       param = param->next;
+                       if (param != NULL)
+                               str_append_c(dest, '\1');
+               }
+               str_append_c(dest, '\t');
+               stmt = stmt->next;
+               if (stmt != NULL)
+                       str_append_c(dest, '\1');
+               else
+                       str_append_c(dest, '\t');
+       }
+       const char *const *vars = program->variables;
+
+       for (; vars != NULL && *vars != NULL; vars++) {
+               /* ensure variable has no \1 in name */
+               i_assert(strchr(*vars, '\1') == NULL);
+               str_append(dest, *vars);
+               str_append_c(dest, '\1');
+       }
+       str_append_c(dest, '\t');
+}
+
+void var_expand_program_export_append(string_t *dest,
+                                     const struct var_expand_program *program)
+{
+       i_assert(program != NULL);
+       i_assert(dest != NULL);
+
+       while (program != NULL) {
+               if (program->only_literal) {
+                       i_assert(program->first->params->value_type ==
+                                VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING);
+                       str_append_c(dest, '\1');
+                       str_append_tabescaped(dest, program->first->params->value.str);
+                       str_append_c(dest, '\r');
+               } else {
+                       str_append_c(dest, '\2');
+                       var_expand_program_export_one(program, dest);
+               }
+
+               program = program->next;
+       }
+}
+
+const char *var_expand_program_export(const struct var_expand_program *program)
+{
+       string_t *dest = t_str_new(64);
+       var_expand_program_export_append(dest, program);
+       return str_c(dest);
+}
+
+/* Import code */
+
+static int extract_name(char *data, size_t size,
+                       const char **value_r, const char **error_r)
+{
+       char *ptr = memchr(data, '\1', size);
+       if (ptr == NULL) {
+               *error_r = "Missing end of name";
+               return -1;
+       }
+       size_t len = ptr - data;
+       if (len == 0) {
+               *value_r = NULL;
+               return 1;
+       }
+       *value_r = data;
+       *ptr = '\0';
+       return len + 1;
+
+}
+
+static int extract_value(char *data, size_t size,
+                        const char **value_r, const char **error_r)
+{
+       char *ptr = memchr(data, '\r', size);
+       if (ptr == NULL) {
+               *error_r = "Missing end of string";
+               return -1;
+       }
+       size_t len = ptr - data;
+       *ptr = '\0';
+       *value_r = str_tabunescape(data);
+       /* make sure we end up in right place. */
+       return len + 1;
+}
+
+static int extract_number(const char *data, size_t size, intmax_t *value_r,
+                         const char **error_r)
+{
+       const unsigned char *ptr = (const unsigned char*)data;
+       bool negative;
+       size_t len = 1;
+
+       if ((*ptr & 0x80) == 0) {
+               /* fast path for small positive number */
+               intmax_t number = *ptr;
+               *value_r = number - 1;
+               return 1;
+       }
+
+       const char sign = *ptr - 0x80;
+       if (sign == '+') {
+               negative = FALSE;
+       } else if (sign == '-') {
+               negative = TRUE;
+       } else {
+               *error_r = "Unknown number";
+               return -1;
+       }
+       ptr++;
+
+       intmax_t value = 0;
+       intmax_t shift = 0;
+
+       /* a number can be at most 9 bytes */
+       for (size_t i = 0; i < I_MIN(size, 9); i++) {
+               len++;
+               value |= ((*(ptr) & 0x7fLL) << shift);
+               /* if high byte is set, the number continues */
+               if ((*ptr & 0x80) == 0)
+                       break;
+               shift += 7;
+               ptr++;
+       }
+
+       if ((*ptr & 0x80) != 0) {
+               *error_r = "Unfinished number";
+               return -1;
+       }
+
+       if (negative)
+               value = -value;
+
+       *value_r = value;
+
+       return len;
+}
+
+#define ADVANCE_INPUT(count) \
+       if (unlikely(size < (size_t)count)) { \
+               *error_r = "Premature end of data"; \
+               return -1; \
+       }\
+       data = data + (count); \
+       size = size - (size_t)(count);
+
+static int var_expand_program_import_stmt(char *data, size_t size,
+                                         struct var_expand_program *program,
+                                         const char **error_r)
+{
+       const char *name;
+       const char *value;
+       size_t orig_size = size;
+
+       /* normal program, starts with filter name */
+       int ret = extract_name(data, size, &name, error_r);
+       if (ret < 0)
+               return -1;
+       if (name == NULL) {
+               *error_r = "missing function name";
+               return -1;
+       }
+       ADVANCE_INPUT(ret);
+
+       struct var_expand_statement *stmt =
+               p_new(program->pool, struct var_expand_statement, 1);
+
+       if (program->first == NULL)
+               program->first = stmt;
+       else {
+                struct var_expand_statement *ptr = program->first;
+                while (ptr->next != NULL) ptr = ptr->next;
+                ptr->next = stmt;
+       }
+
+       stmt->function = name;
+       struct var_expand_parameter *prev = NULL;
+       int idx = -1;
+
+       while (size > 0 && *data != '\t') {
+               struct var_expand_parameter *param =
+                       p_new(program->pool, struct var_expand_parameter, 1);
+               /* check if it's named parameter */
+               if (*data == '\1') {
+                       param->idx = ++idx;
+                       ADVANCE_INPUT(1);
+               } else {
+                       ret = extract_name(data, size,
+                                          &name, error_r);
+                       if (ret < 0)
+                               return -1;
+                       ADVANCE_INPUT(ret);
+                       param->key = name;
+               }
+
+               /* check the parameter type */
+               switch (*data) {
+               case 's':
+                       param->value_type =
+                               VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING;
+                       break;
+               case 'i':
+                       param->value_type =
+                               VAR_EXPAND_PARAMETER_VALUE_TYPE_INT;
+                       break;
+               case 'v':
+                       param->value_type =
+                               VAR_EXPAND_PARAMETER_VALUE_TYPE_VARIABLE;
+                       break;
+               default:
+                       *error_r = "Unsupported parameter type";
+                       return -1;
+               }
+               ADVANCE_INPUT(1);
+
+               if (param->value_type == VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING ||
+                   param->value_type == VAR_EXPAND_PARAMETER_VALUE_TYPE_VARIABLE) {
+                       ret = extract_value(data, size,
+                                           &value, error_r);
+                       if (ret < 0)
+                               return -1;
+                       ADVANCE_INPUT(ret);
+                       param->value.str = value;
+               } else if (param->value_type == VAR_EXPAND_PARAMETER_VALUE_TYPE_INT) {
+                       ret = extract_number(data, size, &param->value.num,
+                                            error_r);
+                       if (ret < 0)
+                               return -1;
+
+                       ADVANCE_INPUT(ret);
+               } else {
+                       *error_r = "Unsupported value type";
+                       return -1;
+               }
+
+               if (prev == NULL)
+                       stmt->params = param;
+               else
+                       prev->next = param;
+               prev = param;
+
+               if (*data == '\t') {
+                       break;
+               } else if (*data == '\1') {
+                       ADVANCE_INPUT(1);
+               } else {
+                       *error_r = "Missing parameter end";
+                       return -1;
+               }
+       }
+
+       if (*data != '\t')
+               *error_r = "Missing parameter statement end";
+
+       ADVANCE_INPUT(1);
+
+       return orig_size - size;
+}
+
+static int var_expand_program_import_one(char **_data, size_t *_size,
+                                        struct var_expand_program *program,
+                                        const char **error_r)
+{
+       char *data = *_data;
+       size_t size = *_size;
+       const char *value;
+       int ret;
+
+       /* Only literal */
+       if (*data == '\1') {
+               ADVANCE_INPUT(1);
+               ret = extract_value(data, size, &value, error_r);
+               if (ret < 0)
+                       return -1;
+               ADVANCE_INPUT(ret);
+
+               /* just literal data */
+               struct var_expand_statement *stmt =
+                       p_new(program->pool, struct var_expand_statement, 1);
+               struct var_expand_parameter *param =
+                       p_new(program->pool, struct var_expand_parameter, 1);
+               param->idx = 0;
+               param->value_type = VAR_EXPAND_PARAMETER_VALUE_TYPE_STRING;
+               param->value.str = value;
+               stmt->params = param;
+               stmt->function = "literal";
+               program->first = stmt;
+               program->only_literal = TRUE;
+       /* A full program */
+       } else if (*data == '\2') {
+               ADVANCE_INPUT(1);
+               while (*data != '\t' && size > 0) {
+                       int ret = var_expand_program_import_stmt(data, size, program, error_r);
+                       if (ret < 0)
+                               return -1;
+                       ADVANCE_INPUT(ret);
+                       if (*data == '\t') {
+                               ADVANCE_INPUT(1);
+                               break;
+                       } else if (*data != '\1') {
+                               *error_r = "Missing statement end";
+                               return -1;
+                       }
+                       ADVANCE_INPUT(1);
+               }
+               /* And finally there should be variables */
+               if (*data != '\t') {
+                       const char *ptr = memchr(data, '\t', size);
+                       if (ptr == NULL) {
+                               *error_r = "Missing variables end";
+                               return -1;
+                       }
+                       size_t len = ptr - data;
+                       program->variables = (const char *const *)
+                               p_strsplit(program->pool, data, "\1");
+                       ADVANCE_INPUT(len + 1);
+               } else {
+                       ADVANCE_INPUT(1);
+               }
+       } else {
+               *error_r = "Unknown input";
+               return -1;
+       }
+       *_data = data;
+       *_size = size;
+
+       return 0;
+}
+
+int var_expand_program_import_sized(const char *data, size_t size,
+                                   struct var_expand_program **program_r,
+                                   const char **error_r)
+{
+       i_assert(data != NULL);
+
+       /* The absolute minimum program is \2 \t or \1 \r. */
+       if (size < 2) {
+               *error_r = "Too short";
+               return -1;
+       }
+
+       pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"var expand program", size);
+       struct var_expand_program *prev = NULL;
+       struct var_expand_program *first = NULL;
+       int ret;
+       char *copy_data = p_strndup(pool, data, size);
+
+       while (size > 0) {
+               struct var_expand_program *program =
+                       p_new(pool, struct var_expand_program, 1);
+               program->pool = pool;
+               T_BEGIN {
+                       ret = var_expand_program_import_one(&copy_data, &size,
+                                                           program, error_r);
+               } T_END;
+               if (ret < 0)
+                       break;
+               if (first == NULL)
+                       first = program;
+               if (prev != NULL)
+                       prev->next = program;
+               prev = program;
+       }
+
+       if (ret < 0)
+               pool_unref(&pool);
+       else
+               *program_r = first;
+
+       return ret;
+}
+
+int var_expand_program_import(const char *data,
+                             struct var_expand_program **program_r,
+                             const char **error_r)
+{
+       i_assert(data != NULL);
+       return var_expand_program_import_sized(data, strlen(data), program_r,
+                                              error_r);
+}
index 4c8582b68d07a4b69c56d1ba2f7cddd7c65981c2..e7238c23a4eb31f0f9aa280273d339cc7c793699 100644 (file)
@@ -15,6 +15,7 @@
 #endif
 
 #include <time.h>
+#include <unistd.h>
 
 struct var_expand_test {
        const char *in;
@@ -22,6 +23,9 @@ struct var_expand_test {
        int ret;
 };
 
+/* Run with -b to set TRUE */
+static bool do_bench = FALSE;
+
 static void run_var_expand_tests(const struct var_expand_params *params,
                                 const struct var_expand_test tests[],
                                 size_t test_count)
@@ -959,7 +963,219 @@ static void test_var_expand_generate(void)
        test_end();
 }
 
-int main(void)
+static void test_var_expand_export_import(void)
+{
+       test_begin("var_expand(export/import)");
+
+       const struct var_expand_params params = {
+               .table = (const struct var_expand_table[]) {
+                       { .key = "variable", .value = "1234567890" },
+                       { .key = "this", .value = "isht" },
+                       { .key = "a", .value = "b" },
+                       { .key = "test", .value = "tset" },
+                       VAR_EXPAND_TABLE_END
+               },
+       };
+
+       const struct test_case {
+               const char *prog_in;
+               const char *export;
+       } test_cases[] = {
+               { "", "\x02\t" },
+               { "literal", "\x01literal\r" },
+               { "\x01\x02\r\t", "\x01\x01""1\x02\x01r\x01t\r" },
+               { "%{variable}", "\x02variable\x01\t\tvariable\x01\t" },
+               { "%{lookup('variable')}", "\x02lookup\x01\x01svariable\r\t\t\t" },
+               {
+                       "%{this} is %{a} simple %{test}",
+                       "\x02this\x01\t\ta\x01test\x01this"
+                       "\x01\t\x01 is \r\x02"
+                       "a\x01\t\t\t\x01 simple \r\x02"
+                       "test\x01\t\t\t"
+               },
+               {
+                       "%{variable | substr(0,1) % 32}",
+                       "\x02variable\x01\t\x01substr\x01\x01i\x01\x01\x01i\x02"
+                       "\t\x01""calculate\x01\x01i\x05\x01\x01i!\t\tvariable"
+                       "\x01\t"
+               },
+               {
+                       "%{variable | substr(0,1) % 32} / %{variable | substr(1,1) % 32}",
+                       "\x02variable\x01\t\x01substr\x01\x01i\x01\x01\x01i\x02"
+                       "\t\x01""calculate\x01\x01i\x05\x01\x01i!\t\tvariable"
+                       "\x01\t\x01 / \r\x02variable\x01\t\x01substr\x01\x01i"
+                       "\x02\x01\x01i\x02\t\x01""calculate\x01\x01i\x05\x01"
+                       "\x01i!\t\t\t"
+               },
+#if UINT32_MAX < INTMAX_MAX
+               {
+                       "%{variable + 4294967296}",
+                       "\x02variable\x01\t\x01""calculate\x01\x01i\x01\x01\x01"
+                       "i\xab\x80\x80\x80\x80\x10\t\tvariable\x01\t"
+               },
+#endif
+               {
+                       "%{variable + -100}",
+                       "\x02variable\x01\t\x01""calculate\x01\x01i\x01\x01\x01"
+                       "i\xad""d\t\tvariable\x01\t"
+               },
+               {
+                       "%{variable + 126}",
+                       "\x02variable\x01\t\x01""calculate\x01\x01i\x01\x01\x01i"
+                       "\x7f\t\tvariable\x01\t"
+               },
+               {
+                       "%{variable + 127}",
+                       "\x02variable\x01\t\x01""calculate\x01\x01i\x01\x01\x01i"
+                       "\xab\x7f\t\tvariable\x01\t"
+               },
+       };
+
+       string_t *dest = t_str_new(64);
+
+       string_t *result_a = t_str_new(64);
+       string_t *result_b = t_str_new(64);
+
+       for(size_t i = 0; i < N_ELEMENTS(test_cases); i++) {
+               const char *error;
+               const struct test_case *t = &test_cases[i];
+               struct var_expand_program *prog;
+               str_truncate(dest, 0);
+               str_truncate(result_a, 0);
+               str_truncate(result_b, 0);
+
+               /* We test two things, that we can export & import the program
+                  and that the result of the imported program matches the
+                  original program. */
+               if (var_expand_program_create(t->prog_in, &prog, &error) <0)
+                       i_error("var_expand_program_create(): %s", error);
+               if (var_expand_program_execute(result_a, prog, &params, &error) < 0)
+                       i_error("var_expand_program_execute(a): %s", error);
+               var_expand_program_dump(prog, dest);
+               str_truncate(dest, 0);
+               var_expand_program_export_append(dest, prog);
+               var_expand_program_free(&prog);
+               test_assert_strcmp_idx(str_c(dest), t->export, i);
+               if (var_expand_program_import(str_c(dest), &prog, &error) < 0)
+                       i_error("var_expand_program_import(): %s", error);
+               if (var_expand_program_execute(result_b, prog, &params, &error) < 0)
+                       i_error("var_expand_program_execute(b): %s", error);
+               test_assert_strcmp_idx(str_c(result_a), str_c(result_b), i);
+               str_truncate(dest, 0);
+               var_expand_program_dump(prog, dest);
+               var_expand_program_free(&prog);
+       }
+
+       const struct test_case_err {
+               const char *input;
+               const char *error;
+       } test_cases_err[] = {
+               { "", "Too short" },
+               { "\x01literal", "Missing end of string" },
+               { "\x03literal", "Unknown input" },
+               { "\x02literal\x01", "Premature end of data" },
+               { "\x02literal\x01text\x01", "Unsupported parameter type" },
+               { "\x02literal\x01\x01stext\t", "Missing end of string" },
+               { "\x02literal\x01\x01i\xa1", "Unknown number" },
+               { "\x02literal\x01\x01i\xab\xf0\t", "Missing parameter end" },
+               {
+                       "\x02literal\x01\x01i\xab\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0",
+                       "Unfinished number"
+               },
+               { "\x02literal\x01\x01stext\r", "Missing parameter end" },
+               { "\x02literal\x01\x01stext\r\t", "Missing statement end" },
+               { "\x02literal\x01\x01stext\r\t\t", "Missing variables end" },
+       };
+
+       for(size_t i = 0; i < N_ELEMENTS(test_cases_err); i++) {
+               struct var_expand_program *prog;
+               const char *error;
+               const struct test_case_err *t = &test_cases_err[i];
+               int ret = var_expand_program_import(t->input, &prog, &error);
+               test_assert_cmp(ret, ==, -1);
+               if (ret == 0) {
+                       var_expand_program_free(&prog);
+                       continue;
+               }
+
+               test_assert_strcmp(error, t->error);
+       }
+
+       test_end();
+}
+
+#define BENCH_ROUNDS 200000
+static void test_var_expand_bench(void)
+{
+       if (!do_bench)
+               return;
+       struct test_cases {
+               const char *program;
+               const char *exported;
+       } test_cases[] = {
+               { "literal", NULL },
+               { "%{variable}", NULL },
+               { "%{lookup('variable')}", NULL },
+               { "%{this} is %{a} simple %{test}", NULL },
+               { "%{variable | substr(0,1) % 32}", NULL },
+               { "%{variable | substr(0,1) % 32} / %{variable | substr(1,1) % 32}", NULL },
+               { "%{variable + 4294967296}", NULL },
+       };
+       test_begin("var_expand(export benchmark)");
+
+       /* prepare exports */
+       for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) {
+               const char *error ATTR_UNUSED;
+               struct var_expand_program *prog;
+               if (var_expand_program_create(test_cases[i].program, &prog,
+                                             &error) < 0)
+                       i_error("%s", error);
+               test_cases[i].exported = var_expand_program_export(prog);
+               var_expand_program_free(&prog);
+       }
+
+       struct timespec ts0, ts1;
+       int ret;
+       for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) {
+               i_debug("%s", test_cases[i].program);
+               /* do speedtest */
+               ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts0);
+               i_assert(ret == 0);
+
+               for (int rounds = 0; rounds < BENCH_ROUNDS; rounds++) {
+                       const char *error ATTR_UNUSED;
+                       struct var_expand_program *prog;
+                       if (var_expand_program_create(test_cases[i].program,
+                                                     &prog, &error) < 0)
+                               i_error("%s", error);
+                       var_expand_program_free(&prog);
+               }
+               ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts1);
+               i_assert(ret == 0);
+               unsigned long long diff = (ts1.tv_sec - ts0.tv_sec) * 1000000000 + (ts1.tv_nsec - ts0.tv_nsec);
+               i_debug("var_expand_program_create: %llu ns total, %llu ns / program",
+                       diff, diff / BENCH_ROUNDS);
+
+               ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts0);
+               i_assert(ret == 0);
+               for (int rounds = 0; rounds < BENCH_ROUNDS; rounds++) {
+                       const char *error ATTR_UNUSED;
+                       struct var_expand_program *prog;
+                       if (var_expand_program_import(test_cases[i].exported,
+                                                     &prog, &error) < 0)
+                               i_error("%s", error);
+                       var_expand_program_free(&prog);
+               }
+               ret = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts1);
+               i_assert(ret == 0);
+               diff = (ts1.tv_sec - ts0.tv_sec) * 1000000000 + (ts1.tv_nsec - ts0.tv_nsec);
+               i_debug("var_expand_program_import: %llu ns total, %llu ns / program",
+                       diff, diff / BENCH_ROUNDS);
+       }
+       test_end();
+}
+
+int main(int argc, char *const argv[])
 {
        void (*const tests[])(void) = {
                test_var_expand_merge_tables,
@@ -977,8 +1193,18 @@ int main(void)
                test_var_expand_perc,
                test_var_expand_set_copy,
                test_var_expand_generate,
+               test_var_expand_export_import,
+               test_var_expand_bench,
                NULL
        };
 
+       char opt;
+       while ((opt = getopt(argc, argv, "b")) != -1) {
+               if (opt == 'b')
+                       do_bench = TRUE;
+               else
+                       i_fatal("Usage: %s [-b]", argv[0]);
+       }
+
        return test_run(tests);
 }
index f8c71a24ef15a7969ea53186bd2e807291f9ba41..58aea4ef032bea00408400ca7552027e1fd67faa 100644 (file)
@@ -167,6 +167,41 @@ static inline void var_expand_table_copy(struct var_expand_table *table,
        entry_b->func = entry_a->func;
 }
 
+/* Export variable expand program to a portable string.
+
+   Output format is a list of programs. Each program is catenated after
+   the previous program.
+   If there is only literal: \x01 <literal>
+   <literal>: tab-escaped string \r
+   Otherwise:
+   <program>: \x02 <function> \x01 <list-of-parameters> \t <list-of-variables> \t
+   <function>: string
+   <list-of-parameters>: <parameter> \x01 <parameter> \x01 ..
+      Note that last parameter has no \x01 at the end.
+   <parameter>: <key> \x01 <type> <value>
+   <key>: string
+   <type>: s = string, i = intmax, v = variable
+   <value>: <encoded-number> | tab-escaped string \r
+   <encoded-number>:
+     The number is expressed in 7-bit bytes and 8th bit indicates the
+     presence of next byte. The number is in little-endian ordering.
+   <list-of-variables>: <variable-name> \x01 <variable-name> \x01 ..
+     Note that last variable has no \x01 at the end.
+*/
+
+const char *var_expand_program_export(const struct var_expand_program *program);
+void var_expand_program_export_append(string_t *dest,
+                                     const struct var_expand_program *program);
+
+/* Imports a variable expansion program exported by var_expand_program_export(). */
+
+int var_expand_program_import(const char *data,
+                             struct var_expand_program **program_r,
+                             const char **error_r);
+int var_expand_program_import_sized(const char *data, size_t size,
+                                   struct var_expand_program **program_r,
+                                   const char **error_r);
+
 void var_expand_crypt_load(void);
 
 #endif