48 changed files with 1845 additions and 149 deletions
-
5.bzrignore
-
48CMakeLists.txt
-
2configure.in
-
24include/mysql/plugin.h
-
27libmysqld/CMakeLists.txt
-
2mysql-test/include/default_mysqld.cnf
-
13mysql-test/r/feedback_plugin_install.result
-
11mysql-test/r/feedback_plugin_load.result
-
15mysql-test/r/feedback_plugin_send.result
-
1mysql-test/t/feedback_plugin_install.opt
-
14mysql-test/t/feedback_plugin_install.test
-
2mysql-test/t/feedback_plugin_load.opt
-
9mysql-test/t/feedback_plugin_load.test
-
24mysql-test/t/feedback_plugin_send.test
-
3mysql-test/t/fulltext_plugin.test
-
1mysys/CMakeLists.txt
-
106mysys/my_gethwaddr.c
-
11plugin/feedback/CMakeLists.txt
-
18plugin/feedback/Makefile.am
-
355plugin/feedback/feedback.cc
-
67plugin/feedback/feedback.h
-
28plugin/feedback/plug.in
-
301plugin/feedback/sender_thread.cc
-
51plugin/feedback/url_base.cc
-
303plugin/feedback/url_http.cc
-
387plugin/feedback/utils.cc
-
3plugin/fulltext/CMakeLists.txt
-
17sql/sql_plugin.cc
-
1storage/blackhole/plug.in
-
1storage/csv/plug.in
-
1storage/heap/plug.in
-
1storage/innobase/plug.in.disabled
-
1storage/innodb_plugin/plug.in
-
22storage/maria/CMakeLists.txt
-
1storage/maria/plug.in
-
3storage/maria/unittest/CMakeLists.txt
-
18storage/myisam/CMakeLists.txt
-
1storage/myisam/plug.in
-
1storage/myisammrg/plug.in
-
43storage/mysql_storage_engine.cmake
-
1storage/ndb/plug.in
-
1storage/pbxt/plug.in
-
10storage/xtradb/CMakeLists.txt
-
1storage/xtradb/plug.in
-
12win/README
-
3win/configure-mariadb.bat
-
16win/configure-mariadb.sh
-
8win/configure.js
@ -0,0 +1,13 @@ |
|||
install plugin feedback soname 'feedback.so'; |
|||
select plugin_status from information_schema.plugins where plugin_name='feedback'; |
|||
plugin_status |
|||
ACTIVE |
|||
select * from information_schema.feedback where variable_name like 'feed%' |
|||
and variable_name not like '%_uid'; |
|||
VARIABLE_NAME VARIABLE_VALUE |
|||
FEEDBACK_SEND_RETRY_WAIT 60 |
|||
FEEDBACK_USER_INFO mysql-test |
|||
FEEDBACK_SEND_TIMEOUT 60 |
|||
FEEDBACK_URL http://mariadb.org/feedback_plugin/post |
|||
FEEDBACK 1.0 |
|||
uninstall plugin feedback; |
|||
@ -0,0 +1,11 @@ |
|||
select plugin_status from information_schema.plugins where plugin_name='feedback'; |
|||
plugin_status |
|||
ACTIVE |
|||
select * from information_schema.feedback where variable_name like 'feed%' |
|||
and variable_name not like '%_uid'; |
|||
VARIABLE_NAME VARIABLE_VALUE |
|||
FEEDBACK_SEND_RETRY_WAIT 60 |
|||
FEEDBACK_USER_INFO mysql-test |
|||
FEEDBACK_SEND_TIMEOUT 60 |
|||
FEEDBACK_URL http://mariadb.org/feedback_plugin/post |
|||
FEEDBACK 1.0 |
|||
@ -0,0 +1,15 @@ |
|||
select plugin_status from information_schema.plugins where plugin_name='feedback'; |
|||
plugin_status |
|||
ACTIVE |
|||
select * from information_schema.feedback where variable_name like 'feed%' |
|||
and variable_name not like '%_uid'; |
|||
VARIABLE_NAME VARIABLE_VALUE |
|||
FEEDBACK_SEND_RETRY_WAIT 60 |
|||
FEEDBACK_USER_INFO mysql-test |
|||
FEEDBACK_SEND_TIMEOUT 60 |
|||
FEEDBACK_URL http://mariadb.org/feedback_plugin/post |
|||
FEEDBACK 1.0 |
|||
feedback plugin: report to 'http://mariadb.org/feedback_plugin/post' was sent |
|||
feedback plugin: server replied 'ok' |
|||
feedback plugin: report to 'http://mariadb.org/feedback_plugin/post' was sent |
|||
feedback plugin: server replied 'ok' |
|||
@ -0,0 +1 @@ |
|||
--loose-feedback |
|||
@ -0,0 +1,14 @@ |
|||
--source include/not_embedded.inc |
|||
|
|||
if (`select length('$FEEDBACK_SO') = 0`) { |
|||
skip No feedback plugin; |
|||
} |
|||
|
|||
--replace_regex /\.dll/.so/ |
|||
eval install plugin feedback soname '$FEEDBACK_SO'; |
|||
select plugin_status from information_schema.plugins where plugin_name='feedback'; |
|||
--replace_result https http |
|||
select * from information_schema.feedback where variable_name like 'feed%' |
|||
and variable_name not like '%_uid'; |
|||
uninstall plugin feedback; |
|||
|
|||
@ -0,0 +1,2 @@ |
|||
--loose-feedback |
|||
--plugin-load=$FEEDBACK_SO |
|||
@ -0,0 +1,9 @@ |
|||
if (`select count(*) = 0 from information_schema.plugins where plugin_name = 'feedback' and plugin_status='active'`) |
|||
{ |
|||
--skip Feedback plugin is not active |
|||
} |
|||
|
|||
select plugin_status from information_schema.plugins where plugin_name='feedback'; |
|||
--replace_result https http |
|||
select * from information_schema.feedback where variable_name like 'feed%' |
|||
and variable_name not like '%_uid'; |
|||
@ -0,0 +1,24 @@ |
|||
source t/feedback_plugin_load.test; |
|||
source include/big_test.inc; |
|||
|
|||
if (!$MTR_FEEDBACK_PLUGIN) { |
|||
skip MTR_FEEDBACK_PLUGIN is not set; |
|||
} |
|||
|
|||
# |
|||
# Yep. The plugin waits 5 minutes before sending anything, |
|||
# and there's no way to force it to send anything sooner. |
|||
# Let's wait, and hope that mtr is started with --parallel and |
|||
# is doing some work in other workers. |
|||
# |
|||
sleep 310; |
|||
source include/restart_mysqld.inc; |
|||
|
|||
replace_result https http; |
|||
perl; |
|||
$log_error= $ENV{'MYSQLTEST_VARDIR'} . '/log/mysqld.1.err'; |
|||
open(LOG, '<', $log_error) or die "open(< $log_error): $!"; |
|||
/feedback plugin:.*/ && print "$&\n" while $_=<LOG>; |
|||
close LOG; |
|||
EOF |
|||
|
|||
@ -0,0 +1,11 @@ |
|||
INCLUDE("${PROJECT_SOURCE_DIR}/storage/mysql_storage_engine.cmake") |
|||
|
|||
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/sql ${CMAKE_SOURCE_DIR}/regex |
|||
${CMAKE_SOURCE_DIR}/extra/yassl/include) |
|||
|
|||
SET(FEEDBACK_SOURCES feedback.cc sender_thread.cc |
|||
url_base.cc url_http.cc utils.cc) |
|||
|
|||
SET(FEEDBACK_LIBS Ws2_32) |
|||
|
|||
MYSQL_PLUGIN(FEEDBACK) |
|||
@ -0,0 +1,18 @@ |
|||
pkgplugindir = $(pkglibdir)/plugin |
|||
INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include \
|
|||
-I$(top_srcdir)/regex -I$(top_srcdir)/sql |
|||
|
|||
EXTRA_LTLIBRARIES = feedback.la libfeedback.la |
|||
pkgplugin_LTLIBRARIES = @plugin_feedback_shared_target@ |
|||
feedback_la_LDFLAGS = -module -rpath $(pkgplugindir) |
|||
feedback_la_CXXFLAGS = -shared -DMYSQL_DYNAMIC_PLUGIN |
|||
feedback_la_SOURCES = feedback.cc utils.cc url_base.cc url_http.cc \
|
|||
sender_thread.cc |
|||
|
|||
noinst_LTLIBRARIES = @plugin_feedback_static_target@ |
|||
libfeedback_la_SOURCES= feedback.cc utils.cc url_base.cc url_http.cc \
|
|||
sender_thread.cc |
|||
|
|||
noinst_HEADERS = feedback.h |
|||
EXTRA_DIST = CMakeLists.txt plug.in |
|||
|
|||
@ -0,0 +1,355 @@ |
|||
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 { |
|||
|
|||
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; |
|||
char *user_info; |
|||
ulong send_timeout, send_retry_wait; |
|||
|
|||
/**
|
|||
these three are used to communicate the shutdown signal to the |
|||
background thread |
|||
*/ |
|||
pthread_mutex_t sleep_mutex; |
|||
pthread_cond_t sleep_condition; |
|||
volatile bool shutdown_plugin; |
|||
static pthread_t sender_thread; |
|||
|
|||
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[] = |
|||
{ |
|||
{"VARIABLE_NAME", 255, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
|||
{"VARIABLE_VALUE", 1024, MYSQL_TYPE_STRING, 0, 0, 0, 0}, |
|||
{0, 0, MYSQL_TYPE_NULL, 0, 0, 0, 0} |
|||
}; |
|||
|
|||
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; |
|||
const char *db= tables->db, *table= tables->alias, |
|||
*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 Item_cond_or(); |
|||
if (!res) |
|||
return OOM; |
|||
|
|||
for (; filter->str; filter++) |
|||
{ |
|||
Item_field *fld= new Item_field(&nrc, db, table, field); |
|||
Item_string *pattern= new Item_string(filter->str, filter->length, cs); |
|||
Item_string *escape= new Item_string("\\", 1, cs); |
|||
|
|||
if (!fld || !pattern || !escape) |
|||
return OOM; |
|||
|
|||
Item_func_like *like= new Item_func_like(fld, pattern, escape, 0); |
|||
|
|||
if (!like) |
|||
return OOM; |
|||
|
|||
res->add(like); |
|||
} |
|||
|
|||
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("%\\_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("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("timed_mutexes")}, |
|||
{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); |
|||
|
|||
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
|
|||
|
|||
if (calculate_server_uid(server_uid_buf)) |
|||
return 1; |
|||
|
|||
prepare_linux_info(); |
|||
|
|||
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(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))) |
|||
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) |
|||
{ |
|||
pthread_mutex_init(&sleep_mutex, 0); |
|||
pthread_cond_init(&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, MYF(0)); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/**
|
|||
plugin deinitialization function |
|||
*/ |
|||
static int free(void *p) |
|||
{ |
|||
if (url_count) |
|||
{ |
|||
pthread_mutex_lock(&sleep_mutex); |
|||
shutdown_plugin= true; |
|||
pthread_cond_signal(&sleep_condition); |
|||
pthread_mutex_unlock(&sleep_mutex); |
|||
pthread_join(sender_thread, NULL); |
|||
|
|||
pthread_mutex_destroy(&sleep_mutex); |
|||
pthread_cond_destroy(&sleep_condition); |
|||
|
|||
for (uint i= 0; i < url_count; i++) |
|||
delete urls[i]; |
|||
my_free(urls, MYF(0)); |
|||
} |
|||
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 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), |
|||
NULL |
|||
}; |
|||
|
|||
|
|||
static struct st_mysql_information_schema feedback = |
|||
{ MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION }; |
|||
|
|||
} // namespace feedback
|
|||
|
|||
mysql_declare_plugin(feedback) |
|||
{ |
|||
MYSQL_INFORMATION_SCHEMA_PLUGIN, |
|||
&feedback::feedback, |
|||
"FEEDBACK", |
|||
"Sergei Golubchik", |
|||
"MariaDB User Feedback Plugin", |
|||
PLUGIN_LICENSE_GPL, |
|||
feedback::init, |
|||
feedback::free, |
|||
0x0100, |
|||
NULL, |
|||
feedback::settings, |
|||
NULL |
|||
} |
|||
mysql_declare_plugin_end; |
|||
|
|||
@ -0,0 +1,67 @@ |
|||
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ |
|||
|
|||
#define MYSQL_SERVER |
|||
#include <mysql_priv.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); |
|||
|
|||
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, MYF(0)); } |
|||
|
|||
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; |
|||
|
|||
static Url* create(const char *url, size_t url_length); |
|||
}; |
|||
|
|||
extern Url **urls; |
|||
extern uint url_count; |
|||
|
|||
/* these are used to communicate with the background thread */ |
|||
extern pthread_mutex_t sleep_mutex; |
|||
extern pthread_cond_t sleep_condition; |
|||
extern volatile bool shutdown_plugin; |
|||
|
|||
} // namespace feedback |
|||
|
|||
@ -0,0 +1,28 @@ |
|||
MYSQL_PLUGIN(feedback,[MariaDB User Feedback Plugin], |
|||
[MariaDB User Feedback Plugin]) |
|||
|
|||
dnl Although it's not exactly obvious, top-level CMakeLists.txt parses plug.in |
|||
dnl files, in particular looking for what the library name should be. It uses |
|||
dnl regexp that matches MYSQL_PLUGIN_DYNAMIC or MYSQL_PLUGIN_STATIC, followed |
|||
dnl by an open parenthesys, and the plugin name. Having engine name enclosed in |
|||
dnl square brackets below causes this regexp to fail and as a result feedback |
|||
dnl plugin will not be considered for dynamic builds on Windows. |
|||
dnl Unfortunately, feedback cannot be built dynamically on Windows, because it |
|||
dnl needs to access server internals that aren't designed for plugin use and |
|||
dnl aren't marked with MYSQL_PLUGIN_IMPORT. |
|||
MYSQL_PLUGIN_DYNAMIC([feedback], [feedback.la]) |
|||
ifelse(index(AC_PACKAGE_NAME, [MariaDB]), -1, [], [ |
|||
|
|||
dnl MariaDB and MySQL define static plugins differently. |
|||
dnl I only support MariaDB here, for now. |
|||
MYSQL_PLUGIN_STATIC(feedback, [libfeedback.la]) |
|||
|
|||
]) |
|||
|
|||
dnl MariaDB before 5.5 needs this define: |
|||
MYSQL_PLUGIN_DEFINE(feedback, [WITH_FEEDBACK_PLUGIN]) |
|||
|
|||
MYSQL_PLUGIN_ACTIONS(feedback, [ |
|||
AC_CHECK_HEADERS([netdb.h sys/utsname.h]) |
|||
]) |
|||
|
|||
@ -0,0 +1,301 @@ |
|||
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ |
|||
|
|||
#include "feedback.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; |
|||
|
|||
static const time_t startup_interval= 60*5; ///< in seconds (5 minutes)
|
|||
static const time_t first_interval= 60*60*24; ///< in seconds (one day)
|
|||
static const time_t 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) |
|||
{ |
|||
bool 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->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 || 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->thread_id= thd->variables.pseudo_thread_id= thd_thread_id; |
|||
pthread_mutex_lock(&LOCK_thread_count); |
|||
thread_count++; |
|||
threads.append(thd); |
|||
pthread_mutex_unlock(&LOCK_thread_count); |
|||
thd->thread_stack= (char*) &tables; |
|||
if (thd->store_globals()) |
|||
return 1; |
|||
|
|||
thd->mysys_var->current_cond= &sleep_condition; |
|||
thd->mysys_var->current_mutex= &sleep_mutex; |
|||
thd->proc_info="feedback"; |
|||
thd->command=COM_SLEEP; |
|||
thd->version=refresh_version; |
|||
thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; // whatever
|
|||
thd->set_time(); |
|||
thd->init_for_queries(); |
|||
thd->real_id= pthread_self(); |
|||
thd->db= NULL; |
|||
thd->db_length= 0; |
|||
thd->security_ctx->host_or_ip= ""; |
|||
thd->security_ctx->db_access= DB_ACLS; |
|||
thd->security_ctx->master_access= ~NO_ACCESS; |
|||
bzero((char*) &thd->net, sizeof(thd->net)); |
|||
lex_start(thd); |
|||
mysql_init_select(thd->lex); |
|||
|
|||
tables->init_one_table(INFORMATION_SCHEMA_NAME.str, |
|||
i_s_feedback->table_name, TL_READ); |
|||
tables->schema_table= i_s_feedback; |
|||
tables->table= i_s_feedback->create_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 || shutdown_in_progress || (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); |
|||
|
|||
pthread_mutex_lock(&sleep_mutex); |
|||
while (!going_down() && ret != ETIMEDOUT) |
|||
ret= pthread_cond_timedwait(&sleep_condition, &sleep_mutex, &abstime); |
|||
pthread_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 IFROEMATION_SCHEMA.feedback is doing, |
|||
read and concatenate table data into a String. |
|||
*/ |
|||
if (!(thd= new THD())) |
|||
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()), 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); |
|||
/*
|
|||
clean up, free the thd. |
|||
reset all thread local status variables to minimize |
|||
the effect of the background thread on SHOW STATUS. |
|||
*/ |
|||
pthread_mutex_lock(&LOCK_thread_count); |
|||
bzero(&thd->status_var, sizeof(thd->status_var)); |
|||
thread_count--; |
|||
thd->killed= THD::KILL_CONNECTION; |
|||
pthread_cond_broadcast(&COND_thread_count); |
|||
pthread_mutex_unlock(&LOCK_thread_count); |
|||
delete thd; |
|||
thd= 0; |
|||
} |
|||
} |
|||
|
|||
/**
|
|||
background sending thread |
|||
*/ |
|||
pthread_handler_t background_thread(void *arg __attribute__((unused))) |
|||
{ |
|||
if (my_thread_init()) |
|||
return 0; |
|||
|
|||
pthread_mutex_lock(&LOCK_thread_count); |
|||
thd_thread_id= thread_id++; |
|||
pthread_mutex_unlock(&LOCK_thread_count); |
|||
|
|||
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
|
|||
|
|||
@ -0,0 +1,51 @@ |
|||
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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(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), MYF(0)); |
|||
|
|||
return self; |
|||
} |
|||
|
|||
} // namespace feedback
|
|||
@ -0,0 +1,303 @@ |
|||
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ |
|||
|
|||
#include "feedback.h"
|
|||
|
|||
#ifdef HAVE_NETDB_H
|
|||
#include <netdb.h>
|
|||
#endif
|
|||
|
|||
#ifdef _WIN32
|
|||
#undef VOID
|
|||
#define VOID void
|
|||
#include <ws2tcpip.h>
|
|||
#define addrinfo ADDRINFOA
|
|||
#endif
|
|||
|
|||
namespace feedback { |
|||
|
|||
static const uint FOR_READING= 0; |
|||
static const uint FOR_WRITING= 1; |
|||
|
|||
#ifdef MARIADB_BASE_VERSION
|
|||
#define ssl_connect(A,B,C,D) sslconnect(A,B,C,D)
|
|||
#else
|
|||
#define ssl_connect(A,B,C,D) sslconnect(A,B,C)
|
|||
#endif
|
|||
|
|||
/**
|
|||
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; |
|||
|
|||
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) |
|||
{} |
|||
~Url_http() |
|||
{ |
|||
my_free(host.str, MYF(0)); |
|||
my_free(port.str, MYF(0)); |
|||
my_free(path.str, MYF(0)); |
|||
} |
|||
|
|||
public: |
|||
int send(const char* data, size_t data_length); |
|||
|
|||
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(host.str, host.length, MYF(MY_WME)); |
|||
port.str= my_strndup(port.str, port.length, MYF(MY_WME)); |
|||
path.str= my_strndup(path.str, path.length, MYF(MY_WME)); |
|||
|
|||
if (!host.str || !port.str || !path.str) |
|||
{ |
|||
my_free(host.str, MYF(MY_ALLOW_ZERO_PTR)); |
|||
my_free(port.str, MYF(MY_ALLOW_ZERO_PTR)); |
|||
my_free(path.str, MYF(MY_ALLOW_ZERO_PTR)); |
|||
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]; |
|||
uint len; |
|||
|
|||
addrinfo *addrs, *addr, filter= {0, AF_UNSPEC, SOCK_STREAM, 6, 0, 0, 0, 0}; |
|||
int res= 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, addr->ai_addrlen) == 0) |
|||
break; |
|||
|
|||
closesocket(fd); |
|||
} |
|||
|
|||
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 *ssl_fd; |
|||
if (ssl) |
|||
{ |
|||
buf[0]= 0; |
|||
if (!(ssl_fd= new_VioSSLConnectorFd(0, 0, 0, 0, 0)) || |
|||
ssl_connect(ssl_fd, vio, send_timeout, buf)) |
|||
{ |
|||
sql_print_error("feedback plugin: ssl failed for url '%s' %s", |
|||
full_url.str, buf); |
|||
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), |
|||
"POST %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= vio_read(vio, (uchar*)buf, sizeof(buf)-1); |
|||
if (len && len < sizeof(buf)) |
|||
{ |
|||
char *from; |
|||
|
|||
buf[len+1]= 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, MYF(0)); |
|||
} |
|||
#endif
|
|||
|
|||
return res; |
|||
} |
|||
|
|||
} // namespace feedback
|
|||
|
|||
@ -0,0 +1,387 @@ |
|||
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ |
|||
|
|||
#include "feedback.h"
|
|||
|
|||
#ifdef HAVE_UNISTD_H
|
|||
#include <unistd.h>
|
|||
#endif
|
|||
|
|||
#include <base64.h>
|
|||
#include <sha1.h>
|
|||
|
|||
#if defined (_WIN32)
|
|||
#define HAVE_SYS_UTSNAME_H
|
|||
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 == 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); |
|||
if (!GetVersionEx((OSVERSIONINFO *)&ver)) |
|||
return -1; |
|||
|
|||
buf->nodename[0]= 0; |
|||
strcpy(buf->sysname, "Windows"); |
|||
sprintf(buf->release, "%d.%d", ver.dwMajorVersion, 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 |
|||
sprintf(buf->version, "%s", ver.szCSDVersion); |
|||
|
|||
#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, 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, 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_plugin_version() - insert a plugin name and its version |
|||
*/ |
|||
static my_bool show_plugins(THD *thd, plugin_ref plugin, void *arg) |
|||
{ |
|||
TABLE *table= (TABLE*) arg; |
|||
char version[20]; |
|||
size_t version_len; |
|||
|
|||
version_len= my_snprintf(version, sizeof(version), "%d.%d", |
|||
(plugin_decl(plugin)->version) >> 8, |
|||
(plugin_decl(plugin)->version) & 0xff); |
|||
|
|||
INSERT2(plugin_name(plugin)->str, plugin_name(plugin)->length, |
|||
(version, version_len, system_charset_info)); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/**
|
|||
inserts all plugins and their versions into I_S.FEEDBACK |
|||
*/ |
|||
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() |
|||
{ |
|||
ulonglong pages= 0; |
|||
#ifdef _SC_PHYS_PAGES
|
|||
pages= sysconf(_SC_PHYS_PAGES); |
|||
#else
|
|||
return 0; |
|||
#endif
|
|||
|
|||
#ifdef _SC_PAGESIZE
|
|||
return pages * sysconf(_SC_PAGESIZE); |
|||
#endif
|
|||
#ifdef _WIN32
|
|||
MEMORYSTATUSEX memstatus; |
|||
memstatus.dwLength= sizeof(memstatus); |
|||
GlobalMemoryStatusEx(&memstatus); |
|||
return memstatus.ullTotalPhys; |
|||
#else
|
|||
return pages * my_getpagesize(); |
|||
#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) |
|||
{ |
|||
TABLE *table= tables->table; |
|||
CHARSET_INFO *cs= system_charset_info; |
|||
|
|||
#ifdef HAVE_SYS_UTSNAME_H
|
|||
if (have_ubuf) |
|||
{ |
|||
INSERT1("Uname_sysname", (ubuf.sysname, strlen(ubuf.sysname), cs)); |
|||
INSERT1("Uname_release", (ubuf.release, strlen(ubuf.release), cs)); |
|||
INSERT1("Uname_version", (ubuf.version, strlen(ubuf.version), cs)); |
|||
INSERT1("Uname_machine", (ubuf.machine, 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; |
|||
|
|||
#ifdef MY_ATOMIC_OK
|
|||
INSERT1("Cpu_count", (my_getncpus(), UNSIGNED)); |
|||
#endif
|
|||
INSERT1("Mem_total", (my_getphysmem(), 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[SHA1_HASH_SIZE]; |
|||
SHA1_CONTEXT ctx; |
|||
|
|||
int2store(rawbuf, mysqld_port); |
|||
if (my_gethwaddr(rawbuf + 2)) |
|||
{ |
|||
sql_print_error("feedback plugin: failed to retrieve the MAC address"); |
|||
return 1; |
|||
} |
|||
|
|||
mysql_sha1_reset(&ctx); |
|||
mysql_sha1_input(&ctx, rawbuf, sizeof(rawbuf)); |
|||
mysql_sha1_result(&ctx, shabuf); |
|||
|
|||
assert(base64_needed_encoded_length(sizeof(shabuf)) <= SERVER_UID_SIZE); |
|||
base64_encode(shabuf, sizeof(shabuf), dest); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
} // namespace feedback
|
|||
@ -0,0 +1,3 @@ |
|||
INCLUDE("${PROJECT_SOURCE_DIR}/storage/mysql_storage_engine.cmake") |
|||
SET(FTEXAMPLE_SOURCES plugin_example.c) |
|||
MYSQL_PLUGIN(FTEXAMPLE) |
|||
@ -1,6 +1,5 @@ |
|||
MYSQL_STORAGE_ENGINE(blackhole,,[Blackhole Storage Engine], |
|||
[Basic Write-only Read-never tables], [max,max-no-ndb]) |
|||
MYSQL_PLUGIN_DIRECTORY(blackhole, [storage/blackhole]) |
|||
MYSQL_PLUGIN_STATIC(blackhole, [libblackhole.la]) |
|||
MYSQL_PLUGIN_DYNAMIC(blackhole, [ha_blackhole.la]) |
|||
|
|||
@ -1,5 +1,4 @@ |
|||
MYSQL_STORAGE_ENGINE(csv,, [CSV Storage Engine], |
|||
[Stores tables in text CSV format]) |
|||
MYSQL_PLUGIN_DIRECTORY(csv, [storage/csv]) |
|||
MYSQL_PLUGIN_STATIC(csv, [libcsv.la]) |
|||
MYSQL_PLUGIN_MANDATORY(csv) dnl Used for logging |
|||
@ -1,6 +1,5 @@ |
|||
MYSQL_STORAGE_ENGINE(heap,no, [Memory Storage Engine], |
|||
[Volatile memory based tables]) |
|||
MYSQL_PLUGIN_DIRECTORY(heap, [storage/heap]) |
|||
MYSQL_PLUGIN_STATIC(heap, [libheap_s.la], [libheap_embedded.la]) |
|||
MYSQL_PLUGIN_MANDATORY(heap) dnl Memory tables |
|||
|
|||
@ -1,6 +1,5 @@ |
|||
dnl MYSQL_STORAGE_ENGINE(myisam,no, [MyISAM Storage Engine], |
|||
dnl [Traditional non-transactional MySQL tables]) |
|||
dnl MYSQL_PLUGIN_DIRECTORY(myisam, [storage/myisam]) |
|||
dnl MYSQL_PLUGIN_STATIC(myisam, [libmyisam_s.la], [libmyisam_embedded.la]) |
|||
dnl MYSQL_PLUGIN_MANDATORY(myisam) dnl Default |
|||
|
|||
@ -1,5 +1,4 @@ |
|||
MYSQL_STORAGE_ENGINE(myisammrg,no,[MyISAM MERGE Engine], |
|||
[Merge multiple MySQL tables into one]) |
|||
MYSQL_PLUGIN_DIRECTORY(myisammrg,[storage/myisammrg]) |
|||
MYSQL_PLUGIN_STATIC(myisammrg, [libmyisammrg_s.la], [libmyisammrg_embedded.la]) |
|||
MYSQL_PLUGIN_MANDATORY(myisammrg) |
|||
@ -1,6 +1,5 @@ |
|||
MYSQL_STORAGE_ENGINE(ndbcluster, ndbcluster, [Cluster Storage Engine], |
|||
[High Availability Clustered tables],) |
|||
MYSQL_PLUGIN_DIRECTORY(ndbcluster,[storage/ndb]) |
|||
MYSQL_PLUGIN_STATIC(ndbcluster, [[\$(ndbcluster_libs) \$(ndbcluster_system_libs) \$(NDB_SCI_LIBS)]]) |
|||
MYSQL_PLUGIN_ACTIONS(ndbcluster,[MYSQL_SETUP_NDBCLUSTER]) |
|||
MYSQL_PLUGIN_DEPENDS(ndbcluster, [partition]) |
|||
@ -1,6 +1,5 @@ |
|||
MYSQL_STORAGE_ENGINE(pbxt,no, [PBXT Storage Engine], |
|||
[MVCC-based transactional engine], [max,max-no-ndb]) |
|||
MYSQL_PLUGIN_DIRECTORY(pbxt, [storage/pbxt]) |
|||
MYSQL_PLUGIN_STATIC(pbxt, [src/libpbxt_s.la], [src/libpbxt_s_embedded.la]) |
|||
MYSQL_PLUGIN_ACTIONS(pbxt, [ |
|||
# AC_CONFIG_FILES(storage/pbxt/src/Makefile) |
|||
|
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue