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; }