diff options
Diffstat (limited to 'libmariadb/plugins/pvio')
-rw-r--r-- | libmariadb/plugins/pvio/CMakeLists.txt | 27 | ||||
-rw-r--r-- | libmariadb/plugins/pvio/pvio_npipe.c | 359 | ||||
-rw-r--r-- | libmariadb/plugins/pvio/pvio_plugin.def | 2 | ||||
-rw-r--r-- | libmariadb/plugins/pvio/pvio_shmem.c | 469 | ||||
-rw-r--r-- | libmariadb/plugins/pvio/pvio_socket.c | 1165 |
5 files changed, 2022 insertions, 0 deletions
diff --git a/libmariadb/plugins/pvio/CMakeLists.txt b/libmariadb/plugins/pvio/CMakeLists.txt new file mode 100644 index 00000000..b74407a6 --- /dev/null +++ b/libmariadb/plugins/pvio/CMakeLists.txt @@ -0,0 +1,27 @@ +SET(PVIO_DIR ${CC_SOURCE_DIR}/plugins/pvio) + +INCLUDE_DIRECTORIES(${PVIO_DIR}) +INCLUDE_DIRECTORIES(${CC_SOURCE_DIR}/include) + +#native password +REGISTER_PLUGIN(TARGET pvio_socket + TYPE MARIADB_CLIENT_PLUGIN_PVIO + CONFIGURATIONS STATIC DYNAMIC DEFAULT + DEFAULT STATIC + SOURCES ${CC_SOURCE_DIR}/plugins/pvio/pvio_socket.c) + +IF(WIN32) + # named pipe + REGISTER_PLUGIN(TARGET pvio_npipe + TYPE MARIADB_CLIENT_PLUGIN_PVIO + CONFIGURATIONS STATIC DYNAMIC DEFAULT + DEFAULT STATIC + SOURCES ${CC_SOURCE_DIR}/plugins/pvio/pvio_npipe.c) + + # shared memory + REGISTER_PLUGIN(TARGET pvio_shmem + TYPE MARIADB_CLIENT_PLUGIN_PVIO + CONFIGURATIONS STATIC DYNAMIC DEFAULT + DEFAULT DYNAMIC + SOURCES ${CC_SOURCE_DIR}/plugins/pvio/pvio_shmem.c) +ENDIF() diff --git a/libmariadb/plugins/pvio/pvio_npipe.c b/libmariadb/plugins/pvio/pvio_npipe.c new file mode 100644 index 00000000..17c59cef --- /dev/null +++ b/libmariadb/plugins/pvio/pvio_npipe.c @@ -0,0 +1,359 @@ +/************************************************************************************ + Copyright (C) 2015 Georg Richter and MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + +*************************************************************************************/ + +/* MariaDB virtual IO plugin for Windows named pipe communication */ + +#ifdef _WIN32 + +#include <ma_global.h> +#include <ma_sys.h> +#include <errmsg.h> +#include <mysql.h> +#include <mysql/client_plugin.h> +#include <string.h> +#include <ma_string.h> + +/* Function prototypes */ +my_bool pvio_npipe_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout); +int pvio_npipe_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type); +ssize_t pvio_npipe_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length); +ssize_t pvio_npipe_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length); + +my_bool pvio_npipe_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo); +my_bool pvio_npipe_close(MARIADB_PVIO *pvio); +int pvio_npipe_fast_send(MARIADB_PVIO *pvio); +int pvio_npipe_keepalive(MARIADB_PVIO *pvio); +my_bool pvio_npipe_get_handle(MARIADB_PVIO *pvio, void *handle); +my_bool pvio_npipe_is_blocking(MARIADB_PVIO *pvio); +int pvio_npipe_shutdown(MARIADB_PVIO *pvio); +my_bool pvio_npipe_is_alive(MARIADB_PVIO *pvio); + +struct st_ma_pvio_methods pvio_npipe_methods= { + pvio_npipe_set_timeout, + pvio_npipe_get_timeout, + pvio_npipe_read, + NULL, + pvio_npipe_write, + NULL, + NULL, + NULL, + pvio_npipe_connect, + pvio_npipe_close, + pvio_npipe_fast_send, + pvio_npipe_keepalive, + pvio_npipe_get_handle, + pvio_npipe_is_blocking, + pvio_npipe_is_alive, + NULL, + pvio_npipe_shutdown +}; + +#ifndef PLUGIN_DYNAMIC +MARIADB_PVIO_PLUGIN pvio_npipe_client_plugin = +#else +MARIADB_PVIO_PLUGIN _mysql_client_plugin_declaration_ = +#endif +{ + MARIADB_CLIENT_PVIO_PLUGIN, + MARIADB_CLIENT_PVIO_PLUGIN_INTERFACE_VERSION, + "pvio_npipe", + "Georg Richter", + "MariaDB virtual IO plugin for named pipe connection", + {1, 0, 0}, + "LGPL", + NULL, + NULL, + NULL, + NULL, + &pvio_npipe_methods +}; + +struct st_pvio_npipe { + HANDLE pipe; + OVERLAPPED overlapped; + MYSQL *mysql; +}; + +my_bool pvio_npipe_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout) +{ + int timeout_ms; + + if (!pvio) + return 1; + if (timeout > INT_MAX/1000) + timeout_ms= -1; + else if (timeout <=0) + timeout_ms= -1; + else + timeout_ms = timeout*1000; + + pvio->timeout[type]= timeout_ms; + return 0; +} + +int pvio_npipe_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type) +{ + if (!pvio) + return -1; + return pvio->timeout[type] / 1000; +} + +static BOOL complete_io(HANDLE file, OVERLAPPED *ov, BOOL ret, DWORD timeout, DWORD *size) +{ + if (ret) + timeout = 0; /* IO completed successfully, do not WaitForSingleObject */ + else + { + assert(timeout); + if (GetLastError() != ERROR_IO_PENDING) + return FALSE; + } + + if (timeout) + { + HANDLE wait_handle= ov->hEvent; + assert(wait_handle && (wait_handle != INVALID_HANDLE_VALUE)); + + DWORD wait_ret= WaitForSingleObject(wait_handle, timeout); + switch (wait_ret) + { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + CancelIoEx(file, ov); + SetLastError(ERROR_TIMEOUT); + return FALSE; + default: + /* WAIT_ABANDONED or WAIT_FAILED unexpected. */ + assert(0); + return FALSE; + } + } + + return GetOverlappedResult(file, ov, size, FALSE); +} + +ssize_t pvio_npipe_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length) +{ + BOOL ret; + ssize_t r= -1; + struct st_pvio_npipe *cpipe= NULL; + DWORD size; + + if (!pvio || !pvio->data) + return -1; + + cpipe= (struct st_pvio_npipe *)pvio->data; + + ret= ReadFile(cpipe->pipe, buffer, (DWORD)length, NULL, &cpipe->overlapped); + ret= complete_io(cpipe->pipe, &cpipe->overlapped, ret, pvio->timeout[PVIO_READ_TIMEOUT], &size); + r= ret? (ssize_t) size:-1; + + return r; +} + +ssize_t pvio_npipe_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length) +{ + ssize_t r= -1; + struct st_pvio_npipe *cpipe= NULL; + BOOL ret; + DWORD size; + + if (!pvio || !pvio->data) + return -1; + + cpipe= (struct st_pvio_npipe *)pvio->data; + + ret= WriteFile(cpipe->pipe, buffer, (DWORD)length, NULL , &cpipe->overlapped); + ret= complete_io(cpipe->pipe, &cpipe->overlapped, ret, pvio->timeout[PVIO_WRITE_TIMEOUT], &size); + r= ret ? (ssize_t)size : -1; + return r; +} + + +int pvio_npipe_keepalive(MARIADB_PVIO *pvio) +{ + /* keep alive is used for TCP/IP connections only */ + return 0; +} + +int pvio_npipe_fast_send(MARIADB_PVIO *pvio) +{ + /* not supported */ + return 0; +} +my_bool pvio_npipe_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo) +{ + struct st_pvio_npipe *cpipe= NULL; + + if (!pvio || !cinfo) + return 1; + + /* if connect timeout is set, we will overwrite read/write timeout */ + if (pvio->timeout[PVIO_CONNECT_TIMEOUT]) + { + pvio->timeout[PVIO_READ_TIMEOUT]= pvio->timeout[PVIO_WRITE_TIMEOUT]= pvio->timeout[PVIO_CONNECT_TIMEOUT]; + } + + if (!(cpipe= (struct st_pvio_npipe *)LocalAlloc(LMEM_ZEROINIT, sizeof(struct st_pvio_npipe)))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, "HY000", 0, ""); + return 1; + } + pvio->data= (void *)cpipe; + cpipe->pipe= INVALID_HANDLE_VALUE; + pvio->mysql= cinfo->mysql; + pvio->type= cinfo->type; + + if (cinfo->type == PVIO_TYPE_NAMEDPIPE) + { + char szPipeName[MAX_PATH]; + ULONGLONG deadline; + LONGLONG wait_ms; + DWORD backoff= 0; /* Avoid busy wait if ERROR_PIPE_BUSY.*/ + if ( ! cinfo->unix_socket || (cinfo->unix_socket)[0] == 0x00) + cinfo->unix_socket = MARIADB_NAMEDPIPE; + if (!cinfo->host || !strcmp(cinfo->host,LOCAL_HOST)) + cinfo->host=LOCAL_HOST_NAMEDPIPE; + + szPipeName[MAX_PATH - 1]= 0; + snprintf(szPipeName, MAX_PATH - 1, "\\\\%s\\pipe\\%s", cinfo->host, cinfo->unix_socket); + + if (pvio->timeout[PVIO_CONNECT_TIMEOUT] > 0) + deadline = GetTickCount64() + pvio->timeout[PVIO_CONNECT_TIMEOUT]; + else + deadline = INFINITE; + + while (1) + { + if ((cpipe->pipe = CreateFile(szPipeName, + GENERIC_READ | + GENERIC_WRITE, + 0, /* no sharing */ + NULL, /* default security attributes */ + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL)) != INVALID_HANDLE_VALUE) + break; + + if (GetLastError() != ERROR_PIPE_BUSY) + { + pvio->set_error(pvio->mysql, CR_NAMEDPIPEOPEN_ERROR, "HY000", 0, + cinfo->host, cinfo->unix_socket, GetLastError()); + goto end; + } + + Sleep(backoff); + if (!backoff) + backoff = 1; + + wait_ms = deadline - GetTickCount64(); + if (wait_ms > INFINITE) + wait_ms = INFINITE; + + if ((wait_ms <= 0) || !WaitNamedPipe(szPipeName, (DWORD)wait_ms)) + { + pvio->set_error(pvio->mysql, CR_NAMEDPIPEWAIT_ERROR, "HY000", 0, + cinfo->host, cinfo->unix_socket, ERROR_TIMEOUT); + goto end; + } + } + + + if (!(cpipe->overlapped.hEvent= CreateEvent(NULL, FALSE, FALSE, NULL))) + { + pvio->set_error(pvio->mysql, CR_EVENT_CREATE_FAILED, "HY000", 0, + GetLastError()); + goto end; + } + return 0; + } +end: + if (cpipe) + { + if (cpipe->pipe != INVALID_HANDLE_VALUE) + CloseHandle(cpipe->pipe); + LocalFree(cpipe); + pvio->data= NULL; + } + return 1; +} + +my_bool pvio_npipe_close(MARIADB_PVIO *pvio) +{ + struct st_pvio_npipe *cpipe= NULL; + int r= 0; + + if (!pvio) + return 1; + + if (pvio->data) + { + cpipe= (struct st_pvio_npipe *)pvio->data; + CloseHandle(cpipe->overlapped.hEvent); + if (cpipe->pipe != INVALID_HANDLE_VALUE) + { + CloseHandle(cpipe->pipe); + cpipe->pipe= INVALID_HANDLE_VALUE; + } + LocalFree(pvio->data); + pvio->data= NULL; + } + return r; +} + +my_bool pvio_npipe_get_handle(MARIADB_PVIO *pvio, void *handle) +{ + if (pvio && pvio->data) + { + *(HANDLE *)handle= ((struct st_pvio_npipe *)pvio->data)->pipe; + return 0; + } + return 1; +} + +my_bool pvio_npipe_is_blocking(MARIADB_PVIO *pvio) +{ + return 1; +} + +int pvio_npipe_shutdown(MARIADB_PVIO *pvio) +{ + HANDLE h; + if (pvio_npipe_get_handle(pvio, &h) == 0) + { + return(CancelIoEx(h, NULL) ? 0 : 1); + } + return 1; +} + +my_bool pvio_npipe_is_alive(MARIADB_PVIO *pvio) +{ + HANDLE handle; + if (!pvio || !pvio->data) + return FALSE; + + handle= ((struct st_pvio_npipe *)pvio->data)->pipe; + /* Copy data from named pipe without removing it */ + if (PeekNamedPipe(handle, NULL, 0, NULL, NULL, NULL)) + return TRUE; + return test(GetLastError() != ERROR_BROKEN_PIPE); +} +#endif diff --git a/libmariadb/plugins/pvio/pvio_plugin.def b/libmariadb/plugins/pvio/pvio_plugin.def new file mode 100644 index 00000000..70af9256 --- /dev/null +++ b/libmariadb/plugins/pvio/pvio_plugin.def @@ -0,0 +1,2 @@ +EXPORTS + _mysql_client_plugin_declaration_ DATA diff --git a/libmariadb/plugins/pvio/pvio_shmem.c b/libmariadb/plugins/pvio/pvio_shmem.c new file mode 100644 index 00000000..f412393b --- /dev/null +++ b/libmariadb/plugins/pvio/pvio_shmem.c @@ -0,0 +1,469 @@ +/************************************************************************************ + Copyright (C) 2015 Georg Richter and MariaDB Corporation AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + +*************************************************************************************/ +/* MariaDB virtual IO plugin for Windows shared memory communication */ + +#ifdef _WIN32 + +#include <ma_global.h> +#include <ma_sys.h> +#include <errmsg.h> +#include <mysql.h> +#include <mysql/client_plugin.h> +#include <string.h> +#include <ma_string.h> + +#define PVIO_SHM_BUFFER_SIZE (16000 + 4) + +my_bool pvio_shm_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout); +int pvio_shm_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type); +ssize_t pvio_shm_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length); +ssize_t pvio_shm_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length); +int pvio_shm_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout); +int pvio_shm_blocking(MARIADB_PVIO *pvio, my_bool value, my_bool *old_value); +my_bool pvio_shm_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo); +my_bool pvio_shm_close(MARIADB_PVIO *pvio); +int pvio_shm_shutdown(MARIADB_PVIO *pvio); +my_bool pvio_shm_is_alive(MARIADB_PVIO *pvio); +my_bool pvio_shm_get_handle(MARIADB_PVIO *pvio, void *handle); + +struct st_ma_pvio_methods pvio_shm_methods= { + pvio_shm_set_timeout, + pvio_shm_get_timeout, + pvio_shm_read, + NULL, + pvio_shm_write, + NULL, + pvio_shm_wait_io_or_timeout, + pvio_shm_blocking, + pvio_shm_connect, + pvio_shm_close, + NULL, + NULL, + pvio_shm_get_handle, + NULL, + pvio_shm_is_alive, + NULL, + pvio_shm_shutdown +}; + +#ifndef PLUGIN_DYNAMIC +MARIADB_PVIO_PLUGIN pvio_shmem_client_plugin= +#else +MARIADB_PVIO_PLUGIN _mysql_client_plugin_declaration_= +#endif +{ + MARIADB_CLIENT_PVIO_PLUGIN, + MARIADB_CLIENT_PVIO_PLUGIN_INTERFACE_VERSION, + "pvio_shmem", + "Georg Richter", + "MariaDB virtual IO plugin for Windows shared memory communication", + {1, 0, 0}, + "LGPPL", + NULL, + NULL, + NULL, + NULL, + &pvio_shm_methods, + +}; + +enum enum_shm_events +{ + PVIO_SHM_SERVER_WROTE= 0, + PVIO_SHM_SERVER_READ, + PVIO_SHM_CLIENT_WROTE, + PVIO_SHM_CLIENT_READ, + PVIO_SHM_CONNECTION_CLOSED +}; + +typedef struct { + HANDLE event[5]; + HANDLE file_map; + LPVOID *map; + char *read_pos; + size_t buffer_size; +} PVIO_SHM; + +const char *StrEvent[]= {"SERVER_WROTE", "SERVER_READ", "CLIENT_WROTE", "CLIENT_READ", "CONNECTION_CLOSED"}; + +struct st_pvio_shm { + char *shm_name; +}; + +my_bool pvio_shm_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout) +{ + if (!pvio) + return 1; + pvio->timeout[type]= (timeout > 0) ? timeout * 1000 : INFINITE; + return 0; +} + +int pvio_shm_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type) +{ + if (!pvio) + return -1; + return pvio->timeout[type] / 1000; +} + +ssize_t pvio_shm_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length) +{ + PVIO_SHM *pvio_shm= (PVIO_SHM *)pvio->data; + size_t copy_size= length; + HANDLE events[2]; + + if (!pvio_shm) + return -1; + + /* we need to wait for write and close events */ + if (!pvio_shm->buffer_size) + { + events[0]= pvio_shm->event[PVIO_SHM_CONNECTION_CLOSED]; + events[1]= pvio_shm->event[PVIO_SHM_SERVER_WROTE]; + + switch(WaitForMultipleObjects(2, events, 0, pvio->timeout[PVIO_READ_TIMEOUT])) + { + case WAIT_OBJECT_0: /* server closed connection */ + SetLastError(ERROR_GRACEFUL_DISCONNECT); + return -1; + case WAIT_OBJECT_0 +1: /* server_wrote event */ + break; + case WAIT_TIMEOUT: + SetLastError(ETIMEDOUT); + default: + return -1; + } + /* server sent data */ + pvio_shm->read_pos= (char *)pvio_shm->map; + pvio_shm->buffer_size= uint4korr(pvio_shm->read_pos); + pvio_shm->read_pos+= 4; + } + + if (pvio_shm->buffer_size < copy_size) + copy_size= pvio_shm->buffer_size; + + if (copy_size) + { + memcpy(buffer, (uchar *)pvio_shm->read_pos, pvio_shm->buffer_size); + pvio_shm->read_pos+= copy_size; + pvio_shm->buffer_size-= copy_size; + } + + /* we need to read again */ + if (!pvio_shm->buffer_size) + if (!SetEvent(pvio_shm->event[PVIO_SHM_CLIENT_READ])) + return -1; + + return (ssize_t)copy_size; +} + +ssize_t pvio_shm_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length) +{ + HANDLE events[2]; + PVIO_SHM *pvio_shm= (PVIO_SHM *)pvio->data; + size_t bytes_to_write= length; + uchar *buffer_pos= (uchar *)buffer; + + if (!pvio_shm) + return -1; + + events[0]= pvio_shm->event[PVIO_SHM_CONNECTION_CLOSED]; + events[1]= pvio_shm->event[PVIO_SHM_SERVER_READ]; + + while (bytes_to_write) + { + size_t pkt_length; + switch (WaitForMultipleObjects(2, events, 0, pvio->timeout[PVIO_WRITE_TIMEOUT])) { + case WAIT_OBJECT_0: /* connection closed */ + SetLastError(ERROR_GRACEFUL_DISCONNECT); + return -1; + case WAIT_OBJECT_0 + 1: /* server_read */ + break; + case WAIT_TIMEOUT: + SetLastError(ETIMEDOUT); + default: + return -1; + } + pkt_length= MIN(PVIO_SHM_BUFFER_SIZE, length); + int4store(pvio_shm->map, pkt_length); + memcpy((uchar *)pvio_shm->map + 4, buffer_pos, length); + buffer_pos+= length; + bytes_to_write-= length; + + if (!SetEvent(pvio_shm->event[PVIO_SHM_CLIENT_WROTE])) + return -1; + } + return (ssize_t)length; +} + + +int pvio_shm_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout) +{ + return 0; +} + +int pvio_shm_blocking(MARIADB_PVIO *pvio, my_bool block, my_bool *previous_mode) +{ + /* not supported */ + return 0; +} + +int pvio_shm_keepalive(MARIADB_PVIO *pvio) +{ + /* not supported */ + return 0; +} + +int pvio_shm_fast_send(MARIADB_PVIO *pvio) +{ + /* not supported */ + return 0; +} + +my_bool pvio_shm_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo) +{ + const char *base_memory_name; + char *prefixes[]= {"", "Global\\", NULL}; + char *shm_name, *shm_suffix, *shm_prefix; + uchar i= 0; + int len; + int cid; + DWORD dwDesiredAccess= EVENT_MODIFY_STATE | SYNCHRONIZE; + HANDLE hdlConnectRequest= NULL, + hdlConnectRequestAnswer= NULL, + file_map= NULL; + LPVOID map= NULL; + PVIO_SHM *pvio_shm= (PVIO_SHM*)LocalAlloc(LMEM_ZEROINIT, sizeof(PVIO_SHM)); + + if (!pvio_shm) + { + PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, "HY000", 0, ""); + return 0; + } + + /* MariaDB server constructs the event name as follows: + "Global\\base_memory_name" or + "\\base_memory_name" + */ + + + base_memory_name= (cinfo->host) ? cinfo->host : SHM_DEFAULT_NAME; + + if (!(shm_name= (char *)LocalAlloc(LMEM_ZEROINIT, strlen(base_memory_name) + 40))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, "HY000", 0, ""); + goto error; + } + + /* iterate through prefixes */ + while (prefixes[i]) + { + len= sprintf(shm_name, "%s%s_", prefixes[i], base_memory_name); + shm_suffix= shm_name + len; + strcpy(shm_suffix, "CONNECT_REQUEST"); + if ((hdlConnectRequest= OpenEvent(dwDesiredAccess, 0, shm_name))) + { + /* save prefix to prevent further loop */ + shm_prefix= prefixes[i]; + break; + } + i++; + } + if (!hdlConnectRequest) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Opening CONNECT_REQUEST event failed", GetLastError()); + goto error; + } + + strcpy(shm_suffix, "CONNECT_ANSWER"); + if (!(hdlConnectRequestAnswer= OpenEvent(dwDesiredAccess, 0, shm_name))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Opening CONNECT_ANSWER event failed", GetLastError()); + goto error; + } + + /* get connection id, so we can build the filename used for connection */ + strcpy(shm_suffix, "CONNECT_DATA"); + if (!(file_map= OpenFileMapping(FILE_MAP_WRITE, 0, shm_name))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "OpenFileMapping failed", GetLastError()); + goto error; + } + + /* try to get first 4 bytes, which represents connection_id */ + if (!(map= MapViewOfFile(file_map, FILE_MAP_WRITE, 0, 0, sizeof(cid)))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Reading connection_id failed", GetLastError()); + goto error; + } + + /* notify server */ + if (!SetEvent(hdlConnectRequest)) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Failed sending connection request", GetLastError()); + goto error; + } + + /* Wait for server answer */ + switch(WaitForSingleObject(hdlConnectRequestAnswer, pvio->timeout[PVIO_CONNECT_TIMEOUT])) { + case WAIT_ABANDONED: + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Mutex was not released in time", GetLastError()); + goto error; + break; + case WAIT_FAILED: + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Operation wait failed", GetLastError()); + goto error; + break; + case WAIT_TIMEOUT: + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Operation timed out", GetLastError()); + goto error; + break; + case WAIT_OBJECT_0: + break; + default: + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Wait for server failed", GetLastError()); + break; + } + + cid= uint4korr(map); + + len= sprintf(shm_name, "%s%s_%d_", shm_prefix, base_memory_name, cid); + shm_suffix= shm_name + len; + + strcpy(shm_suffix, "DATA"); + pvio_shm->file_map= OpenFileMapping(FILE_MAP_WRITE, 0, shm_name); + if (pvio_shm->file_map == NULL) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "OpenFileMapping failed", GetLastError()); + goto error; + } + if (!(pvio_shm->map= MapViewOfFile(pvio_shm->file_map, FILE_MAP_WRITE, 0, 0, PVIO_SHM_BUFFER_SIZE))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "MapViewOfFile failed", GetLastError()); + goto error; + } + + for (i=0; i < 5; i++) + { + strcpy(shm_suffix, StrEvent[i]); + if (!(pvio_shm->event[i]= OpenEvent(dwDesiredAccess, 0, shm_name))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SHARED_MEMORY_CONNECT_ERROR, "HY000", 0, "Couldn't create event", GetLastError()); + goto error; + } + } + /* we will first read from server */ + SetEvent(pvio_shm->event[PVIO_SHM_SERVER_READ]); + +error: + if (hdlConnectRequest) + CloseHandle(hdlConnectRequest); + if (hdlConnectRequestAnswer) + CloseHandle(hdlConnectRequestAnswer); + if (shm_name) + LocalFree(shm_name); + if (map) + UnmapViewOfFile(map); + if (file_map) + CloseHandle(file_map); + if (pvio_shm) + { + /* check if all events are set */ + if (pvio_shm->event[4]) + { + pvio->data= (void *)pvio_shm; + pvio->mysql= cinfo->mysql; + pvio->type= cinfo->type; + pvio_shm->read_pos= (char *)pvio_shm->map; + pvio->mysql->net.pvio= pvio; + return 0; + } + for (i=0;i < 5; i++) + if (pvio_shm->event[i]) + CloseHandle(pvio_shm->event[i]); + if (pvio_shm->map) + UnmapViewOfFile(pvio_shm->map); + if (pvio_shm->file_map) + CloseHandle(pvio_shm->file_map); + LocalFree(pvio_shm); + } + return 1; + +} + +my_bool pvio_shm_close(MARIADB_PVIO *pvio) +{ + PVIO_SHM *pvio_shm= (PVIO_SHM *)pvio->data; + int i; + + if (!pvio_shm) + return 1; + + /* notify server */ + SetEvent(pvio_shm->event[PVIO_SHM_CONNECTION_CLOSED]); + + UnmapViewOfFile(pvio_shm->map); + CloseHandle(pvio_shm->file_map); + + for (i=0; i < 5; i++) + CloseHandle(pvio_shm->event[i]); + + LocalFree(pvio_shm); + pvio->data= NULL; + return 0; +} + +my_bool pvio_shm_get_socket(MARIADB_PVIO *pvio, void *handle) +{ + return 1; +} + +my_bool pvio_shm_is_blocking(MARIADB_PVIO *pvio) +{ + return 1; +} + +int pvio_shm_shutdown(MARIADB_PVIO *pvio) +{ + PVIO_SHM *pvio_shm= (PVIO_SHM *)pvio->data; + if (pvio_shm) + return (SetEvent(pvio_shm->event[PVIO_SHM_CONNECTION_CLOSED]) ? 0 : 1); + return 1; +} + +my_bool pvio_shm_is_alive(MARIADB_PVIO *pvio) +{ + PVIO_SHM *pvio_shm; + if (!pvio || !pvio->data) + return FALSE; + pvio_shm= (PVIO_SHM *)pvio->data; + return WaitForSingleObject(pvio_shm->event[PVIO_SHM_CONNECTION_CLOSED], 0)!=WAIT_OBJECT_0; +} + +my_bool pvio_shm_get_handle(MARIADB_PVIO *pvio, void *handle) +{ + + *(HANDLE **)handle= 0; + if (!pvio || !pvio->data) + return FALSE; + *(HANDLE **)handle= ((PVIO_SHM*)pvio->data)->event; + return TRUE; +} +#endif + diff --git a/libmariadb/plugins/pvio/pvio_socket.c b/libmariadb/plugins/pvio/pvio_socket.c new file mode 100644 index 00000000..550fb831 --- /dev/null +++ b/libmariadb/plugins/pvio/pvio_socket.c @@ -0,0 +1,1165 @@ +/************************************************************************************ + Copyright (C) 2015,2016 MariaDB Corporation AB, + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA +*************************************************************************************/ + +/* + MariaDB virtual IO plugin for socket communication: + + The plugin handles connections via unix and network sockets. it is enabled by + default and compiled into Connector/C. +*/ + +#include <ma_global.h> +#include <ma_sys.h> +#include <errmsg.h> +#include <mysql.h> +#include <mysql/client_plugin.h> +#include <ma_context.h> +#include <mariadb_async.h> +#include <ma_common.h> +#include <string.h> +#include <time.h> +#ifndef _WIN32 +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_POLL +#include <sys/poll.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netdb.h> +#include <netinet/tcp.h> +#define IS_SOCKET_EINTR(err) ((err) == SOCKET_EINTR) +#else +#include <ws2tcpip.h> +#define O_NONBLOCK 1 +#define MSG_DONTWAIT 0 +#define IS_SOCKET_EINTR(err) 0 +#endif + +#ifndef SOCKET_ERROR +#define SOCKET_ERROR -1 +#endif + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET -1 +#endif + +#define DNS_TIMEOUT 30 + +#ifndef O_NONBLOCK +#if defined(O_NDELAY) +#define O_NONBLOCK O_NODELAY +#elif defined (O_FNDELAY) +#define O_NONBLOCK O_FNDELAY +#else +#error socket blocking is not supported on this platform +#endif +#endif + +#if SOCKET_EAGAIN != SOCKET_EWOULDBLOCK +#define HAVE_SOCKET_EWOULDBLOCK 1 +#endif + +#ifdef _AIX +#ifndef MSG_DONTWAIT +#define MSG_DONTWAIT 0 +#endif +#endif + +/* Function prototypes */ +my_bool pvio_socket_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout); +int pvio_socket_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type); +ssize_t pvio_socket_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length); +ssize_t pvio_socket_async_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length); +ssize_t pvio_socket_async_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length); +ssize_t pvio_socket_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length); +int pvio_socket_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout); +int pvio_socket_blocking(MARIADB_PVIO *pvio, my_bool value, my_bool *old_value); +my_bool pvio_socket_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo); +my_bool pvio_socket_close(MARIADB_PVIO *pvio); +int pvio_socket_fast_send(MARIADB_PVIO *pvio); +int pvio_socket_keepalive(MARIADB_PVIO *pvio); +my_bool pvio_socket_get_handle(MARIADB_PVIO *pvio, void *handle); +my_bool pvio_socket_is_blocking(MARIADB_PVIO *pvio); +my_bool pvio_socket_is_alive(MARIADB_PVIO *pvio); +my_bool pvio_socket_has_data(MARIADB_PVIO *pvio, ssize_t *data_len); +int pvio_socket_shutdown(MARIADB_PVIO *pvio); + +static int pvio_socket_init(char *unused1, + size_t unused2, + int unused3, + va_list); +static int pvio_socket_end(void); +static ssize_t ma_send(my_socket socket, const uchar *buffer, size_t length, int flags); +static ssize_t ma_recv(my_socket socket, uchar *buffer, size_t length, int flags); + +struct st_ma_pvio_methods pvio_socket_methods= { + pvio_socket_set_timeout, + pvio_socket_get_timeout, + pvio_socket_read, + pvio_socket_async_read, + pvio_socket_write, + pvio_socket_async_write, + pvio_socket_wait_io_or_timeout, + pvio_socket_blocking, + pvio_socket_connect, + pvio_socket_close, + pvio_socket_fast_send, + pvio_socket_keepalive, + pvio_socket_get_handle, + pvio_socket_is_blocking, + pvio_socket_is_alive, + pvio_socket_has_data, + pvio_socket_shutdown +}; + +#ifndef PLUGIN_DYNAMIC +MARIADB_PVIO_PLUGIN pvio_socket_client_plugin= +#else +MARIADB_PVIO_PLUGIN _mysql_client_plugin_declaration_= +#endif +{ + MARIADB_CLIENT_PVIO_PLUGIN, + MARIADB_CLIENT_PVIO_PLUGIN_INTERFACE_VERSION, + "pvio_socket", + "Georg Richter", + "MariaDB virtual IO plugin for socket communication", + {1, 0, 0}, + "LGPL", + NULL, + &pvio_socket_init, + &pvio_socket_end, + NULL, + &pvio_socket_methods +}; + +struct st_pvio_socket { + my_socket socket; + int fcntl_mode; + MYSQL *mysql; +}; + +static my_bool pvio_socket_initialized= FALSE; + +static int pvio_socket_init(char *errmsg __attribute__((unused)), + size_t errmsg_length __attribute__((unused)), + int unused __attribute__((unused)), + va_list va __attribute__((unused))) +{ + pvio_socket_initialized= TRUE; + return 0; +} + +static int pvio_socket_end(void) +{ + if (!pvio_socket_initialized) + return 1; + return 0; +} + +my_bool pvio_socket_change_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout) +{ + struct timeval tm= {0}; + int rc= 0; + struct st_pvio_socket *csock= NULL; + if (!pvio) + return 1; + if (!(csock= (struct st_pvio_socket *)pvio->data)) + return 1; + tm.tv_sec= timeout / 1000; + tm.tv_usec= (timeout % 1000) * 1000; + switch(type) + { + case PVIO_WRITE_TIMEOUT: +#ifndef _WIN32 + rc= setsockopt(csock->socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tm, sizeof(tm)); +#else + rc= setsockopt(csock->socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(int)); +#endif + break; + case PVIO_READ_TIMEOUT: +#ifndef _WIN32 + rc= setsockopt(csock->socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tm, sizeof(tm)); +#else + rc= setsockopt(csock->socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(int)); +#endif + break; + default: + break; + } + return rc; +} + +/* {{{ pvio_socket_set_timeout */ +/* + set timeout value + + SYNOPSIS + pvio_socket_set_timeout + pvio PVIO + type timeout type (connect, read, write) + timeout timeout in seconds + + DESCRIPTION + Sets timeout values for connection-, read or write time out. + PVIO internally stores all timeout values in milliseconds, but + accepts and returns all time values in seconds (like api does). + + RETURNS + 0 Success + 1 Error +*/ +my_bool pvio_socket_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout) +{ + struct st_pvio_socket *csock= NULL; + if (!pvio) + return 1; + csock= (struct st_pvio_socket *)pvio->data; + pvio->timeout[type]= (timeout > 0) ? timeout * 1000 : -1; + if (csock) + return pvio_socket_change_timeout(pvio, type, timeout * 1000); + return 0; +} +/* }}} */ + +/* {{{ pvio_socket_get_timeout */ +/* + get timeout value + + SYNOPSIS + pvio_socket_get_timeout + pvio PVIO + type timeout type (connect, read, write) + + DESCRIPTION + Returns timeout values for connection-, read or write time out. + PVIO internally stores all timeout values in milliseconds, but + accepts and returns all time values in seconds (like api does). + + RETURNS + 0...n time out value + -1 error +*/ +int pvio_socket_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type) +{ + if (!pvio) + return -1; + return pvio->timeout[type] / 1000; +} +/* }}} */ + +/* {{{ pvio_socket_read */ +/* + read from socket + + SYNOPSIS + pvio_socket_read() + pvio PVIO + buffer read buffer + length buffer length + + DESCRIPTION + reads up to length bytes into specified buffer. In the event of an + error erno is set to indicate it. + + RETURNS + 1..n number of bytes read + 0 peer has performed shutdown + -1 on error + +*/ +ssize_t pvio_socket_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length) +{ + ssize_t r; + int read_flags= MSG_DONTWAIT; + struct st_pvio_socket *csock; + int timeout; + + if (!pvio || !pvio->data) + return -1; + + csock= (struct st_pvio_socket *)pvio->data; + timeout = pvio->timeout[PVIO_READ_TIMEOUT]; + + while ((r = ma_recv(csock->socket, (void *)buffer, length, read_flags)) == -1) + { + int err = socket_errno; + if ((err != SOCKET_EAGAIN +#ifdef HAVE_SOCKET_EWOULDBLOCK + && err != SOCKET_EWOULDBLOCK +#endif + ) || timeout == 0) + return r; + + if (pvio_socket_wait_io_or_timeout(pvio, TRUE, timeout) < 1) + return -1; + } + return r; +} +/* }}} */ + +/* {{{ pvio_socket_async_read */ +/* + read from socket + + SYNOPSIS + pvio_socket_async_read() + pvio PVIO + buffer read buffer + length buffer length + + DESCRIPTION + reads up to length bytes into specified buffer. In the event of an + error erno is set to indicate it. + + RETURNS + 1..n number of bytes read + 0 peer has performed shutdown + -1 on error + +*/ +ssize_t pvio_socket_async_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length) +{ + ssize_t r= -1; +#ifndef _WIN32 + int read_flags= MSG_DONTWAIT; +#endif + struct st_pvio_socket *csock= NULL; + + if (!pvio || !pvio->data) + return -1; + + csock= (struct st_pvio_socket *)pvio->data; + +#ifndef _WIN32 + r= recv(csock->socket,(void *)buffer, length, read_flags); +#else + /* Windows doesn't support MSG_DONTWAIT, so we need to set + socket to non blocking */ + pvio_socket_blocking(pvio, 0, 0); + r= recv(csock->socket, (char *)buffer, (int)length, 0); +#endif + return r; +} +/* }}} */ + +static ssize_t ma_send(my_socket socket, const uchar *buffer, size_t length, int flags) +{ + ssize_t r; +#if !defined(MSG_NOSIGNAL) && !defined(SO_NOSIGPIPE) && !defined(_WIN32) + struct sigaction act, oldact; + act.sa_handler= SIG_IGN; + sigaction(SIGPIPE, &act, &oldact); +#endif + do { + r = send(socket, (const char *)buffer, IF_WIN((int)length,length), flags); + } + while (r == -1 && IS_SOCKET_EINTR(socket_errno)); +#if !defined(MSG_NOSIGNAL) && !defined(SO_NOSIGPIPE) && !defined(_WIN32) + sigaction(SIGPIPE, &oldact, NULL); +#endif + return r; +} + +static ssize_t ma_recv(my_socket socket, uchar *buffer, size_t length, int flags) +{ + ssize_t r; + do { + r = recv(socket, (char*) buffer, IF_WIN((int)length, length), flags); + } + while (r == -1 && IS_SOCKET_EINTR(socket_errno)); + return r; +} + +/* {{{ pvio_socket_async_write */ +/* + write to socket + + SYNOPSIS + pvio_socket_async_write() + pvio PVIO + buffer read buffer + length buffer length + + DESCRIPTION + writes up to length bytes to socket. In the event of an + error erno is set to indicate it. + + RETURNS + 1..n number of bytes read + 0 peer has performed shutdown + -1 on error + +*/ +ssize_t pvio_socket_async_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length) +{ + ssize_t r= -1; + struct st_pvio_socket *csock= NULL; +#ifndef _WIN32 + int write_flags= MSG_DONTWAIT; +#ifdef MSG_NOSIGNAL + write_flags|= MSG_NOSIGNAL; +#endif +#endif + + if (!pvio || !pvio->data) + return -1; + + csock= (struct st_pvio_socket *)pvio->data; + +#ifndef WIN32 + r= ma_send(csock->socket, buffer, length, write_flags); +#else + /* Windows doesn't support MSG_DONTWAIT, so we need to set + socket to non blocking */ + pvio_socket_blocking(pvio, 0, 0); + r= send(csock->socket, (const char *)buffer, (int)length, 0); +#endif + + return r; +} +/* }}} */ + +/* {{{ pvio_socket_write */ +/* + write to socket + + SYNOPSIS + pvio_socket_write() + pvio PVIO + buffer read buffer + length buffer length + + DESCRIPTION + writes up to length bytes to socket. In the event of an + error erno is set to indicate it. + + RETURNS + 1..n number of bytes read + 0 peer has performed shutdown + -1 on error + +*/ +ssize_t pvio_socket_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length) +{ + ssize_t r; + struct st_pvio_socket *csock; + int timeout; + int send_flags= MSG_DONTWAIT; +#ifdef MSG_NOSIGNAL + send_flags|= MSG_NOSIGNAL; +#endif + if (!pvio || !pvio->data) + return -1; + + csock= (struct st_pvio_socket *)pvio->data; + timeout = pvio->timeout[PVIO_WRITE_TIMEOUT]; + + while ((r = ma_send(csock->socket, (void *)buffer, length,send_flags)) == -1) + { + int err = socket_errno; + if ((err != SOCKET_EAGAIN +#ifdef HAVE_SOCKET_EWOULDBLOCK + && err != SOCKET_EWOULDBLOCK +#endif + )|| timeout == 0) + return r; + if (pvio_socket_wait_io_or_timeout(pvio, FALSE, timeout) < 1) + return -1; + } + return r; +} +/* }}} */ + +int pvio_socket_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout) +{ + int rc; + struct st_pvio_socket *csock= NULL; + +#ifndef _WIN32 + struct pollfd p_fd; +#else + struct timeval tv= {0,0}; + fd_set fds, exc_fds; +#endif + + if (!pvio || !pvio->data) + return 0; + + if (pvio->mysql->options.extension && + pvio->mysql->options.extension->io_wait != NULL) { + my_socket handle; + if (pvio_socket_get_handle(pvio, &handle)) + return 0; + return pvio->mysql->options.extension->io_wait(handle, is_read, timeout); + } + + csock= (struct st_pvio_socket *)pvio->data; + { +#ifndef _WIN32 + memset(&p_fd, 0, sizeof(p_fd)); + p_fd.fd= csock->socket; + p_fd.events= (is_read) ? POLLIN : POLLOUT; + + if (!timeout) + timeout= -1; + + do { + rc= poll(&p_fd, 1, timeout); + } while (rc == -1 && errno == EINTR); + + if (rc == 0) + errno= ETIMEDOUT; +#else + FD_ZERO(&fds); + FD_ZERO(&exc_fds); + + FD_SET(csock->socket, &fds); + FD_SET(csock->socket, &exc_fds); + + if (timeout >= 0) + { + tv.tv_sec= timeout / 1000; + tv.tv_usec= (timeout % 1000) * 1000; + } + + rc= select(0, (is_read) ? &fds : NULL, + (is_read) ? NULL : &fds, + &exc_fds, + (timeout >= 0) ? &tv : NULL); + + if (rc == SOCKET_ERROR) + { + errno= WSAGetLastError(); + } + else if (rc == 0) + { + rc= SOCKET_ERROR; + WSASetLastError(WSAETIMEDOUT); + errno= ETIMEDOUT; + } + else if (FD_ISSET(csock->socket, &exc_fds)) + { + int err; + int len = sizeof(int); + if (getsockopt(csock->socket, SOL_SOCKET, SO_ERROR, (char *)&err, &len) != SOCKET_ERROR) + { + WSASetLastError(err); + errno= err; + } + rc= SOCKET_ERROR; + } + +#endif + } + return rc; +} + +int pvio_socket_blocking(MARIADB_PVIO *pvio, my_bool block, my_bool *previous_mode) +{ + my_bool is_blocking; + struct st_pvio_socket *csock; + int new_fcntl_mode; + + if (!pvio || !pvio->data) + return 1; + + csock = (struct st_pvio_socket *)pvio->data; + + is_blocking = !(csock->fcntl_mode & O_NONBLOCK); + if (previous_mode) + *previous_mode = is_blocking; + + if (is_blocking == block) + return 0; + + if (block) + new_fcntl_mode = csock->fcntl_mode & ~O_NONBLOCK; + else + new_fcntl_mode = csock->fcntl_mode | O_NONBLOCK; + +#ifdef _WIN32 + { + ulong arg = block ? 0 : 1; + if (ioctlsocket(csock->socket, FIONBIO, (void *)&arg)) + { + return(WSAGetLastError()); + } + } +#else + if (fcntl(csock->socket, F_SETFL, new_fcntl_mode) == -1) + { + return errno; + } +#endif + csock->fcntl_mode = new_fcntl_mode; + return 0; +} + +static int pvio_socket_internal_connect(MARIADB_PVIO *pvio, + const struct sockaddr *name, + size_t namelen) +{ + int rc= 0; + struct st_pvio_socket *csock= NULL; + int timeout; +#ifndef _WIN32 + unsigned int wait_conn= 1; + time_t start_t= time(NULL); +#endif + + if (!pvio || !pvio->data) + return 1; + + csock= (struct st_pvio_socket *)pvio->data; + timeout= pvio->timeout[PVIO_CONNECT_TIMEOUT]; + + /* set non blocking */ + pvio_socket_blocking(pvio, 0, 0); + +#ifndef _WIN32 + do { + int save_errno; + rc= connect(csock->socket, (struct sockaddr*) name, (int)namelen); + + if (time(NULL) - start_t > (time_t)timeout/1000) + break; + + /* CONC-612: Since usleep may fail and will set errno (On MacOSX usleep + always sets errno=ETIMEDOUT), we need to save and restore errno */ + save_errno= errno; + usleep(wait_conn); + errno= save_errno; + + wait_conn= wait_conn >= 1000000 ? 1000000 : wait_conn * 2; + + } while (rc == -1 && (errno == EINTR || errno == EAGAIN)); + /* in case a timeout values was set we need to check error values + EINPROGRESS */ + if (timeout != 0 && rc == -1 && errno == EINPROGRESS) + { + rc= pvio_socket_wait_io_or_timeout(pvio, FALSE, timeout); + if (rc < 1) + return -1; + { + int error; + socklen_t error_len= sizeof(error); + if ((rc = getsockopt(csock->socket, SOL_SOCKET, SO_ERROR, + (char *)&error, &error_len)) < 0) + return errno; + else if (error) + return error; + } + } +#ifdef __APPLE__ + if (csock->socket) + { + int val= 1; + setsockopt(csock->socket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&val, sizeof(int)); + } +#endif +#else + rc= connect(csock->socket, (struct sockaddr*) name, (int)namelen); + if (rc == SOCKET_ERROR) + { + if (WSAGetLastError() == WSAEWOULDBLOCK) + { + if (pvio_socket_wait_io_or_timeout(pvio, FALSE, timeout) < 0) + return -1; + rc= 0; + } + } +#endif + return rc; +} + +int pvio_socket_keepalive(MARIADB_PVIO *pvio) +{ + int opt= 1; + struct st_pvio_socket *csock= NULL; + + if (!pvio || !pvio->data) + return 1; + + csock= (struct st_pvio_socket *)pvio->data; + + return setsockopt(csock->socket, SOL_SOCKET, SO_KEEPALIVE, +#ifndef _WIN32 + (const void *)&opt, sizeof(opt)); +#else + (char *)&opt, (int)sizeof(opt)); +#endif +} + +int pvio_socket_fast_send(MARIADB_PVIO *pvio) +{ + int r= 0; + struct st_pvio_socket *csock= NULL; + + if (!pvio || !pvio->data) + return 1; + + csock= (struct st_pvio_socket *)pvio->data; + +/* Setting IP_TOS is not recommended on Windows. See + http://msdn.microsoft.com/en-us/library/windows/desktop/ms738586(v=vs.85).aspx +*/ +#if !defined(_WIN32) && defined(IPTOS_THROUGHPUT) + { + int tos = IPTOS_THROUGHPUT; + r= setsockopt(csock->socket, IPPROTO_IP, IP_TOS, + (const void *)&tos, sizeof(tos)); + } +#endif /* !_WIN32 && IPTOS_THROUGHPUT */ + if (!r) + { + int opt = 1; + /* turn off nagle algorithm */ + r= setsockopt(csock->socket, IPPROTO_TCP, TCP_NODELAY, +#ifdef _WIN32 + (const char *)&opt, (int)sizeof(opt)); +#else + (const void *)&opt, sizeof(opt)); +#endif + } + return r; +} + +static int +pvio_socket_connect_async(MARIADB_PVIO *pvio, + const struct sockaddr *name, uint namelen) +{ + MYSQL *mysql= pvio->mysql; + mysql->options.extension->async_context->pvio= pvio; + pvio_socket_blocking(pvio, 0, 0); + return my_connect_async(pvio, name, namelen, pvio->timeout[PVIO_CONNECT_TIMEOUT]); +} + +static int +pvio_socket_connect_sync_or_async(MARIADB_PVIO *pvio, + const struct sockaddr *name, uint namelen) +{ + MYSQL *mysql= pvio->mysql; + if (mysql->options.extension && mysql->options.extension->async_context && + mysql->options.extension->async_context->active) + { + /* even if we are not connected yet, application needs to check socket + * via mysql_get_socket api call, so we need to assign pvio */ + return pvio_socket_connect_async(pvio, name, namelen); + } + + return pvio_socket_internal_connect(pvio, name, namelen); +} + +my_bool pvio_socket_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo) +{ + struct st_pvio_socket *csock= NULL; + MYSQL *mysql; + + if (!pvio || !cinfo) + return 1; + + if (!(csock= (struct st_pvio_socket *)calloc(1, sizeof(struct st_pvio_socket)))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0, ""); + return 1; + } + pvio->data= (void *)csock; + csock->socket= INVALID_SOCKET; + mysql= pvio->mysql= cinfo->mysql; + pvio->type= cinfo->type; + + if (cinfo->type == PVIO_TYPE_UNIXSOCKET) + { +#ifndef _WIN32 +#ifdef HAVE_SYS_UN_H + size_t port_length; + struct sockaddr_un UNIXaddr; + if ((csock->socket = socket(AF_UNIX,SOCK_STREAM,0)) == INVALID_SOCKET || + (port_length=strlen(cinfo->unix_socket)) >= (sizeof(UNIXaddr.sun_path))) + { + PVIO_SET_ERROR(cinfo->mysql, CR_SOCKET_CREATE_ERROR, unknown_sqlstate, 0, errno); + goto error; + } + memset((char*) &UNIXaddr, 0, sizeof(UNIXaddr)); + UNIXaddr.sun_family = AF_UNIX; +#if defined(__linux__) + /* Abstract socket */ + if (cinfo->unix_socket[0] == '@') + { + strncpy(UNIXaddr.sun_path + 1, cinfo->unix_socket + 1, 106); + port_length+= offsetof(struct sockaddr_un, sun_path); + } + else +#endif + { + size_t sun_path_size = sizeof(UNIXaddr.sun_path); + strncpy(UNIXaddr.sun_path, cinfo->unix_socket, sun_path_size - 1); + if (sun_path_size == strlen(UNIXaddr.sun_path) + 1 && UNIXaddr.sun_path[sun_path_size - 1] != '\0') + { + /* Making the string null-terminated */ + UNIXaddr.sun_path[sun_path_size - 1] = '\0'; + } + port_length= sizeof(UNIXaddr); + } + if (pvio_socket_connect_sync_or_async(pvio, (struct sockaddr *) &UNIXaddr, port_length)) + { + PVIO_SET_ERROR(cinfo->mysql, CR_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_CONNECTION_ERROR), cinfo->unix_socket, socket_errno); + goto error; + } + if (pvio_socket_blocking(pvio, 1, 0) == SOCKET_ERROR) + { + goto error; + } +#else +/* todo: error, not supported */ +#endif +#endif + } else if (cinfo->type == PVIO_TYPE_SOCKET) + { + struct addrinfo hints, *save_res= 0, *bind_res= 0, *res= 0, *bres= 0; + char server_port[NI_MAXSERV]; + int gai_rc; + int rc= 0; + time_t start_t= time(NULL); +#ifdef _WIN32 + DWORD wait_gai; +#else + unsigned int wait_gai; +#endif + + memset(&server_port, 0, NI_MAXSERV); + snprintf(server_port, NI_MAXSERV, "%d", cinfo->port); + + /* set hints for getaddrinfo */ + memset(&hints, 0, sizeof(hints)); + hints.ai_protocol= IPPROTO_TCP; /* TCP connections only */ + hints.ai_family= AF_UNSPEC; /* includes: IPv4, IPv6 or hostname */ + hints.ai_socktype= SOCK_STREAM; + + /* if client has multiple interfaces, we will bind socket to given + * bind_address */ + if (cinfo->mysql->options.bind_address) + { + wait_gai= 1; + while ((gai_rc= getaddrinfo(cinfo->mysql->options.bind_address, 0, + &hints, &bind_res)) == EAI_AGAIN) + { + unsigned int timeout= mysql->options.connect_timeout ? + mysql->options.connect_timeout : DNS_TIMEOUT; + if (time(NULL) - start_t > (time_t)timeout) + break; +#ifndef _WIN32 + usleep(wait_gai); +#else + Sleep(wait_gai); +#endif + wait_gai*= 2; + } + if (gai_rc != 0 || !bind_res) + { + PVIO_SET_ERROR(cinfo->mysql, CR_BIND_ADDR_FAILED, SQLSTATE_UNKNOWN, + CER(CR_BIND_ADDR_FAILED), cinfo->mysql->options.bind_address, gai_rc); + goto error; + } + } + /* Get the address information for the server using getaddrinfo() */ + wait_gai= 1; + while ((gai_rc= getaddrinfo(cinfo->host, server_port, + &hints, &res)) == EAI_AGAIN) + { + unsigned int timeout= mysql->options.connect_timeout ? + mysql->options.connect_timeout : DNS_TIMEOUT; + if (time(NULL) - start_t > (time_t)timeout) + break; +#ifndef _WIN32 + usleep(wait_gai); +#else + Sleep(wait_gai); +#endif + wait_gai*= 2; + } + if (gai_rc != 0 || !res) + { + PVIO_SET_ERROR(cinfo->mysql, CR_UNKNOWN_HOST, SQLSTATE_UNKNOWN, + ER(CR_UNKNOWN_HOST), cinfo->host, gai_rc); + if (bind_res) + freeaddrinfo(bind_res); + goto error; + } + + /* res is a linked list of addresses for the given hostname. We loop until + we are able to connect to one address or all connect attempts failed */ + for (save_res= res; save_res; save_res= save_res->ai_next) + { + /* CONC-364: Avoid leak of open sockets */ + if (csock->socket != INVALID_SOCKET) + closesocket(csock->socket); + csock->socket= socket(save_res->ai_family, save_res->ai_socktype, + save_res->ai_protocol); + if (csock->socket == INVALID_SOCKET) + /* Errors will be handled after loop finished */ + continue; + + if (bind_res) + { + for (bres= bind_res; bres; bres= bres->ai_next) + { + if (!(rc= bind(csock->socket, bres->ai_addr, (int)bres->ai_addrlen))) + break; + } + if (rc) + { + closesocket(csock->socket); + csock->socket= INVALID_SOCKET; + continue; + } + } + + if (mysql->options.extension && mysql->options.extension->async_context && + mysql->options.extension->async_context->active) + { + mysql->options.extension->async_context->pending_gai_res = res; + rc= pvio_socket_connect_async(pvio, save_res->ai_addr, (uint)save_res->ai_addrlen); + mysql->options.extension->async_context->pending_gai_res = NULL; + } + else + { + rc= pvio_socket_connect_sync_or_async(pvio, save_res->ai_addr, (uint)save_res->ai_addrlen); + } + + if (!rc) + { + MYSQL *mysql= pvio->mysql; + if (mysql->options.extension && mysql->options.extension->async_context && + mysql->options.extension->async_context->active) + break; + if (pvio_socket_blocking(pvio, 0, 0) == SOCKET_ERROR) + { + closesocket(csock->socket); + csock->socket= INVALID_SOCKET; + continue; + } + break; /* success! */ + } + } + + freeaddrinfo(res); + if (bind_res) + freeaddrinfo(bind_res); + + if (csock->socket == INVALID_SOCKET) + { + PVIO_SET_ERROR(cinfo->mysql, CR_IPSOCK_ERROR, SQLSTATE_UNKNOWN, ER(CR_IPSOCK_ERROR), + socket_errno); + goto error; + } + + /* last call to connect 2 failed */ + if (rc) + { + PVIO_SET_ERROR(cinfo->mysql, CR_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_CONN_HOST_ERROR), cinfo->host, +#ifdef _WIN32 + errno); +#else + socket_errno); +#endif + goto error; + } + if (pvio_socket_blocking(pvio, 1, 0) == SOCKET_ERROR) + goto error; + } + /* apply timeouts */ + if (pvio->timeout[PVIO_CONNECT_TIMEOUT] > 0) + { + if (pvio_socket_change_timeout(pvio, PVIO_READ_TIMEOUT, pvio->timeout[PVIO_CONNECT_TIMEOUT]) || + pvio_socket_change_timeout(pvio, PVIO_WRITE_TIMEOUT, pvio->timeout[PVIO_CONNECT_TIMEOUT])) + goto error; + } + else + { + if (pvio->timeout[PVIO_WRITE_TIMEOUT] > 0) + if (pvio_socket_change_timeout(pvio, PVIO_WRITE_TIMEOUT, pvio->timeout[PVIO_WRITE_TIMEOUT])) + goto error; + if (pvio->timeout[PVIO_READ_TIMEOUT] > 0) + if (pvio_socket_change_timeout(pvio, PVIO_READ_TIMEOUT, pvio->timeout[PVIO_READ_TIMEOUT])) + goto error; + } + return 0; +error: + /* close socket: MDEV-10891 */ + if (csock->socket != INVALID_SOCKET) + { + closesocket(csock->socket); + csock->socket= INVALID_SOCKET; + } + if (pvio->data) + { + free((gptr)pvio->data); + pvio->data= NULL; + } + return 1; +} + +/* {{{ my_bool pvio_socket_close() */ +my_bool pvio_socket_close(MARIADB_PVIO *pvio) +{ + struct st_pvio_socket *csock= NULL; + int r= 0; + + if (!pvio) + return 1; + + if (pvio->data) + { + csock= (struct st_pvio_socket *)pvio->data; + if (csock && csock->socket != INVALID_SOCKET) + { + r= closesocket(csock->socket); + csock->socket= INVALID_SOCKET; + } + free((gptr)pvio->data); + pvio->data= NULL; + } + return r; +} +/* }}} */ + +/* {{{ my_socket pvio_socket_get_handle */ +my_bool pvio_socket_get_handle(MARIADB_PVIO *pvio, void *handle) +{ + if (pvio && pvio->data && handle) + { + *(my_socket *)handle= ((struct st_pvio_socket *)pvio->data)->socket; + return 0; + } + return 1; +} +/* }}} */ + +/* {{{ my_bool pvio_socket_is_blocking(MARIADB_PVIO *pvio) */ +my_bool pvio_socket_is_blocking(MARIADB_PVIO *pvio) +{ + struct st_pvio_socket *csock= NULL; + my_bool r; + + if (!pvio || !pvio->data) + return 0; + + csock= (struct st_pvio_socket *)pvio->data; + r = !(csock->fcntl_mode & O_NONBLOCK); + return r; +} +/* }}} */ + +/* {{{ my_bool pvio_socket_is_alive(MARIADB_PVIO *pvio) */ +my_bool pvio_socket_is_alive(MARIADB_PVIO *pvio) +{ + struct st_pvio_socket *csock= NULL; + #ifndef _WIN32 + struct pollfd poll_fd; +#else + FD_SET sfds; + struct timeval tv= {0,0}; +#endif + int res; + + if (!pvio || !pvio->data) + return 0; + + csock= (struct st_pvio_socket *)pvio->data; +#ifndef _WIN32 + memset(&poll_fd, 0, sizeof(struct pollfd)); + poll_fd.events= POLLPRI | POLLIN; + poll_fd.fd= csock->socket; + + res= poll(&poll_fd, 1, 0); + if (res <= 0) /* timeout or error */ + return FALSE; + if (!(poll_fd.revents & (POLLIN | POLLPRI))) + return FALSE; + return TRUE; +#else + /* We can't use the WSAPoll function, it's broken :-( + (see Windows 8 Bugs 309411 - WSAPoll does not report failed connections) + Instead we need to use select function: + If TIMEVAL is initialized to {0, 0}, select will return immediately; + this is used to poll the state of the selected sockets. + */ + FD_ZERO(&sfds); + FD_SET(csock->socket, &sfds); + + res= select((int)csock->socket + 1, &sfds, NULL, NULL, &tv); + if (res > 0 && FD_ISSET(csock->socket, &sfds)) + return TRUE; + return FALSE; +#endif +} +/* }}} */ + +/* {{{ my_boool pvio_socket_has_data */ +my_bool pvio_socket_has_data(MARIADB_PVIO *pvio, ssize_t *data_len) +{ + struct st_pvio_socket *csock= NULL; + char tmp_buf; + ssize_t len; + my_bool mode; + + if (!pvio || !pvio->data) + return 0; + + csock= (struct st_pvio_socket *)pvio->data; + /* MSG_PEEK: Peeks at the incoming data. The data is copied into the buffer, + but is not removed from the input queue. + */ + pvio_socket_blocking(pvio, 0, &mode); + len= recv(csock->socket, &tmp_buf, sizeof(tmp_buf), MSG_PEEK); + pvio_socket_blocking(pvio, mode, 0); + if (len < 0) + return 1; + *data_len= len; + return 0; +} +/* }}} */ + +int pvio_socket_shutdown(MARIADB_PVIO *pvio) +{ + if (pvio && pvio->data) + { + my_socket s = ((struct st_pvio_socket *)pvio->data)->socket; +#ifdef _WIN32 + shutdown(s, SD_BOTH); + CancelIoEx((HANDLE)s, NULL); +#else + shutdown(s, SHUT_RDWR); +#endif + } + return -1; +} + |