1 /************************************************************************************ 2 Copyright (C) 2015 MariaDB Corporation AB 3 4 This library is free software; you can redistribute it and/or 5 modify it under the terms of the GNU Library General Public 6 License as published by the Free Software Foundation; either 7 version 2 of the License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Library General Public License for more details. 13 14 You should have received a copy of the GNU Library General Public 15 License along with this library; if not see <http://www.gnu.org/licenses> 16 or write to the Free Software Foundation, Inc., 17 51 Franklin St., Fifth Floor, Boston, MA 02110, USA 18 *************************************************************************************/ 19 #ifndef _WIN32 20 #define _GNU_SOURCE 1 21 #endif 22 23 #include <ma_global.h> 24 #include <mysql.h> 25 #include <mysql/client_plugin.h> 26 #include <string.h> 27 #include <memory.h> 28 29 #ifndef WIN32 30 #include <dlfcn.h> 31 #endif 32 33 #define READ 0 34 #define WRITE 1 35 36 /* function prototypes */ 37 static int trace_init(char *errormsg, 38 size_t errormsg_size, 39 int unused __attribute__((unused)), 40 va_list unused1 __attribute__((unused))); 41 static int trace_deinit(void); 42 43 int (*register_callback)(my_bool register_callback, 44 void (*callback_function)(int mode, MYSQL *mysql, const uchar *buffer, size_t length)); 45 void trace_callback(int mode, MYSQL *mysql, const uchar *buffer, size_t length); 46 47 #ifndef HAVE_TRACE_EXAMPLE_PLUGIN_DYNAMIC 48 struct st_mysql_client_plugin trace_example_plugin= 49 #else 50 struct st_mysql_client_plugin _mysql_client_plugin_declaration_ = 51 #endif 52 { 53 MARIADB_CLIENT_TRACE_PLUGIN, 54 MARIADB_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION, 55 "trace_example", 56 "Georg Richter", 57 "Trace example plugin", 58 {1,0,0}, 59 "LGPL", 60 NULL, 61 &trace_init, 62 &trace_deinit, 63 NULL 64 }; 65 66 static const char *commands[]= { 67 "COM_SLEEP", 68 "COM_QUIT", 69 "COM_INIT_DB", 70 "COM_QUERY", 71 "COM_FIELD_LIST", 72 "COM_CREATE_DB", 73 "COM_DROP_DB", 74 "COM_REFRESH", 75 "COM_SHUTDOWN", 76 "COM_STATISTICS", 77 "COM_PROCESS_INFO", 78 "COM_CONNECT", 79 "COM_PROCESS_KILL", 80 "COM_DEBUG", 81 "COM_PING", 82 "COM_TIME", 83 "COM_DELAYED_INSERT", 84 "COM_CHANGE_USER", 85 "COM_BINLOG_DUMP", 86 "COM_TABLE_DUMP", 87 "COM_CONNECT_OUT", 88 "COM_REGISTER_SLAVE", 89 "COM_STMT_PREPARE", 90 "COM_STMT_EXECUTE", 91 "COM_STMT_SEND_LONG_DATA", 92 "COM_STMT_CLOSE", 93 "COM_STMT_RESET", 94 "COM_SET_OPTION", 95 "COM_STMT_FETCH", 96 "COM_DAEMON", 97 "COM_END" 98 }; 99 100 typedef struct { 101 unsigned long thread_id; 102 int last_command; /* COM_* values, -1 for handshake */ 103 unsigned int max_packet_size; 104 unsigned int num_commands; 105 size_t total_size[2]; 106 unsigned int client_flags; 107 char *username; 108 char *db; 109 char *command; 110 char *filename; 111 unsigned long refid; /* stmt_id, thread_id for kill */ 112 uchar charset; 113 void *next; 114 int local_infile; 115 unsigned long pkt_length; 116 } TRACE_INFO; 117 118 #define TRACE_STATUS(a) ((!a) ? "ok" : "error") 119 120 TRACE_INFO *trace_info= NULL; 121 122 static TRACE_INFO *get_trace_info(unsigned long thread_id) 123 { 124 TRACE_INFO *info= trace_info; 125 126 /* search connection */ 127 while (info) 128 { 129 if (info->thread_id == thread_id) 130 return info; 131 else 132 info= (TRACE_INFO *)info->next; 133 } 134 135 if (!(info= (TRACE_INFO *)calloc(sizeof(TRACE_INFO), 1))) 136 return NULL; 137 info->thread_id= thread_id; 138 info->next= trace_info; 139 trace_info= info; 140 return info; 141 } 142 143 static void delete_trace_info(unsigned long thread_id) 144 { 145 TRACE_INFO *last= NULL, *current; 146 current= trace_info; 147 148 while (current) 149 { 150 if (current->thread_id == thread_id) 151 { 152 printf("deleting thread %lu\n", thread_id); 153 154 if (last) 155 last->next= current->next; 156 else 157 trace_info= (TRACE_INFO *)current->next; 158 if (current->command) 159 free(current->command); 160 if (current->db) 161 free(current->db); 162 if (current->username) 163 free(current->username); 164 if (current->filename) 165 free(current->filename); 166 free(current); 167 } 168 last= current; 169 current= (TRACE_INFO *)current->next; 170 } 171 172 } 173 174 175 /* {{{ static int trace_init */ 176 /* 177 Initialization routine 178 179 SYNOPSIS 180 trace_init 181 unused1 182 unused2 183 unused3 184 unused4 185 186 DESCRIPTION 187 Init function registers a callback handler for PVIO interface. 188 189 RETURN 190 0 success 191 */ 192 static int trace_init(char *errormsg, 193 size_t errormsg_size, 194 int unused1 __attribute__((unused)), 195 va_list unused2 __attribute__((unused))) 196 { 197 void *func; 198 199 #ifdef WIN32 200 if (!(func= GetProcAddress(GetModuleHandle(NULL), "ma_pvio_register_callback"))) 201 #else 202 if (!(func= dlsym(RTLD_DEFAULT, "ma_pvio_register_callback"))) 203 #endif 204 { 205 strncpy(errormsg, "Can't find ma_pvio_register_callback function", errormsg_size); 206 return 1; 207 } 208 register_callback= func; 209 register_callback(TRUE, trace_callback); 210 211 return 0; 212 } 213 /* }}} */ 214 215 static int trace_deinit(void) 216 { 217 /* unregister plugin */ 218 while(trace_info) 219 { 220 printf("Warning: Connection for thread %lu not properly closed\n", trace_info->thread_id); 221 trace_info= (TRACE_INFO *)trace_info->next; 222 } 223 register_callback(FALSE, trace_callback); 224 return 0; 225 } 226 227 static void trace_set_command(TRACE_INFO *info, char *buffer, size_t size) 228 { 229 if (info->command) 230 free(info->command); 231 232 info->command= calloc(1, size + 1); 233 memcpy(info->command, buffer, size); 234 } 235 236 void dump_buffer(uchar *buffer, size_t len) 237 { 238 uchar *p= buffer; 239 while (p < buffer + len) 240 { 241 printf("%02x ", *p); 242 p++; 243 } 244 printf("\n"); 245 } 246 247 static void dump_simple(TRACE_INFO *info, my_bool is_error) 248 { 249 printf("%8lu: %s %s\n", info->thread_id, commands[info->last_command], TRACE_STATUS(is_error)); 250 } 251 252 static void dump_reference(TRACE_INFO *info, my_bool is_error) 253 { 254 printf("%8lu: %s(%lu) %s\n", info->thread_id, commands[info->last_command], (long)info->refid, TRACE_STATUS(is_error)); 255 } 256 257 static void dump_command(TRACE_INFO *info, my_bool is_error) 258 { 259 size_t i; 260 printf("%8lu: %s(", info->thread_id, commands[info->last_command]); 261 for (i= 0; info->command && i < strlen(info->command); i++) 262 if (info->command[i] == '\n') 263 printf("\\n"); 264 else if (info->command[i] == '\r') 265 printf("\\r"); 266 else if (info->command[i] == '\t') 267 printf("\\t"); 268 else 269 printf("%c", info->command[i]); 270 printf(") %s\n", TRACE_STATUS(is_error)); 271 } 272 273 void trace_callback(int mode, MYSQL *mysql, const uchar *buffer, size_t length) 274 { 275 unsigned long thread_id= mysql->thread_id; 276 TRACE_INFO *info; 277 278 /* check if package is server greeting package, 279 * and set thread_id */ 280 if (!thread_id && mode == READ) 281 { 282 char *p= (char *)buffer; 283 p+= 4; /* packet length */ 284 if ((uchar)*p != 0xFF) /* protocol version 0xFF indicates error */ 285 { 286 p+= strlen(p + 1) + 2; 287 thread_id= uint4korr(p); 288 } 289 info= get_trace_info(thread_id); 290 info->last_command= -1; 291 } 292 else 293 { 294 char *p= (char *)buffer; 295 info= get_trace_info(thread_id); 296 297 if (info->last_command == -1) 298 { 299 if (mode == WRITE) 300 { 301 /* client authentication reply packet: 302 * 303 * ofs description length 304 * ------------------------ 305 * 0 length 3 306 * 3 packet_no 1 307 * 4 client capab. 4 308 * 8 max_packet_size 4 309 * 12 character set 1 310 * 13 reserved 23 311 * ------------------------ 312 * 36 username (zero terminated) 313 * len (1 byte) + password or 314 */ 315 316 p+= 4; 317 info->client_flags= uint4korr(p); 318 p+= 4; 319 info->max_packet_size= uint4korr(p); 320 p+= 4; 321 info->charset= *p; 322 p+= 24; 323 info->username= strdup(p); 324 p+= strlen(p) + 1; 325 if (*p) /* we are not interested in authentication data */ 326 p+= *p; 327 p++; 328 if (info->client_flags & CLIENT_CONNECT_WITH_DB) 329 info->db= strdup(p); 330 } 331 else 332 { 333 p++; 334 if ((uchar)*p == 0xFF) 335 printf("%8lu: CONNECT_ERROR(%d)\n", info->thread_id, uint4korr(p+1)); 336 else 337 printf("%8lu: CONNECT_SUCCESS(host=%s,user=%s,db=%s)\n", info->thread_id, 338 mysql->host, info->username, info->db ? info->db : "'none'"); 339 info->last_command= COM_SLEEP; 340 } 341 } 342 else { 343 char *p= (char *)buffer; 344 int len; 345 346 if (mode == WRITE) 347 { 348 if (info->pkt_length > 0) 349 { 350 info->pkt_length-= length; 351 return; 352 } 353 len= uint3korr(p); 354 info->pkt_length= len + 4 - length; 355 p+= 4; 356 info->last_command= *p; 357 p++; 358 359 switch (info->last_command) { 360 case COM_INIT_DB: 361 case COM_DROP_DB: 362 case COM_CREATE_DB: 363 case COM_DEBUG: 364 case COM_QUERY: 365 case COM_STMT_PREPARE: 366 trace_set_command(info, p, len - 1); 367 break; 368 case COM_PROCESS_KILL: 369 info->refid= uint4korr(p); 370 break; 371 case COM_QUIT: 372 printf("%8lu: COM_QUIT\n", info->thread_id); 373 delete_trace_info(info->thread_id); 374 break; 375 case COM_PING: 376 printf("%8lu: COM_PING\n", info->thread_id); 377 break; 378 case COM_STMT_EXECUTE: 379 case COM_STMT_RESET: 380 case COM_STMT_CLOSE: 381 info->refid= uint4korr(p); 382 break; 383 case COM_CHANGE_USER: 384 break; 385 default: 386 if (info->local_infile == 1) 387 { 388 printf("%8lu: SEND_LOCAL_INFILE(%s) ", info->thread_id, info->filename); 389 if (len) 390 printf("sent %d bytes\n", len); 391 else 392 printf("- error\n"); 393 info->local_infile= 2; 394 } 395 else 396 printf("%8lu: UNKNOWN_COMMAND: %d\n", info->thread_id, info->last_command); 397 break; 398 } 399 } 400 else 401 { 402 my_bool is_error; 403 404 len= uint3korr(p); 405 p+= 4; 406 407 is_error= (len == -1); 408 409 switch(info->last_command) { 410 case COM_STMT_EXECUTE: 411 case COM_STMT_RESET: 412 case COM_STMT_CLOSE: 413 case COM_PROCESS_KILL: 414 dump_reference(info, is_error); 415 info->refid= 0; 416 info->last_command= 0; 417 break; 418 case COM_QUIT: 419 dump_simple(info, is_error); 420 break; 421 case COM_QUERY: 422 case COM_INIT_DB: 423 case COM_DROP_DB: 424 case COM_CREATE_DB: 425 case COM_DEBUG: 426 case COM_CHANGE_USER: 427 if (info->last_command == COM_QUERY && (uchar)*p == 251) 428 { 429 info->local_infile= 1; 430 p++; 431 info->filename= (char *)malloc(len); 432 strncpy(info->filename, (char *)p, len); 433 dump_command(info, is_error); 434 break; 435 } 436 dump_command(info, is_error); 437 if (info->local_infile != 1) 438 { 439 free(info->command); 440 info->command= NULL; 441 } 442 break; 443 case COM_STMT_PREPARE: 444 printf("%8lu: COM_STMT_PREPARE(%s) ", info->thread_id, info->command); 445 if (!*p) 446 { 447 unsigned long stmt_id= uint4korr(p+1); 448 printf("-> stmt_id(%lu)\n", stmt_id); 449 } 450 else 451 printf("error\n"); 452 break; 453 } 454 } 455 } 456 } 457 info->total_size[mode]+= length; 458 } 459