Extending MySQL using the Component Infrastructure – part 6: functions

This post is the sixth post of a series of articles on extending MySQL with the Component Infrastructure, the list above will be updated as new articles are published:

Our component is nicely evolving. In this article we will start the creation of our first function.

We will check the previously created privilege to access the function.

We again start with scan.h where we add the header for the udf registration service we will use to register our first function on line 26 and the place holder on line 34:

/* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#define LOG_COMPONENT_TAG "viruscan"
#include <mysql/components/component_implementation.h>
#include <mysql/components/services/log_builtins.h> /* LogComponentErr */
#include <mysqld_error.h> /* Errors */
#include <mysql/components/services/dynamic_privilege.h>
#include <mysql/components/services/udf_metadata.h>
#include <mysql/components/services/udf_registration.h>
#include <list>
#include <string>
extern REQUIRES_SERVICE_PLACEHOLDER(log_builtins);
extern REQUIRES_SERVICE_PLACEHOLDER(log_builtins_string);
extern REQUIRES_SERVICE_PLACEHOLDER(dynamic_privilege_register);
extern REQUIRES_SERVICE_PLACEHOLDER(udf_registration);
extern REQUIRES_SERVICE_PLACEHOLDER(mysql_udf_metadata);
extern SERVICE_TYPE(log_builtins) * log_bi;
extern SERVICE_TYPE(log_builtins_string) * log_bs;
view raw scan.h hosted with ❤ by GitHub

As our function will return a string, we also need to specify the character set otherwise it will return binary. Therefore, we need to use udf metadata service (line 25 and 35).

We also need some other standards headers on lines 28 and 29.

Creating a function

For the code of scan.cc, we will split it in two parts. First we will create the function and after we will add the check regarding the privilege.

Let’s start by the function creation:

/* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#define LOG_COMPONENT_TAG "viruscan"
#define NO_SIGNATURE_CHANGE 0
#define SIGNATURE_CHANGE 1
#include <components/viruscan/scan.h>
REQUIRES_SERVICE_PLACEHOLDER(log_builtins);
REQUIRES_SERVICE_PLACEHOLDER(log_builtins_string);
REQUIRES_SERVICE_PLACEHOLDER(dynamic_privilege_register);
REQUIRES_SERVICE_PLACEHOLDER(udf_registration);
REQUIRES_SERVICE_PLACEHOLDER(mysql_udf_metadata);
SERVICE_TYPE(log_builtins) * log_bi;
SERVICE_TYPE(log_builtins_string) * log_bs;
static const char *SCAN_PRIVILEGE_NAME = "VIRUS_SCAN";
class udf_list {
typedef std::list<std::string> udf_list_t;
public:
~udf_list() { unregister(); }
bool add_scalar(const char *func_name, enum Item_result return_type,
Udf_func_any func, Udf_func_init init_func = NULL,
Udf_func_deinit deinit_func = NULL) {
if (!mysql_service_udf_registration->udf_register(
func_name, return_type, func, init_func, deinit_func)) {
set.push_back(func_name);
return false;
}
return true;
}
bool unregister() {
udf_list_t delete_set;
/* try to unregister all of the udfs */
for (auto udf : set) {
int was_present = 0;
if (!mysql_service_udf_registration->udf_unregister(udf.c_str(),
&was_present) ||
!was_present)
delete_set.push_back(udf);
}
/* remove the unregistered ones from the list */
for (auto udf : delete_set) set.remove(udf);
/* success: empty set */
if (set.empty()) return false;
/* failure: entries still in the set */
return true;
}
private:
udf_list_t set;
} * list;
namespace udf_impl {
const char *udf_init = "udf_init", *my_udf = "my_udf",
*my_udf_clear = "my_clear", *my_udf_add = "my_udf_add";
static bool viruscan_udf_init(UDF_INIT *initid, UDF_ARGS *, char *) {
const char* name = "utf8mb4";
char *value = const_cast<char*>(name);
initid->ptr = const_cast<char *>(udf_init);
if (mysql_service_mysql_udf_metadata->result_set(
initid, "charset",
const_cast<char *>(value))) {
LogComponentErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG, "failed to set result charset");
return false;
}
return 0;
}
static void viruscan_udf_deinit(__attribute__((unused)) UDF_INIT *initid) {
assert(initid->ptr == udf_init || initid->ptr == my_udf);
}
const char *viruscan_udf(UDF_INIT *initid, UDF_ARGS *args, char *outp,
unsigned long *length, char *is_null, char *error) {
strcpy(outp, "we do nothing yet");
*length = strlen(outp);
return const_cast<char *>(outp);
}
} /* namespace udf_impl */
static mysql_service_status_t viruscan_service_init() {
mysql_service_status_t result = 0;
log_bi = mysql_service_log_builtins;
log_bs = mysql_service_log_builtins_string;
LogComponentErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG, "initializing…");
// Registration of the privilege
if (mysql_service_dynamic_privilege_register->register_privilege(SCAN_PRIVILEGE_NAME, strlen(SCAN_PRIVILEGE_NAME))) {
LogComponentErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
"could not register privilege 'VIRUS_SCAN'.");
result = 1;
} else {
LogComponentErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG,
"new privilege 'VIRUS_SCAN' has been registered successfully.");
}
list = new udf_list();
if (list->add_scalar("virus_scan", Item_result::STRING_RESULT,
(Udf_func_any)udf_impl::viruscan_udf,
udf_impl::viruscan_udf_init,
udf_impl::viruscan_udf_deinit)) {
delete list;
return 1; /* failure: one of the UDF registrations failed */
}
return result;
}
static mysql_service_status_t viruscan_service_deinit() {
mysql_service_status_t result = 0;
if (mysql_service_dynamic_privilege_register->unregister_privilege(SCAN_PRIVILEGE_NAME, strlen(SCAN_PRIVILEGE_NAME))) {
LogComponentErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
"could not unregister privilege 'VIRUS_SCAN'.");
result = 1;
} else {
LogComponentErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG,
"privilege 'VIRUS_SCAN' has been unregistered successfully.");
}
if (list->unregister()) return 1; /* failure: some UDFs still in use */
delete list;
LogComponentErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG, "uninstalled.");
return result;
}
BEGIN_COMPONENT_PROVIDES(viruscan_service)
END_COMPONENT_PROVIDES();
BEGIN_COMPONENT_REQUIRES(viruscan_service)
REQUIRES_SERVICE(log_builtins),
REQUIRES_SERVICE(log_builtins_string),
REQUIRES_SERVICE(dynamic_privilege_register),
REQUIRES_SERVICE(mysql_udf_metadata),
REQUIRES_SERVICE(udf_registration),
END_COMPONENT_REQUIRES();
/* A list of metadata to describe the Component. */
BEGIN_COMPONENT_METADATA(viruscan_service)
METADATA("mysql.author", "Oracle Corporation"),
METADATA("mysql.license", "GPL"), METADATA("mysql.dev", "lefred"),
END_COMPONENT_METADATA();
/* Declaration of the Component. */
DECLARE_COMPONENT(viruscan_service,
"mysql:viruscan_service")
viruscan_service_init,
viruscan_service_deinit END_DECLARE_COMPONENT();
/* Defines list of Components contained in this library. Note that for now
we assume that library will have exactly one Component. */
DECLARE_LIBRARY_COMPONENTS &COMPONENT_REF(viruscan_service)
END_DECLARE_LIBRARY_COMPONENTS
view raw scan.cc hosted with ❤ by GitHub

On line 34 and 35 we added the place holder and we declare the requirement for our component on lines 178 and 179.

As we plan to have more than one user function with our component, the easiest is to create a list of a udf class. This is what we do from line 41 to 80. The class has public methods to register (add) and remove (unregister) the function.

From line 86 to 110, we implement the functions called during initialization, uninitialization and call of the new component function.

We set the charsert on line 90 via mysql_service_mysql_udf_metadata->result_set.

We use the list class from line 134 to 145. On lines 147 and 166 we remove the function when the component is uninstalled.

We can compile it ( make component_viruscan), we will get some warnings as we don’t use all parameters (we will use them later).

We can already test this version:

Checking the privilege

Now that our function is working, we can of course extend it. The first thing we gonna add is the check for the privilege we created.

For this we will use 3 new services:

  • mysql_thd_security_context
  • global_grants_check
  • mysql_current_thread_reader

We start by adding them in scan.h, the are part of two new includes (lines 26 and 27):

/* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#define LOG_COMPONENT_TAG "viruscan"
#include <mysql/components/component_implementation.h>
#include <mysql/components/services/log_builtins.h> /* LogComponentErr */
#include <mysqld_error.h> /* Errors */
#include <mysql/components/services/dynamic_privilege.h>
#include <mysql/components/services/udf_metadata.h>
#include <mysql/components/services/udf_registration.h>
#include <mysql/components/services/security_context.h>
#include <mysql/components/services/mysql_current_thread_reader.h>
#include <list>
#include <string>
extern REQUIRES_SERVICE_PLACEHOLDER(log_builtins);
extern REQUIRES_SERVICE_PLACEHOLDER(log_builtins_string);
extern REQUIRES_SERVICE_PLACEHOLDER(dynamic_privilege_register);
extern REQUIRES_SERVICE_PLACEHOLDER(udf_registration);
extern REQUIRES_SERVICE_PLACEHOLDER(mysql_udf_metadata);
extern REQUIRES_SERVICE_PLACEHOLDER(mysql_thd_security_context);
extern REQUIRES_SERVICE_PLACEHOLDER(global_grants_check);
extern REQUIRES_SERVICE_PLACEHOLDER(mysql_current_thread_reader);
extern SERVICE_TYPE(log_builtins) * log_bi;
extern SERVICE_TYPE(log_builtins_string) * log_bs;
view raw scan.h hosted with ❤ by GitHub

We added the requires service place holder as usual and we will add them in scan.cc too:

/* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#define LOG_COMPONENT_TAG "viruscan"
#define NO_SIGNATURE_CHANGE 0
#define SIGNATURE_CHANGE 1
#include <components/viruscan/scan.h>
REQUIRES_SERVICE_PLACEHOLDER(log_builtins);
REQUIRES_SERVICE_PLACEHOLDER(log_builtins_string);
REQUIRES_SERVICE_PLACEHOLDER(dynamic_privilege_register);
REQUIRES_SERVICE_PLACEHOLDER(udf_registration);
REQUIRES_SERVICE_PLACEHOLDER(mysql_udf_metadata);
REQUIRES_SERVICE_PLACEHOLDER(mysql_thd_security_context);
REQUIRES_SERVICE_PLACEHOLDER(global_grants_check);
REQUIRES_SERVICE_PLACEHOLDER(mysql_current_thread_reader);
SERVICE_TYPE(log_builtins) * log_bi;
SERVICE_TYPE(log_builtins_string) * log_bs;
static const char *SCAN_PRIVILEGE_NAME = "VIRUS_SCAN";
class udf_list {
typedef std::list<std::string> udf_list_t;
public:
~udf_list() { unregister(); }
bool add_scalar(const char *func_name, enum Item_result return_type,
Udf_func_any func, Udf_func_init init_func = NULL,
Udf_func_deinit deinit_func = NULL) {
if (!mysql_service_udf_registration->udf_register(
func_name, return_type, func, init_func, deinit_func)) {
set.push_back(func_name);
return false;
}
return true;
}
bool unregister() {
udf_list_t delete_set;
/* try to unregister all of the udfs */
for (auto udf : set) {
int was_present = 0;
if (!mysql_service_udf_registration->udf_unregister(udf.c_str(),
&was_present) ||
!was_present)
delete_set.push_back(udf);
}
/* remove the unregistered ones from the list */
for (auto udf : delete_set) set.remove(udf);
/* success: empty set */
if (set.empty()) return false;
/* failure: entries still in the set */
return true;
}
private:
udf_list_t set;
} * list;
namespace udf_impl {
bool have_virus_scan_privilege(void *opaque_thd) {
// get the security context of the thread
Security_context_handle ctx = nullptr;
if (mysql_service_mysql_thd_security_context->get(opaque_thd, &ctx) || !ctx) {
LogComponentErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
"problem trying to get security context");
return false;
}
if (mysql_service_global_grants_check->has_global_grant(
ctx, SCAN_PRIVILEGE_NAME, strlen(SCAN_PRIVILEGE_NAME)))
return true;
return false;
}
const char *udf_init = "udf_init", *my_udf = "my_udf",
*my_udf_clear = "my_clear", *my_udf_add = "my_udf_add";
static bool viruscan_udf_init(UDF_INIT *initid, UDF_ARGS *, char *) {
const char* name = "utf8mb4";
char *value = const_cast<char*>(name);
initid->ptr = const_cast<char *>(udf_init);
if (mysql_service_mysql_udf_metadata->result_set(
initid, "charset",
const_cast<char *>(value))) {
LogComponentErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG, "failed to set result charset");
return false;
}
return 0;
}
static void viruscan_udf_deinit(__attribute__((unused)) UDF_INIT *initid) {
assert(initid->ptr == udf_init || initid->ptr == my_udf);
}
const char *viruscan_udf(UDF_INIT *initid, UDF_ARGS *args, char *outp,
unsigned long *length, char *is_null, char *error) {
MYSQL_THD thd;
mysql_service_mysql_current_thread_reader->get(&thd);
if(!have_virus_scan_privilege(thd)) {
//to handle better next time
LogComponentErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG, "Wrong privilege");
return 0;
}
strcpy(outp, "we do nothing yet");
*length = strlen(outp);
return const_cast<char *>(outp);
}
} /* namespace udf_impl */
static mysql_service_status_t viruscan_service_init() {
mysql_service_status_t result = 0;
log_bi = mysql_service_log_builtins;
log_bs = mysql_service_log_builtins_string;
LogComponentErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG, "initializing…");
// Registration of the privilege
if (mysql_service_dynamic_privilege_register->register_privilege(SCAN_PRIVILEGE_NAME, strlen(SCAN_PRIVILEGE_NAME))) {
LogComponentErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
"could not register privilege 'VIRUS_SCAN'.");
result = 1;
} else {
LogComponentErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG,
"new privilege 'VIRUS_SCAN' has been registered successfully.");
}
list = new udf_list();
if (list->add_scalar("virus_scan", Item_result::STRING_RESULT,
(Udf_func_any)udf_impl::viruscan_udf,
udf_impl::viruscan_udf_init,
udf_impl::viruscan_udf_deinit)) {
delete list;
return 1; /* failure: one of the UDF registrations failed */
}
return result;
}
static mysql_service_status_t viruscan_service_deinit() {
mysql_service_status_t result = 0;
if (mysql_service_dynamic_privilege_register->unregister_privilege(SCAN_PRIVILEGE_NAME, strlen(SCAN_PRIVILEGE_NAME))) {
LogComponentErr(ERROR_LEVEL, ER_LOG_PRINTF_MSG,
"could not unregister privilege 'VIRUS_SCAN'.");
result = 1;
} else {
LogComponentErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG,
"privilege 'VIRUS_SCAN' has been unregistered successfully.");
}
if (list->unregister()) return 1; /* failure: some UDFs still in use */
delete list;
LogComponentErr(INFORMATION_LEVEL, ER_LOG_PRINTF_MSG, "uninstalled.");
return result;
}
BEGIN_COMPONENT_PROVIDES(viruscan_service)
END_COMPONENT_PROVIDES();
BEGIN_COMPONENT_REQUIRES(viruscan_service)
REQUIRES_SERVICE(log_builtins),
REQUIRES_SERVICE(log_builtins_string),
REQUIRES_SERVICE(dynamic_privilege_register),
REQUIRES_SERVICE(mysql_udf_metadata),
REQUIRES_SERVICE(udf_registration),
REQUIRES_SERVICE(mysql_thd_security_context),
REQUIRES_SERVICE(global_grants_check),
REQUIRES_SERVICE(mysql_current_thread_reader),
END_COMPONENT_REQUIRES();
/* A list of metadata to describe the Component. */
BEGIN_COMPONENT_METADATA(viruscan_service)
METADATA("mysql.author", "Oracle Corporation"),
METADATA("mysql.license", "GPL"), METADATA("mysql.dev", "lefred"),
END_COMPONENT_METADATA();
/* Declaration of the Component. */
DECLARE_COMPONENT(viruscan_service,
"mysql:viruscan_service")
viruscan_service_init,
viruscan_service_deinit END_DECLARE_COMPONENT();
/* Defines list of Components contained in this library. Note that for now
we assume that library will have exactly one Component. */
DECLARE_LIBRARY_COMPONENTS &COMPONENT_REF(viruscan_service)
END_DECLARE_LIBRARY_COMPONENTS
view raw scan.cc hosted with ❤ by GitHub

On line 88 we created a new boolean function that checks if the user in the pointed thd has the required privilege (VIRUS_SCAN) or not.

Then on line 130 we extend the function viruscan_udf to use our new have_virus_scan_privilege function.

You can notice that we need to pass the thd we get on line 128.

As usual we need to update the required services for our component on lines 210 to 212.

We can compile the component and test it:

Conclusion

In this article we covered how to create a function that we can call in MySQL. We also saw how to check for a privilege.

Currently we only print a message in error log, on the next article, we will extend this by printing a better message in error log but also return a message to the user.

Keep coding and enjoy MySQL !

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

6 Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.