summaryrefslogtreecommitdiffstats
path: root/plugin/feedback
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/feedback')
-rw-r--r--plugin/feedback/CMakeLists.txt23
-rw-r--r--plugin/feedback/feedback.cc428
-rw-r--r--plugin/feedback/feedback.h79
-rw-r--r--plugin/feedback/sender_thread.cc298
-rw-r--r--plugin/feedback/url_base.cc96
-rw-r--r--plugin/feedback/url_http.cc341
-rw-r--r--plugin/feedback/utils.cc442
7 files changed, 1707 insertions, 0 deletions
diff --git a/plugin/feedback/CMakeLists.txt b/plugin/feedback/CMakeLists.txt
new file mode 100644
index 00000000..2103250e
--- /dev/null
+++ b/plugin/feedback/CMakeLists.txt
@@ -0,0 +1,23 @@
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/sql
+ ${PCRE_INCLUDES}
+ ${SSL_INCLUDE_DIRS})
+
+SET(FEEDBACK_SOURCES feedback.cc sender_thread.cc
+ url_base.cc url_http.cc utils.cc)
+
+ADD_DEFINITIONS(${SSL_DEFINES})
+
+INCLUDE (CheckIncludeFiles)
+CHECK_INCLUDE_FILES (netdb.h HAVE_NETDB_H)
+IF(HAVE_NETDB_H)
+ ADD_DEFINITIONS(-DHAVE_NETDB_H)
+ENDIF(HAVE_NETDB_H)
+
+IF(WIN32)
+ SET(MAYBE_STATIC_ONLY STATIC_ONLY)
+ENDIF(WIN32)
+
+MYSQL_ADD_PLUGIN(FEEDBACK ${FEEDBACK_SOURCES}
+ LINK_LIBRARIES ${SSL_LIBRARIES}
+ ${MAYBE_STATIC_ONLY} RECOMPILE_FOR_EMBEDDED DEFAULT)
+
diff --git a/plugin/feedback/feedback.cc b/plugin/feedback/feedback.cc
new file mode 100644
index 00000000..eb3f562f
--- /dev/null
+++ b/plugin/feedback/feedback.cc
@@ -0,0 +1,428 @@
+/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include "feedback.h"
+
+/* MySQL functions/variables not declared in mysql_priv.h */
+int fill_variables(THD *thd, TABLE_LIST *tables, COND *cond);
+int fill_status(THD *thd, TABLE_LIST *tables, COND *cond);
+extern ST_SCHEMA_TABLE schema_tables[];
+
+namespace feedback {
+
+#ifndef DBUG_OFF
+ulong debug_startup_interval, debug_first_interval, debug_interval;
+#endif
+
+char server_uid_buf[SERVER_UID_SIZE+1]; ///< server uid will be written here
+
+/* backing store for system variables */
+static char *server_uid= server_uid_buf, *url, *http_proxy;
+char *user_info;
+ulong send_timeout, send_retry_wait;
+
+/**
+ these three are used to communicate the shutdown signal to the
+ background thread
+*/
+mysql_mutex_t sleep_mutex;
+mysql_cond_t sleep_condition;
+volatile bool shutdown_plugin;
+static pthread_t sender_thread;
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_sleep_mutex;
+static PSI_mutex_info mutex_list[]=
+{{ &key_sleep_mutex, "sleep_mutex", PSI_FLAG_GLOBAL}};
+
+static PSI_cond_key key_sleep_cond;
+static PSI_cond_info cond_list[]=
+{{ &key_sleep_cond, "sleep_condition", PSI_FLAG_GLOBAL}};
+
+static PSI_thread_key key_sender_thread;
+static PSI_thread_info thread_list[] =
+{{&key_sender_thread, "sender_thread", 0}};
+#endif
+
+Url **urls; ///< list of urls to send the report to
+uint url_count;
+
+ST_SCHEMA_TABLE *i_s_feedback; ///< table descriptor for our I_S table
+
+/*
+ the column names *must* match column names in GLOBAL_VARIABLES and
+ GLOBAL_STATUS tables otherwise condition pushdown below will not work
+*/
+static ST_FIELD_INFO feedback_fields[] =
+{
+ Show::Column("VARIABLE_NAME", Show::Varchar(255), NOT_NULL),
+ Show::Column("VARIABLE_VALUE", Show::Varchar(1024), NOT_NULL),
+ Show::CEnd()
+};
+
+static COND * const OOM= (COND*)1;
+
+/**
+ Generate the COND tree for the condition pushdown
+
+ This function takes a list of strings and generates an Item tree
+ corresponding to the following expression:
+
+ field LIKE str1 OR field LIKE str2 OR field LIKE str3 OR ...
+
+ where 'field' is the first field in the table - VARIABLE_NAME field -
+ and str1, str2... are strings from the list.
+
+ This condition is used to filter the selected rows, emulating
+
+ SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE ...
+*/
+static COND* make_cond(THD *thd, TABLE_LIST *tables, LEX_STRING *filter)
+{
+ Item_cond_or *res= NULL;
+ Name_resolution_context nrc;
+ LEX_CSTRING &db= tables->db;
+ LEX_CSTRING &table= tables->alias;
+ LEX_CSTRING &field= tables->table->field[0]->field_name;
+ CHARSET_INFO *cs= &my_charset_latin1;
+
+ if (!filter->str)
+ return 0;
+
+ nrc.init();
+ nrc.resolve_in_table_list_only(tables);
+
+ res= new (thd->mem_root) Item_cond_or(thd);
+ if (!res)
+ return OOM;
+
+ for (; filter->str; filter++)
+ {
+ Item_field *fld= new (thd->mem_root) Item_field(thd, &nrc, db, table,
+ field);
+ Item_string *pattern= new (thd->mem_root) Item_string(thd, filter->str,
+ (uint) filter->length, cs);
+ Item_string *escape= new (thd->mem_root) Item_string(thd, "\\", 1, cs);
+
+ if (!fld || !pattern || !escape)
+ return OOM;
+
+ Item_func_like *like= new (thd->mem_root) Item_func_like(thd, fld, pattern,
+ escape, 0);
+
+ if (!like)
+ return OOM;
+
+ res->add(like, thd->mem_root);
+ }
+
+ if (res->fix_fields(thd, (Item**)&res))
+ return OOM;
+
+ return res;
+}
+
+/**
+ System variables that we want to see in the feedback report
+*/
+static LEX_STRING vars_filter[]= {
+ {C_STRING_WITH_LEN("auto\\_increment%")},
+ {C_STRING_WITH_LEN("binlog\\_format")},
+ {C_STRING_WITH_LEN("character\\_set\\_%")},
+ {C_STRING_WITH_LEN("collation%")},
+ {C_STRING_WITH_LEN("engine\\_condition\\_pushdown")},
+ {C_STRING_WITH_LEN("event\\_scheduler")},
+ {C_STRING_WITH_LEN("feedback\\_%")},
+ {C_STRING_WITH_LEN("ft\\_m%")},
+ {C_STRING_WITH_LEN("have\\_%")},
+ {C_STRING_WITH_LEN("%\\_size")},
+ {C_STRING_WITH_LEN("innodb_f%")},
+ {C_STRING_WITH_LEN("%\\_length%")},
+ {C_STRING_WITH_LEN("%\\_timeout")},
+ {C_STRING_WITH_LEN("large\\_%")},
+ {C_STRING_WITH_LEN("lc_time_names")},
+ {C_STRING_WITH_LEN("log")},
+ {C_STRING_WITH_LEN("log_bin")},
+ {C_STRING_WITH_LEN("log_output")},
+ {C_STRING_WITH_LEN("log_slow_queries")},
+ {C_STRING_WITH_LEN("log_slow_time")},
+ {C_STRING_WITH_LEN("lower_case%")},
+ {C_STRING_WITH_LEN("max_allowed_packet")},
+ {C_STRING_WITH_LEN("max_connections")},
+ {C_STRING_WITH_LEN("max_prepared_stmt_count")},
+ {C_STRING_WITH_LEN("max_sp_recursion_depth")},
+ {C_STRING_WITH_LEN("max_user_connections")},
+ {C_STRING_WITH_LEN("max_write_lock_count")},
+ {C_STRING_WITH_LEN("myisam_recover_options")},
+ {C_STRING_WITH_LEN("myisam_repair_threads")},
+ {C_STRING_WITH_LEN("myisam_stats_method")},
+ {C_STRING_WITH_LEN("myisam_use_mmap")},
+ {C_STRING_WITH_LEN("net\\_%")},
+ {C_STRING_WITH_LEN("new")},
+ {C_STRING_WITH_LEN("old%")},
+ {C_STRING_WITH_LEN("optimizer%")},
+ {C_STRING_WITH_LEN("profiling")},
+ {C_STRING_WITH_LEN("query_cache%")},
+ {C_STRING_WITH_LEN("secure_auth")},
+ {C_STRING_WITH_LEN("slow_launch_time")},
+ {C_STRING_WITH_LEN("sql%")},
+ {C_STRING_WITH_LEN("default_storage_engine")},
+ {C_STRING_WITH_LEN("sync_binlog")},
+ {C_STRING_WITH_LEN("table_definition_cache")},
+ {C_STRING_WITH_LEN("table_open_cache")},
+ {C_STRING_WITH_LEN("thread_handling")},
+ {C_STRING_WITH_LEN("time_zone")},
+ {C_STRING_WITH_LEN("version%")},
+ {0, 0}
+};
+
+/**
+ Status variables that we want to see in the feedback report
+
+ (empty list = no WHERE condition)
+*/
+static LEX_STRING status_filter[]= {{0, 0}};
+
+/**
+ Fill our I_S table with data
+
+ This function works by invoking fill_variables() and
+ fill_status() of the corresponding I_S tables - to have
+ their data UNION-ed in the same target table.
+ After that it invokes our own fill_* functions
+ from the utils.cc - to get the data that aren't available in the
+ I_S.GLOBAL_VARIABLES and I_S.GLOBAL_STATUS.
+*/
+int fill_feedback(THD *thd, TABLE_LIST *tables, COND *unused)
+{
+ int res;
+ COND *cond;
+
+ tables->schema_table= schema_tables + SCH_GLOBAL_VARIABLES;
+ cond= make_cond(thd, tables, vars_filter);
+ res= (cond == OOM) ? 1 : fill_variables(thd, tables, cond);
+
+ tables->schema_table= schema_tables + SCH_GLOBAL_STATUS;
+ if (!res)
+ {
+ cond= make_cond(thd, tables, status_filter);
+ res= (cond == OOM) ? 1 : fill_status(thd, tables, cond);
+ }
+
+ tables->schema_table= i_s_feedback;
+ res= res || fill_plugin_version(thd, tables)
+ || fill_misc_data(thd, tables)
+ || fill_linux_info(thd, tables)
+ || fill_collation_statistics(thd, tables);
+
+ return res;
+}
+
+/**
+ plugin initialization function
+*/
+static int init(void *p)
+{
+ i_s_feedback= (ST_SCHEMA_TABLE*) p;
+ /* initialize the I_S descriptor structure */
+ i_s_feedback->fields_info= feedback_fields; ///< field descriptor
+ i_s_feedback->fill_table= fill_feedback; ///< how to fill the I_S table
+ i_s_feedback->idx_field1 = 0; ///< virtual index on the 1st col
+
+#ifdef HAVE_PSI_INTERFACE
+#define PSI_register(X) \
+ if(PSI_server) PSI_server->register_ ## X("feedback", X ## _list, array_elements(X ## _list))
+#else
+#define PSI_register(X) /* no-op */
+#endif
+
+ PSI_register(mutex);
+ PSI_register(cond);
+ PSI_register(thread);
+
+ if (calculate_server_uid(server_uid_buf))
+ return 1;
+
+ prepare_linux_info();
+
+#ifndef DBUG_OFF
+ if (startup_interval != debug_startup_interval ||
+ first_interval != debug_first_interval ||
+ interval != debug_interval)
+ {
+ startup_interval= debug_startup_interval;
+ first_interval= debug_first_interval;
+ interval= debug_interval;
+ user_info= const_cast<char*>("mysql-test");
+ }
+#endif
+
+ url_count= 0;
+ if (*url)
+ {
+ // now we split url on spaces and store them in Url objects
+ int slot;
+ char *s, *e;
+
+ for (s= url, url_count= 1; *s; s++)
+ if (*s == ' ')
+ url_count++;
+
+ urls= (Url **)my_malloc(PSI_INSTRUMENT_ME, url_count*sizeof(Url*), MYF(MY_WME));
+ if (!urls)
+ return 1;
+
+ for (s= url, e = url+1, slot= 0; e[-1]; e++)
+ if (*e == 0 || *e == ' ')
+ {
+ if (e > s && (urls[slot]= Url::create(s, e - s)))
+ {
+ if (urls[slot]->set_proxy(http_proxy,
+ http_proxy ? strlen(http_proxy) : 0))
+ sql_print_error("feedback plugin: invalid proxy '%s'",
+ http_proxy ? http_proxy : "");
+ slot++;
+ }
+ else
+ {
+ if (e > s)
+ sql_print_error("feedback plugin: invalid url '%.*s'", (int)(e-s), s);
+ url_count--;
+ }
+ s= e + 1;
+ }
+
+ // create a background thread to handle urls, if any
+ if (url_count)
+ {
+ mysql_mutex_init(0, &sleep_mutex, 0);
+ mysql_cond_init(0, &sleep_condition, 0);
+ shutdown_plugin= false;
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ if (pthread_create(&sender_thread, &attr, background_thread, 0) != 0)
+ {
+ sql_print_error("feedback plugin: failed to start a background thread");
+ return 1;
+ }
+ }
+ else
+ my_free(urls);
+ }
+
+ return 0;
+}
+
+/**
+ plugin deinitialization function
+*/
+static int free(void *p)
+{
+ if (url_count)
+ {
+ mysql_mutex_lock(&sleep_mutex);
+ shutdown_plugin= true;
+ mysql_cond_signal(&sleep_condition);
+ mysql_mutex_unlock(&sleep_mutex);
+ pthread_join(sender_thread, NULL);
+
+ mysql_mutex_destroy(&sleep_mutex);
+ mysql_cond_destroy(&sleep_condition);
+
+ for (uint i= 0; i < url_count; i++)
+ delete urls[i];
+ my_free(urls);
+ }
+ return 0;
+}
+
+#ifdef HAVE_OPENSSL
+#define DEFAULT_PROTO "https://"
+#else
+#define DEFAULT_PROTO "http://"
+#endif
+
+static MYSQL_SYSVAR_STR(server_uid, server_uid,
+ PLUGIN_VAR_READONLY | PLUGIN_VAR_NOCMDOPT,
+ "Automatically calculated server unique id hash.", NULL, NULL, 0);
+static MYSQL_SYSVAR_STR(user_info, user_info,
+ PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG,
+ "User specified string that will be included in the feedback report.",
+ NULL, NULL, "");
+static MYSQL_SYSVAR_STR(url, url, PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG,
+ "Space separated URLs to send the feedback report to.", NULL, NULL,
+ DEFAULT_PROTO "mariadb.org/feedback_plugin/post");
+static MYSQL_SYSVAR_ULONG(send_timeout, send_timeout, PLUGIN_VAR_RQCMDARG,
+ "Timeout (in seconds) for the sending the report.",
+ NULL, NULL, 60, 1, 60*60*24, 1);
+static MYSQL_SYSVAR_ULONG(send_retry_wait, send_retry_wait, PLUGIN_VAR_RQCMDARG,
+ "Wait this many seconds before retrying a failed send.",
+ NULL, NULL, 60, 1, 60*60*24, 1);
+static MYSQL_SYSVAR_STR(http_proxy, http_proxy,
+ PLUGIN_VAR_READONLY | PLUGIN_VAR_RQCMDARG,
+ "Proxy server host:port.", NULL, NULL,0);
+
+#ifndef DBUG_OFF
+static MYSQL_SYSVAR_ULONG(debug_startup_interval, debug_startup_interval,
+ PLUGIN_VAR_RQCMDARG, "for debugging only",
+ NULL, NULL, startup_interval, 1, INT_MAX32, 1);
+static MYSQL_SYSVAR_ULONG(debug_first_interval, debug_first_interval,
+ PLUGIN_VAR_RQCMDARG, "for debugging only",
+ NULL, NULL, first_interval, 1, INT_MAX32, 1);
+static MYSQL_SYSVAR_ULONG(debug_interval, debug_interval,
+ PLUGIN_VAR_RQCMDARG, "for debugging only",
+ NULL, NULL, interval, 1, INT_MAX32, 1);
+#endif
+
+static struct st_mysql_sys_var* settings[] = {
+ MYSQL_SYSVAR(server_uid),
+ MYSQL_SYSVAR(user_info),
+ MYSQL_SYSVAR(url),
+ MYSQL_SYSVAR(send_timeout),
+ MYSQL_SYSVAR(send_retry_wait),
+ MYSQL_SYSVAR(http_proxy),
+#ifndef DBUG_OFF
+ MYSQL_SYSVAR(debug_startup_interval),
+ MYSQL_SYSVAR(debug_first_interval),
+ MYSQL_SYSVAR(debug_interval),
+#endif
+ NULL
+};
+
+
+static struct st_mysql_information_schema feedback =
+{ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION };
+
+} // namespace feedback
+
+maria_declare_plugin(feedback)
+{
+ MYSQL_INFORMATION_SCHEMA_PLUGIN,
+ &feedback::feedback,
+ "FEEDBACK",
+ "Sergei Golubchik",
+ "MariaDB User Feedback Plugin",
+ PLUGIN_LICENSE_GPL,
+ feedback::init,
+ feedback::free,
+ 0x0101,
+ NULL,
+ feedback::settings,
+ "1.1",
+ MariaDB_PLUGIN_MATURITY_STABLE
+}
+maria_declare_plugin_end;
diff --git a/plugin/feedback/feedback.h b/plugin/feedback/feedback.h
new file mode 100644
index 00000000..04fe1ab6
--- /dev/null
+++ b/plugin/feedback/feedback.h
@@ -0,0 +1,79 @@
+/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#define MYSQL_SERVER 1
+#include <my_global.h>
+#include <sql_class.h>
+
+namespace feedback {
+
+int fill_feedback(THD *thd, TABLE_LIST *tables, COND *cond);
+int fill_plugin_version(THD *thd, TABLE_LIST *tables);
+int fill_misc_data(THD *thd, TABLE_LIST *tables);
+int fill_linux_info(THD *thd, TABLE_LIST *tables);
+int fill_collation_statistics(THD *thd, TABLE_LIST *tables);
+
+static const int SERVER_UID_SIZE= 29;
+extern char server_uid_buf[SERVER_UID_SIZE+1], *user_info;
+int calculate_server_uid(char *);
+int prepare_linux_info();
+
+extern ST_SCHEMA_TABLE *i_s_feedback;
+
+extern ulong send_timeout, send_retry_wait;
+
+pthread_handler_t background_thread(void *arg);
+
+/**
+ The class for storing urls to send report data to.
+
+ Constructors are private, the object should be created with create() method.
+ send() method does the actual sending.
+*/
+class Url {
+ protected:
+ Url(LEX_STRING &url_arg) : full_url(url_arg) {}
+ const LEX_STRING full_url;
+
+ public:
+ virtual ~Url() { my_free(full_url.str); }
+
+ const char *url() { return full_url.str; }
+ size_t url_length() { return full_url.length; }
+ virtual int send(const char* data, size_t data_length) = 0;
+ virtual int set_proxy(const char *proxy, size_t proxy_len)
+ {
+ return 0;
+ }
+
+ static Url* create(const char *url, size_t url_length);
+ static int parse_proxy_server(const char *proxy_server, size_t proxy_length,
+ LEX_STRING *host, LEX_STRING *port);
+};
+
+extern Url **urls;
+extern uint url_count;
+
+extern ulong startup_interval;
+extern ulong first_interval;
+extern ulong interval;
+
+/* these are used to communicate with the background thread */
+extern mysql_mutex_t sleep_mutex;
+extern mysql_cond_t sleep_condition;
+extern volatile bool shutdown_plugin;
+
+} // namespace feedback
+
diff --git a/plugin/feedback/sender_thread.cc b/plugin/feedback/sender_thread.cc
new file mode 100644
index 00000000..73b1bc67
--- /dev/null
+++ b/plugin/feedback/sender_thread.cc
@@ -0,0 +1,298 @@
+/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include "feedback.h"
+#include <sql_acl.h>
+#include <sql_parse.h>
+#include <sql_show.h>
+#include <time.h>
+
+namespace feedback {
+
+static THD *thd= 0; ///< background thread thd
+static my_thread_id thd_thread_id; ///< its thread_id
+
+static size_t needed_size= 20480;
+
+ulong startup_interval= 60*5; ///< in seconds (5 minutes)
+ulong first_interval= 60*60*24; ///< in seconds (one day)
+ulong interval= 60*60*24*7; ///< in seconds (one week)
+
+/**
+ reads the rows from a table and puts them, concatenated, in a String
+
+ @note
+ 1. only supports two column tables - no less, no more.
+ 2. it emulates mysql -e "select * from..." and thus it separates
+ columns with \t and starts the output with column names.
+*/
+static int table_to_string(TABLE *table, String *result)
+{
+ int res;
+ char buff1[MAX_FIELD_WIDTH], buff2[MAX_FIELD_WIDTH];
+ String str1(buff1, sizeof(buff1), system_charset_info);
+ String str2(buff2, sizeof(buff2), system_charset_info);
+
+ res= table->file->ha_rnd_init(1);
+
+ dbug_tmp_use_all_columns(table, &table->read_set);
+
+ while(!res && !table->file->ha_rnd_next(table->record[0]))
+ {
+ table->field[0]->val_str(&str1);
+ table->field[1]->val_str(&str2);
+ if (result->reserve(str1.length() + str2.length() + 3))
+ res= 1;
+ else
+ {
+ result->qs_append(str1.ptr(), str1.length());
+ result->qs_append('\t');
+ result->qs_append(str2.ptr(), str2.length());
+ result->qs_append('\n');
+ }
+ }
+
+ res = res || (int)result->append('\n');
+
+ /*
+ Note, "|=" and not "||" - because we want to call ha_rnd_end()
+ even if res is already 1.
+ */
+ res |= table->file->ha_rnd_end();
+
+ return res;
+}
+
+/**
+ Initialize the THD and TABLE_LIST
+
+ The structures must be sufficiently initialized for create_tmp_table()
+ and fill_feedback() to work.
+*/
+static int prepare_for_fill(TABLE_LIST *tables)
+{
+ /*
+ Add our thd to the list, for it to be visible in SHOW PROCESSLIST.
+ But don't generate thread_id every time - use the saved value
+ (every increment of global thread_id counts as a new connection
+ in SHOW STATUS and we want to avoid skewing the statistics)
+ */
+ thd->variables.pseudo_thread_id= thd->thread_id;
+ server_threads.insert(thd);
+ thd->thread_stack= (char*) &tables;
+ thd->store_globals();
+
+ thd->mysys_var->current_cond= &sleep_condition;
+ thd->mysys_var->current_mutex= &sleep_mutex;
+ thd->proc_info="feedback";
+ thd->set_command(COM_SLEEP);
+ thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; // whatever
+ thd->set_time();
+ thd->init_for_queries();
+ thd->real_id= pthread_self();
+ thd->db= null_clex_str;
+ thd->security_ctx->host_or_ip= "";
+ thd->security_ctx->db_access= DB_ACLS;
+ thd->security_ctx->master_access= ALL_KNOWN_ACL;
+ bzero((char*) &thd->net, sizeof(thd->net));
+ lex_start(thd);
+ mysql_init_select(thd->lex);
+
+ LEX_CSTRING tbl_name= {i_s_feedback->table_name, strlen(i_s_feedback->table_name) };
+
+ tables->init_one_table(&INFORMATION_SCHEMA_NAME, &tbl_name, 0, TL_READ);
+ tables->schema_table= i_s_feedback;
+ tables->schema_table_reformed= 1;
+ tables->select_lex= thd->lex->first_select_lex();
+ DBUG_ASSERT(tables->select_lex);
+ tables->table= create_schema_table(thd, tables);
+ if (!tables->table)
+ return 1;
+
+ tables->table->pos_in_table_list= tables;
+
+ return 0;
+}
+
+/**
+ Try to detect if this thread is going down
+
+ which can happen for different reasons:
+ * plugin is being unloaded
+ * mysqld server is being shut down
+ * the thread is being killed
+
+*/
+static bool going_down()
+{
+ return shutdown_plugin || abort_loop || (thd && thd->killed);
+}
+
+/**
+ just like sleep, but waits on a condition and checks "plugin shutdown" status
+*/
+static int slept_ok(time_t sec)
+{
+ struct timespec abstime;
+ int ret= 0;
+
+ set_timespec(abstime, sec);
+
+ mysql_mutex_lock(&sleep_mutex);
+ while (!going_down() && ret != ETIMEDOUT)
+ ret= mysql_cond_timedwait(&sleep_condition, &sleep_mutex, &abstime);
+ mysql_mutex_unlock(&sleep_mutex);
+
+ return !going_down();
+}
+
+/**
+ create a feedback report and send it to all specified urls
+
+ If "when" argument is not null, only it and the server uid are sent.
+ Otherwise a full report is generated.
+*/
+static void send_report(const char *when)
+{
+ TABLE_LIST tables;
+ String str;
+ int i, last_todo;
+ Url **todo= (Url**)alloca(url_count*sizeof(Url*));
+
+ str.alloc(needed_size); // preallocate it to avoid many small mallocs
+
+ /*
+ on startup and shutdown the server may not be completely
+ initialized, and full report won't work.
+ We send a short status notice only.
+ */
+ if (when)
+ {
+ str.length(0);
+ str.append(STRING_WITH_LEN("FEEDBACK_SERVER_UID"));
+ str.append('\t');
+ str.append(server_uid_buf);
+ str.append('\n');
+ str.append(STRING_WITH_LEN("FEEDBACK_WHEN"));
+ str.append('\t');
+ str.append(when);
+ str.append('\n');
+ str.append(STRING_WITH_LEN("FEEDBACK_USER_INFO"));
+ str.append('\t');
+ str.append(user_info);
+ str.append('\n');
+ str.append('\n');
+ }
+ else
+ {
+ /*
+ otherwise, prepare the THD and TABLE_LIST,
+ create and fill the temporary table with data just like
+ SELECT * FROM INFORMATION_SCHEMA.FEEDBACK is doing,
+ read and concatenate table data into a String.
+ */
+ if (!(thd= new THD(thd_thread_id)))
+ return;
+
+ if (prepare_for_fill(&tables))
+ goto ret;
+
+ if (fill_feedback(thd, &tables, NULL))
+ goto ret;
+
+ if (table_to_string(tables.table, &str))
+ goto ret;
+
+ needed_size= (size_t)(str.length() * 1.1);
+
+ free_tmp_table(thd, tables.table);
+ tables.table= 0;
+ }
+
+ /*
+ Try to send the report on every url from the list, remove url on success,
+ keep failed in the list. Repeat until the list is empty.
+ */
+ memcpy(todo, urls, url_count*sizeof(Url*));
+ last_todo= url_count - 1;
+ do
+ {
+ for (i= 0; i <= last_todo;)
+ {
+ Url *url= todo[i];
+
+ if (thd) // for nicer SHOW PROCESSLIST
+ thd->set_query(const_cast<char*>(url->url()), (uint) url->url_length());
+
+ if (url->send(str.ptr(), str.length()))
+ i++;
+ else
+ todo[i]= todo[last_todo--];
+ }
+ if (last_todo < 0)
+ break;
+ } while (slept_ok(send_retry_wait)); // wait a little bit before retrying
+
+ret:
+ if (thd)
+ {
+ if (tables.table)
+ free_tmp_table(thd, tables.table);
+ thd->cleanup_after_query();
+ /*
+ clean up, free the thd.
+ reset all thread local status variables to minimize
+ the effect of the background thread on SHOW STATUS.
+ */
+ server_threads.erase(thd);
+ thd->set_status_var_init();
+ thd->killed= KILL_CONNECTION;
+ delete thd;
+ thd= 0;
+ }
+}
+
+/**
+ background sending thread
+*/
+pthread_handler_t background_thread(void *arg __attribute__((unused)))
+{
+ if (my_thread_init())
+ return 0;
+
+ thd_thread_id= next_thread_id();
+
+ if (slept_ok(startup_interval))
+ {
+ send_report("startup");
+
+ if (slept_ok(first_interval))
+ {
+ send_report(NULL);
+
+ while(slept_ok(interval))
+ send_report(NULL);
+ }
+
+ send_report("shutdown");
+ }
+
+ my_thread_end();
+ pthread_exit(0);
+ return 0;
+}
+
+} // namespace feedback
+
diff --git a/plugin/feedback/url_base.cc b/plugin/feedback/url_base.cc
new file mode 100644
index 00000000..1ba21306
--- /dev/null
+++ b/plugin/feedback/url_base.cc
@@ -0,0 +1,96 @@
+/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include "feedback.h"
+
+namespace feedback {
+
+Url* http_create(const char *url, size_t url_length);
+
+/**
+ creates an Url object out of an url, if possible.
+
+ This is done by invoking corresponding creator functions
+ of the derived classes, until the first not NULL result.
+*/
+Url* Url::create(const char *url, size_t url_length)
+{
+ url= my_strndup(PSI_INSTRUMENT_ME, url, url_length, MYF(MY_WME));
+
+ if (!url)
+ return NULL;
+
+ Url *self= http_create(url, url_length);
+
+ /*
+ here we can add
+
+ if (!self) self= smtp_create(url, url_length);
+ if (!self) self= tftp_create(url, url_length);
+ etc
+ */
+
+ if (!self)
+ my_free(const_cast<char*>(url));
+
+ return self;
+}
+
+int Url::parse_proxy_server(const char *proxy_server, size_t proxy_length,
+ LEX_STRING *host, LEX_STRING *port)
+{
+ const char *s;
+
+ host->length= 0;
+ if (proxy_server == NULL)
+ return 0;
+
+ for (; proxy_length > 0 && my_isspace(system_charset_info, *proxy_server);
+ proxy_server++, proxy_length--) /* no-op */;
+
+ if (proxy_length == 0)
+ return 0;
+
+ for (s=proxy_server; *s && *s != ':'; s++) /* no-op */;
+
+ host->str= const_cast<char*>(proxy_server);
+ if ((host->length= s-proxy_server) == 0)
+ return 0;
+
+ port->length= 0;
+
+ if (*s == ':')
+ {
+ s++;
+ port->str= const_cast<char*>(s);
+ while (*s >= '0' && *s <= '9')
+ {
+ s++;
+ port->length++;
+ }
+ }
+
+ if (port->length == 0)
+ {
+ port->str= const_cast<char*>("80");
+ port->length= 2;
+ }
+
+ host->str= my_strndup(PSI_INSTRUMENT_ME, host->str, host->length, MYF(MY_WME));
+ port->str= my_strndup(PSI_INSTRUMENT_ME, port->str, port->length, MYF(MY_WME));
+ return 0;
+}
+
+} // namespace feedback
diff --git a/plugin/feedback/url_http.cc b/plugin/feedback/url_http.cc
new file mode 100644
index 00000000..98116dd0
--- /dev/null
+++ b/plugin/feedback/url_http.cc
@@ -0,0 +1,341 @@
+/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include "feedback.h"
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef _WIN32
+#include <ws2tcpip.h>
+#define addrinfo ADDRINFOA
+#endif
+
+namespace feedback {
+
+static const uint FOR_READING= 0;
+static const uint FOR_WRITING= 1;
+
+/**
+ implementation of the Url class that sends the data via HTTP POST request.
+
+ Both http:// and https:// protocols are supported.
+*/
+class Url_http: public Url {
+ protected:
+ const LEX_STRING host, port, path;
+ bool ssl;
+ LEX_STRING proxy_host, proxy_port;
+
+ bool use_proxy()
+ {
+ return proxy_host.length != 0;
+ }
+
+ Url_http(LEX_STRING &url_arg, LEX_STRING &host_arg,
+ LEX_STRING &port_arg, LEX_STRING &path_arg, bool ssl_arg) :
+ Url(url_arg), host(host_arg), port(port_arg), path(path_arg), ssl(ssl_arg)
+ {
+ proxy_host.length= 0;
+ }
+ ~Url_http()
+ {
+ my_free(host.str);
+ my_free(port.str);
+ my_free(path.str);
+ set_proxy(0,0);
+ }
+
+ public:
+ int send(const char* data, size_t data_length);
+ int set_proxy(const char *proxy, size_t proxy_len)
+ {
+ if (use_proxy())
+ {
+ my_free(proxy_host.str);
+ my_free(proxy_port.str);
+ }
+
+ return parse_proxy_server(proxy, proxy_len, &proxy_host, &proxy_port);
+ }
+
+ friend Url* http_create(const char *url, size_t url_length);
+};
+
+/**
+ create a Url_http object out of the url, if possible.
+
+ @note
+ Arbitrary limitations here.
+
+ The url must be http[s]://hostname[:port]/path
+ No username:password@ or ?script=parameters are supported.
+
+ But it's ok. This is not a generic purpose www browser - it only needs to be
+ good enough to POST the data to mariadb.org.
+*/
+Url* http_create(const char *url, size_t url_length)
+{
+ const char *s;
+ LEX_STRING full_url= {const_cast<char*>(url), url_length};
+ LEX_STRING host, port, path;
+ bool ssl= false;
+
+ if (is_prefix(url, "http://"))
+ s= url + 7;
+#ifdef HAVE_OPENSSL
+ else if (is_prefix(url, "https://"))
+ {
+ ssl= true;
+ s= url + 8;
+ }
+#endif
+ else
+ return NULL;
+
+ for (url= s; *s && *s != ':' && *s != '/'; s++) /* no-op */;
+ host.str= const_cast<char*>(url);
+ host.length= s-url;
+
+ if (*s == ':')
+ {
+ for (url= ++s; *s && *s >= '0' && *s <= '9'; s++) /* no-op */;
+ port.str= const_cast<char*>(url);
+ port.length= s-url;
+ }
+ else
+ {
+ if (ssl)
+ {
+ port.str= const_cast<char*>("443");
+ port.length=3;
+ }
+ else
+ {
+ port.str= const_cast<char*>("80");
+ port.length=2;
+ }
+ }
+
+ if (*s == 0)
+ {
+ path.str= const_cast<char*>("/");
+ path.length= 1;
+ }
+ else
+ {
+ path.str= const_cast<char*>(s);
+ path.length= strlen(s);
+ }
+ if (!host.length || !port.length || path.str[0] != '/')
+ return NULL;
+
+ host.str= my_strndup(PSI_INSTRUMENT_ME, host.str, host.length, MYF(MY_WME));
+ port.str= my_strndup(PSI_INSTRUMENT_ME, port.str, port.length, MYF(MY_WME));
+ path.str= my_strndup(PSI_INSTRUMENT_ME, path.str, path.length, MYF(MY_WME));
+
+ if (!host.str || !port.str || !path.str)
+ {
+ my_free(host.str);
+ my_free(port.str);
+ my_free(path.str);
+ return NULL;
+ }
+
+ return new Url_http(full_url, host, port, path, ssl);
+}
+
+/* do the vio_write and check that all data were sent ok */
+#define write_check(VIO, DATA, LEN) \
+ (vio_write((VIO), (uchar*)(DATA), (LEN)) != (LEN))
+
+int Url_http::send(const char* data, size_t data_length)
+{
+ my_socket fd= INVALID_SOCKET;
+ char buf[1024];
+ size_t len= 0;
+
+ addrinfo *addrs, *addr, filter= {0, AF_UNSPEC, SOCK_STREAM, 6, 0, 0, 0, 0};
+ int res= use_proxy() ?
+ getaddrinfo(proxy_host.str, proxy_port.str, &filter, &addrs) :
+ getaddrinfo(host.str, port.str, &filter, &addrs);
+
+ if (res)
+ {
+ sql_print_error("feedback plugin: getaddrinfo() failed for url '%s': %s",
+ full_url.str, gai_strerror(res));
+ return 1;
+ }
+
+ for (addr= addrs; addr != NULL; addr= addr->ai_next)
+ {
+ fd= socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+ if (fd == INVALID_SOCKET)
+ continue;
+
+ if (connect(fd, addr->ai_addr, (int) addr->ai_addrlen) == 0)
+ break;
+
+ closesocket(fd);
+ fd= INVALID_SOCKET;
+ }
+
+ freeaddrinfo(addrs);
+
+ if (fd == INVALID_SOCKET)
+ {
+ sql_print_error("feedback plugin: could not connect for url '%s'",
+ full_url.str);
+ return 1;
+ }
+
+ Vio *vio= vio_new(fd, VIO_TYPE_TCPIP, 0);
+ if (!vio)
+ {
+ sql_print_error("feedback plugin: vio_new failed for url '%s'",
+ full_url.str);
+ closesocket(fd);
+ return 1;
+ }
+
+#ifdef HAVE_OPENSSL
+ struct st_VioSSLFd *UNINIT_VAR(ssl_fd);
+ if (ssl)
+ {
+ enum enum_ssl_init_error ssl_init_error= SSL_INITERR_NOERROR;
+ ulong ssl_error= 0;
+ if (!(ssl_fd= new_VioSSLConnectorFd(0, 0, 0, 0, 0, &ssl_init_error, 0, 0)) ||
+ sslconnect(ssl_fd, vio, send_timeout, &ssl_error))
+ {
+ const char *err;
+ if (ssl_init_error != SSL_INITERR_NOERROR)
+ err= sslGetErrString(ssl_init_error);
+ else
+ {
+ ERR_error_string_n(ssl_error, buf, sizeof(buf));
+ buf[sizeof(buf)-1]= 0;
+ err= buf;
+ }
+
+ sql_print_error("feedback plugin: ssl failed for url '%s' %s",
+ full_url.str, err);
+ if (ssl_fd)
+ free_vio_ssl_acceptor_fd(ssl_fd);
+ closesocket(fd);
+ vio_delete(vio);
+ return 1;
+ }
+ }
+#endif
+
+ static const LEX_STRING boundary=
+ { C_STRING_WITH_LEN("----------------------------ba4f3696b39f") };
+ static const LEX_STRING header=
+ { C_STRING_WITH_LEN("\r\n"
+ "Content-Disposition: form-data; name=\"data\"; filename=\"-\"\r\n"
+ "Content-Type: application/octet-stream\r\n\r\n")
+ };
+
+ len= my_snprintf(buf, sizeof(buf),
+ use_proxy() ? "POST http://%s:%s/" : "POST ",
+ host.str, port.str);
+
+ len+= my_snprintf(buf+len, sizeof(buf)-len,
+ "%s HTTP/1.0\r\n"
+ "User-Agent: MariaDB User Feedback Plugin\r\n"
+ "Host: %s:%s\r\n"
+ "Accept: */*\r\n"
+ "Content-Length: %u\r\n"
+ "Content-Type: multipart/form-data; boundary=%s\r\n"
+ "\r\n",
+ path.str, host.str, port.str,
+ (uint)(2*boundary.length + header.length + data_length + 4),
+ boundary.str + 2);
+
+ vio_timeout(vio, FOR_READING, send_timeout);
+ vio_timeout(vio, FOR_WRITING, send_timeout);
+ res = write_check(vio, buf, len)
+ || write_check(vio, boundary.str, boundary.length)
+ || write_check(vio, header.str, header.length)
+ || write_check(vio, data, data_length)
+ || write_check(vio, boundary.str, boundary.length)
+ || write_check(vio, "--\r\n", 4);
+
+ if (res)
+ sql_print_error("feedback plugin: failed to send report to '%s'",
+ full_url.str);
+ else
+ {
+ sql_print_information("feedback plugin: report to '%s' was sent",
+ full_url.str);
+
+ /*
+ if the data were send successfully, read the reply.
+ Extract the first string between <h1>...</h1> tags
+ and put it as a server reply into the error log.
+ */
+ len= 0;
+ for (;;)
+ {
+ size_t i= sizeof(buf) - len - 1;
+ if (i)
+ i= vio_read(vio, (uchar*)buf + len, i);
+ if ((int)i <= 0)
+ break;
+ len+= i;
+ }
+ if (len)
+ {
+ char *from;
+
+ buf[len]= 0; // safety
+
+ if ((from= strstr(buf, "<h1>")))
+ {
+ from+= 4;
+ char *to= strstr(from, "</h1>");
+ if (to)
+ *to= 0;
+ else
+ from= NULL;
+ }
+ if (from)
+ sql_print_information("feedback plugin: server replied '%s'", from);
+ else
+ sql_print_warning("feedback plugin: failed to parse server reply");
+ }
+ else
+ {
+ res= 1;
+ sql_print_error("feedback plugin: failed to read server reply");
+ }
+ }
+
+ vio_delete(vio);
+
+#ifdef HAVE_OPENSSL
+ if (ssl)
+ {
+ SSL_CTX_free(ssl_fd->ssl_context);
+ my_free(ssl_fd);
+ }
+#endif
+
+ return res;
+}
+
+} // namespace feedback
+
diff --git a/plugin/feedback/utils.cc b/plugin/feedback/utils.cc
new file mode 100644
index 00000000..e3624462
--- /dev/null
+++ b/plugin/feedback/utils.cc
@@ -0,0 +1,442 @@
+/* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include "feedback.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if defined (_WIN32)
+#define HAVE_SYS_UTSNAME_H
+
+#ifndef VER_SUITE_WH_SERVER
+#define VER_SUITE_WH_SERVER 0x00008000
+#endif
+
+struct utsname {
+ char sysname[16]; // Name of this implementation of the operating system.
+ char nodename[16]; // Name of this node within the communications
+ // network to which this node is attached, if any.
+ char release[16]; // Current release level of this implementation.
+ char version[256]; // Current version level of this release.
+ char machine[16]; // Name of the hardware type on which the system is running.
+};
+
+/* Get commonly used name for Windows version */
+static const char *get_os_version_name(OSVERSIONINFOEX *ver)
+{
+ DWORD major = ver->dwMajorVersion;
+ DWORD minor = ver->dwMinorVersion;
+ if (major == 10 && minor == 0)
+ {
+ return (ver->wProductType == VER_NT_WORKSTATION) ?
+ "Windows 10" : "Windows Server 2016";
+ }
+ if (major == 6 && minor == 3)
+ {
+ return (ver->wProductType == VER_NT_WORKSTATION)?
+ "Windows 8.1":"Windows Server 2012 R2";
+ }
+ if (major == 6 && minor == 2)
+ {
+ return (ver->wProductType == VER_NT_WORKSTATION)?
+ "Windows 8":"Windows Server 2012";
+ }
+ if (major == 6 && minor == 1)
+ {
+ return (ver->wProductType == VER_NT_WORKSTATION)?
+ "Windows 7":"Windows Server 2008 R2";
+ }
+ if (major == 6 && minor == 0)
+ {
+ return (ver->wProductType == VER_NT_WORKSTATION)?
+ "Windows Vista":"Windows Server 2008";
+ }
+ if (major == 5 && minor == 2)
+ {
+ if (GetSystemMetrics(SM_SERVERR2) != 0)
+ return "Windows Server 2003 R2";
+ if (ver->wSuiteMask & VER_SUITE_WH_SERVER)
+ return "Windows Home Server";
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+ if (ver->wProductType == VER_NT_WORKSTATION &&
+ sysinfo.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64)
+ return "Windows XP Professional x64 Edition";
+
+ return "Windows Server 2003";
+ }
+ if (major == 5 && minor == 1)
+ return "Windows XP";
+ if (major == 5 && minor == 0)
+ return "Windows 2000";
+
+ return "";
+}
+
+
+static int uname(struct utsname *buf)
+{
+ OSVERSIONINFOEX ver;
+ ver.dwOSVersionInfoSize = (DWORD)sizeof(ver);
+ /* GetVersionEx got deprecated, we need it anyway, so disable deprecation warnings. */
+#ifdef _MSC_VER
+#pragma warning (disable : 4996)
+#endif
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ if (!GetVersionEx((OSVERSIONINFO *)&ver))
+ return -1;
+
+ buf->nodename[0]= 0;
+ strcpy(buf->sysname, "Windows");
+ sprintf(buf->release, "%d.%d", (int)ver.dwMajorVersion, (int)ver.dwMinorVersion);
+
+ const char *version_str= get_os_version_name(&ver);
+ if(version_str && version_str[0])
+ sprintf(buf->version, "%s %s",version_str, ver.szCSDVersion);
+ else
+ {
+ /* Fallback for unknown versions, e.g "Windows <major_ver>.<minor_ver>" */
+ sprintf(buf->version, "Windows %d.%d%s",
+ (int)ver.dwMajorVersion, (int)ver.dwMinorVersion,
+ (ver.wProductType == VER_NT_WORKSTATION ? "" : " Server"));
+ }
+
+#ifdef _WIN64
+ strcpy(buf->machine, "x64");
+#else
+ BOOL isX64;
+ if (IsWow64Process(GetCurrentProcess(), &isX64) && isX64)
+ strcpy(buf->machine, "x64");
+ else
+ strcpy(buf->machine,"x86");
+#endif
+ return 0;
+}
+
+#elif defined(HAVE_SYS_UTSNAME_H)
+#include <sys/utsname.h>
+#endif
+
+#ifdef HAVE_SYS_UTSNAME_H
+static bool have_ubuf= false;
+static struct utsname ubuf;
+#endif
+
+#ifdef TARGET_OS_LINUX
+#include <glob.h>
+static bool have_distribution= false;
+static char distribution[256];
+
+static const char *masks[]= {
+ "/etc/*-version", "/etc/*-release",
+ "/etc/*_version", "/etc/*_release"
+};
+#endif
+
+bool schema_table_store_record(THD *thd, TABLE *table);
+
+namespace feedback {
+
+/*
+ convenience macros for inserting rows into I_S table.
+*/
+#define INSERT2(NAME,LEN,VALUE) \
+ do { \
+ table->field[0]->store(NAME, (uint) LEN, system_charset_info); \
+ table->field[1]->store VALUE; \
+ if (schema_table_store_record(thd, table)) \
+ return 1; \
+ } while (0)
+
+#define INSERT1(NAME,VALUE) \
+ do { \
+ table->field[0]->store(NAME, (uint) sizeof(NAME)-1, system_charset_info); \
+ table->field[1]->store VALUE; \
+ if (schema_table_store_record(thd, table)) \
+ return 1; \
+ } while (0)
+
+static const bool UNSIGNED= true; ///< used below when inserting integers
+
+/**
+ callback for fill_plugins()
+*/
+static my_bool show_plugins(THD *thd, plugin_ref plugin, void *arg)
+{
+ TABLE *table= (TABLE*) arg;
+ char name[NAME_LEN*2];
+ size_t name_len;
+ char version[20];
+ size_t version_len;
+
+ name_len= my_snprintf(name, sizeof(name), "%s version",
+ plugin_name(plugin)->str);
+
+ version_len= my_snprintf(version, sizeof(version), "%d.%d",
+ (plugin_decl(plugin)->version) >> 8,
+ (plugin_decl(plugin)->version) & 0xff);
+
+ INSERT2(name, name_len,
+ (version, (uint)version_len, system_charset_info));
+
+ name_len= my_snprintf(name, sizeof(name), "%s used",
+ plugin_name(plugin)->str);
+
+ INSERT2(name, name_len, (plugin_ref_to_int(plugin)->locks_total, UNSIGNED));
+
+ return 0;
+}
+
+/**
+ inserts all plugins, their versions, and usage counters
+*/
+int fill_plugin_version(THD *thd, TABLE_LIST *tables)
+{
+ return plugin_foreach_with_mask(thd, show_plugins, MYSQL_ANY_PLUGIN,
+ ~PLUGIN_IS_FREED, tables->table);
+}
+
+#if defined(_SC_PAGE_SIZE) && !defined(_SC_PAGESIZE)
+#define _SC_PAGESIZE _SC_PAGE_SIZE
+#endif
+
+/**
+ return the amount of physical memory
+*/
+static ulonglong my_getphysmem()
+{
+#ifdef _WIN32
+ MEMORYSTATUSEX memstatus;
+ memstatus.dwLength= sizeof(memstatus);
+ GlobalMemoryStatusEx(&memstatus);
+ return memstatus.ullTotalPhys;
+#else
+ ulonglong pages= 0;
+
+#ifdef _SC_PHYS_PAGES
+ pages= sysconf(_SC_PHYS_PAGES);
+#endif
+
+#ifdef _SC_PAGESIZE
+ return pages * sysconf(_SC_PAGESIZE);
+#else
+ return pages * my_getpagesize();
+#endif
+#endif
+}
+
+/* get the number of (online) CPUs */
+int my_getncpus()
+{
+#ifdef _SC_NPROCESSORS_ONLN
+ return sysconf(_SC_NPROCESSORS_ONLN);
+#elif defined(__WIN__)
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+ return sysinfo.dwNumberOfProcessors;
+#else
+ return 0;
+#endif
+}
+
+/**
+ Find the version of the kernel and the linux distribution
+*/
+int prepare_linux_info()
+{
+#ifdef HAVE_SYS_UTSNAME_H
+ have_ubuf= (uname(&ubuf) != -1);
+#endif
+
+#ifdef TARGET_OS_LINUX
+ /*
+ let's try to find what linux distribution it is
+ we read *[-_]{release,version} file in /etc.
+
+ Either it will be /etc/lsb-release, such as
+
+ ==> /etc/lsb-release <==
+ DISTRIB_ID=Ubuntu
+ DISTRIB_RELEASE=8.04
+ DISTRIB_CODENAME=hardy
+ DISTRIB_DESCRIPTION="Ubuntu 8.04.4 LTS"
+
+ Or a one-liner with the description (/etc/SuSE-release has more
+ than one line, but the description is the first, so it can be
+ treated as a one-liner).
+
+ We'll read lsb-release first, and if it's not found will search
+ for other files (*-version *-release *_version *_release)
+*/
+ int fd;
+ have_distribution= false;
+ if ((fd= my_open("/etc/lsb-release", O_RDONLY, MYF(0))) != -1)
+ {
+ /* Cool, LSB-compliant distribution! */
+ size_t len= my_read(fd, (uchar*)distribution, sizeof(distribution)-1, MYF(0));
+ my_close(fd, MYF(0));
+ if (len != (size_t)-1)
+ {
+ distribution[len]= 0; // safety
+ char *found= strstr(distribution, "DISTRIB_DESCRIPTION=");
+ if (found)
+ {
+ have_distribution= true;
+ char *end= strstr(found, "\n");
+ if (end == NULL)
+ end= distribution + len;
+ found+= 20;
+
+ if (*found == '"' && end[-1] == '"')
+ {
+ found++;
+ end--;
+ }
+ *end= 0;
+
+ char *to= strmov(distribution, "lsb: ");
+ memmove(to, found, end - found + 1);
+ }
+ }
+ }
+
+ /* if not an LSB-compliant distribution */
+ for (uint i= 0; !have_distribution && i < array_elements(masks); i++)
+ {
+ glob_t found;
+ if (glob(masks[i], GLOB_NOSORT, NULL, &found) == 0)
+ {
+ int fd;
+ if ((fd= my_open(found.gl_pathv[0], O_RDONLY, MYF(0))) != -1)
+ {
+ /*
+ +5 and -8 below cut the file name part out of the
+ full pathname that corresponds to the mask as above.
+ */
+ char *to= strmov(distribution, found.gl_pathv[0] + 5) - 8;
+ *to++= ':';
+ *to++= ' ';
+
+ size_t to_len= distribution + sizeof(distribution) - 1 - to;
+ size_t len= my_read(fd, (uchar*)to, to_len, MYF(0));
+ my_close(fd, MYF(0));
+ if (len != (size_t)-1)
+ {
+ to[len]= 0; // safety
+ char *end= strstr(to, "\n");
+ if (end)
+ *end= 0;
+ have_distribution= true;
+ }
+ }
+ }
+ globfree(&found);
+ }
+#endif
+ return 0;
+}
+
+/**
+ Add the linux distribution and the kernel version
+*/
+int fill_linux_info(THD *thd, TABLE_LIST *tables)
+{
+#if defined(HAVE_SYS_UTSNAME_H) || defined(TARGET_OS_LINUX)
+ TABLE *table= tables->table;
+ CHARSET_INFO *cs= system_charset_info;
+#endif
+
+#ifdef HAVE_SYS_UTSNAME_H
+ if (have_ubuf)
+ {
+ INSERT1("Uname_sysname", (ubuf.sysname, (uint) strlen(ubuf.sysname), cs));
+ INSERT1("Uname_release", (ubuf.release, (uint) strlen(ubuf.release), cs));
+ INSERT1("Uname_version", (ubuf.version, (uint) strlen(ubuf.version), cs));
+ INSERT1("Uname_machine", (ubuf.machine, (uint) strlen(ubuf.machine), cs));
+ }
+#endif
+
+#ifdef TARGET_OS_LINUX
+ if (have_distribution)
+ INSERT1("Uname_distribution", (distribution, strlen(distribution), cs));
+#endif
+
+ return 0;
+}
+
+/**
+ Adds varios bits of information to the I_S.FEEDBACK
+*/
+int fill_misc_data(THD *thd, TABLE_LIST *tables)
+{
+ TABLE *table= tables->table;
+
+ INSERT1("Cpu_count", (my_getncpus(), UNSIGNED));
+ INSERT1("Mem_total", (my_getphysmem(), UNSIGNED));
+ INSERT1("Now", (thd->query_start(), UNSIGNED));
+
+ return 0;
+}
+
+int fill_collation_statistics(THD *thd, TABLE_LIST *tables)
+{
+ TABLE *table= tables->table;
+ for (uint id= 1; id < MY_ALL_CHARSETS_SIZE; id++)
+ {
+ ulonglong count;
+ if (my_collation_is_known_id(id) &&
+ (count= my_collation_statistics_get_use_count(id)))
+ {
+ char name[MY_CS_NAME_SIZE + 32];
+ size_t namelen= my_snprintf(name, sizeof(name),
+ "Collation used %s",
+ get_charset_name(id));
+ INSERT2(name, namelen, (count, UNSIGNED));
+ }
+ }
+ return 0;
+};
+
+/**
+ calculates the server unique identifier
+
+ UID is a base64 encoded SHA1 hash of the MAC address of one of
+ the interfaces, and the tcp port that the server is listening on
+*/
+int calculate_server_uid(char *dest)
+{
+ uchar rawbuf[2 + 6];
+ uchar shabuf[MY_SHA1_HASH_SIZE];
+
+ int2store(rawbuf, mysqld_port);
+ if (my_gethwaddr(rawbuf + 2))
+ {
+ sql_print_error("feedback plugin: failed to retrieve the MAC address");
+ return 1;
+ }
+
+ my_sha1((uint8*) shabuf, (char*) rawbuf, sizeof(rawbuf));
+
+ assert(my_base64_needed_encoded_length(sizeof(shabuf)) <= SERVER_UID_SIZE);
+ my_base64_encode(shabuf, sizeof(shabuf), dest);
+
+ return 0;
+}
+
+} // namespace feedback