
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.
567 lines
14 KiB
C
567 lines
14 KiB
C
/*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
|
|
*
|
|
* Change Date: 2019-07-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
|
|
#include <maxscale/alloc.h>
|
|
#include <maxscale/config.h>
|
|
#include <maxscale/modulecmd.h>
|
|
#include <maxscale/pcre2.h>
|
|
#include <maxscale/platform.h>
|
|
#include <maxscale/spinlock.h>
|
|
|
|
/** Size of the error buffer */
|
|
#define MODULECMD_ERRBUF_SIZE 512
|
|
|
|
/** Thread local error buffer */
|
|
thread_local char *errbuf = NULL;
|
|
|
|
/** Parameter passed to functions that do not always expect arguments */
|
|
static const MODULECMD_ARG MODULECMD_NO_ARGUMENTS = {0, NULL};
|
|
|
|
/**
|
|
* A registered domain
|
|
*/
|
|
typedef struct modulecmd_domain
|
|
{
|
|
char *domain; /**< The domain */
|
|
MODULECMD *commands; /**< List of registered commands */
|
|
struct modulecmd_domain *next; /**< Next domain */
|
|
} MODULECMD_DOMAIN;
|
|
|
|
/**
|
|
* Internal functions
|
|
*/
|
|
|
|
/** The global list of registered domains */
|
|
static MODULECMD_DOMAIN *modulecmd_domains = NULL;
|
|
static SPINLOCK modulecmd_lock = SPINLOCK_INIT;
|
|
|
|
static inline void prepare_error()
|
|
{
|
|
if (errbuf == NULL)
|
|
{
|
|
errbuf = MXS_MALLOC(MODULECMD_ERRBUF_SIZE);
|
|
MXS_ABORT_IF_NULL(errbuf);
|
|
errbuf[0] = '\0';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Reset error message
|
|
*
|
|
* This should be the first function called in every API function that can
|
|
* generate errors.
|
|
*/
|
|
static void reset_error()
|
|
{
|
|
prepare_error();
|
|
errbuf[0] = '\0';
|
|
}
|
|
|
|
static void report_argc_mismatch(const MODULECMD *cmd, int argc)
|
|
{
|
|
if (cmd->arg_count_min == cmd->arg_count_max)
|
|
{
|
|
modulecmd_set_error("Expected %d arguments, got %d.", cmd->arg_count_min, argc);
|
|
}
|
|
else
|
|
{
|
|
modulecmd_set_error("Expected between %d and %d arguments, got %d.", cmd->arg_count_min, cmd->arg_count_max,
|
|
argc);
|
|
}
|
|
}
|
|
|
|
static MODULECMD_DOMAIN* domain_create(const char *domain)
|
|
{
|
|
MODULECMD_DOMAIN *rval = MXS_MALLOC(sizeof(*rval));
|
|
char *dm = MXS_STRDUP(domain);
|
|
|
|
if (rval && dm)
|
|
{
|
|
rval->domain = dm;
|
|
rval->commands = NULL;
|
|
rval->next = NULL;
|
|
}
|
|
else
|
|
{
|
|
MXS_FREE(rval);
|
|
MXS_FREE(dm);
|
|
rval = NULL;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
static void domain_free(MODULECMD_DOMAIN *dm)
|
|
{
|
|
if (dm)
|
|
{
|
|
MXS_FREE(dm->domain);
|
|
MXS_FREE(dm);
|
|
}
|
|
}
|
|
|
|
static MODULECMD_DOMAIN* get_or_create_domain(const char *domain)
|
|
{
|
|
|
|
MODULECMD_DOMAIN *dm;
|
|
|
|
for (dm = modulecmd_domains; dm; dm = dm->next)
|
|
{
|
|
if (strcmp(dm->domain, domain) == 0)
|
|
{
|
|
return dm;
|
|
}
|
|
}
|
|
|
|
if ((dm = domain_create(domain)))
|
|
{
|
|
dm->next = modulecmd_domains;
|
|
modulecmd_domains = dm;
|
|
}
|
|
|
|
return dm;
|
|
}
|
|
|
|
static MODULECMD* command_create(const char *identifier, const char *domain,
|
|
MODULECMDFN entry_point, int argc,
|
|
modulecmd_arg_type_t* argv)
|
|
{
|
|
MODULECMD *rval = MXS_MALLOC(sizeof(*rval));
|
|
char *id = MXS_STRDUP(identifier);
|
|
char *dm = MXS_STRDUP(domain);
|
|
modulecmd_arg_type_t *types = MXS_MALLOC(sizeof(*types) * argc);
|
|
|
|
if (rval && id && dm && types)
|
|
{
|
|
int argc_min = 0;
|
|
|
|
for (int i = 0; i < argc; i++)
|
|
{
|
|
if (MODULECMD_ARG_IS_REQUIRED(argv[i]))
|
|
{
|
|
argc_min++;
|
|
}
|
|
types[i] = argv[i];
|
|
}
|
|
|
|
rval->func = entry_point;
|
|
rval->identifier = id;
|
|
rval->domain = dm;
|
|
rval->arg_types = types;
|
|
rval->arg_count_min = argc_min;
|
|
rval->arg_count_max = argc;
|
|
rval->next = NULL;
|
|
}
|
|
else
|
|
{
|
|
MXS_FREE(rval);
|
|
MXS_FREE(id);
|
|
MXS_FREE(dm);
|
|
MXS_FREE(types);
|
|
rval = NULL;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
static void command_free(MODULECMD *cmd)
|
|
{
|
|
if (cmd)
|
|
{
|
|
MXS_FREE(cmd->identifier);
|
|
MXS_FREE(cmd->domain);
|
|
MXS_FREE(cmd->arg_types);
|
|
MXS_FREE(cmd);
|
|
}
|
|
}
|
|
|
|
static bool domain_has_command(MODULECMD_DOMAIN *dm, const char *id)
|
|
{
|
|
for (MODULECMD *cmd = dm->commands; cmd; cmd = cmd->next)
|
|
{
|
|
if (strcmp(cmd->identifier, id) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool process_argument(modulecmd_arg_type_t type, const char* value,
|
|
struct arg_node *arg, const char **err)
|
|
{
|
|
bool rval = false;
|
|
|
|
if (!MODULECMD_ARG_IS_REQUIRED(type) && value == NULL)
|
|
{
|
|
arg->type = MODULECMD_ARG_NONE;
|
|
rval = true;
|
|
}
|
|
else if (value)
|
|
{
|
|
switch (MODULECMD_GET_TYPE(type))
|
|
{
|
|
case MODULECMD_ARG_NONE:
|
|
arg->type = MODULECMD_ARG_NONE;
|
|
rval = true;
|
|
break;
|
|
|
|
case MODULECMD_ARG_STRING:
|
|
if ((arg->value.string = MXS_STRDUP(value)))
|
|
{
|
|
arg->type = MODULECMD_ARG_STRING;
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
*err = "memory allocation failed";
|
|
}
|
|
break;
|
|
|
|
case MODULECMD_ARG_BOOLEAN:
|
|
{
|
|
int truthval = config_truth_value((char*)value);
|
|
if (truthval != -1)
|
|
{
|
|
arg->value.boolean = truthval;
|
|
arg->type = MODULECMD_ARG_BOOLEAN;
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
*err = "not a boolean value";
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MODULECMD_ARG_SERVICE:
|
|
if ((arg->value.service = service_find((char*)value)))
|
|
{
|
|
arg->type = MODULECMD_ARG_SERVICE;
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
*err = "service not found";
|
|
}
|
|
break;
|
|
|
|
case MODULECMD_ARG_SERVER:
|
|
if ((arg->value.server = server_find_by_unique_name(value)))
|
|
{
|
|
arg->type = MODULECMD_ARG_SERVER;
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
*err = "server not found";
|
|
}
|
|
break;
|
|
|
|
case MODULECMD_ARG_SESSION:
|
|
// TODO: Implement this
|
|
break;
|
|
|
|
case MODULECMD_ARG_DCB:
|
|
// TODO: Implement this
|
|
break;
|
|
|
|
case MODULECMD_ARG_MONITOR:
|
|
if ((arg->value.monitor = monitor_find((char*)value)))
|
|
{
|
|
arg->type = MODULECMD_ARG_MONITOR;
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
*err = "monitor not found";
|
|
}
|
|
break;
|
|
|
|
case MODULECMD_ARG_FILTER:
|
|
if ((arg->value.filter = filter_find((char*)value)))
|
|
{
|
|
arg->type = MODULECMD_ARG_FILTER;
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
*err = "filter not found";
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ss_dassert(false);
|
|
MXS_ERROR("Undefined argument type: %0lx", type);
|
|
*err = "internal error";
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*err = "required argument";
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
static MODULECMD_ARG* modulecmd_arg_create(int argc)
|
|
{
|
|
MODULECMD_ARG* arg = MXS_MALLOC(sizeof(*arg));
|
|
struct arg_node *argv = MXS_CALLOC(argc, sizeof(*argv));
|
|
|
|
if (arg && argv)
|
|
{
|
|
arg->argc = argc;
|
|
arg->argv = argv;
|
|
}
|
|
else
|
|
{
|
|
MXS_FREE(argv);
|
|
MXS_FREE(arg);
|
|
arg = NULL;
|
|
}
|
|
|
|
return arg;
|
|
}
|
|
|
|
static void free_argument(struct arg_node *arg)
|
|
{
|
|
switch (arg->type)
|
|
{
|
|
case MODULECMD_ARG_STRING:
|
|
MXS_FREE(arg->value.string);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Public functions declared in modulecmd.h
|
|
*/
|
|
|
|
bool modulecmd_register_command(const char *domain, const char *identifier,
|
|
MODULECMDFN entry_point, int argc, modulecmd_arg_type_t *argv)
|
|
{
|
|
reset_error();
|
|
bool rval = false;
|
|
spinlock_acquire(&modulecmd_lock);
|
|
|
|
MODULECMD_DOMAIN *dm = get_or_create_domain(domain);
|
|
|
|
if (dm)
|
|
{
|
|
if (domain_has_command(dm, identifier))
|
|
{
|
|
modulecmd_set_error("Command registered more than once: %s::%s", domain, identifier);
|
|
MXS_ERROR("Command registered more than once: %s::%s", domain, identifier);
|
|
}
|
|
else
|
|
{
|
|
MODULECMD *cmd = command_create(identifier, domain, entry_point, argc, argv);
|
|
|
|
if (cmd)
|
|
{
|
|
cmd->next = dm->commands;
|
|
dm->commands = cmd;
|
|
rval = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
spinlock_release(&modulecmd_lock);
|
|
|
|
return rval;
|
|
}
|
|
|
|
const MODULECMD* modulecmd_find_command(const char *domain, const char *identifier)
|
|
{
|
|
reset_error();
|
|
MODULECMD *rval = NULL;
|
|
spinlock_acquire(&modulecmd_lock);
|
|
|
|
for (MODULECMD_DOMAIN *dm = modulecmd_domains; dm; dm = dm->next)
|
|
{
|
|
if (strcmp(domain, dm->domain) == 0)
|
|
{
|
|
for (MODULECMD *cmd = dm->commands; cmd; cmd = cmd->next)
|
|
{
|
|
if (strcmp(cmd->identifier, identifier) == 0)
|
|
{
|
|
rval = cmd;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
spinlock_release(&modulecmd_lock);
|
|
|
|
if (rval == NULL)
|
|
{
|
|
modulecmd_set_error("Command not found: %s::%s", domain, identifier);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
MODULECMD_ARG* modulecmd_arg_parse(const MODULECMD *cmd, int argc, const char **argv)
|
|
{
|
|
reset_error();
|
|
|
|
MODULECMD_ARG* arg = NULL;
|
|
|
|
if (argc >= cmd->arg_count_min && argc <= cmd->arg_count_max)
|
|
{
|
|
arg = modulecmd_arg_create(cmd->arg_count_max);
|
|
bool error = false;
|
|
|
|
if (arg)
|
|
{
|
|
for (int i = 0; i < cmd->arg_count_max && i < argc; i++)
|
|
{
|
|
const char *err = "";
|
|
|
|
if (!process_argument(cmd->arg_types[i], argv[i], &arg->argv[i], &err))
|
|
{
|
|
error = true;
|
|
modulecmd_set_error("Argument %d, %s: %s", i + 1, err, argv[i] ? argv[i] : "No argument given");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (error)
|
|
{
|
|
modulecmd_arg_free(arg);
|
|
arg = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
report_argc_mismatch(cmd, argc);
|
|
}
|
|
|
|
return arg;
|
|
}
|
|
|
|
void modulecmd_arg_free(MODULECMD_ARG* arg)
|
|
{
|
|
if (arg)
|
|
{
|
|
for (int i = 0; i < arg->argc; i++)
|
|
{
|
|
free_argument(&arg->argv[i]);
|
|
}
|
|
|
|
MXS_FREE(arg->argv);
|
|
MXS_FREE(arg);
|
|
}
|
|
}
|
|
|
|
bool modulecmd_call_command(const MODULECMD *cmd, const MODULECMD_ARG *args)
|
|
{
|
|
bool rval = false;
|
|
reset_error();
|
|
|
|
if (cmd->arg_count_min > 0 && args == NULL)
|
|
{
|
|
report_argc_mismatch(cmd, 0);
|
|
}
|
|
else
|
|
{
|
|
if (args == NULL)
|
|
{
|
|
args = &MODULECMD_NO_ARGUMENTS;
|
|
}
|
|
|
|
rval = cmd->func(args);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
void modulecmd_set_error(const char *format, ...)
|
|
{
|
|
prepare_error();
|
|
|
|
va_list list;
|
|
va_start(list, format);
|
|
vsnprintf(errbuf, MODULECMD_ERRBUF_SIZE, format, list);
|
|
va_end(list);
|
|
}
|
|
|
|
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;
|
|
}
|