diff options
Diffstat (limited to 'plugin/feedback')
-rw-r--r-- | plugin/feedback/CMakeLists.txt | 23 | ||||
-rw-r--r-- | plugin/feedback/feedback.cc | 429 | ||||
-rw-r--r-- | plugin/feedback/feedback.h | 79 | ||||
-rw-r--r-- | plugin/feedback/sender_thread.cc | 298 | ||||
-rw-r--r-- | plugin/feedback/url_base.cc | 96 | ||||
-rw-r--r-- | plugin/feedback/url_http.cc | 341 | ||||
-rw-r--r-- | plugin/feedback/utils.cc | 442 |
7 files changed, 1708 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..ba4850f4 --- /dev/null +++ b/plugin/feedback/feedback.cc @@ -0,0 +1,429 @@ +/* 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; + /* A reference to this context will be stored in Item_field */ + Name_resolution_context *nrc= new (thd->mem_root) Name_resolution_context; + 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 || !nrc) + return 0; + + nrc->resolve_in_table_list_only(tables); + nrc->select_lex= tables->select_lex; + + 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 "feedback.mariadb.org/rest/v1/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..6b5be475 --- /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); + thd->lex->init_select(); + + 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, sizeof(server_uid_buf)-1); + str.append('\n'); + str.append(STRING_WITH_LEN("FEEDBACK_WHEN")); + str.append('\t'); + str.append(when, strlen(when)); + str.append('\n'); + str.append(STRING_WITH_LEN("FEEDBACK_USER_INFO")); + str.append('\t'); + str.append(user_info, strlen(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..c31422cc --- /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(_WIN32) + 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_COLLATION_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 |