Add convenience functions for common tasks with regular expressions
Several filters use a match-or-ignore logic with two regular expressions when filtering queries. This commit adds a convenience function for this task. Also adds a convenience function for reading several regular expression parameters at once, compiling them and saving the code while checking for errors. Also, use the new functions in QLA and CCR filters.
This commit is contained in:
parent
5fd690eb1f
commit
14bb6cf99b
@ -369,6 +369,28 @@ pcre2_code* config_get_compiled_regex(const MXS_CONFIG_PARAMETER *params,
|
||||
const char *key, uint32_t options,
|
||||
uint32_t* output_ovec_size);
|
||||
|
||||
/**
|
||||
* Get multiple compiled regular expressions and the maximum ovector size of
|
||||
* the patterns. The returned regex codes should be freed by the caller.
|
||||
*
|
||||
* @param params List of configuration parameters
|
||||
* @param keys An array of parameter names. If an element is not found in @c params,
|
||||
* the corresponding spot in @c out_codes is set to NULL.
|
||||
* @param keys_size Size of both @c keys and @c out_arr
|
||||
* @param options PCRE2 compilation options
|
||||
* @param out_ovec_size If not NULL, the maximum ovector size of successfully
|
||||
* compiled patterns is written here.
|
||||
* @param out_codes An array of handles to compiled codes. The referenced pointers
|
||||
* will be set to point to the compiled codes. The array size must be equal to
|
||||
* @c keys array size and it must contain valid pointers.
|
||||
*
|
||||
* @return True, if all patterns given by @c keys were successfully compiled.
|
||||
* False otherwise.
|
||||
*/
|
||||
bool config_get_compiled_regexes(const MXS_CONFIG_PARAMETER *params,
|
||||
const char* keys[], int keys_size,
|
||||
uint32_t options, uint32_t* out_ovec_size,
|
||||
pcre2_code** out_codes[]);
|
||||
/**
|
||||
* Parse a list of server names and write the results in an array of strings
|
||||
* with one server name in each. The output array and its elements should be
|
||||
|
@ -55,4 +55,26 @@ mxs_pcre2_result_t mxs_pcre2_simple_match(const char* pattern, const char* subje
|
||||
void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *filename,
|
||||
int line_num, const char* func_name);
|
||||
|
||||
/**
|
||||
* Check that @c subject is valid. A valid subject matches @c re_match yet does
|
||||
* not match @c re_exclude. If an error occurs, an error code is written to
|
||||
* @c match_error_out.
|
||||
*
|
||||
* @param re_match If not NULL, the subject must match this to be valid. If NULL,
|
||||
* all inputs are considered valid.
|
||||
* @param re_exclude If not NULL, will invalidate a matching subject. Even subjects
|
||||
* validated by @c re_match can be invalidated. If NULL, invalidates nothing.
|
||||
* @param md PCRE2 match data block
|
||||
* @param subject Subject string. Should NOT be an empty string.
|
||||
* @param length Length of subject. Can be zero for 0-terminated strings.
|
||||
* @param calling_module Which module the function was called from. Can be NULL.
|
||||
* Used for log messages.
|
||||
*
|
||||
* @return True, if subject is considered valid. False if subject is not valid or
|
||||
* an error occurred.
|
||||
*/
|
||||
bool mxs_pcre2_check_match_exclude(pcre2_code* re_match, pcre2_code* re_exclude,
|
||||
pcre2_match_data* md, const char* subject,
|
||||
int length, const char* calling_module);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
@ -1256,6 +1256,40 @@ pcre2_code* config_get_compiled_regex(const MXS_CONFIG_PARAMETER *params,
|
||||
return code;
|
||||
}
|
||||
|
||||
bool config_get_compiled_regexes(const MXS_CONFIG_PARAMETER *params,
|
||||
const char* keys[], int keys_size,
|
||||
uint32_t options, uint32_t* out_ovec_size,
|
||||
pcre2_code** out_codes[])
|
||||
{
|
||||
bool rval = true;
|
||||
uint32_t max_ovec_size = 0;
|
||||
uint32_t ovec_size_temp = 0;
|
||||
for (int i = 0; i < keys_size; i++)
|
||||
{
|
||||
ss_dassert(out_codes[i]);
|
||||
*out_codes[i] = config_get_compiled_regex(params, keys[i], options,
|
||||
&ovec_size_temp);
|
||||
if (*out_codes[i])
|
||||
{
|
||||
if (ovec_size_temp > max_ovec_size)
|
||||
{
|
||||
max_ovec_size = ovec_size_temp;
|
||||
}
|
||||
}
|
||||
/* config_get_compiled_regex() returns null also if the config setting
|
||||
* didn't exist. Check that before setting error state. */
|
||||
else if (*(config_get_value_string(params, keys[i])))
|
||||
{
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
if (out_ovec_size)
|
||||
{
|
||||
*out_ovec_size = max_ovec_size;
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
MXS_CONFIG_PARAMETER* config_clone_param(const MXS_CONFIG_PARAMETER* param)
|
||||
{
|
||||
MXS_CONFIG_PARAMETER *p2 = (MXS_CONFIG_PARAMETER*)MXS_MALLOC(sizeof(MXS_CONFIG_PARAMETER));
|
||||
|
@ -141,7 +141,6 @@ mxs_pcre2_result_t mxs_pcre2_simple_match(const char* pattern, const char* subje
|
||||
void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *filename,
|
||||
int line_num, const char* func_name)
|
||||
{
|
||||
ss_dassert(module_name);
|
||||
ss_dassert(filename);
|
||||
ss_dassert(func_name);
|
||||
|
||||
@ -158,3 +157,53 @@ void mxs_pcre2_print_error(int errorcode, const char *module_name, const char *f
|
||||
"message.");
|
||||
}
|
||||
}
|
||||
|
||||
bool mxs_pcre2_check_match_exclude(pcre2_code* re_match, pcre2_code* re_exclude,
|
||||
pcre2_match_data* md, const char* subject,
|
||||
int length, const char* calling_module)
|
||||
{
|
||||
ss_dassert((!re_match && !re_exclude) || (md && subject));
|
||||
bool rval = true;
|
||||
int string_len = ((size_t)length == PCRE2_ZERO_TERMINATED) ? strlen(subject) : length;
|
||||
if (re_match)
|
||||
{
|
||||
int result = pcre2_match(re_match, (PCRE2_SPTR)subject, string_len, 0, 0, md, NULL);
|
||||
if (result == PCRE2_ERROR_NOMATCH)
|
||||
{
|
||||
rval = false; // Didn't match the "match"-regex
|
||||
if (mxs_log_priority_is_enabled(LOG_INFO))
|
||||
{
|
||||
mxs_log_message(LOG_INFO, calling_module, __FILE__, __LINE__, __func__,
|
||||
"Subject does not match the 'match' pattern: %.*s",
|
||||
string_len, subject);
|
||||
}
|
||||
}
|
||||
else if (result < 0)
|
||||
{
|
||||
rval = false;
|
||||
/* The __FILE__ etc macros here do not match calling_module, but
|
||||
* the values are only used for throttling messages. */
|
||||
mxs_pcre2_print_error(result, calling_module, __FILE__, __LINE__, __func__);
|
||||
}
|
||||
}
|
||||
if (rval && re_exclude)
|
||||
{
|
||||
int result = pcre2_match(re_exclude, (PCRE2_SPTR)subject, string_len, 0, 0, md, NULL);
|
||||
if (result >= 0)
|
||||
{
|
||||
rval = false; // Matched the "exclude"-regex
|
||||
if (mxs_log_priority_is_enabled(LOG_INFO))
|
||||
{
|
||||
mxs_log_message(LOG_INFO, calling_module, __FILE__, __LINE__, __func__,
|
||||
"Query matches the 'exclude' pattern: %.*s",
|
||||
string_len, subject);
|
||||
}
|
||||
}
|
||||
else if (result != PCRE2_ERROR_NOMATCH)
|
||||
{
|
||||
rval = false;
|
||||
mxs_pcre2_print_error(result, calling_module, __FILE__, __LINE__, __func__);
|
||||
}
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
@ -13,16 +13,18 @@
|
||||
|
||||
#define MXS_MODULE_NAME "ccrfilter"
|
||||
|
||||
#include <maxscale/cdefs.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <maxscale/alloc.h>
|
||||
#include <maxscale/filter.h>
|
||||
#include <maxscale/hint.h>
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <maxscale/modinfo.h>
|
||||
#include <maxscale/modutil.h>
|
||||
#include <maxscale/log_manager.h>
|
||||
#include <string.h>
|
||||
#include <maxscale/hint.h>
|
||||
#include <maxscale/pcre2.h>
|
||||
#include <maxscale/query_classifier.h>
|
||||
#include <regex.h>
|
||||
#include <maxscale/alloc.h>
|
||||
|
||||
/**
|
||||
* @file ccrfilter.c - a very simple filter designed to send queries to the
|
||||
@ -81,8 +83,9 @@ typedef struct
|
||||
int count; /*< Number of hints to add after each operation
|
||||
* that modifies data. */
|
||||
LAGSTATS stats;
|
||||
regex_t re; /* Compiled regex text of match */
|
||||
regex_t nore; /* Compiled regex text of ignore */
|
||||
pcre2_code* re; /* Compiled regex text of match */
|
||||
pcre2_code* nore; /* Compiled regex text of ignore */
|
||||
uint32_t ovector_size; /* PCRE2 match data ovector size */
|
||||
} CCR_INSTANCE;
|
||||
|
||||
/**
|
||||
@ -93,16 +96,20 @@ typedef struct
|
||||
MXS_DOWNSTREAM down; /*< The downstream filter */
|
||||
int hints_left; /*< Number of hints left to add to queries*/
|
||||
time_t last_modification; /*< Time of the last data modifying operation */
|
||||
pcre2_match_data* md; /*< PCRE2 match data */
|
||||
} CCR_SESSION;
|
||||
|
||||
static const MXS_ENUM_VALUE option_values[] =
|
||||
{
|
||||
{"ignorecase", REG_ICASE},
|
||||
{"ignorecase", PCRE2_CASELESS},
|
||||
{"case", 0},
|
||||
{"extended", REG_EXTENDED},
|
||||
{"extended", PCRE2_EXTENDED},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
static const char PARAM_MATCH[] = "match";
|
||||
static const char PARAM_IGNORE[] = "ignore";
|
||||
|
||||
typedef enum ccr_hint_value_t
|
||||
{
|
||||
CCR_HINT_NONE,
|
||||
@ -154,8 +161,8 @@ MXS_MODULE* MXS_CREATE_MODULE()
|
||||
{
|
||||
{"count", MXS_MODULE_PARAM_COUNT, "0"},
|
||||
{"time", MXS_MODULE_PARAM_COUNT, CCR_DEFAULT_TIME},
|
||||
{"match", MXS_MODULE_PARAM_STRING},
|
||||
{"ignore", MXS_MODULE_PARAM_STRING},
|
||||
{PARAM_MATCH, MXS_MODULE_PARAM_REGEX},
|
||||
{PARAM_IGNORE, MXS_MODULE_PARAM_REGEX},
|
||||
{
|
||||
"options",
|
||||
MXS_MODULE_PARAM_ENUM,
|
||||
@ -192,23 +199,26 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params)
|
||||
my_instance->stats.n_add_count = 0;
|
||||
my_instance->stats.n_add_time = 0;
|
||||
my_instance->stats.n_modified = 0;
|
||||
my_instance->ovector_size = 0;
|
||||
my_instance->re = NULL;
|
||||
my_instance->nore = NULL;
|
||||
|
||||
int cflags = config_get_enum(params, "options", option_values);
|
||||
my_instance->match = config_copy_string(params, PARAM_MATCH);
|
||||
my_instance->nomatch = config_copy_string(params, PARAM_IGNORE);
|
||||
const char* keys[] = {PARAM_MATCH, PARAM_IGNORE};
|
||||
pcre2_code** code_arr[] = {&my_instance->re, &my_instance->nore};
|
||||
|
||||
if ((my_instance->match = config_copy_string(params, "match")))
|
||||
if (!config_get_compiled_regexes(params, keys, sizeof(keys)/sizeof(char*),
|
||||
cflags, &my_instance->ovector_size,
|
||||
code_arr))
|
||||
{
|
||||
if (regcomp(&my_instance->re, my_instance->match, cflags))
|
||||
{
|
||||
MXS_ERROR("Failed to compile regex '%s'.", my_instance->match);
|
||||
}
|
||||
}
|
||||
|
||||
if ((my_instance->nomatch = config_copy_string(params, "ignore")))
|
||||
{
|
||||
if (regcomp(&my_instance->nore, my_instance->nomatch, cflags))
|
||||
{
|
||||
MXS_ERROR("Failed to compile regex '%s'.", my_instance->nomatch);
|
||||
}
|
||||
MXS_FREE(my_instance->match);
|
||||
MXS_FREE(my_instance->nomatch);
|
||||
pcre2_code_free(my_instance->re);
|
||||
pcre2_code_free(my_instance->nore);
|
||||
MXS_FREE(my_instance);
|
||||
my_instance = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,12 +236,23 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params)
|
||||
static MXS_FILTER_SESSION *
|
||||
newSession(MXS_FILTER *instance, MXS_SESSION *session)
|
||||
{
|
||||
CCR_INSTANCE *my_instance = (CCR_INSTANCE *)instance;
|
||||
CCR_SESSION *my_session = MXS_MALLOC(sizeof(CCR_SESSION));
|
||||
|
||||
if (my_session)
|
||||
{
|
||||
bool error = false;
|
||||
my_session->hints_left = 0;
|
||||
my_session->last_modification = 0;
|
||||
if (my_instance->ovector_size)
|
||||
{
|
||||
my_session->md = pcre2_match_data_create(my_instance->ovector_size, NULL);
|
||||
if (!my_session->md)
|
||||
{
|
||||
MXS_FREE(my_session);
|
||||
my_session = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (MXS_FILTER_SESSION*)my_session;
|
||||
@ -296,7 +317,7 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue)
|
||||
CCR_INSTANCE *my_instance = (CCR_INSTANCE *)instance;
|
||||
CCR_SESSION *my_session = (CCR_SESSION *)session;
|
||||
char *sql;
|
||||
regmatch_t limits[] = {{0, 0}};
|
||||
int length;
|
||||
time_t now = time(NULL);
|
||||
|
||||
if (modutil_is_SQL(queue))
|
||||
@ -307,7 +328,7 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue)
|
||||
*/
|
||||
if (qc_query_is_type(qc_get_type_mask(queue), QUERY_TYPE_WRITE))
|
||||
{
|
||||
if (modutil_extract_SQL(queue, &sql, &limits[0].rm_eo))
|
||||
if (modutil_extract_SQL(queue, &sql, &length))
|
||||
{
|
||||
bool trigger_ccr = true;
|
||||
bool decided = false; // Set by hints to take precedence.
|
||||
@ -323,18 +344,10 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue)
|
||||
}
|
||||
if (!decided)
|
||||
{
|
||||
if (my_instance->nomatch &&
|
||||
regexec(&my_instance->nore, sql, 0, limits, 0) == 0)
|
||||
{
|
||||
// Nomatch was present and sql matched it.
|
||||
trigger_ccr = false;
|
||||
}
|
||||
else if (my_instance->match &&
|
||||
(regexec(&my_instance->re, sql, 0, limits, 0) != 0))
|
||||
{
|
||||
// Match was present but sql did *not* match it.
|
||||
trigger_ccr = false;
|
||||
}
|
||||
trigger_ccr =
|
||||
mxs_pcre2_check_match_exclude(my_instance->re, my_instance->nore,
|
||||
my_session->md, sql, length,
|
||||
MXS_MODULE_NAME);
|
||||
}
|
||||
if (trigger_ccr)
|
||||
{
|
||||
@ -435,7 +448,7 @@ static json_t* diagnostic_json(const MXS_FILTER *instance, const MXS_FILTER_SESS
|
||||
|
||||
if (my_instance->match)
|
||||
{
|
||||
json_object_set_new(rval, "match", json_string(my_instance->match));
|
||||
json_object_set_new(rval, PARAM_MATCH, json_string(my_instance->match));
|
||||
}
|
||||
|
||||
if (my_instance->nomatch)
|
||||
|
@ -101,8 +101,6 @@ typedef struct
|
||||
|
||||
/* Avoid repeatedly printing some errors/warnings. */
|
||||
bool write_warning_given;
|
||||
bool match_error_printed;
|
||||
bool exclude_error_printed;
|
||||
} QLA_INSTANCE;
|
||||
|
||||
/* The session structure for this QLA filter. */
|
||||
@ -122,8 +120,6 @@ typedef struct
|
||||
static FILE* open_log_file(uint32_t, QLA_INSTANCE *, const char *);
|
||||
static int write_log_entry(uint32_t, FILE*, QLA_INSTANCE*, QLA_SESSION*, const char*,
|
||||
const char*, size_t);
|
||||
static bool regex_check(QLA_INSTANCE* my_instance, QLA_SESSION* my_session,
|
||||
const char* ptr, int length);
|
||||
|
||||
static const MXS_ENUM_VALUE option_values[] =
|
||||
{
|
||||
@ -283,8 +279,6 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params)
|
||||
my_instance->ovec_size = 0;
|
||||
my_instance->unified_fp = NULL;
|
||||
my_instance->write_warning_given = false;
|
||||
my_instance->match_error_printed = false;
|
||||
my_instance->exclude_error_printed = false;
|
||||
|
||||
my_instance->source = config_copy_string(params, PARAM_SOURCE);
|
||||
my_instance->user_name = config_copy_string(params, PARAM_USER);
|
||||
@ -302,29 +296,13 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params)
|
||||
bool error = false;
|
||||
|
||||
int cflags = config_get_enum(params, PARAM_OPTIONS, option_values);
|
||||
if (my_instance->match)
|
||||
{
|
||||
my_instance->re_match =
|
||||
config_get_compiled_regex(params, PARAM_MATCH, cflags, &my_instance->ovec_size);
|
||||
if (!my_instance->re_match)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (my_instance->exclude)
|
||||
const char* keys[] = {PARAM_MATCH, PARAM_EXCLUDE};
|
||||
pcre2_code** code_arr[] = {&my_instance->re_match, &my_instance->re_exclude};
|
||||
if (!config_get_compiled_regexes(params, keys, sizeof(keys) / sizeof(char*),
|
||||
cflags, &my_instance->ovec_size, code_arr))
|
||||
{
|
||||
uint32_t ovec_size_temp = 0;
|
||||
my_instance->re_exclude =
|
||||
config_get_compiled_regex(params, PARAM_EXCLUDE, cflags, &ovec_size_temp);
|
||||
if (ovec_size_temp > my_instance->ovec_size)
|
||||
{
|
||||
my_instance->ovec_size = ovec_size_temp;
|
||||
}
|
||||
if (!my_instance->re_exclude)
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
error = true;
|
||||
}
|
||||
|
||||
// Try to open the unified log file
|
||||
@ -531,7 +509,8 @@ routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue)
|
||||
|
||||
if (my_session->active &&
|
||||
modutil_extract_SQL(queue, &ptr, &length) &&
|
||||
regex_check(my_instance, my_session, ptr, length))
|
||||
mxs_pcre2_check_match_exclude(my_instance->re_match, my_instance->re_exclude,
|
||||
my_session->match_data, ptr, length, MXS_MODULE_NAME))
|
||||
{
|
||||
char buffer[QLA_DATE_BUFFER_SIZE];
|
||||
gettimeofday(&tv, NULL);
|
||||
@ -924,46 +903,3 @@ static int write_log_entry(uint32_t data_flags, FILE *logfile, QLA_INSTANCE *ins
|
||||
return rval;
|
||||
}
|
||||
}
|
||||
|
||||
static bool regex_check(QLA_INSTANCE* my_instance, QLA_SESSION* my_session,
|
||||
const char* ptr, int length)
|
||||
{
|
||||
bool rval = true;
|
||||
if (my_instance->re_match)
|
||||
{
|
||||
int result = pcre2_match(my_instance->re_match, (PCRE2_SPTR)ptr,
|
||||
length, 0, 0, my_session->match_data, NULL);
|
||||
if (result == PCRE2_ERROR_NOMATCH)
|
||||
{
|
||||
rval = false; // Didn't match the "match"-regex
|
||||
}
|
||||
else if (result < 0)
|
||||
{
|
||||
rval = false;
|
||||
if (!my_instance->match_error_printed)
|
||||
{
|
||||
MXS_PCRE2_PRINT_ERROR(result);
|
||||
my_instance->match_error_printed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rval && my_instance->re_exclude)
|
||||
{
|
||||
int result = pcre2_match(my_instance->re_exclude, (PCRE2_SPTR)ptr,
|
||||
length, 0, 0, my_session->match_data, NULL);
|
||||
if (result >= 0)
|
||||
{
|
||||
rval = false; // Matched the "exclude"-regex
|
||||
}
|
||||
else if (result != PCRE2_ERROR_NOMATCH)
|
||||
{
|
||||
rval = false;
|
||||
if (!my_instance->exclude_error_printed)
|
||||
{
|
||||
MXS_PCRE2_PRINT_ERROR(result);
|
||||
my_instance->exclude_error_printed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user