Commit 4c34bcf8 authored by Sergei Golubchik's avatar Sergei Golubchik

initial checkin

parents
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)
pkgplugindir = $(pkglibdir)/plugin
INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include \
-I$(top_srcdir)/regex -I$(top_srcdir)/sql
EXTRA_LTLIBRARIES = feedback.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
EXTRA_LIBRARIES = libfeedback.a
noinst_LIBRARIES = @plugin_feedback_static_target@
libfeedback_a_SOURCES= feedback.cc utils.cc url_base.cc url_http.cc \
sender_thread.cc
noinst_HEADERS = feedback.h
EXTRA_DIST = CMakeLists.txt plug.in
/* 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;
}
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,
"https://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;
/* 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
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])
MYSQL_PLUGIN_STATIC(feedback, [libfeedback.a])
MYSQL_PLUGIN_ACTIONS(feedback, [
AC_CHECK_HEADERS([netdb.h sys/utsname.h])
])
/* 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 next_interval= 60*60*24*7; ///< in seconds (one week)
static const time_t first_interval= 60*60*24; ///< in seconds (one day)
/**
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 delay(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 (delay(send_retry_wait) == 0); // 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)))
{
time_t interval;
if (my_thread_init())
return 0;
pthread_mutex_lock(&LOCK_thread_count);
thd_thread_id= thread_id++;
pthread_mutex_unlock(&LOCK_thread_count);
send_report("startup");
for (interval= first_interval; delay(interval) == 0; interval= next_interval)
send_report(NULL);
send_report("shutdown");
my_thread_end();
pthread_exit(0);
return 0;
}
} // namespace feedback
/* 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
/* 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 char *http= "http://";
static const size_t http_len= 7;
static const char *https= "https://";
static const size_t https_len= 8;
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 + http_len;
#ifdef HAVE_OPENSSL
else if (is_prefix(url, https))
{
ssl= true;
s= url + https_len;
}
#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
/* 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>
#ifdef HAVE_SYS_UTSNAME_H
#include <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);
#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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment