From d68172260d72ed43a6a8956e80998168f48f7db7 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 19 Nov 2016 04:29:03 +0200 Subject: [PATCH] MXS-929: Add mapping function for module commands The modulecmd_foreach function allows commands to be iterated without having to manage the locking of the system. This allows the commands to be easily iterated and gathered into filtered lists without having to build it into the module command system itself. --- include/maxscale/modulecmd.h | 29 ++++++++++++++- server/core/modulecmd.c | 56 ++++++++++++++++++++++++++++ server/core/test/testmodulecmd.c | 63 ++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) diff --git a/include/maxscale/modulecmd.h b/include/maxscale/modulecmd.h index bfa8d6d5e..ebd18541a 100644 --- a/include/maxscale/modulecmd.h +++ b/include/maxscale/modulecmd.h @@ -173,7 +173,7 @@ bool modulecmd_call_command(const MODULECMD *cmd, const MODULECMD_ARG *args); * @brief Set the current error message * * Modules that register commands should use this function to report errors. - * This will overwrite the existing error message. + * This will overwrite any existing error message. * * @param format Format string * @param ... Format string arguments @@ -187,4 +187,31 @@ void modulecmd_set_error(const char *format, ...); */ const char* modulecmd_get_error(); + +/** + * @brief Call a function for each command + * + * Calls a function for each matching command in the matched domains. The filters + * for the domain and identifier are PCRE2 expressions that are matched against + * the domain and identifier. These are optional and both @c domain and @c ident + * can be NULL. + * + * @param domain_re Command domain filter, NULL for all domains + * + * @param ident_re Command identifier filter, NULL for all commands + * + * @param fn Function that is called for every command. The first parameter is + * the current command. The second parameter is the value of @c data. + * The function should return true to continue iteration or false to + * stop iteration early. The function must not call any of the functions + * declared in modulecmd.h. + * + * @param data User defined data passed as the second parameter to @c fn + * + * @return True on success, false on PCRE2 error. Use modulecmd_get_error() + * to retrieve the error. + */ +bool modulecmd_foreach(const char *domain_re, const char *ident_re, + bool(*fn)(const MODULECMD *cmd, void *data), void *data); + MXS_END_DECLS diff --git a/server/core/modulecmd.c b/server/core/modulecmd.c index 045f0b2e7..09801764f 100644 --- a/server/core/modulecmd.c +++ b/server/core/modulecmd.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -508,3 +509,58 @@ const char* modulecmd_get_error() prepare_error(); return errbuf; } + +bool modulecmd_foreach(const char *domain_re, const char *ident_re, + bool(*fn)(const MODULECMD *cmd, void *data), void *data) +{ + bool rval = true; + bool stop = false; + spinlock_acquire(&modulecmd_lock); + + for (MODULECMD_DOMAIN *domain = modulecmd_domains; domain && rval && !stop; domain = domain->next) + { + int err; + mxs_pcre2_result_t d_res = domain_re ? + mxs_pcre2_simple_match(domain_re, domain->domain, 0, &err) : + MXS_PCRE2_MATCH; + + if (d_res == MXS_PCRE2_MATCH) + { + for (MODULECMD *cmd = domain->commands; cmd && rval; cmd = cmd->next) + { + mxs_pcre2_result_t i_res = ident_re ? + mxs_pcre2_simple_match(ident_re, cmd->identifier, 0, &err) : + MXS_PCRE2_MATCH; + + if (i_res == MXS_PCRE2_MATCH) + { + if (!fn(cmd, data)) + { + stop = true; + break; + } + } + else if (i_res == MXS_PCRE2_ERROR) + { + PCRE2_UCHAR errbuf[MXS_STRERROR_BUFLEN]; + pcre2_get_error_message(err, errbuf, sizeof(errbuf)); + MXS_ERROR("Failed to match command identifier with '%s': %s", ident_re, errbuf); + modulecmd_set_error("Failed to match command identifier with '%s': %s", ident_re, errbuf); + rval = false; + } + + } + } + else if (d_res == MXS_PCRE2_ERROR) + { + PCRE2_UCHAR errbuf[MXS_STRERROR_BUFLEN]; + pcre2_get_error_message(err, errbuf, sizeof(errbuf)); + MXS_ERROR("Failed to match command domain with '%s': %s", domain_re, errbuf); + modulecmd_set_error("Failed to match command domain with '%s': %s", domain_re, errbuf); + rval = false; + } + } + + spinlock_release(&modulecmd_lock); + return rval; +} diff --git a/server/core/test/testmodulecmd.c b/server/core/test/testmodulecmd.c index be4a0a498..96b9fd419 100644 --- a/server/core/test/testmodulecmd.c +++ b/server/core/test/testmodulecmd.c @@ -236,6 +236,68 @@ int test_module_errors() return 0; } +bool test_fn_map(const MODULECMD_ARG *args) +{ + return true; +} + +const char *map_dom = "test_map"; + +bool mapfn(const MODULECMD *cmd, void *data) +{ + int *i = (int*)data; + (*i)++; + return true; +} + +int test_map() +{ + for (int i = 0; i < 10; i++) + { + char id[200]; + sprintf(id, "test_map%d", i + 1); + TEST(modulecmd_register_command(map_dom, id, test_fn_map, 0, NULL), + "Registering a command should succeed"); + } + + int n = 0; + TEST(modulecmd_foreach(NULL, NULL, mapfn, &n), "Mapping function should succeed"); + TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty"); + TEST(n == 10, "Every registered command should be called"); + + n = 0; + TEST(modulecmd_foreach("test_map", NULL, mapfn, &n), "Mapping function should succeed"); + TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty"); + TEST(n == 10, "Every registered command should be called"); + + n = 0; + TEST(modulecmd_foreach(NULL, "test_map", mapfn, &n), "Mapping function should succeed"); + TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty"); + TEST(n == 10, "Every registered command should be called"); + + n = 0; + TEST(modulecmd_foreach("test_map", "test_map", mapfn, &n), "Mapping function should succeed"); + TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty"); + TEST(n == 10, "Every registered command should be called"); + + n = 0; + TEST(modulecmd_foreach("wrong domain", "test_map", mapfn, &n), "Mapping function should succeed"); + TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty"); + TEST(n == 0, "No registered command should be called"); + + n = 0; + TEST(modulecmd_foreach("test_map", "test_map[2-4]", mapfn, &n), "Mapping function should succeed"); + TEST(strlen(modulecmd_get_error()) == 0, "Error message should be empty"); + TEST(n == 3, "Three registered commands should be called"); + + n = 0; + TEST(!modulecmd_foreach("(", NULL, mapfn, &n), "Mapping function should fail"); + TEST(strlen(modulecmd_get_error()), "Error message should not be empty"); + TEST(n == 0, "No registered command should be called"); + + return 0; +} + int main(int argc, char **argv) { int rc = 0; @@ -243,6 +305,7 @@ int main(int argc, char **argv) rc += test_arguments(); rc += test_optional_arguments(); rc += test_module_errors(); + rc += test_map(); return rc; }