Files
MaxScale/server/core/modulecmd.c
Markus Makela adbd666991 Change module command parameter types
This commit introduces safe session references that can be handled without
holding locks. This allows the safe searching of sessions with the unique
ID of the session.

Remove the use of raw pointers passed as strings. Change the comments of
the argument types and add more details to the parsing function
documentation.
2016-12-02 14:26:56 +02:00

662 lines
16 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)
{
ss_dassert((argc && argv) || (argc == 0 && argv == NULL));
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 ? argc : 1));
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];
}
if (argc == 0)
{
/** The command requires no arguments */
types[0].type = MODULECMD_ARG_NONE;
types[0].description = "";
}
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 void* value,
struct arg_node *arg, const char **err)
{
bool rval = false;
if (!MODULECMD_ARG_IS_REQUIRED(type) && value == NULL)
{
arg->type.type = MODULECMD_ARG_NONE;
rval = true;
}
else if (value)
{
switch (MODULECMD_GET_TYPE(type))
{
case MODULECMD_ARG_NONE:
arg->type.type = MODULECMD_ARG_NONE;
rval = true;
break;
case MODULECMD_ARG_STRING:
if ((arg->value.string = MXS_STRDUP((char*)value)))
{
arg->type.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.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.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((char*)value)))
{
arg->type.type = MODULECMD_ARG_SERVER;
rval = true;
}
else
{
*err = "server not found";
}
break;
case MODULECMD_ARG_SESSION:
if ((arg->value.session = session_get_ref(atoi(value))))
{
arg->type.type = MODULECMD_ARG_SESSION;
}
rval = true;
break;
case MODULECMD_ARG_DCB:
arg->type.type = MODULECMD_ARG_DCB;
arg->value.dcb = (DCB*)value;
rval = true;
break;
case MODULECMD_ARG_MONITOR:
if ((arg->value.monitor = monitor_find((char*)value)))
{
arg->type.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.type = MODULECMD_ARG_FILTER;
rval = true;
}
else
{
*err = "filter not found";
}
break;
case MODULECMD_ARG_OUTPUT:
arg->type.type = MODULECMD_ARG_OUTPUT;
arg->value.dcb = (DCB*)value;
rval = true;
break;
default:
ss_dassert(false);
MXS_ERROR("Undefined argument type: %0lx", type->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.type)
{
case MODULECMD_ARG_STRING:
MXS_FREE(arg->value.string);
break;
case MODULECMD_ARG_SESSION:
session_put_ref(arg->value.session);
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 void **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;
}
char* modulecmd_argtype_to_str(modulecmd_arg_type_t *type)
{
const char *strtype = "UNKNOWN";
switch (MODULECMD_GET_TYPE(type))
{
case MODULECMD_ARG_NONE:
strtype = "NONE";
break;
case MODULECMD_ARG_STRING:
strtype = "STRING";
break;
case MODULECMD_ARG_BOOLEAN:
strtype = "BOOLEAN";
break;
case MODULECMD_ARG_SERVICE:
strtype = "SERVICE";
break;
case MODULECMD_ARG_SERVER:
strtype = "SERVER";
break;
case MODULECMD_ARG_SESSION:
strtype = "SESSION";
break;
case MODULECMD_ARG_DCB:
strtype = "DCB";
break;
case MODULECMD_ARG_MONITOR:
strtype = "MONITOR";
break;
case MODULECMD_ARG_FILTER:
strtype = "FILTER";
break;
case MODULECMD_ARG_OUTPUT:
strtype = "OUTPUT";
break;
default:
ss_dassert(false);
MXS_ERROR("Unknown type");
break;
}
size_t slen = strlen(strtype);
size_t extra = MODULECMD_ARG_IS_REQUIRED(type) ? 0 : 2;
char *rval = MXS_MALLOC(slen + extra + 1);
if (rval)
{
const char *fmtstr = extra ? "[%s]" : "%s";
sprintf(rval, fmtstr, strtype);
}
return rval;
}
bool modulecmd_arg_is_present(const MODULECMD_ARG *arg, int idx)
{
return arg->argc > idx &&
MODULECMD_GET_TYPE(&arg->argv[idx].type) != MODULECMD_ARG_NONE;
}