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
get_trace_info(unsigned long thread_id)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
delete_trace_info(unsigned long thread_id)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 */
trace_init(char * errormsg,size_t errormsg_size,int unused1,va_list unused2)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
trace_deinit(void)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
trace_set_command(TRACE_INFO * info,char * buffer,size_t size)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
dump_buffer(uchar * buffer,size_t len)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
dump_simple(TRACE_INFO * info,my_bool is_error)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
dump_reference(TRACE_INFO * info,my_bool is_error)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
dump_command(TRACE_INFO * info,my_bool is_error)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
trace_callback(int mode,MYSQL * mysql,const uchar * buffer,size_t length)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