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
20/* MariaDB Communication IO (PVIO) interface
21
22 PVIO is the interface for client server communication and replaces former vio
23 component of the client library.
24
25 PVIO support various protcols like sockets, pipes and shared memory, which are
26 implemented as plugins and can be extended therefore easily.
27
28 Interface function description:
29
30 ma_pvio_init allocates a new PVIO object which will be used
31 for the current connection
32
33 ma_pvio_close frees all resources of previously allocated PVIO object
34 and closes open connections
35
36 ma_pvio_read reads data from server
37
38 ma_pvio_write sends data to server
39
40 ma_pvio_set_timeout sets timeout for connection, read and write
41
42 ma_pvio_register_callback
43 register callback functions for read and write
44 */
45
46#include <ma_global.h>
47#include <ma_sys.h>
48#include <mysql.h>
49#include <errmsg.h>
50#include <mysql/client_plugin.h>
51#include <string.h>
52#include <ma_common.h>
53#include <ma_pvio.h>
54#include <mariadb_async.h>
55#include <ma_context.h>
56
57/* callback functions for read/write */
58LIST *pvio_callback= NULL;
59
60#define IS_BLOCKING_ERROR() \
61 IF_WIN(WSAGetLastError() != WSAEWOULDBLOCK, \
62 (errno != EAGAIN && errno != EINTR))
63
64/* {{{ MARIADB_PVIO *ma_pvio_init */
65MARIADB_PVIO *ma_pvio_init(MA_PVIO_CINFO *cinfo)
66{
67 /* check connection type and load the required plugin.
68 * Currently we support the following pvio types:
69 * pvio_socket
70 * pvio_namedpipe
71 * pvio_sharedmed
72 */
73 const char *pvio_plugins[] = {"pvio_socket", "pvio_npipe", "pvio_shmem"};
74 int type;
75 MARIADB_PVIO_PLUGIN *pvio_plugin;
76 MARIADB_PVIO *pvio= NULL;
77
78 switch (cinfo->type)
79 {
80 case PVIO_TYPE_UNIXSOCKET:
81 case PVIO_TYPE_SOCKET:
82 type= 0;
83 break;
84#ifdef _WIN32
85 case PVIO_TYPE_NAMEDPIPE:
86 type= 1;
87 break;
88 case PVIO_TYPE_SHAREDMEM:
89 type= 2;
90 break;
91#endif
92 default:
93 return NULL;
94 }
95
96 if (!(pvio_plugin= (MARIADB_PVIO_PLUGIN *)
97 mysql_client_find_plugin(cinfo->mysql,
98 pvio_plugins[type],
99 MARIADB_CLIENT_PVIO_PLUGIN)))
100 {
101 /* error already set in mysql_client_find_plugin */
102 return NULL;
103 }
104
105
106 if (!(pvio= (MARIADB_PVIO *)calloc(1, sizeof(MARIADB_PVIO))))
107 {
108 PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0);
109 return NULL;
110 }
111
112 /* register error routine and methods */
113 pvio->methods= pvio_plugin->methods;
114 pvio->set_error= my_set_error;
115 pvio->type= cinfo->type;
116
117 /* set timeout to connect timeout - after successful connect we will set
118 * correct values for read and write */
119 if (pvio->methods->set_timeout)
120 {
121 pvio->methods->set_timeout(pvio, PVIO_CONNECT_TIMEOUT, cinfo->mysql->options.connect_timeout);
122 pvio->methods->set_timeout(pvio, PVIO_READ_TIMEOUT, cinfo->mysql->options.connect_timeout);
123 pvio->methods->set_timeout(pvio, PVIO_WRITE_TIMEOUT, cinfo->mysql->options.connect_timeout);
124 }
125
126 if (!(pvio->cache= calloc(1, PVIO_READ_AHEAD_CACHE_SIZE)))
127 {
128 PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0);
129 free(pvio);
130 return NULL;
131 }
132 pvio->cache_size= 0;
133 pvio->cache_pos= pvio->cache;
134
135 return pvio;
136}
137/* }}} */
138
139/* {{{ my_bool ma_pvio_is_alive */
140my_bool ma_pvio_is_alive(MARIADB_PVIO *pvio)
141{
142 if (!pvio)
143 return FALSE;
144 if (pvio->methods->is_alive)
145 return pvio->methods->is_alive(pvio);
146 return TRUE;
147}
148/* }}} */
149
150/* {{{ int ma_pvio_fast_send */
151int ma_pvio_fast_send(MARIADB_PVIO *pvio)
152{
153 if (!pvio || !pvio->methods->fast_send)
154 return 1;
155 return pvio->methods->fast_send(pvio);
156}
157/* }}} */
158
159/* {{{ int ma_pvio_keepalive */
160int ma_pvio_keepalive(MARIADB_PVIO *pvio)
161{
162 if (!pvio || !pvio->methods->keepalive)
163 return 1;
164 return pvio->methods->keepalive(pvio);
165}
166/* }}} */
167
168/* {{{ my_bool ma_pvio_set_timeout */
169my_bool ma_pvio_set_timeout(MARIADB_PVIO *pvio,
170 enum enum_pvio_timeout type,
171 int timeout)
172{
173 if (!pvio)
174 return 1;
175
176 if (pvio->methods->set_timeout)
177 return pvio->methods->set_timeout(pvio, type, timeout);
178 return 1;
179}
180/* }}} */
181
182/* {{{ size_t ma_pvio_read_async */
183static size_t ma_pvio_read_async(MARIADB_PVIO *pvio, uchar *buffer, size_t length)
184{
185 ssize_t res= 0;
186 struct mysql_async_context *b= pvio->mysql->options.extension->async_context;
187 int timeout= pvio->timeout[PVIO_READ_TIMEOUT];
188
189 if (!pvio->methods->async_read)
190 {
191 PVIO_SET_ERROR(pvio->mysql, CR_ASYNC_NOT_SUPPORTED, unknown_sqlstate, 0);
192 return -1;
193 }
194
195 for (;;)
196 {
197 if (pvio->methods->async_read)
198 res= pvio->methods->async_read(pvio, buffer, length);
199 if (res >= 0 || IS_BLOCKING_ERROR())
200 return res;
201 b->events_to_wait_for= MYSQL_WAIT_READ;
202 if (timeout >= 0)
203 {
204 b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT;
205 b->timeout_value= timeout;
206 }
207 if (b->suspend_resume_hook)
208 (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data);
209 my_context_yield(&b->async_context);
210 if (b->suspend_resume_hook)
211 (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data);
212 if (b->events_occured & MYSQL_WAIT_TIMEOUT)
213 return -1;
214 }
215}
216/* }}} */
217
218/* {{{ size_t ma_pvio_read */
219ssize_t ma_pvio_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length)
220{
221 ssize_t r= -1;
222 if (!pvio)
223 return -1;
224 if (IS_PVIO_ASYNC_ACTIVE(pvio))
225 {
226 r=
227#if defined(HAVE_TLS) && !defined(HAVE_SCHANNEL)
228 (pvio->ctls) ? ma_tls_read_async(pvio, buffer, length) :
229#endif
230 (ssize_t)ma_pvio_read_async(pvio, buffer, length);
231 goto end;
232 }
233 else
234 {
235 if (IS_PVIO_ASYNC(pvio))
236 {
237 /*
238 If switching from non-blocking to blocking API usage, set the socket
239 back to blocking mode.
240 */
241 my_bool old_mode;
242 ma_pvio_blocking(pvio, TRUE, &old_mode);
243 }
244 }
245
246 /* secure connection */
247#ifdef HAVE_TLS
248 if (pvio->ctls)
249 {
250 r= ma_pvio_tls_read(pvio->ctls, buffer, length);
251 goto end;
252 }
253#endif
254 if (pvio->methods->read)
255 r= pvio->methods->read(pvio, buffer, length);
256end:
257 if (pvio_callback)
258 {
259 void (*callback)(int mode, MYSQL *mysql, const uchar *buffer, size_t length);
260 LIST *p= pvio_callback;
261 while (p)
262 {
263 callback= p->data;
264 callback(0, pvio->mysql, buffer, r);
265 p= p->next;
266 }
267 }
268 return r;
269}
270/* }}} */
271
272/* {{{ size_t ma_pvio_cache_read */
273ssize_t ma_pvio_cache_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length)
274{
275 ssize_t r;
276
277 if (!pvio)
278 return -1;
279
280 if (!pvio->cache)
281 return ma_pvio_read(pvio, buffer, length);
282
283 if (pvio->cache + pvio->cache_size > pvio->cache_pos)
284 {
285 ssize_t remaining = pvio->cache + pvio->cache_size - pvio->cache_pos;
286 assert(remaining > 0);
287 r= MIN((ssize_t)length, remaining);
288 memcpy(buffer, pvio->cache_pos, r);
289 pvio->cache_pos+= r;
290 }
291 else if (length >= PVIO_READ_AHEAD_CACHE_MIN_SIZE)
292 {
293 r= ma_pvio_read(pvio, buffer, length);
294 }
295 else
296 {
297 r= ma_pvio_read(pvio, pvio->cache, PVIO_READ_AHEAD_CACHE_SIZE);
298 if (r > 0)
299 {
300 if (length < (size_t)r)
301 {
302 pvio->cache_size= r;
303 pvio->cache_pos= pvio->cache + length;
304 r= length;
305 }
306 memcpy(buffer, pvio->cache, r);
307 }
308 }
309 return r;
310}
311/* }}} */
312
313/* {{{ size_t ma_pvio_write_async */
314static ssize_t ma_pvio_write_async(MARIADB_PVIO *pvio, const uchar *buffer, size_t length)
315{
316 ssize_t res;
317 struct mysql_async_context *b= pvio->mysql->options.extension->async_context;
318 int timeout= pvio->timeout[PVIO_WRITE_TIMEOUT];
319
320 for (;;)
321 {
322 res= pvio->methods->async_write(pvio, buffer, length);
323 if (res >= 0 || IS_BLOCKING_ERROR())
324 return res;
325 b->events_to_wait_for= MYSQL_WAIT_WRITE;
326 if (timeout >= 0)
327 {
328 b->events_to_wait_for|= MYSQL_WAIT_TIMEOUT;
329 b->timeout_value= timeout;
330 }
331 if (b->suspend_resume_hook)
332 (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data);
333 my_context_yield(&b->async_context);
334 if (b->suspend_resume_hook)
335 (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data);
336 if (b->events_occured & MYSQL_WAIT_TIMEOUT)
337 return -1;
338 }
339}
340/* }}} */
341
342/* {{{ size_t ma_pvio_write */
343ssize_t ma_pvio_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length)
344{
345 ssize_t r= 0;
346
347 if (!pvio)
348 return -1;
349
350 if (IS_PVIO_ASYNC_ACTIVE(pvio))
351 {
352 r=
353#if defined(HAVE_TLS) && !defined(HAVE_SCHANNEL)
354 (pvio->ctls) ? ma_tls_write_async(pvio, buffer, length) :
355#endif
356 ma_pvio_write_async(pvio, buffer, length);
357 goto end;
358 }
359 else
360 {
361 if (IS_PVIO_ASYNC(pvio))
362 {
363 /*
364 If switching from non-blocking to blocking API usage, set the socket
365 back to blocking mode.
366 */
367 my_bool old_mode;
368 ma_pvio_blocking(pvio, TRUE, &old_mode);
369 }
370 }
371 /* secure connection */
372#ifdef HAVE_TLS
373 if (pvio->ctls)
374 {
375 r= ma_pvio_tls_write(pvio->ctls, buffer, length);
376 goto end;
377 }
378#endif
379
380 if (pvio->methods->write)
381 r= pvio->methods->write(pvio, buffer, length);
382end:
383 if (pvio_callback)
384 {
385 void (*callback)(int mode, MYSQL *mysql, const uchar *buffer, size_t length);
386 LIST *p= pvio_callback;
387 while (p)
388 {
389 callback= p->data;
390 callback(1, pvio->mysql, buffer, r);
391 p= p->next;
392 }
393 }
394 return r;
395}
396/* }}} */
397
398/* {{{ void ma_pvio_close */
399void ma_pvio_close(MARIADB_PVIO *pvio)
400{
401 /* free internal structures and close connection */
402#ifdef HAVE_TLS
403 if (pvio && pvio->ctls)
404 {
405 ma_pvio_tls_close(pvio->ctls);
406 free(pvio->ctls);
407 }
408#endif
409 if (pvio && pvio->methods->close)
410 pvio->methods->close(pvio);
411
412 if (pvio->cache)
413 free(pvio->cache);
414
415 free(pvio);
416}
417/* }}} */
418
419/* {{{ my_bool ma_pvio_get_handle */
420my_bool ma_pvio_get_handle(MARIADB_PVIO *pvio, void *handle)
421{
422 if (pvio && pvio->methods->get_handle)
423 return pvio->methods->get_handle(pvio, handle);
424 return 1;
425}
426/* }}} */
427
428/* {{{ ma_pvio_wait_async */
429static my_bool
430ma_pvio_wait_async(struct mysql_async_context *b, enum enum_pvio_io_event event,
431 int timeout)
432{
433 switch (event)
434 {
435 case VIO_IO_EVENT_READ:
436 b->events_to_wait_for = MYSQL_WAIT_READ;
437 break;
438 case VIO_IO_EVENT_WRITE:
439 b->events_to_wait_for = MYSQL_WAIT_WRITE;
440 break;
441 case VIO_IO_EVENT_CONNECT:
442 b->events_to_wait_for = MYSQL_WAIT_WRITE | IF_WIN(0, MYSQL_WAIT_EXCEPT);
443 break;
444 }
445
446 if (timeout >= 0)
447 {
448 b->events_to_wait_for |= MYSQL_WAIT_TIMEOUT;
449 b->timeout_value= timeout;
450 }
451 if (b->suspend_resume_hook)
452 (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data);
453 my_context_yield(&b->async_context);
454 if (b->suspend_resume_hook)
455 (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data);
456 return (b->events_occured & MYSQL_WAIT_TIMEOUT) ? 0 : 1;
457}
458/* }}} */
459
460/* {{{ ma_pvio_wait_io_or_timeout */
461int ma_pvio_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout)
462{
463 if (IS_PVIO_ASYNC_ACTIVE(pvio))
464 return ma_pvio_wait_async(pvio->mysql->options.extension->async_context,
465 (is_read) ? VIO_IO_EVENT_READ : VIO_IO_EVENT_WRITE,
466 timeout);
467
468 if (pvio && pvio->methods->wait_io_or_timeout)
469 return pvio->methods->wait_io_or_timeout(pvio, is_read, timeout);
470 return 1;
471}
472/* }}} */
473
474/* {{{ my_bool ma_pvio_connect */
475my_bool ma_pvio_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo)
476{
477 if (pvio && pvio->methods->connect)
478 return pvio->methods->connect(pvio, cinfo);
479 return 1;
480}
481/* }}} */
482
483/* {{{ my_bool ma_pvio_blocking */
484my_bool ma_pvio_blocking(MARIADB_PVIO *pvio, my_bool block, my_bool *previous_mode)
485{
486 if (pvio && pvio->methods->blocking)
487 return pvio->methods->blocking(pvio, block, previous_mode);
488 return 1;
489}
490/* }}} */
491
492/* {{{ my_bool ma_pvio_is_blocking */
493my_bool ma_pvio_is_blocking(MARIADB_PVIO *pvio)
494{
495 if (pvio && pvio->methods->is_blocking)
496 return pvio->methods->is_blocking(pvio);
497 return 1;
498}
499/* }}} */
500
501/* {{{ ma_pvio_has_data */
502my_bool ma_pvio_has_data(MARIADB_PVIO *pvio, ssize_t *data_len)
503{
504 /* check if we still have unread data in cache */
505 if (pvio && pvio->cache)
506 if (pvio->cache_pos > pvio->cache)
507 return test(pvio->cache_pos - pvio->cache);
508 if (pvio && pvio->methods->has_data)
509 return pvio->methods->has_data(pvio, data_len);
510 return 1;
511}
512/* }}} */
513
514#ifdef HAVE_TLS
515/* {{{ my_bool ma_pvio_start_ssl */
516my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio)
517{
518 if (!pvio || !pvio->mysql)
519 return 1;
520 CLEAR_CLIENT_ERROR(pvio->mysql);
521 if (!(pvio->ctls= ma_pvio_tls_init(pvio->mysql)))
522 {
523 return 1;
524 }
525 if (ma_pvio_tls_connect(pvio->ctls))
526 {
527 free(pvio->ctls);
528 pvio->ctls= NULL;
529 return 1;
530 }
531
532 /* default behaviour:
533 1. peer certificate verification
534 2. verify CN (requires option ssl_verify_check)
535 3. verrify finger print
536 */
537 if ((pvio->mysql->options.ssl_ca || pvio->mysql->options.ssl_capath) &&
538 (pvio->mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) &&
539 ma_pvio_tls_verify_server_cert(pvio->ctls))
540 return 1;
541
542 if (pvio->mysql->options.extension &&
543 ((pvio->mysql->options.extension->tls_fp && pvio->mysql->options.extension->tls_fp[0]) ||
544 (pvio->mysql->options.extension->tls_fp_list && pvio->mysql->options.extension->tls_fp_list[0])))
545 {
546 if (ma_pvio_tls_check_fp(pvio->ctls,
547 pvio->mysql->options.extension->tls_fp,
548 pvio->mysql->options.extension->tls_fp_list))
549 return 1;
550 }
551
552 return 0;
553}
554/* }}} */
555#endif
556
557/* {{{ ma_pvio_register_callback */
558int ma_pvio_register_callback(my_bool register_callback,
559 void (*callback_function)(int mode, MYSQL *mysql, const uchar *buffer, size_t length))
560{
561 LIST *list;
562
563 if (!callback_function)
564 return 1;
565
566 /* plugin will unregister in it's deinit function */
567 if (register_callback)
568 {
569 list= (LIST *)malloc(sizeof(LIST));
570
571 list->data= (void *)callback_function;
572 pvio_callback= list_add(pvio_callback, list);
573 }
574 else /* unregister callback function */
575 {
576 LIST *p= pvio_callback;
577 while (p)
578 {
579 if (p->data == callback_function)
580 {
581 list_delete(pvio_callback, p);
582 break;
583 }
584 p= p->next;
585 }
586 }
587 return 0;
588}
589/* }}} */
590