From 89a2d1d4c73ddb3550638fe285342ccf442db705 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 3 Sep 2014 14:02:35 +0300 Subject: [PATCH 1/7] RabbitMQ consumer client --- rabbitmq_consumer/CMakeLists.txt | 47 ++ rabbitmq_consumer/Makefile | 15 + rabbitmq_consumer/README | 39 ++ rabbitmq_consumer/buildconfig.inc | 8 + rabbitmq_consumer/consumer.c | 538 ++++++++++++++++++ rabbitmq_consumer/consumer.cnf | 28 + rabbitmq_consumer/inih/._LICENSE.txt | Bin 0 -> 212 bytes rabbitmq_consumer/inih/._README.txt | Bin 0 -> 212 bytes rabbitmq_consumer/inih/._cpp | Bin 0 -> 212 bytes rabbitmq_consumer/inih/._examples | Bin 0 -> 212 bytes rabbitmq_consumer/inih/._extra | Bin 0 -> 212 bytes rabbitmq_consumer/inih/._ini.c | Bin 0 -> 212 bytes rabbitmq_consumer/inih/._ini.h | Bin 0 -> 212 bytes rabbitmq_consumer/inih/._tests | Bin 0 -> 212 bytes rabbitmq_consumer/inih/.gitignore | 3 + rabbitmq_consumer/inih/CMakeLists.txt | 1 + rabbitmq_consumer/inih/LICENSE.txt | 27 + rabbitmq_consumer/inih/README.txt | 5 + rabbitmq_consumer/inih/cpp/._INIReader.cpp | Bin 0 -> 212 bytes rabbitmq_consumer/inih/cpp/._INIReader.h | Bin 0 -> 212 bytes .../inih/cpp/._INIReaderTest.cpp | Bin 0 -> 212 bytes rabbitmq_consumer/inih/cpp/INIReader.cpp | 67 +++ rabbitmq_consumer/inih/cpp/INIReader.h | 48 ++ rabbitmq_consumer/inih/cpp/INIReaderTest.cpp | 20 + rabbitmq_consumer/inih/examples/._config.def | Bin 0 -> 212 bytes rabbitmq_consumer/inih/examples/._ini_dump.c | Bin 0 -> 212 bytes .../inih/examples/._ini_example.c | Bin 0 -> 212 bytes .../inih/examples/._ini_xmacros.c | Bin 0 -> 212 bytes rabbitmq_consumer/inih/examples/._test.ini | Bin 0 -> 212 bytes rabbitmq_consumer/inih/examples/config.def | 8 + rabbitmq_consumer/inih/examples/ini_dump.c | 40 ++ rabbitmq_consumer/inih/examples/ini_example.c | 44 ++ rabbitmq_consumer/inih/examples/ini_xmacros.c | 46 ++ rabbitmq_consumer/inih/examples/test.ini | 9 + .../inih/extra/._Makefile.static | Bin 0 -> 212 bytes rabbitmq_consumer/inih/extra/Makefile.static | 19 + rabbitmq_consumer/inih/ini.c | 176 ++++++ rabbitmq_consumer/inih/ini.h | 72 +++ .../inih/tests/._bad_comment.ini | Bin 0 -> 212 bytes rabbitmq_consumer/inih/tests/._bad_multi.ini | Bin 0 -> 212 bytes .../inih/tests/._bad_section.ini | Bin 0 -> 212 bytes .../inih/tests/._baseline_multi.txt | Bin 0 -> 212 bytes .../inih/tests/._baseline_single.txt | Bin 0 -> 212 bytes rabbitmq_consumer/inih/tests/._bom.ini | Bin 0 -> 212 bytes rabbitmq_consumer/inih/tests/._multi_line.ini | Bin 0 -> 212 bytes rabbitmq_consumer/inih/tests/._normal.ini | Bin 0 -> 212 bytes rabbitmq_consumer/inih/tests/._unittest.bat | Bin 0 -> 212 bytes rabbitmq_consumer/inih/tests/._unittest.c | Bin 0 -> 212 bytes rabbitmq_consumer/inih/tests/._user_error.ini | Bin 0 -> 212 bytes rabbitmq_consumer/inih/tests/bad_comment.ini | 1 + rabbitmq_consumer/inih/tests/bad_multi.ini | 1 + rabbitmq_consumer/inih/tests/bad_section.ini | 5 + .../inih/tests/baseline_multi.txt | 47 ++ .../inih/tests/baseline_single.txt | 43 ++ rabbitmq_consumer/inih/tests/bom.ini | 3 + rabbitmq_consumer/inih/tests/multi_line.ini | 15 + rabbitmq_consumer/inih/tests/normal.ini | 25 + rabbitmq_consumer/inih/tests/unittest.bat | 2 + rabbitmq_consumer/inih/tests/unittest.c | 58 ++ rabbitmq_consumer/inih/tests/user_error.ini | 4 + 60 files changed, 1464 insertions(+) create mode 100644 rabbitmq_consumer/CMakeLists.txt create mode 100644 rabbitmq_consumer/Makefile create mode 100644 rabbitmq_consumer/README create mode 100644 rabbitmq_consumer/buildconfig.inc create mode 100644 rabbitmq_consumer/consumer.c create mode 100644 rabbitmq_consumer/consumer.cnf create mode 100755 rabbitmq_consumer/inih/._LICENSE.txt create mode 100755 rabbitmq_consumer/inih/._README.txt create mode 100755 rabbitmq_consumer/inih/._cpp create mode 100755 rabbitmq_consumer/inih/._examples create mode 100755 rabbitmq_consumer/inih/._extra create mode 100755 rabbitmq_consumer/inih/._ini.c create mode 100755 rabbitmq_consumer/inih/._ini.h create mode 100755 rabbitmq_consumer/inih/._tests create mode 100644 rabbitmq_consumer/inih/.gitignore create mode 100644 rabbitmq_consumer/inih/CMakeLists.txt create mode 100755 rabbitmq_consumer/inih/LICENSE.txt create mode 100755 rabbitmq_consumer/inih/README.txt create mode 100755 rabbitmq_consumer/inih/cpp/._INIReader.cpp create mode 100755 rabbitmq_consumer/inih/cpp/._INIReader.h create mode 100755 rabbitmq_consumer/inih/cpp/._INIReaderTest.cpp create mode 100755 rabbitmq_consumer/inih/cpp/INIReader.cpp create mode 100755 rabbitmq_consumer/inih/cpp/INIReader.h create mode 100755 rabbitmq_consumer/inih/cpp/INIReaderTest.cpp create mode 100755 rabbitmq_consumer/inih/examples/._config.def create mode 100755 rabbitmq_consumer/inih/examples/._ini_dump.c create mode 100755 rabbitmq_consumer/inih/examples/._ini_example.c create mode 100755 rabbitmq_consumer/inih/examples/._ini_xmacros.c create mode 100755 rabbitmq_consumer/inih/examples/._test.ini create mode 100755 rabbitmq_consumer/inih/examples/config.def create mode 100755 rabbitmq_consumer/inih/examples/ini_dump.c create mode 100755 rabbitmq_consumer/inih/examples/ini_example.c create mode 100755 rabbitmq_consumer/inih/examples/ini_xmacros.c create mode 100755 rabbitmq_consumer/inih/examples/test.ini create mode 100755 rabbitmq_consumer/inih/extra/._Makefile.static create mode 100755 rabbitmq_consumer/inih/extra/Makefile.static create mode 100755 rabbitmq_consumer/inih/ini.c create mode 100755 rabbitmq_consumer/inih/ini.h create mode 100755 rabbitmq_consumer/inih/tests/._bad_comment.ini create mode 100755 rabbitmq_consumer/inih/tests/._bad_multi.ini create mode 100755 rabbitmq_consumer/inih/tests/._bad_section.ini create mode 100755 rabbitmq_consumer/inih/tests/._baseline_multi.txt create mode 100755 rabbitmq_consumer/inih/tests/._baseline_single.txt create mode 100755 rabbitmq_consumer/inih/tests/._bom.ini create mode 100755 rabbitmq_consumer/inih/tests/._multi_line.ini create mode 100755 rabbitmq_consumer/inih/tests/._normal.ini create mode 100755 rabbitmq_consumer/inih/tests/._unittest.bat create mode 100755 rabbitmq_consumer/inih/tests/._unittest.c create mode 100755 rabbitmq_consumer/inih/tests/._user_error.ini create mode 100755 rabbitmq_consumer/inih/tests/bad_comment.ini create mode 100755 rabbitmq_consumer/inih/tests/bad_multi.ini create mode 100755 rabbitmq_consumer/inih/tests/bad_section.ini create mode 100755 rabbitmq_consumer/inih/tests/baseline_multi.txt create mode 100755 rabbitmq_consumer/inih/tests/baseline_single.txt create mode 100755 rabbitmq_consumer/inih/tests/bom.ini create mode 100755 rabbitmq_consumer/inih/tests/multi_line.ini create mode 100755 rabbitmq_consumer/inih/tests/normal.ini create mode 100755 rabbitmq_consumer/inih/tests/unittest.bat create mode 100755 rabbitmq_consumer/inih/tests/unittest.c create mode 100755 rabbitmq_consumer/inih/tests/user_error.ini diff --git a/rabbitmq_consumer/CMakeLists.txt b/rabbitmq_consumer/CMakeLists.txt new file mode 100644 index 000000000..02e0e7125 --- /dev/null +++ b/rabbitmq_consumer/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required (VERSION 2.6) + +set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64 /usr/lib/mariadb /usr/lib64/mariadb) +set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/include /usr/local/include /usr/include/mysql /usr/local/include/mysql /usr/include/mariadb /usr/local/include/mariadb) + +include(InstallRequiredSystemLibraries) + +project (consumer) + +find_path(MYSQL_INCLUDE_DIRS mysql.h) +find_library(MYSQL_LIBRARIES NAMES mysqlclient) +find_library(RABBITMQ_C_LIBRARIES NAMES rabbitmq) + +include_directories(${MYSQL_INCLUDE_DIRS}) +include_directories(${RABBITMQ_C_INCLUDE_DIRS}) +include_directories(${CMAKE_SOURCE_DIR}/inih) + +add_subdirectory (inih) +link_directories(${CMAKE_SOURCE_DIR}/inih) + +if(RABBITMQ_C_LIBRARIES AND MYSQL_LIBRARIES AND MYSQL_INCLUDE_DIRS) + +add_definitions(-DCONFIG_IN_ETC) +add_definitions(-DCONSUMER_CONFIG_PREFIX="${CMAKE_INSTALL_PREFIX}/share/consumer") + +add_executable (consumer consumer.c ${MYSQL_LIBRARIES} ${RABBITMQ_C_LIBRARIES}) +target_link_libraries(consumer mysqlclient) +target_link_libraries(consumer rabbitmq) +target_link_libraries(consumer inih) +install(TARGETS consumer DESTINATION bin) +install(FILES consumer.cnf DESTINATION share/consumer) + + +else(RABBITMQ_C_LIBRARIES AND MYSQL_LIBRARIES AND MYSQL_INCLUDE_DIRS) +message(FATAL_ERROR "Error: Can not find requred libraries: libmysqld, librabbitmq.") + +endif(RABBITMQ_C_LIBRARIES AND MYSQL_LIBRARIES AND MYSQL_INCLUDE_DIRS) + +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "RabbitMQ Consumer Client") +set(CPACK_PACKAGE_NAME "RabbitMQ Consumer") +set(CPACK_GENERATOR "RPM") +set(CPACK_PACKAGE_VERSION_MAJOR "1") +set(CPACK_PACKAGE_VERSION_MINOR "0") +set(CPACK_RPM_PACKAGE_NAME "rabbitmq-consumer") +set(CPACK_RPM_PACKAGE_VENDOR "SkySQL Ab") +set(CPACK_RPM_PACKAGE_AUTOREQPROV " no") +include(CPack) \ No newline at end of file diff --git a/rabbitmq_consumer/Makefile b/rabbitmq_consumer/Makefile new file mode 100644 index 000000000..6bc8049eb --- /dev/null +++ b/rabbitmq_consumer/Makefile @@ -0,0 +1,15 @@ +include buildconfig.inc + +CC=gcc +CFLAGS=-c -Wall -g -Iinih $(INCLUDE_DIRS) +LDFLAGS= $(LIBRARY_DIRS) -lrabbitmq -lmysqlclient +SRCS= inih/ini.c consumer.c +OBJ=$(SRCS:.c=.o) +all:$(OBJ) + $(CC) $(LDFLAGS) $(OBJ) -o consumer +%.o:%.c + $(CC) $(CFLAGS) $< -o $@ + +clean: + -rm *.o + -rm *~ diff --git a/rabbitmq_consumer/README b/rabbitmq_consumer/README new file mode 100644 index 000000000..6dc2fb982 --- /dev/null +++ b/rabbitmq_consumer/README @@ -0,0 +1,39 @@ +This program requires the librabbitmq and libmysqlclient libraries. + +librabbitmq-c - https://github.com/alanxz/rabbitmq-c +MariaDB Client Library for C 2.0 Series - https://mariadb.com/kb/en/mariadb/client-libraries/client-library-for-c/ + +Building with CMake: +'cmake .' + +Variables to pass for CMake: + +Path to headers -DCMAKE_INCLUDE_PATH= +Path to libraries -DCMAKE_LIBRARY_PATH= +Install prefix -DCMAKE_INSTALL_PREFIX= + + +Separate multiple folders with colons, for example: +'path1:path2:path3' + +After running CMake run 'make' to build the binaries and 'make package' to build RPMs. + +To build without CMake, use the provided makefile and update the +include and library directories 'in buildvars.inc' + +The configuration for the consumer client are red from 'consumer.cnf'. + +Options for the configuration file: + +hostname Hostname of the RabbitMQ server +port Port of the RabbitMQ server +vhost Virtual host location of the RabbitMQ server +user Username for the RabbitMQ server +passwd Password for the RabbitMQ server +queue Queue to consume from +dbserver Hostname of the SQL server +dbport Port of the SQL server +dbname Name of the SQL database to use +dbuser Database username +dbpasswd Database passwork +logfile Message log filename diff --git a/rabbitmq_consumer/buildconfig.inc b/rabbitmq_consumer/buildconfig.inc new file mode 100644 index 000000000..ab0f2f887 --- /dev/null +++ b/rabbitmq_consumer/buildconfig.inc @@ -0,0 +1,8 @@ +#Use the '-I' prefix for include and '-L' for library directories +#You can use multiple library and include directories + +#Path to the rabbitmq-c and mysqlclient libraries +LIBRARY_DIRS :=-L/usr/lib64 + +#path to headers +INCLUDE_DIRS :=-I/usr/include -I/usr/include/mysql \ No newline at end of file diff --git a/rabbitmq_consumer/consumer.c b/rabbitmq_consumer/consumer.c new file mode 100644 index 000000000..defdbaa81 --- /dev/null +++ b/rabbitmq_consumer/consumer.c @@ -0,0 +1,538 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_IN_ETC +#define CONFIG 1 +#else +#define CONFIG 0 +#endif + +#ifndef CONSUMER_CONFIG_PREFIX +#define CONSUMER_CONFIG_PREFIX "/usr/share/consumer" +#endif + +typedef struct delivery_t +{ + uint64_t dtag; + amqp_message_t* message; + struct delivery_t *next,*prev; +}DELIVERY; + +typedef struct consumer_t +{ + char *hostname,*vhost,*user,*passwd,*queue,*dbserver,*dbname,*dbuser,*dbpasswd; + DELIVERY* query_stack; + int port,dbport; +}CONSUMER; + +static int all_ok; +static FILE* out_fd; +static CONSUMER* c_inst; +static char* DB_DATABASE = "CREATE DATABASE IF NOT EXISTS %s;"; +static char* DB_TABLE = "CREATE TABLE IF NOT EXISTS pairs (tag VARCHAR(64) PRIMARY KEY NOT NULL, query VARCHAR(2048), reply VARCHAR(2048), date_in DATETIME NOT NULL, date_out DATETIME DEFAULT NULL, counter INT DEFAULT 1)"; +static char* DB_INSERT = "INSERT INTO pairs(tag, query, date_in) VALUES ('%s','%s',FROM_UNIXTIME(%s))"; +static char* DB_UPDATE = "UPDATE pairs SET reply='%s', date_out=FROM_UNIXTIME(%s) WHERE tag='%s'"; +static char* DB_INCREMENT = "UPDATE pairs SET counter = counter+1, date_out=FROM_UNIXTIME(%s) WHERE query='%s'"; + +void sighndl(int signum) +{ + if(signum == SIGINT){ + all_ok = 0; + alarm(1); + } +} + +int handler(void* user, const char* section, const char* name, + const char* value) +{ + if(strcmp(section,"consumer") == 0){ + + if(strcmp(name,"hostname") == 0){ + c_inst->hostname = strdup(value); + }else if(strcmp(name,"vhost") == 0){ + c_inst->vhost = strdup(value); + }else if(strcmp(name,"port") == 0){ + c_inst->port = atoi(value); + }else if(strcmp(name,"user") == 0){ + c_inst->user = strdup(value); + }else if(strcmp(name,"passwd") == 0){ + c_inst->passwd = strdup(value); + }else if(strcmp(name,"queue") == 0){ + c_inst->queue = strdup(value); + }else if(strcmp(name,"dbserver") == 0){ + c_inst->dbserver = strdup(value); + }else if(strcmp(name,"dbport") == 0){ + c_inst->dbport = atoi(value); + }else if(strcmp(name,"dbname") == 0){ + c_inst->dbname = strdup(value); + }else if(strcmp(name,"dbuser") == 0){ + c_inst->dbuser = strdup(value); + }else if(strcmp(name,"dbpasswd") == 0){ + c_inst->dbpasswd = strdup(value); + }else if(strcmp(name,"logfile") == 0){ + out_fd = fopen(value,"ab"); + } + + } + + return 1; +} + +int isPair(amqp_message_t* a, amqp_message_t* b) +{ + int keylen = a->properties.correlation_id.len >= + b->properties.correlation_id.len ? + a->properties.correlation_id.len : + b->properties.correlation_id.len; + + return strncmp(a->properties.correlation_id.bytes, + b->properties.correlation_id.bytes, + keylen) == 0 ? 1 : 0; +} + +int connectToServer(MYSQL* server) +{ + + + mysql_init(server); + + mysql_options(server,MYSQL_READ_DEFAULT_GROUP,"client"); + mysql_options(server,MYSQL_OPT_USE_REMOTE_CONNECTION,0); + my_bool tr = 1; + mysql_options(server,MYSQL_OPT_RECONNECT,&tr); + + + MYSQL* result = mysql_real_connect(server, + c_inst->dbserver, + c_inst->dbuser, + c_inst->dbpasswd, + NULL, + c_inst->dbport, + NULL, + 0); + + + if(result==NULL){ + fprintf(out_fd,"Error: Could not connect to MySQL server: %s\n",mysql_error(server)); + return 0; + } + + int bsz = 1024; + char *qstr = calloc(bsz,sizeof(char)); + + + if(!qstr){ + fprintf(stderr, "Fatal Error: Cannot allocate enough memory.\n"); + return 0; + } + + + /**Connection ok, check that the database and table exist*/ + + memset(qstr,0,bsz); + sprintf(qstr,DB_DATABASE,c_inst->dbname); + if(mysql_query(server,qstr)){ + fprintf(stderr,"Error: Could not send query MySQL server: %s\n",mysql_error(server)); + } + memset(qstr,0,bsz); + sprintf(qstr,"USE %s;",c_inst->dbname); + if(mysql_query(server,qstr)){ + fprintf(stderr,"Error: Could not send query MySQL server: %s\n",mysql_error(server)); + } + + memset(qstr,0,bsz); + sprintf(qstr,DB_TABLE); + if(mysql_query(server,qstr)){ + fprintf(stderr,"Error: Could not send query MySQL server: %s\n",mysql_error(server)); + } + + free(qstr); + return 1; +} + +int sendMessage(MYSQL* server, amqp_message_t* msg) +{ + int buffsz = (int)((msg->body.len + 1)*2+1) + + (int)((msg->properties.correlation_id.len + 1)*2+1) + + strlen(DB_INSERT), + rval = 0; + char *qstr = calloc(buffsz,sizeof(char)), + *rawmsg = calloc((msg->body.len + 1),sizeof(char)), + *clnmsg = calloc(((msg->body.len + 1)*2+1),sizeof(char)), + *rawdate = calloc((msg->body.len + 1),sizeof(char)), + *clndate = calloc(((msg->body.len + 1)*2+1),sizeof(char)), + *rawtag = calloc((msg->properties.correlation_id.len + 1),sizeof(char)), + *clntag = calloc(((msg->properties.correlation_id.len + 1)*2+1),sizeof(char)); + + + + sprintf(qstr,"%.*s",(int)msg->body.len,(char *)msg->body.bytes); + fprintf(out_fd,"Received: %s\n",qstr); + char *ptr = strtok(qstr,"|"); + sprintf(rawdate,"%s",ptr); + ptr = strtok(NULL,"\n\0"); + if(ptr == NULL){ + fprintf(out_fd,"Message content not valid.\n"); + rval = 1; + goto cleanup; + } + sprintf(rawmsg,"%s",ptr); + sprintf(rawtag,"%.*s",(int)msg->properties.correlation_id.len,(char *)msg->properties.correlation_id.bytes); + memset(qstr,0,buffsz); + + mysql_real_escape_string(server,clnmsg,rawmsg,strnlen(rawmsg,msg->body.len + 1)); + mysql_real_escape_string(server,clndate,rawdate,strnlen(rawdate,msg->body.len + 1)); + mysql_real_escape_string(server,clntag,rawtag,strnlen(rawtag,msg->properties.correlation_id.len + 1)); + + if(strncmp(msg->properties.message_id.bytes, + "query",msg->properties.message_id.len) == 0) + { + + sprintf(qstr,DB_INCREMENT,clndate,clnmsg); + rval = mysql_query(server,qstr); + + if(mysql_affected_rows(server) == 0){ + memset(qstr,0,buffsz); + sprintf(qstr,DB_INSERT,clntag,clnmsg,clndate); + rval = mysql_query(server,qstr); + } + + }else if(strncmp(msg->properties.message_id.bytes, + "reply",msg->properties.message_id.len) == 0){ + + sprintf(qstr,DB_UPDATE,clnmsg,clndate,clntag); + rval = mysql_query(server,qstr); + + }else{ + rval = 1; + goto cleanup; + } + + + if(rval){ + fprintf(stderr,"Could not send query to SQL server:%s\n",mysql_error(server)); + goto cleanup; + } + + cleanup: + free(qstr); + free(rawmsg); + free(clnmsg); + free(rawdate); + free(clndate); + free(rawtag); + free(clntag); + + return rval; +} + +int sendToServer(MYSQL* server, amqp_message_t* a, amqp_message_t* b){ + + amqp_message_t *msg, *reply; + int buffsz = 2048; + char *qstr = calloc(buffsz,sizeof(char)); + + if(!qstr){ + fprintf(out_fd, "Fatal Error: Cannot allocate enough memory.\n"); + free(qstr); + return 0; + } + + if( a->properties.message_id.len == strlen("query") && + strncmp(a->properties.message_id.bytes,"query", + a->properties.message_id.len) == 0){ + + msg = a; + reply = b; + + }else{ + + msg = b; + reply = a; + + } + + + printf("pair: %.*s\nquery: %.*s\nreply: %.*s\n", + (int)msg->properties.correlation_id.len, + (char *)msg->properties.correlation_id.bytes, + (int)msg->body.len, + (char *)msg->body.bytes, + (int)reply->body.len, + (char *)reply->body.bytes); + + if((int)msg->body.len + + (int)reply->body.len + + (int)msg->properties.correlation_id.len + 50 >= buffsz) + { + char *qtmp = calloc(buffsz*2,sizeof(char)); + free(qstr); + + if(qtmp){ + qstr = qtmp; + buffsz *= 2; + }else{ + fprintf(stderr, "Fatal Error: Cannot allocate enough memory.\n"); + return 0; + } + + } + + char *rawmsg = calloc((msg->body.len + 1),sizeof(char)), + *clnmsg = calloc(((msg->body.len + 1)*2+1),sizeof(char)), + *rawrpl = calloc((reply->body.len + 1),sizeof(char)), + *clnrpl = calloc(((reply->body.len + 1)*2+1),sizeof(char)), + *rawtag = calloc((msg->properties.correlation_id.len + 1),sizeof(char)), + *clntag = calloc(((msg->properties.correlation_id.len + 1)*2+1),sizeof(char)); + + sprintf(rawmsg,"%.*s",(int)msg->body.len,(char *)msg->body.bytes); + sprintf(rawrpl,"%.*s",(int)reply->body.len,(char *)reply->body.bytes); + sprintf(rawtag,"%.*s",(int)msg->properties.correlation_id.len,(char *)msg->properties.correlation_id.bytes); + + char *ptr; + while((ptr = strchr(rawmsg,'\n'))){ + *ptr = ' '; + } + while((ptr = strchr(rawrpl,'\n'))){ + *ptr = ' '; + } + while((ptr = strchr(rawtag,'\n'))){ + *ptr = ' '; + } + + mysql_real_escape_string(server,clnmsg,rawmsg,strnlen(rawmsg,msg->body.len + 1)); + mysql_real_escape_string(server,clnrpl,rawrpl,strnlen(rawrpl,reply->body.len + 1)); + mysql_real_escape_string(server,clntag,rawtag,strnlen(rawtag,msg->properties.correlation_id.len + 1)); + + + + sprintf(qstr,"INSERT INTO pairs VALUES ('%s','%s','%s');",clnmsg,clnrpl,clntag); + free(rawmsg); + free(clnmsg); + free(rawrpl); + free(clnrpl); + free(rawtag); + free(clntag); + + if(mysql_query(server,qstr)){ + fprintf(stderr,"Could not send query to SQL server:%s\n",mysql_error(server)); + free(qstr); + return 0; + } + + free(qstr); + return 1; +} +int main(int argc, char** argv) +{ + int channel = 1, status = AMQP_STATUS_OK, cnfnlen; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + amqp_rpc_reply_t ret; + amqp_message_t *reply = NULL; + amqp_frame_t frame; + struct timeval timeout; + MYSQL db_inst; + char ch, *cnfname = NULL, *cnfpath = NULL; + static const char* fname = "consumer.cnf"; + static const char* fprefix = CONSUMER_CONFIG_PREFIX; + + if(signal(SIGINT,sighndl) == SIG_IGN){ + signal(SIGINT,SIG_IGN); + } + + while((ch = getopt(argc,argv,"c:"))!= -1){ + switch(ch){ + case 'c': + cnfnlen = strlen(optarg); + cnfpath = strdup(optarg); + break; + default: + + break; + } + } + + cnfname = calloc(cnfnlen + strlen(fname) + 1,sizeof(char)); + + if(cnfpath){ + + /**Config file path as argument*/ + strcpy(cnfname,cnfpath); + if(cnfpath[cnfnlen-1] != '/'){ + strcat(cnfname,"/"); + } + + }else if(CONFIG){ + + /**Config file location was set at install*/ + strcat(cnfname,fprefix); + if(cnfname[strlen(cnfname) - 1] != '/'){ + strcat(cnfname,"/"); + } + + } + + + strcat(cnfname,fname); + + timeout.tv_sec = 1; + timeout.tv_usec = 0; + all_ok = 1; + out_fd = NULL; + + if((c_inst = calloc(1,sizeof(CONSUMER))) == NULL){ + fprintf(stderr, "Fatal Error: Cannot allocate enough memory.\n"); + return 1; + } + + /**Parse the INI file*/ + if(ini_parse(cnfname,handler,NULL) < 0){ + + /**Try to parse a config in the same directory*/ + if(ini_parse(fname,handler,NULL) < 0){ + fprintf(out_fd, "Fatal Error: Error parsing configuration file!\n"); + goto fatal_error; + + } + } + + if(out_fd == NULL){ + out_fd = stdout; + } + + fprintf(out_fd,"\n--------------------------------------------------------------\n"); + + /**Confirm that all parameters were in the configuration file*/ + if(!c_inst->hostname||!c_inst->vhost||!c_inst->user|| + !c_inst->passwd||!c_inst->dbpasswd||!c_inst->queue|| + !c_inst->dbserver||!c_inst->dbname||!c_inst->dbuser){ + fprintf(stderr, "Fatal Error: Inadequate configuration file!\n"); + goto fatal_error; + } + + connectToServer(&db_inst); + + if((conn = amqp_new_connection()) == NULL || + (socket = amqp_tcp_socket_new(conn)) == NULL){ + fprintf(stderr, "Fatal Error: Cannot create connection object or socket.\n"); + goto fatal_error; + } + + if(amqp_socket_open(socket, c_inst->hostname, c_inst->port)){ + fprintf(stderr, "RabbitMQ Error: Cannot open socket.\n"); + goto error; + } + + ret = amqp_login(conn, c_inst->vhost, 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, c_inst->user, c_inst->passwd); + + if(ret.reply_type != AMQP_RESPONSE_NORMAL){ + fprintf(stderr, "RabbitMQ Error: Cannot login to server.\n"); + goto error; + } + + amqp_channel_open(conn, channel); + ret = amqp_get_rpc_reply(conn); + + if(ret.reply_type != AMQP_RESPONSE_NORMAL){ + fprintf(stderr, "RabbitMQ Error: Cannot open channel.\n"); + goto error; + } + + reply = malloc(sizeof(amqp_message_t)); + if(!reply){ + fprintf(stderr, "Error: Cannot allocate enough memory.\n"); + goto error; + } + amqp_basic_consume(conn,channel,amqp_cstring_bytes(c_inst->queue),amqp_empty_bytes,0,0,0,amqp_empty_table); + + while(all_ok){ + + status = amqp_simple_wait_frame_noblock(conn,&frame,&timeout); + + /**No frames to read from server, possibly out of messages*/ + if(status == AMQP_STATUS_TIMEOUT){ + sleep(timeout.tv_sec); + continue; + } + + if(frame.payload.method.id == AMQP_BASIC_DELIVER_METHOD){ + + amqp_basic_deliver_t* decoded = (amqp_basic_deliver_t*)frame.payload.method.decoded; + + amqp_read_message(conn,channel,reply,0); + + if(sendMessage(&db_inst,reply)){ + + fprintf(stderr,"RabbitMQ Error: Received malformed message.\n"); + amqp_basic_reject(conn,channel,decoded->delivery_tag,0); + amqp_destroy_message(reply); + + }else{ + + amqp_basic_ack(conn,channel,decoded->delivery_tag,0); + amqp_destroy_message(reply); + + } + + }else{ + fprintf(stderr,"RabbitMQ Error: Received method from server: %s\n",amqp_method_name(frame.payload.method.id)); + all_ok = 0; + goto error; + } + + } + + fprintf(out_fd,"Shutting down...\n"); + error: + + mysql_close(&db_inst); + mysql_library_end(); + if(c_inst && c_inst->query_stack){ + + while(c_inst->query_stack){ + DELIVERY* d = c_inst->query_stack->next; + amqp_destroy_message(c_inst->query_stack->message); + free(c_inst->query_stack); + c_inst->query_stack = d; + } + + } + + amqp_channel_close(conn, channel, AMQP_REPLY_SUCCESS); + amqp_connection_close(conn, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(conn); + fatal_error: + + fclose(out_fd); + + if(c_inst){ + + free(c_inst->hostname); + free(c_inst->vhost); + free(c_inst->user); + free(c_inst->passwd); + free(c_inst->queue); + free(c_inst->dbserver); + free(c_inst->dbname); + free(c_inst->dbuser); + free(c_inst->dbpasswd); + free(c_inst); + + } + + + + return all_ok; +} diff --git a/rabbitmq_consumer/consumer.cnf b/rabbitmq_consumer/consumer.cnf new file mode 100644 index 000000000..82296edc6 --- /dev/null +++ b/rabbitmq_consumer/consumer.cnf @@ -0,0 +1,28 @@ +# +#The options for the consumer are: +#hostname RabbitMQ hostname +#port RabbitMQ port +#vhost RabbitMQ virtual host +#user RabbitMQ username +#passwd RabbitMQ password +#queue Name of the queue to use +#dbserver SQL server name +#dbport SQL server port +#dbname Name of the databse to use +#dbuser SQL server username +#dbpasswd SQL server password +#logfile Message log filename +# +[consumer] +hostname=127.0.0.1 +port=5673 +vhost=/ +user=guest +passwd=guest +queue=q1 +dbserver=127.0.0.1 +dbport=3000 +dbname=mqpairs +dbuser=maxuser +dbpasswd=maxpwd +#logfile=consumer.log \ No newline at end of file diff --git a/rabbitmq_consumer/inih/._LICENSE.txt b/rabbitmq_consumer/inih/._LICENSE.txt new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/._README.txt b/rabbitmq_consumer/inih/._README.txt new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/._cpp b/rabbitmq_consumer/inih/._cpp new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/._examples b/rabbitmq_consumer/inih/._examples new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/._extra b/rabbitmq_consumer/inih/._extra new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/._ini.c b/rabbitmq_consumer/inih/._ini.c new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/._ini.h b/rabbitmq_consumer/inih/._ini.h new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/._tests b/rabbitmq_consumer/inih/._tests new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/.gitignore b/rabbitmq_consumer/inih/.gitignore new file mode 100644 index 000000000..2a5429025 --- /dev/null +++ b/rabbitmq_consumer/inih/.gitignore @@ -0,0 +1,3 @@ +*.o +*.a +make.depend diff --git a/rabbitmq_consumer/inih/CMakeLists.txt b/rabbitmq_consumer/inih/CMakeLists.txt new file mode 100644 index 000000000..492566fd2 --- /dev/null +++ b/rabbitmq_consumer/inih/CMakeLists.txt @@ -0,0 +1 @@ +add_library(inih ini.c) diff --git a/rabbitmq_consumer/inih/LICENSE.txt b/rabbitmq_consumer/inih/LICENSE.txt new file mode 100755 index 000000000..44a3093a3 --- /dev/null +++ b/rabbitmq_consumer/inih/LICENSE.txt @@ -0,0 +1,27 @@ + +The "inih" library is distributed under the New BSD license: + +Copyright (c) 2009, Brush Technology +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Brush Technology nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY BRUSH TECHNOLOGY ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BRUSH TECHNOLOGY BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/rabbitmq_consumer/inih/README.txt b/rabbitmq_consumer/inih/README.txt new file mode 100755 index 000000000..4bff76126 --- /dev/null +++ b/rabbitmq_consumer/inih/README.txt @@ -0,0 +1,5 @@ + +inih is a simple .INI file parser written in C, released under the New BSD +license (see LICENSE.txt). Go to the project home page for more info: + +http://code.google.com/p/inih/ diff --git a/rabbitmq_consumer/inih/cpp/._INIReader.cpp b/rabbitmq_consumer/inih/cpp/._INIReader.cpp new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/cpp/._INIReader.h b/rabbitmq_consumer/inih/cpp/._INIReader.h new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/cpp/._INIReaderTest.cpp b/rabbitmq_consumer/inih/cpp/._INIReaderTest.cpp new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/cpp/INIReader.cpp b/rabbitmq_consumer/inih/cpp/INIReader.cpp new file mode 100755 index 000000000..43f695149 --- /dev/null +++ b/rabbitmq_consumer/inih/cpp/INIReader.cpp @@ -0,0 +1,67 @@ +// Read an INI file into easy-to-access name/value pairs. + +#include +#include +#include +#include "../ini.h" +#include "INIReader.h" + +using std::string; + +INIReader::INIReader(string filename) +{ + _error = ini_parse(filename.c_str(), ValueHandler, this); +} + +int INIReader::ParseError() +{ + return _error; +} + +string INIReader::Get(string section, string name, string default_value) +{ + string key = MakeKey(section, name); + return _values.count(key) ? _values[key] : default_value; +} + +long INIReader::GetInteger(string section, string name, long default_value) +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + long n = strtol(value, &end, 0); + return end > value ? n : default_value; +} + +bool INIReader::GetBoolean(string section, string name, bool default_value) +{ + string valstr = Get(section, name, ""); + // Convert to lower case to make string comparisons case-insensitive + std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); + if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") + return true; + else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") + return false; + else + return default_value; +} + +string INIReader::MakeKey(string section, string name) +{ + string key = section + "." + name; + // Convert to lower case to make section/name lookups case-insensitive + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + return key; +} + +int INIReader::ValueHandler(void* user, const char* section, const char* name, + const char* value) +{ + INIReader* reader = (INIReader*)user; + string key = MakeKey(section, name); + if (reader->_values[key].size() > 0) + reader->_values[key] += "\n"; + reader->_values[key] += value; + return 1; +} diff --git a/rabbitmq_consumer/inih/cpp/INIReader.h b/rabbitmq_consumer/inih/cpp/INIReader.h new file mode 100755 index 000000000..7571a29d2 --- /dev/null +++ b/rabbitmq_consumer/inih/cpp/INIReader.h @@ -0,0 +1,48 @@ +// Read an INI file into easy-to-access name/value pairs. + +// inih and INIReader are released under the New BSD license (see LICENSE.txt). +// Go to the project home page for more info: +// +// http://code.google.com/p/inih/ + +#ifndef __INIREADER_H__ +#define __INIREADER_H__ + +#include +#include + +// Read an INI file into easy-to-access name/value pairs. (Note that I've gone +// for simplicity here rather than speed, but it should be pretty decent.) +class INIReader +{ +public: + // Construct INIReader and parse given filename. See ini.h for more info + // about the parsing. + INIReader(std::string filename); + + // Return the result of ini_parse(), i.e., 0 on success, line number of + // first error on parse error, or -1 on file open error. + int ParseError(); + + // Get a string value from INI file, returning default_value if not found. + std::string Get(std::string section, std::string name, + std::string default_value); + + // Get an integer (long) value from INI file, returning default_value if + // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). + long GetInteger(std::string section, std::string name, long default_value); + + // Get a boolean value from INI file, returning default_value if not found or if + // not a valid true/false value. Valid true values are "true", "yes", "on", "1", + // and valid false values are "false", "no", "off", "0" (not case sensitive). + bool GetBoolean(std::string section, std::string name, bool default_value); + +private: + int _error; + std::map _values; + static std::string MakeKey(std::string section, std::string name); + static int ValueHandler(void* user, const char* section, const char* name, + const char* value); +}; + +#endif // __INIREADER_H__ diff --git a/rabbitmq_consumer/inih/cpp/INIReaderTest.cpp b/rabbitmq_consumer/inih/cpp/INIReaderTest.cpp new file mode 100755 index 000000000..cb13b62c1 --- /dev/null +++ b/rabbitmq_consumer/inih/cpp/INIReaderTest.cpp @@ -0,0 +1,20 @@ +// Example that shows simple usage of the INIReader class + +#include +#include "INIReader.h" + +int main() +{ + INIReader reader("../examples/test.ini"); + + if (reader.ParseError() < 0) { + std::cout << "Can't load 'test.ini'\n"; + return 1; + } + std::cout << "Config loaded from 'test.ini': version=" + << reader.GetInteger("protocol", "version", -1) << ", name=" + << reader.Get("user", "name", "UNKNOWN") << ", email=" + << reader.Get("user", "email", "UNKNOWN") << ", active=" + << reader.GetBoolean("user", "active", true) << "\n"; + return 0; +} diff --git a/rabbitmq_consumer/inih/examples/._config.def b/rabbitmq_consumer/inih/examples/._config.def new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/examples/._ini_dump.c b/rabbitmq_consumer/inih/examples/._ini_dump.c new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/examples/._ini_example.c b/rabbitmq_consumer/inih/examples/._ini_example.c new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/examples/._ini_xmacros.c b/rabbitmq_consumer/inih/examples/._ini_xmacros.c new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/examples/._test.ini b/rabbitmq_consumer/inih/examples/._test.ini new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/examples/config.def b/rabbitmq_consumer/inih/examples/config.def new file mode 100755 index 000000000..f5c7ed5fe --- /dev/null +++ b/rabbitmq_consumer/inih/examples/config.def @@ -0,0 +1,8 @@ +// CFG(section, name, default) + +CFG(protocol, version, "0") + +CFG(user, name, "Fatty Lumpkin") +CFG(user, email, "fatty@lumpkin.com") + +#undef CFG diff --git a/rabbitmq_consumer/inih/examples/ini_dump.c b/rabbitmq_consumer/inih/examples/ini_dump.c new file mode 100755 index 000000000..5c8c6d115 --- /dev/null +++ b/rabbitmq_consumer/inih/examples/ini_dump.c @@ -0,0 +1,40 @@ +/* ini.h example that simply dumps an INI file without comments */ + +#include +#include +#include "../ini.h" + +static int dumper(void* user, const char* section, const char* name, + const char* value) +{ + static char prev_section[50] = ""; + + if (strcmp(section, prev_section)) { + printf("%s[%s]\n", (prev_section[0] ? "\n" : ""), section); + strncpy(prev_section, section, sizeof(prev_section)); + prev_section[sizeof(prev_section) - 1] = '\0'; + } + printf("%s = %s\n", name, value); + return 1; +} + +int main(int argc, char* argv[]) +{ + int error; + + if (argc <= 1) { + printf("Usage: ini_dump filename.ini\n"); + return 1; + } + + error = ini_parse(argv[1], dumper, NULL); + if (error < 0) { + printf("Can't read '%s'!\n", argv[1]); + return 2; + } + else if (error) { + printf("Bad config file (first error on line %d)!\n", error); + return 3; + } + return 0; +} diff --git a/rabbitmq_consumer/inih/examples/ini_example.c b/rabbitmq_consumer/inih/examples/ini_example.c new file mode 100755 index 000000000..962cef57a --- /dev/null +++ b/rabbitmq_consumer/inih/examples/ini_example.c @@ -0,0 +1,44 @@ +/* Example: parse a simple configuration file */ + +#include +#include +#include +#include "../ini.h" + +typedef struct +{ + int version; + const char* name; + const char* email; +} configuration; + +static int handler(void* user, const char* section, const char* name, + const char* value) +{ + configuration* pconfig = (configuration*)user; + + #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 + if (MATCH("protocol", "version")) { + pconfig->version = atoi(value); + } else if (MATCH("user", "name")) { + pconfig->name = strdup(value); + } else if (MATCH("user", "email")) { + pconfig->email = strdup(value); + } else { + return 0; /* unknown section/name, error */ + } + return 1; +} + +int main(int argc, char* argv[]) +{ + configuration config; + + if (ini_parse("test.ini", handler, &config) < 0) { + printf("Can't load 'test.ini'\n"); + return 1; + } + printf("Config loaded from 'test.ini': version=%d, name=%s, email=%s\n", + config.version, config.name, config.email); + return 0; +} diff --git a/rabbitmq_consumer/inih/examples/ini_xmacros.c b/rabbitmq_consumer/inih/examples/ini_xmacros.c new file mode 100755 index 000000000..7d867acd6 --- /dev/null +++ b/rabbitmq_consumer/inih/examples/ini_xmacros.c @@ -0,0 +1,46 @@ +/* Parse a configuration file into a struct using X-Macros */ + +#include +#include +#include "../ini.h" + +/* define the config struct type */ +typedef struct { + #define CFG(s, n, default) char *s##_##n; + #include "config.def" +} config; + +/* create one and fill in its default values */ +config Config = { + #define CFG(s, n, default) default, + #include "config.def" +}; + +/* process a line of the INI file, storing valid values into config struct */ +int handler(void *user, const char *section, const char *name, + const char *value) +{ + config *cfg = (config *)user; + + if (0) ; + #define CFG(s, n, default) else if (strcmp(section, #s)==0 && \ + strcmp(name, #n)==0) cfg->s##_##n = strdup(value); + #include "config.def" + + return 1; +} + +/* print all the variables in the config, one per line */ +void dump_config(config *cfg) +{ + #define CFG(s, n, default) printf("%s_%s = %s\n", #s, #n, cfg->s##_##n); + #include "config.def" +} + +int main(int argc, char* argv[]) +{ + if (ini_parse("test.ini", handler, &Config) < 0) + printf("Can't load 'test.ini', using defaults\n"); + dump_config(&Config); + return 0; +} diff --git a/rabbitmq_consumer/inih/examples/test.ini b/rabbitmq_consumer/inih/examples/test.ini new file mode 100755 index 000000000..e06e7f9ba --- /dev/null +++ b/rabbitmq_consumer/inih/examples/test.ini @@ -0,0 +1,9 @@ +; Test config file for ini_example.c and INIReaderTest.cpp + +[protocol] ; Protocol configuration +version=6 ; IPv6 + +[user] +name = Bob Smith ; Spaces around '=' are stripped +email = bob@smith.com ; And comments (like this) ignored +active = true ; Test a boolean diff --git a/rabbitmq_consumer/inih/extra/._Makefile.static b/rabbitmq_consumer/inih/extra/._Makefile.static new file mode 100755 index 0000000000000000000000000000000000000000..17b8574a41eb3852d574972f6f70e928759648f5 GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>*z*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=y(A720f literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/extra/Makefile.static b/rabbitmq_consumer/inih/extra/Makefile.static new file mode 100755 index 000000000..0d6519e38 --- /dev/null +++ b/rabbitmq_consumer/inih/extra/Makefile.static @@ -0,0 +1,19 @@ +# Simple makefile to build inih as a static library using g++ + +SRC = ../ini.c +OBJ = $(SRC:.c=.o) +OUT = libinih.a +INCLUDES = -I.. +CCFLAGS = -g -O2 +CC = g++ + +default: $(OUT) + +.c.o: + $(CC) $(INCLUDES) $(CCFLAGS) $(EXTRACCFLAGS) -c $< -o $@ + +$(OUT): $(OBJ) + ar rcs $(OUT) $(OBJ) $(EXTRAARFLAGS) + +clean: + rm -f $(OBJ) $(OUT) diff --git a/rabbitmq_consumer/inih/ini.c b/rabbitmq_consumer/inih/ini.c new file mode 100755 index 000000000..9f9110eaf --- /dev/null +++ b/rabbitmq_consumer/inih/ini.c @@ -0,0 +1,176 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +http://code.google.com/p/inih/ + +*/ + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char c or ';' comment in given string, or pointer to + null at end of string if neither found. ';' must be prefixed by a whitespace + character to register as a comment. */ +static char* find_char_or_comment(const char* s, char c) +{ + int was_whitespace = 0; + while (*s && *s != c && !(was_whitespace && *s == ';')) { + was_whitespace = isspace((unsigned char)(*s)); + s++; + } + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, + int (*handler)(void*, const char*, const char*, + const char*), + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + + /* Scan through file line by line */ + while (fgets(line, INI_MAX_LINE, file) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python ConfigParser, allow '#' comments at start of line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-black line with leading whitespace, treat as continuation + of previous name's value (as per Python ConfigParser). */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_char_or_comment(start + 1, ']'); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start && *start != ';') { + /* Not a comment, must be a name[=:]value pair */ + end = find_char_or_comment(start, '='); + if (*end != '=') { + end = find_char_or_comment(start, ':'); + } + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); + end = find_char_or_comment(value, '\0'); + if (*end == ';') + *end = '\0'; + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, + int (*handler)(void*, const char*, const char*, const char*), + void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} diff --git a/rabbitmq_consumer/inih/ini.h b/rabbitmq_consumer/inih/ini.h new file mode 100755 index 000000000..b3a494a24 --- /dev/null +++ b/rabbitmq_consumer/inih/ini.h @@ -0,0 +1,72 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +http://code.google.com/p/inih/ + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's ConfigParser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, + int (*handler)(void* user, const char* section, + const char* name, const char* value), + void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, + int (*handler)(void* user, const char* section, + const char* name, const char* value), + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + ConfigParser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/rabbitmq_consumer/inih/tests/._bad_comment.ini b/rabbitmq_consumer/inih/tests/._bad_comment.ini new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._bad_multi.ini b/rabbitmq_consumer/inih/tests/._bad_multi.ini new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._bad_section.ini b/rabbitmq_consumer/inih/tests/._bad_section.ini new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._baseline_multi.txt b/rabbitmq_consumer/inih/tests/._baseline_multi.txt new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._baseline_single.txt b/rabbitmq_consumer/inih/tests/._baseline_single.txt new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._bom.ini b/rabbitmq_consumer/inih/tests/._bom.ini new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._multi_line.ini b/rabbitmq_consumer/inih/tests/._multi_line.ini new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._normal.ini b/rabbitmq_consumer/inih/tests/._normal.ini new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._unittest.bat b/rabbitmq_consumer/inih/tests/._unittest.bat new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._unittest.c b/rabbitmq_consumer/inih/tests/._unittest.c new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/._user_error.ini b/rabbitmq_consumer/inih/tests/._user_error.ini new file mode 100755 index 0000000000000000000000000000000000000000..7fa6eb8a60820e42dc1aba46a86861309c18c3db GIT binary patch literal 212 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3Cj$e65d#CmA_fL9{|W<9z%e8w2&xdIV+I2Q zgAJ4qqZk<&7@*>!$@#f@i3J5Ysd|N_iA9NdC7F4t42Ajz1_nmfriRHThQ>)|*1?Hs tiA9;#u4b;5mS(0dx-Nz;uDT}9j?TK4=7tu!hNdP?PDaj#W=>8{3;=zSA7B6g literal 0 HcmV?d00001 diff --git a/rabbitmq_consumer/inih/tests/bad_comment.ini b/rabbitmq_consumer/inih/tests/bad_comment.ini new file mode 100755 index 000000000..7f4602eb9 --- /dev/null +++ b/rabbitmq_consumer/inih/tests/bad_comment.ini @@ -0,0 +1 @@ +This is an error diff --git a/rabbitmq_consumer/inih/tests/bad_multi.ini b/rabbitmq_consumer/inih/tests/bad_multi.ini new file mode 100755 index 000000000..655017500 --- /dev/null +++ b/rabbitmq_consumer/inih/tests/bad_multi.ini @@ -0,0 +1 @@ + indented diff --git a/rabbitmq_consumer/inih/tests/bad_section.ini b/rabbitmq_consumer/inih/tests/bad_section.ini new file mode 100755 index 000000000..90e31ac09 --- /dev/null +++ b/rabbitmq_consumer/inih/tests/bad_section.ini @@ -0,0 +1,5 @@ +[section1] +name1=value1 +[section2 +[section3 ; comment ] +name2=value2 diff --git a/rabbitmq_consumer/inih/tests/baseline_multi.txt b/rabbitmq_consumer/inih/tests/baseline_multi.txt new file mode 100755 index 000000000..637f75258 --- /dev/null +++ b/rabbitmq_consumer/inih/tests/baseline_multi.txt @@ -0,0 +1,47 @@ +no_file.ini: e=-1 user=0 +... [section1] +... one=This is a test; +... two=1234; +... [ section 2 ] +... happy=4; +... sad=; +... [comment_test] +... test1=1;2;3; +... test2=2;3;4;this won't be a comment, needs whitespace before ';'; +... test;3=345; +... test4=4#5#6; +... [colon_tests] +... Content-Type=text/html; +... foo=bar; +... adams=42; +normal.ini: e=0 user=101 +... [section1] +... name1=value1; +... name2=value2; +bad_section.ini: e=3 user=102 +bad_comment.ini: e=1 user=102 +... [section] +... a=b; +... user=parse_error; +... c=d; +user_error.ini: e=3 user=104 +... [section1] +... single1=abc; +... multi=this is a; +... multi=multi-line value; +... single2=xyz; +... [section2] +... multi=a; +... multi=b; +... multi=c; +... [section3] +... single=ghi; +... multi=the quick; +... multi=brown fox; +... name=bob smith; +multi_line.ini: e=0 user=105 +bad_multi.ini: e=1 user=105 +... [bom_section] +... bom_name=bom_value; +... key“=value“; +bom.ini: e=0 user=107 diff --git a/rabbitmq_consumer/inih/tests/baseline_single.txt b/rabbitmq_consumer/inih/tests/baseline_single.txt new file mode 100755 index 000000000..30d8a2600 --- /dev/null +++ b/rabbitmq_consumer/inih/tests/baseline_single.txt @@ -0,0 +1,43 @@ +no_file.ini: e=-1 user=0 +... [section1] +... one=This is a test; +... two=1234; +... [ section 2 ] +... happy=4; +... sad=; +... [comment_test] +... test1=1;2;3; +... test2=2;3;4;this won't be a comment, needs whitespace before ';'; +... test;3=345; +... test4=4#5#6; +... [colon_tests] +... Content-Type=text/html; +... foo=bar; +... adams=42; +normal.ini: e=0 user=101 +... [section1] +... name1=value1; +... name2=value2; +bad_section.ini: e=3 user=102 +bad_comment.ini: e=1 user=102 +... [section] +... a=b; +... user=parse_error; +... c=d; +user_error.ini: e=3 user=104 +... [section1] +... single1=abc; +... multi=this is a; +... single2=xyz; +... [section2] +... multi=a; +... [section3] +... single=ghi; +... multi=the quick; +... name=bob smith; +multi_line.ini: e=4 user=105 +bad_multi.ini: e=1 user=105 +... [bom_section] +... bom_name=bom_value; +... key“=value“; +bom.ini: e=0 user=107 diff --git a/rabbitmq_consumer/inih/tests/bom.ini b/rabbitmq_consumer/inih/tests/bom.ini new file mode 100755 index 000000000..44c519f47 --- /dev/null +++ b/rabbitmq_consumer/inih/tests/bom.ini @@ -0,0 +1,3 @@ +[bom_section] +bom_name=bom_value +key“ = value“ diff --git a/rabbitmq_consumer/inih/tests/multi_line.ini b/rabbitmq_consumer/inih/tests/multi_line.ini new file mode 100755 index 000000000..d6eb10445 --- /dev/null +++ b/rabbitmq_consumer/inih/tests/multi_line.ini @@ -0,0 +1,15 @@ +[section1] +single1 = abc +multi = this is a + multi-line value +single2 = xyz +[section2] +multi = a + b + c +[section3] +single: ghi +multi: the quick + brown fox +name = bob smith ; comment line 1 + ; comment line 2 diff --git a/rabbitmq_consumer/inih/tests/normal.ini b/rabbitmq_consumer/inih/tests/normal.ini new file mode 100755 index 000000000..787ff8174 --- /dev/null +++ b/rabbitmq_consumer/inih/tests/normal.ini @@ -0,0 +1,25 @@ +; This is an INI file +[section1] ; section comment +one=This is a test ; name=value comment +two = 1234 +; x=y + +[ section 2 ] +happy = 4 +sad = + +[empty] +; do nothing + +[comment_test] +test1 = 1;2;3 ; only this will be a comment +test2 = 2;3;4;this won't be a comment, needs whitespace before ';' +test;3 = 345 ; key should be "test;3" +test4 = 4#5#6 ; '#' only starts a comment at start of line +#test5 = 567 ; entire line commented + # test6 = 678 ; entire line commented, except in MULTILINE mode + +[colon_tests] +Content-Type: text/html +foo:bar +adams : 42 diff --git a/rabbitmq_consumer/inih/tests/unittest.bat b/rabbitmq_consumer/inih/tests/unittest.bat new file mode 100755 index 000000000..90969fe3f --- /dev/null +++ b/rabbitmq_consumer/inih/tests/unittest.bat @@ -0,0 +1,2 @@ +@call tcc ..\ini.c -I..\ -run unittest.c > baseline_multi.txt +@call tcc ..\ini.c -I..\ -DINI_ALLOW_MULTILINE=0 -run unittest.c > baseline_single.txt diff --git a/rabbitmq_consumer/inih/tests/unittest.c b/rabbitmq_consumer/inih/tests/unittest.c new file mode 100755 index 000000000..5e8f8904c --- /dev/null +++ b/rabbitmq_consumer/inih/tests/unittest.c @@ -0,0 +1,58 @@ +/* inih -- unit tests + +This works simply by dumping a bunch of info to standard output, which is +redirected to an output file (baseline_*.txt) and checked into the Subversion +repository. This baseline file is the test output, so the idea is to check it +once, and if it changes -- look at the diff and see which tests failed. + +Here's how I produced the two baseline files (with Tiny C Compiler): + +tcc -DINI_ALLOW_MULTILINE=1 ../ini.c -run unittest.c > baseline_multi.txt +tcc -DINI_ALLOW_MULTILINE=0 ../ini.c -run unittest.c > baseline_single.txt + +*/ + +#include +#include +#include +#include "../ini.h" + +int User; +char Prev_section[50]; + +int dumper(void* user, const char* section, const char* name, + const char* value) +{ + User = (int)user; + if (strcmp(section, Prev_section)) { + printf("... [%s]\n", section); + strncpy(Prev_section, section, sizeof(Prev_section)); + Prev_section[sizeof(Prev_section) - 1] = '\0'; + } + printf("... %s=%s;\n", name, value); + + return strcmp(name, "user")==0 && strcmp(value, "parse_error")==0 ? 0 : 1; +} + +void parse(const char* fname) { + static int u = 100; + int e; + + *Prev_section = '\0'; + e = ini_parse(fname, dumper, (void*)u); + printf("%s: e=%d user=%d\n", fname, e, User); + u++; +} + +int main(void) +{ + parse("no_file.ini"); + parse("normal.ini"); + parse("bad_section.ini"); + parse("bad_comment.ini"); + parse("user_error.ini"); + parse("multi_line.ini"); + parse("bad_multi.ini"); + parse("bom.ini"); + return 0; +} diff --git a/rabbitmq_consumer/inih/tests/user_error.ini b/rabbitmq_consumer/inih/tests/user_error.ini new file mode 100755 index 000000000..9798af35e --- /dev/null +++ b/rabbitmq_consumer/inih/tests/user_error.ini @@ -0,0 +1,4 @@ +[section] +a = b +user = parse_error +c = d From 537fb879680871107a738240fcb319544229acd9 Mon Sep 17 00:00:00 2001 From: Timofey Turenko Date: Wed, 3 Sep 2014 16:46:39 +0300 Subject: [PATCH 2/7] add .spec --- rabbitmq_consumer/Makefile | 2 +- .../rabbitmq-message-consumer.spec | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 rabbitmq_consumer/rabbitmq-message-consumer.spec diff --git a/rabbitmq_consumer/Makefile b/rabbitmq_consumer/Makefile index 6bc8049eb..456727ef8 100644 --- a/rabbitmq_consumer/Makefile +++ b/rabbitmq_consumer/Makefile @@ -6,7 +6,7 @@ LDFLAGS= $(LIBRARY_DIRS) -lrabbitmq -lmysqlclient SRCS= inih/ini.c consumer.c OBJ=$(SRCS:.c=.o) all:$(OBJ) - $(CC) $(LDFLAGS) $(OBJ) -o consumer + $(CC) $(LDFLAGS) $(OBJ) -o consumer `mysql_config --cflags --libs` %.o:%.c $(CC) $(CFLAGS) $< -o $@ diff --git a/rabbitmq_consumer/rabbitmq-message-consumer.spec b/rabbitmq_consumer/rabbitmq-message-consumer.spec new file mode 100644 index 000000000..cd513d502 --- /dev/null +++ b/rabbitmq_consumer/rabbitmq-message-consumer.spec @@ -0,0 +1,53 @@ +%define _topdir %(echo $PWD)/ +%define name rabbitmq-message-consumer +%define release beta +%define version 1.0 +%define install_path /usr/local/skysql/maxscale/ + +BuildRoot: %{buildroot} +Summary: rabbitmq-message-consumer +License: GPL +Name: %{name} +Version: %{version} +Release: %{release} +Source: %{name}-%{version}-%{release}.tar.gz +Prefix: / +Group: Development/Tools +#Requires: + +%if 0%{?suse_version} +BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc_s1 perl make libtool libopenssl-devel libaio libaio-devel mariadb libedit-devel librabbitmq-devel +%else +BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc perl make libtool openssl-devel libaio libaio-devel librabbitmq-devel +%if 0%{?rhel} == 6 +BuildRequires: libedit-devel +%endif +%if 0%{?rhel} == 7 +BuildRequires: mariadb-devel mariadb-embedded-devel libedit-devel +%else +BuildRequires: MariaDB-devel MariaDB-server +%endif +%endif + +%description +rabbitmq-message-consumer + +%prep + +%setup -q + +%build +make clean +make + +%install +mkdir -p $RPM_BUILD_ROOT%{install_path} +cp consumer $RPM_BUILD_ROOT%{install_path} + +%clean + +%files +%defattr(-,root,root) +%{install_path}/consumer + +%changelog From 13f63ab92b98704ed2cdc651bd5969fd0abc0a50 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 3 Sep 2014 18:35:20 +0300 Subject: [PATCH 3/7] additional checks for object and schema triggers minor bug fixes in consumer.c --- rabbitmq_consumer/CMakeLists.txt | 3 --- rabbitmq_consumer/consumer.c | 40 +++++++++++--------------------- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/rabbitmq_consumer/CMakeLists.txt b/rabbitmq_consumer/CMakeLists.txt index 02e0e7125..30d16d630 100644 --- a/rabbitmq_consumer/CMakeLists.txt +++ b/rabbitmq_consumer/CMakeLists.txt @@ -20,9 +20,6 @@ link_directories(${CMAKE_SOURCE_DIR}/inih) if(RABBITMQ_C_LIBRARIES AND MYSQL_LIBRARIES AND MYSQL_INCLUDE_DIRS) -add_definitions(-DCONFIG_IN_ETC) -add_definitions(-DCONSUMER_CONFIG_PREFIX="${CMAKE_INSTALL_PREFIX}/share/consumer") - add_executable (consumer consumer.c ${MYSQL_LIBRARIES} ${RABBITMQ_C_LIBRARIES}) target_link_libraries(consumer mysqlclient) target_link_libraries(consumer rabbitmq) diff --git a/rabbitmq_consumer/consumer.c b/rabbitmq_consumer/consumer.c index defdbaa81..8ccabf401 100644 --- a/rabbitmq_consumer/consumer.c +++ b/rabbitmq_consumer/consumer.c @@ -12,15 +12,6 @@ #include #include #include -#ifdef CONFIG_IN_ETC -#define CONFIG 1 -#else -#define CONFIG 0 -#endif - -#ifndef CONSUMER_CONFIG_PREFIX -#define CONSUMER_CONFIG_PREFIX "/usr/share/consumer" -#endif typedef struct delivery_t { @@ -346,7 +337,11 @@ int main(int argc, char** argv) MYSQL db_inst; char ch, *cnfname = NULL, *cnfpath = NULL; static const char* fname = "consumer.cnf"; - static const char* fprefix = CONSUMER_CONFIG_PREFIX; + + if((c_inst = calloc(1,sizeof(CONSUMER))) == NULL){ + fprintf(stderr, "Fatal Error: Cannot allocate enough memory.\n"); + return 1; + } if(signal(SIGINT,sighndl) == SIG_IGN){ signal(SIGINT,SIG_IGN); @@ -374,16 +369,7 @@ int main(int argc, char** argv) strcat(cnfname,"/"); } - }else if(CONFIG){ - - /**Config file location was set at install*/ - strcat(cnfname,fprefix); - if(cnfname[strlen(cnfname) - 1] != '/'){ - strcat(cnfname,"/"); - } - - } - + } strcat(cnfname,fname); @@ -392,17 +378,14 @@ int main(int argc, char** argv) all_ok = 1; out_fd = NULL; - if((c_inst = calloc(1,sizeof(CONSUMER))) == NULL){ - fprintf(stderr, "Fatal Error: Cannot allocate enough memory.\n"); - return 1; - } + /**Parse the INI file*/ if(ini_parse(cnfname,handler,NULL) < 0){ /**Try to parse a config in the same directory*/ if(ini_parse(fname,handler,NULL) < 0){ - fprintf(out_fd, "Fatal Error: Error parsing configuration file!\n"); + fprintf(stderr, "Fatal Error: Error parsing configuration file!\n"); goto fatal_error; } @@ -514,8 +497,11 @@ int main(int argc, char** argv) amqp_connection_close(conn, AMQP_REPLY_SUCCESS); amqp_destroy_connection(conn); fatal_error: - - fclose(out_fd); + + if(out_fd){ + fclose(out_fd); + } + if(c_inst){ From 1928710cd893fac0eddc854c97837cc96b15636c Mon Sep 17 00:00:00 2001 From: Timofey Turenko Date: Wed, 3 Sep 2014 23:33:33 +0300 Subject: [PATCH 4/7] add MariaDB-shared to buildrequires --- rabbitmq_consumer/rabbitmq-message-consumer.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rabbitmq_consumer/rabbitmq-message-consumer.spec b/rabbitmq_consumer/rabbitmq-message-consumer.spec index cd513d502..50224b416 100644 --- a/rabbitmq_consumer/rabbitmq-message-consumer.spec +++ b/rabbitmq_consumer/rabbitmq-message-consumer.spec @@ -16,9 +16,9 @@ Group: Development/Tools #Requires: %if 0%{?suse_version} -BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc_s1 perl make libtool libopenssl-devel libaio libaio-devel mariadb libedit-devel librabbitmq-devel +BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc_s1 perl make libtool libopenssl-devel libaio libaio-devel mariadb libedit-devel librabbitmq-devel MariaDB-shared %else -BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc perl make libtool openssl-devel libaio libaio-devel librabbitmq-devel +BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc perl make libtool openssl-devel libaio libaio-devel librabbitmq-devel MariaDB-shared %if 0%{?rhel} == 6 BuildRequires: libedit-devel %endif From 46ad53695eaaffe6df258536ea6d3e272f363d5f Mon Sep 17 00:00:00 2001 From: Timofey Turenko Date: Thu, 4 Sep 2014 12:08:36 +0300 Subject: [PATCH 5/7] fix binary location and add .cnf --- rabbitmq_consumer/rabbitmq-message-consumer.spec | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rabbitmq_consumer/rabbitmq-message-consumer.spec b/rabbitmq_consumer/rabbitmq-message-consumer.spec index 50224b416..bc43a7715 100644 --- a/rabbitmq_consumer/rabbitmq-message-consumer.spec +++ b/rabbitmq_consumer/rabbitmq-message-consumer.spec @@ -2,7 +2,7 @@ %define name rabbitmq-message-consumer %define release beta %define version 1.0 -%define install_path /usr/local/skysql/maxscale/ +%define install_path /usr/local/skysql/maxscale/extra/consumer/ BuildRoot: %{buildroot} Summary: rabbitmq-message-consumer @@ -13,7 +13,7 @@ Release: %{release} Source: %{name}-%{version}-%{release}.tar.gz Prefix: / Group: Development/Tools -#Requires: +Requires: maxscale %if 0%{?suse_version} BuildRequires: gcc gcc-c++ ncurses-devel bison glibc-devel cmake libgcc_s1 perl make libtool libopenssl-devel libaio libaio-devel mariadb libedit-devel librabbitmq-devel MariaDB-shared @@ -43,11 +43,13 @@ make %install mkdir -p $RPM_BUILD_ROOT%{install_path} cp consumer $RPM_BUILD_ROOT%{install_path} +cp consumer.cnf $RPM_BUILD_ROOT%{install_path} %clean %files %defattr(-,root,root) %{install_path}/consumer +%{install_path}/consumer.cnf %changelog From 4e11ea9b0690a4bfb72b2e19f08abe5e9e2f1aec Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 4 Sep 2014 13:31:20 +0300 Subject: [PATCH 6/7] Merged some of the rabbitmq branch changes query_classifier.cc: updated skygw_get_table_names to allow for partial or full table names readwritesplit.c: transferred temporary table detection to separate functions --- query_classifier/query_classifier.cc | 190 ++++---- query_classifier/query_classifier.h | 5 +- .../routing/readwritesplit/readwritesplit.c | 424 +++++++++++------- 3 files changed, 355 insertions(+), 264 deletions(-) diff --git a/query_classifier/query_classifier.cc b/query_classifier/query_classifier.cc index 1d9e9f3e1..372af2c9c 100644 --- a/query_classifier/query_classifier.cc +++ b/query_classifier/query_classifier.cc @@ -890,26 +890,61 @@ char* skygw_query_classifier_get_stmtname( } +/** + *Returns the LEX struct of the parsed GWBUF + *@param The parsed GWBUF + *@return Pointer to the LEX struct or NULL if an error occurred or the query was not parsed + */ +LEX* get_lex(GWBUF* querybuf) +{ + + parsing_info_t* pi; + MYSQL* mysql; + THD* thd; + + if (!GWBUF_IS_PARSED(querybuf)) + { + return NULL; + } + pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf, + GWBUF_PARSING_INFO); + + if (pi == NULL) + { + return NULL; + } + + if ((mysql = (MYSQL *)pi->pi_handle) == NULL || + (thd = (THD *)mysql->thd) == NULL) + { + ss_dassert(mysql != NULL && + thd != NULL); + return NULL; + } + + return thd->lex; +} + + + /** * Finds the head of the list of tables affected by the current select statement. * @param thd Pointer to a valid THD * @return Pointer to the head of the TABLE_LIST chain or NULL in case of an error */ -void* skygw_get_affected_tables(void* thdp) +void* skygw_get_affected_tables(void* lexptr) { - THD* thd = (THD*)thdp; + LEX* lex = (LEX*)lexptr; - if(thd == NULL || - thd->lex == NULL || - thd->lex->current_select == NULL) + if(lex == NULL || + lex->current_select == NULL) { - ss_dassert(thd != NULL && - thd->lex != NULL && - thd->lex->current_select != NULL); + ss_dassert(lex != NULL && + lex->current_select != NULL); return NULL; } - return (void*)thd->lex->current_select->table_list.first; + return (void*)lex->current_select->table_list.first; } @@ -922,45 +957,25 @@ void* skygw_get_affected_tables(void* thdp) * @param tblsize Pointer where the number of tables is written * @return Array of null-terminated strings with the table names */ -char** skygw_get_table_names(GWBUF* querybuf,int* tblsize) +char** skygw_get_table_names(GWBUF* querybuf,int* tblsize, bool fullnames) { - parsing_info_t* pi; - MYSQL* mysql; - THD* thd; - TABLE_LIST* tbl; - int i = 0, currtblsz = 0; - char**tables,**tmp; - - if (!GWBUF_IS_PARSED(querybuf)) + LEX* lex; + TABLE_LIST* tbl; + int i = 0, + currtblsz = 0; + char **tables, + **tmp; + + if((lex = get_lex(querybuf)) == NULL) { - tables = NULL; goto retblock; - } - pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf, - GWBUF_PARSING_INFO); + } - if (pi == NULL) - { - tables = NULL; - goto retblock; - } - - if (pi->pi_query_plain_str == NULL || - (mysql = (MYSQL *)pi->pi_handle) == NULL || - (thd = (THD *)mysql->thd) == NULL) - { - ss_dassert(pi->pi_query_plain_str != NULL && - mysql != NULL && - thd != NULL); - tables = NULL; - goto retblock; - } + lex->current_select = lex->all_selects_list; - thd->lex->current_select = thd->lex->all_selects_list; - - while(thd->lex->current_select){ + while(lex->current_select){ - tbl = (TABLE_LIST*)skygw_get_affected_tables(thd); + tbl = (TABLE_LIST*)skygw_get_affected_tables(lex); while (tbl) { @@ -980,53 +995,59 @@ char** skygw_get_table_names(GWBUF* querybuf,int* tblsize) tables = tmp; currtblsz = currtblsz*2 + 1; - } - + } } - tables[i++] = strdup(tbl->alias); + + char *catnm = NULL; + + if(fullnames) + { + if(tbl->db && strcmp(tbl->db,"skygw_virtual") != 0) + { + catnm = (char*)calloc(strlen(tbl->db) + strlen(tbl->table_name) + 2,sizeof(char)); + strcpy(catnm,tbl->db); + strcat(catnm,"."); + strcat(catnm,tbl->table_name); + } + } + + if(catnm) + { + tables[i++] = catnm; + } + else + { + tables[i++] = strdup(tbl->table_name); + } + tbl=tbl->next_local; } - thd->lex->current_select = thd->lex->current_select->next_select_in_list(); + lex->current_select = lex->current_select->next_select_in_list(); } retblock: *tblsize = i; return tables; } + /** - * Extract the name of the created table. + * Extract, allocate memory and copy the name of the created table. * @param querybuf Buffer to use. * @return A pointer to the name if a table was created, otherwise NULL */ char* skygw_get_created_table_name(GWBUF* querybuf) { - parsing_info_t* pi; - MYSQL* mysql; - THD* thd; - if (!GWBUF_IS_PARSED(querybuf)) + LEX* lex; + + if((lex = get_lex(querybuf)) == NULL) { return NULL; } - pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf, - GWBUF_PARSING_INFO); - - if (pi == NULL) - { - return NULL; - } - - if ((mysql = (MYSQL *)pi->pi_handle) == NULL || - (thd = (THD *)mysql->thd) == NULL) - { - ss_dassert(mysql != NULL && - thd != NULL); - return NULL; - } - - if(thd->lex->create_last_non_select_table && - thd->lex->create_last_non_select_table->table_name){ - char* name = strdup(thd->lex->create_last_non_select_table->table_name); + + if(lex->create_last_non_select_table && + lex->create_last_non_select_table->table_name){ + char* name = strdup(lex->create_last_non_select_table->table_name); return name; }else{ return NULL; @@ -1040,31 +1061,10 @@ char* skygw_get_created_table_name(GWBUF* querybuf) */ bool is_drop_table_query(GWBUF* querybuf) { - parsing_info_t* pi; - MYSQL* mysql; - THD* thd; + LEX* lex; - if (!GWBUF_IS_PARSED(querybuf)) - { - return false; - } - pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf, - GWBUF_PARSING_INFO); - - if (pi == NULL) - { - return false; - } - - if ((mysql = (MYSQL *)pi->pi_handle) == NULL || - (thd = (THD *)mysql->thd) == NULL) - { - ss_dassert(mysql != NULL && - thd != NULL); - return false; - } - - return thd->lex->sql_command == SQLCOM_DROP_TABLE; + return (lex = get_lex(querybuf)) != NULL && + lex->sql_command == SQLCOM_DROP_TABLE; } /* @@ -1264,4 +1264,4 @@ static void parsing_info_set_plain_str( CHK_PARSING_INFO(pi); pi->pi_query_plain_str = str; -} \ No newline at end of file +} diff --git a/query_classifier/query_classifier.h b/query_classifier/query_classifier.h index ccf08a6ea..e3976b4ba 100644 --- a/query_classifier/query_classifier.h +++ b/query_classifier/query_classifier.h @@ -76,8 +76,9 @@ skygw_query_type_t query_classifier_get_type(GWBUF* querybuf); char* skygw_query_classifier_get_stmtname(MYSQL* mysql); char* skygw_get_created_table_name(GWBUF* querybuf); bool is_drop_table_query(GWBUF* querybuf); -void* skygw_get_affected_tables(void* thdp); -char** skygw_get_table_names(GWBUF* querybuf,int* tblsize); +bool skygw_is_real_query(GWBUF* querybuf); +void* skygw_get_affected_tables(void* lexptr); +char** skygw_get_table_names(GWBUF* querybuf,int* tblsize,bool fullnames); char* skygw_get_canonical(GWBUF* querybuf); bool parse_query (GWBUF* querybuf); parsing_info_t* parsing_info_init(void (*donefun)(void *)); diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 00bcf0b59..f5c5e820a 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -1215,7 +1215,255 @@ static route_target_t get_route_target ( return target; } +/** + * Check if the query is a DROP TABLE... query and + * if it targets a temporary table, remove it from the hashtable. + * @param instance Router instance + * @param router_session Router client session + * @param querybuf GWBUF containing the query + * @param type The type of the query resolved so far + */ +void check_drop_tmp_table( + ROUTER* instance, + void* router_session, + GWBUF* querybuf, + skygw_query_type_t type) +{ + int tsize = 0, klen = 0,i; + char** tbl; + char *hkey,*dbname; + MYSQL_session* data; + + ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + DCB* master_dcb = NULL; + rses_property_t* rses_prop_tmp; + + rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; + master_dcb = router_cli_ses->rses_master_ref->bref_dcb; + + CHK_DCB(master_dcb); + + data = (MYSQL_session*)master_dcb->session->data; + dbname = (char*)data->db; + + if (is_drop_table_query(querybuf)) + { + tbl = skygw_get_table_names(querybuf,&tsize,false); + + for(i = 0; irses_prop_data.temp_tables) + { + if (hashtable_delete(rses_prop_tmp->rses_prop_data.temp_tables, + (void *)hkey)) + { + LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, + "Temporary table dropped: %s",hkey))); + } + } + free(tbl[i]); + free(hkey); + } + free(tbl); + } +} + +/** + * Check if the query targets a temporary table. + * @param instance Router instance + * @param router_session Router client session + * @param querybuf GWBUF containing the query + * @param type The type of the query resolved so far + * @return The type of the query + */ +skygw_query_type_t is_read_tmp_table( + ROUTER* instance, + void* router_session, + GWBUF* querybuf, + skygw_query_type_t type) +{ + + bool target_tmp_table = false; + int tsize = 0, klen = 0,i; + char** tbl; + char *hkey,*dbname; + MYSQL_session* data; + + ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + DCB* master_dcb = NULL; + skygw_query_type_t qtype = type; + rses_property_t* rses_prop_tmp; + + rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; + master_dcb = router_cli_ses->rses_master_ref->bref_dcb; + + CHK_DCB(master_dcb); + + data = (MYSQL_session*)master_dcb->session->data; + dbname = (char*)data->db; + + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)) + { + tbl = skygw_get_table_names(querybuf,&tsize,false); + + if (tsize > 0) + { + /** Query targets at least one table */ + for(i = 0; irses_prop_data.temp_tables) + { + + if( (target_tmp_table = + (bool)hashtable_fetch(rses_prop_tmp->rses_prop_data.temp_tables,(void *)hkey))) + { + /**Query target is a temporary table*/ + qtype = QUERY_TYPE_READ_TMP_TABLE; + LOGIF(LT, + (skygw_log_write(LOGFILE_TRACE, + "Query targets a temporary table: %s",hkey))); + } + } + + free(hkey); + free(tbl[i]); + } + + free(tbl); + } + } + + return qtype; +} + +/** + * If query is of type QUERY_TYPE_CREATE_TMP_TABLE then find out + * the database and table name, create a hashvalue and + * add it to the router client session's property. If property + * doesn't exist then create it first. + * + * @param instance Router instance + * @param router_session Router client session + * @param querybuf GWBUF containing the query + * @param type The type of the query resolved so far + */ +void check_create_tmp_table( + ROUTER* instance, + void* router_session, + GWBUF* querybuf, + skygw_query_type_t type) +{ + + int klen = 0; + + char *hkey,*dbname; + MYSQL_session* data; + + ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; + DCB* master_dcb = NULL; + rses_property_t* rses_prop_tmp; + HASHTABLE* h; + + rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; + master_dcb = router_cli_ses->rses_master_ref->bref_dcb; + + CHK_DCB(master_dcb); + + data = (MYSQL_session*)master_dcb->session->data; + dbname = (char*)data->db; + + + if (QUERY_IS_TYPE(type, QUERY_TYPE_CREATE_TMP_TABLE)) + { + bool is_temp = true; + char* tblname = NULL; + + tblname = skygw_get_created_table_name(querybuf); + + if (tblname && strlen(tblname) > 0) + { + klen = strlen(dbname) + strlen(tblname) + 2; + hkey = calloc(klen,sizeof(char)); + strcpy(hkey,dbname); + strcat(hkey,"."); + strcat(hkey,tblname); + } + else + { + hkey = NULL; + } + + if(rses_prop_tmp == NULL) + { + if((rses_prop_tmp = + (rses_property_t*)calloc(1,sizeof(rses_property_t)))) + { +#if defined(SS_DEBUG) + rses_prop_tmp->rses_prop_chk_top = CHK_NUM_ROUTER_PROPERTY; + rses_prop_tmp->rses_prop_chk_tail = CHK_NUM_ROUTER_PROPERTY; +#endif + rses_prop_tmp->rses_prop_rsession = router_cli_ses; + rses_prop_tmp->rses_prop_refcount = 1; + rses_prop_tmp->rses_prop_next = NULL; + rses_prop_tmp->rses_prop_type = RSES_PROP_TYPE_TMPTABLES; + router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES] = rses_prop_tmp; + } + } + + if (rses_prop_tmp->rses_prop_data.temp_tables == NULL) + { + h = hashtable_alloc(7, hashkeyfun, hashcmpfun); + hashtable_memory_fns(h,hstrdup,NULL,hfree,NULL); + if (h != NULL) + { + rses_prop_tmp->rses_prop_data.temp_tables = h; + } + } + + if (hkey && + hashtable_add(rses_prop_tmp->rses_prop_data.temp_tables, + (void *)hkey, + (void *)is_temp) == 0) /*< Conflict in hash table */ + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Temporary table conflict in hashtable: %s", + hkey))); + } +#if defined(SS_DEBUG) + { + bool retkey = + hashtable_fetch( + rses_prop_tmp->rses_prop_data.temp_tables, + hkey); + if (retkey) + { + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Temporary table added: %s", + hkey))); + } + } +#endif + free(hkey); + free(tblname); + } +} /** * The main routing entry, this is called with every packet that is @@ -1246,28 +1494,15 @@ static int routeQuery( GWBUF* querybuf) { skygw_query_type_t qtype = QUERY_TYPE_UNKNOWN; - GWBUF* plainsqlbuf = NULL; char* querystr = NULL; - char* startpos; mysql_server_cmd_t packet_type; uint8_t* packet; int ret = 0; - int tsize = 0; - int klen = 0; - int i = 0; DCB* master_dcb = NULL; DCB* target_dcb = NULL; ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; ROUTER_CLIENT_SES* router_cli_ses = (ROUTER_CLIENT_SES *)router_session; - rses_property_t* rses_prop_tmp; bool rses_is_closed = false; - bool target_tmp_table = false; - char* dbname; - char* hkey; - char** tbl; - HASHTABLE* h; - MYSQL_session* data; - size_t len; route_target_t route_target; bool succp = false; int rlag_max = MAX_RLAG_UNDEFINED; @@ -1285,7 +1520,7 @@ static int routeQuery( packet = GWBUF_DATA(querybuf); packet_type = packet[4]; - rses_prop_tmp = router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES]; + if (rses_is_closed) { @@ -1317,8 +1552,6 @@ static int routeQuery( master_dcb = router_cli_ses->rses_master_ref->bref_dcb; CHK_DCB(master_dcb); - data = (MYSQL_session*)master_dcb->session->data; - dbname = data->db; switch(packet_type) { case MYSQL_COM_QUIT: /*< 1 QUIT will close all sessions */ @@ -1364,48 +1597,15 @@ static int routeQuery( break; } /**< switch by packet type */ + /** - * Check if the query targets a temporary table + * Check if the query has anything to do with temporary tables. */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ)) - { - tbl = skygw_get_table_names(querybuf,&tsize); - if (tsize > 0) - { - /** Query targets at least one table */ - for(i = 0; irses_prop_data.temp_tables) - { - - if( (target_tmp_table = - (bool)hashtable_fetch(rses_prop_tmp->rses_prop_data.temp_tables,(void *)hkey))) - { - /**Query target is a temporary table*/ - qtype = QUERY_TYPE_READ_TMP_TABLE; - LOGIF(LT, - (skygw_log_write(LOGFILE_TRACE, - "Query targets a temporary table: %s",hkey))); - } - } - free(hkey); - } - - for (i = 0; irses_transaction_active = false; } - /** - * If query is of type QUERY_TYPE_CREATE_TMP_TABLE then find out - * the database and table name, create a hashvalue and - * add it to the router client session's property. If property - * doesn't exist then create it first. If the query is DROP TABLE... - * then see if it targets a temporary table and remove it from the hashtable - * if it does. - */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_CREATE_TMP_TABLE)) - { - bool is_temp = true; - char* tblname = NULL; - - tblname = skygw_get_created_table_name(querybuf); - - if (tblname && strlen(tblname) > 0) - { - klen = strlen(dbname) + strlen(tblname) + 2; - hkey = calloc(klen,sizeof(char)); - strcpy(hkey,dbname); - strcat(hkey,"."); - strcat(hkey,tblname); - } - else - { - hkey = NULL; - } - - if(rses_prop_tmp == NULL) - { - if((rses_prop_tmp = - (rses_property_t*)calloc(1,sizeof(rses_property_t)))) - { - #if defined(SS_DEBUG) - rses_prop_tmp->rses_prop_chk_top = CHK_NUM_ROUTER_PROPERTY; - rses_prop_tmp->rses_prop_chk_tail = CHK_NUM_ROUTER_PROPERTY; - #endif - rses_prop_tmp->rses_prop_rsession = router_cli_ses; - rses_prop_tmp->rses_prop_refcount = 1; - rses_prop_tmp->rses_prop_next = NULL; - rses_prop_tmp->rses_prop_type = RSES_PROP_TYPE_TMPTABLES; - router_cli_ses->rses_properties[RSES_PROP_TYPE_TMPTABLES] = rses_prop_tmp; - } - } - - if (rses_prop_tmp->rses_prop_data.temp_tables == NULL) - { - h = hashtable_alloc(7, hashkeyfun, hashcmpfun); - hashtable_memory_fns(h,hstrdup,NULL,hfree,NULL); - if (h != NULL) - { - rses_prop_tmp->rses_prop_data.temp_tables = h; - } - } - - if (hkey && - hashtable_add(rses_prop_tmp->rses_prop_data.temp_tables, - (void *)hkey, - (void *)is_temp) == 0) /*< Conflict in hash table */ - { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Temporary table conflict in hashtable: %s", - hkey))); - } -#if defined(SS_DEBUG) - { - bool retkey = - hashtable_fetch( - rses_prop_tmp->rses_prop_data.temp_tables, - hkey); - if (retkey) - { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Temporary table added: %s", - hkey))); - } - } -#endif - free(hkey); - } - - /** Check if DROP TABLE... targets a temporary table */ - if (is_drop_table_query(querybuf)) - { - tbl = skygw_get_table_names(querybuf,&tsize); - - for(i = 0; irses_prop_data.temp_tables) - { - if (hashtable_delete(rses_prop_tmp->rses_prop_data.temp_tables, - (void *)hkey)) - { - LOGIF(LT, (skygw_log_write(LOGFILE_TRACE, - "Temporary table dropped: %s",hkey))); - } - } - free(tbl[i]); - free(hkey); - } - free(tbl); - } + /** * Find out where to route the query. Result may not be clear; it is * possible to have a hint for routing to a named server which can @@ -3774,8 +3864,8 @@ static void print_error_packet( } } ss_dassert(srv != NULL); - - bufstr = strndup(&ptr[7], len-3); + char* str = (char*)&ptr[7]; + bufstr = strndup(str, len-3); LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, From 9c7781e062e6fc68d847bd9f361f2405f9c3aaf9 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Thu, 4 Sep 2014 15:01:05 +0300 Subject: [PATCH 7/7] Merging rabbitmq into Z3 --- server/modules/filter/Makefile | 35 +- server/modules/filter/mqfilter.c | 1457 ++++++++++++++++++++++++++++++ 2 files changed, 1477 insertions(+), 15 deletions(-) create mode 100644 server/modules/filter/mqfilter.c diff --git a/server/modules/filter/Makefile b/server/modules/filter/Makefile index 31b41ae93..02336580c 100644 --- a/server/modules/filter/Makefile +++ b/server/modules/filter/Makefile @@ -21,15 +21,16 @@ include ../../../build_gateway.inc LOGPATH := $(ROOT_PATH)/log_manager +QCLASSPATH := $(ROOT_PATH)/query_classifier UTILSPATH := $(ROOT_PATH)/utils CC=cc -CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \ - -I$(UTILSPATH) -Wall -g +CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) -I$(QCLASSPATH) \ + -I$(UTILSPATH) -I$(MYSQL_ROOT) -Wall -g include ../../../makefile.inc -LDFLAGS=-shared -L$(LOGPATH) -Wl,-rpath,$(DEST)/lib \ +LDFLAGS=-shared -L$(LOGPATH) -L$(QCLASSPATH) -Wl,-rpath,$(DEST)/lib \ -Wl,-rpath,$(LOGPATH) -Wl,-rpath,$(UTILSPATH) TESTSRCS=testfilter.c @@ -42,10 +43,12 @@ TOPNSRCS=topfilter.c TOPNOBJ=$(TOPNSRCS:.c=.o) TEESRCS=tee.c TEEOBJ=$(TEESRCS:.c=.o) -SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) $(TOPNSRCS) $(TEESRCS) +MQSRCS=mqfilter.c +MQOBJ=$(MQSRCS:.c=.o) +SRCS=$(TESTSRCS) $(QLASRCS) $(REGEXSRCS) $(TOPNSRCS) $(TEESRCS) $(MQSRCS) OBJ=$(SRCS:.c=.o) -LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so libhintfilter.so +LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -lrabbitmq -lquery_classifier +MODULES= libtestfilter.so libqlafilter.so libregexfilter.so libtopfilter.so libtee.so libmqfilter.so all: $(MODULES) @@ -53,6 +56,9 @@ all: $(MODULES) libtestfilter.so: $(TESTOBJ) $(CC) $(LDFLAGS) $(TESTOBJ) $(LIBS) -o $@ +libmqfilter.so: $(MQOBJ) + $(CC) $(LDFLAGS) $(MQOBJ) $(LIBS) -o $@ + libqlafilter.so: $(QLAOBJ) $(CC) $(LDFLAGS) $(QLAOBJ) $(LIBS) -o $@ @@ -66,8 +72,7 @@ libtee.so: $(TEEOBJ) $(CC) $(LDFLAGS) $(TEEOBJ) $(LIBS) -o $@ libhintfilter.so: - (cd hint; touch depend.mk ; make; cp $@ ..) - +# (cd hint; touch depend.mk ; make; cp $@ ..) .c.o: $(CC) $(CFLAGS) $< -o $@ @@ -78,26 +83,26 @@ clean: tags: ctags $(SRCS) $(HDRS) - (cd hint; touch depend.mk; make tags) +# (cd hint; touch depend.mk; make tags) depend: - @$(DEL) depend.mk + @rm -f depend.mk cc -M $(CFLAGS) $(SRCS) > depend.mk - (cd hint; touch depend.mk; make depend) +# (cd hint; touch depend.mk; make depend) install: $(MODULES) install -D $(MODULES) $(DEST)/modules cleantests: $(MAKE) -C test cleantests - + buildtests: $(MAKE) -C test DEBUG=Y buildtests - + runtests: $(MAKE) -C test runtests - + testall: $(MAKE) -C test testall - + include depend.mk diff --git a/server/modules/filter/mqfilter.c b/server/modules/filter/mqfilter.c new file mode 100644 index 000000000..5404696b3 --- /dev/null +++ b/server/modules/filter/mqfilter.c @@ -0,0 +1,1457 @@ +/* + * This file is distributed as part of MaxScale by SkySQL. It 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. + * + * 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-1301 USA. + * + * Copyright SkySQL Ab 2014 + */ + +/** + * @file mqfilter.c + * MQ Filter - AMQP Filter. + * A filter that logs and publishes canonized queries on to a RabbitMQ server. + * + * The filter reads the routed query, forms a canonized version of it and publishes the + * message on the RabbitMQ server. The messages are timestamped with a pure unix-timestamp that + * is meant to be easily transformable in various environments. Replies to the queries are also logged + * and published on the RabbitMQ server. + * + * The filter makes no attempt to deal with queries that do not fit + * in a single GWBUF or result sets that span multiple GWBUFs. + * + * To use a SSL connection the CA certificate, the client certificate and the client public key must be provided. + * By default this filter uses a TCP connection. + *@verbatim + * The options for this filter are: + * + * logging_trigger Set the logging level + * logging_strict Sets whether to trigger when any of the parameters match or only if all parameters match + * logging_log_all Log only SELECT, UPDATE, DELETE and INSERT or all posddible queries + * hostname The server hostname where the messages are sent + * port Port to send the messages to + * username Server login username + * password Server login password + * vhost The virtual host location on the server, where the messages are sent + * exchange The name of the exchange + * exchange_type The type of the exchange, defaults to direct + * key The routing key used when sending messages to the exchange + * queue The queue that will be bound to the used exchange + * ssl_CA_cert Path to the CA certificate in PEM format + * ssl_client_cert Path to the client cerificate in PEM format + * ssl_client_key Path to the client public key in PEM format + * + * The logging trigger levels are: + * all Log everything + * source Trigger on statements originating from a particular source (database user and host combination) + * schema Trigger on a certain schema + * object Trigger on a particular database object (table or view) + *@endverbatim + * See the individual struct documentations for logging trigger parameters + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +MODULE_INFO info = { + MODULE_API_FILTER, + MODULE_ALPHA_RELEASE, + FILTER_VERSION, + "A RabbitMQ query logging filter" +}; + +static char *version_str = "V1.0.2"; +static int uid_gen; + +/* + * The filter entry points + */ +static FILTER *createInstance(char **options, FILTER_PARAMETER **); +static void *newSession(FILTER *instance, SESSION *session); +static void closeSession(FILTER *instance, void *session); +static void freeSession(FILTER *instance, void *session); +static void setDownstream(FILTER *instance, void *fsession, DOWNSTREAM *downstream); +static void setUpstream(FILTER *instance, void *fsession, UPSTREAM *upstream); +static int routeQuery(FILTER *instance, void *fsession, GWBUF *queue); +static int clientReply(FILTER *instance, void *fsession, GWBUF *queue); +static void diagnostic(FILTER *instance, void *fsession, DCB *dcb); + + +static FILTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + setDownstream, + setUpstream, + routeQuery, + clientReply, + diagnostic, +}; + +/** + *Structure used to store messages and their properties. + */ +typedef struct mqmessage_t { + amqp_basic_properties_t *prop; + char *msg; + struct mqmessage_t *next; +}mqmessage; + +/** + *Logging trigger levels + */ +enum log_trigger_t{ + TRG_ALL = 0x00, + TRG_SOURCE = 0x01, + TRG_SCHEMA = 0x02, + TRG_OBJECT = 0x04 +}; + +/** + * Source logging trigger + * + * Log only those queries that come from a valid pair of username and hostname combinations. + * Both options allow multiple values separated by a ','. + * @verbatim + * Trigger options: + * logging_source_user Comma-separated list of usernames to log + * logging_source_host Comma-separated list of hostnames to log + * @endverbatim + */ +typedef struct source_trigger_t{ + char** user; + int usize; + char** host; + int hsize; +}SRC_TRIG; + +/** + * Schema logging trigger + * + * Log only those queries that target a specific database. + * + * Trigger options: + * logging_schema Comma-separated list of databases + */ +typedef struct schema_trigger_t{ + char** objects; + int size; +}SHM_TRIG; + + +/** + * Database object logging trigger + * + * Log only those queries that target specific database objects. + *@verbatim + * Trigger options: + * logging_object Comma-separated list of database objects + *@endverbatim + */ +typedef struct object_trigger_t{ + char** objects; + int size; +}OBJ_TRIG; + + +/** + * A instance structure, containing the hostname, login credentials, + * virtual host location and the names of the exchange and the key. + * Also contains the paths to the CA certificate and client certificate and key. + * + * Default values assume that a local RabbitMQ server is running on port 5672 with the default + * user 'guest' and the password 'guest' using a default exchange named 'default_exchange' with a + * routing key named 'key'. Type of the exchange is 'direct' by default and all queries are logged. + * + */ + +typedef struct { + int port; + char *hostname; + char *username; + char *password; + char *vhost; + char *exchange; + char *exchange_type; + char *key; + char *queue; + bool use_ssl; + bool log_all; + bool strict_logging; + char *ssl_CA_cert; + char *ssl_client_cert; + char *ssl_client_key; + amqp_connection_state_t conn; /**The connection object*/ + amqp_socket_t* sock; /**The currently active socket*/ + amqp_channel_t channel; /**The current channel in use*/ + int conn_stat; /**state of the connection to the server*/ + int rconn_intv; /**delay for reconnects, in seconds*/ + time_t last_rconn; /**last reconnect attempt*/ + SPINLOCK* rconn_lock; + mqmessage* messages; + enum log_trigger_t trgtype; + SRC_TRIG* src_trg; + SHM_TRIG* shm_trg; + OBJ_TRIG* obj_trg; +} MQ_INSTANCE; + +/** + * The session structure for this MQ filter. + * This stores the downstream filter information, such that the + * filter is able to pass the query on to the next filter (or router) + * in the chain. + * + * Also holds the necessary session connection information. + * + */ +typedef struct { + char* uid; /**Unique identifier used to tag messages*/ + char* db; /**The currently active database*/ + DOWNSTREAM down; + UPSTREAM up; + SESSION* session; + bool was_query; /**True if the previous routeQuery call had valid content*/ +} MQ_SESSION; + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +FILTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Internal function used to initialize the connection to + * the RabbitMQ server. Also used to reconnect to the server + * in case the connection fails and to redeclare exchanges + * and queues if they are lost + * + */ +static int +init_conn(MQ_INSTANCE *my_instance) +{ + int rval = 0; + int amqp_ok = AMQP_STATUS_OK; + + if(my_instance->use_ssl){ + + if((my_instance->sock = amqp_ssl_socket_new(my_instance->conn)) != NULL){ + + if((amqp_ok = amqp_ssl_socket_set_cacert(my_instance->sock,my_instance->ssl_CA_cert)) != AMQP_STATUS_OK){ + skygw_log_write(LOGFILE_ERROR, + "Error : Failed to set CA certificate: %s", amqp_error_string2(amqp_ok)); + goto cleanup; + } + if((amqp_ok = amqp_ssl_socket_set_key(my_instance->sock, + my_instance->ssl_client_cert, + my_instance->ssl_client_key)) != AMQP_STATUS_OK){ + skygw_log_write(LOGFILE_ERROR, + "Error : Failed to set client certificate and key: %s", amqp_error_string2(amqp_ok)); + goto cleanup; + } + }else{ + + amqp_ok = AMQP_STATUS_SSL_CONNECTION_FAILED; + skygw_log_write(LOGFILE_ERROR, + "Error : SSL socket creation failed."); + goto cleanup; + } + + /**SSL is not used, falling back to TCP*/ + }else if((my_instance->sock = amqp_tcp_socket_new(my_instance->conn)) == NULL){ + skygw_log_write(LOGFILE_ERROR, + "Error : TCP socket creation failed."); + goto cleanup; + + } + + /**Socket creation was successful, trying to open the socket*/ + if((amqp_ok = amqp_socket_open(my_instance->sock,my_instance->hostname,my_instance->port)) != AMQP_STATUS_OK){ + skygw_log_write(LOGFILE_ERROR, + "Error : Failed to open socket: %s", amqp_error_string2(amqp_ok)); + goto cleanup; + } + amqp_rpc_reply_t reply; + reply = amqp_login(my_instance->conn,my_instance->vhost,0,AMQP_DEFAULT_FRAME_SIZE,0,AMQP_SASL_METHOD_PLAIN,my_instance->username,my_instance->password); + if(reply.reply_type != AMQP_RESPONSE_NORMAL){ + skygw_log_write(LOGFILE_ERROR, + "Error : Login to RabbitMQ server failed."); + + goto cleanup; + } + amqp_channel_open(my_instance->conn,my_instance->channel); + reply = amqp_get_rpc_reply(my_instance->conn); + if(reply.reply_type != AMQP_RESPONSE_NORMAL){ + skygw_log_write(LOGFILE_ERROR, + "Error : Channel creation failed."); + goto cleanup; + } + + amqp_exchange_declare(my_instance->conn,my_instance->channel, + amqp_cstring_bytes(my_instance->exchange), + amqp_cstring_bytes(my_instance->exchange_type), + 0, 1, + amqp_empty_table); + + reply = amqp_get_rpc_reply(my_instance->conn); + + if(reply.reply_type != AMQP_RESPONSE_NORMAL){ + skygw_log_write(LOGFILE_ERROR, + "Error : Exchange declaration failed,trying to redeclare the exchange."); + if(reply.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION){ + if(reply.reply.id == AMQP_CHANNEL_CLOSE_METHOD){ + amqp_send_method(my_instance->conn,my_instance->channel,AMQP_CHANNEL_CLOSE_OK_METHOD,NULL); + }else if(reply.reply.id == AMQP_CONNECTION_CLOSE_METHOD){ + amqp_send_method(my_instance->conn,my_instance->channel,AMQP_CONNECTION_CLOSE_OK_METHOD,NULL); + } + + my_instance->channel++; + amqp_channel_open(my_instance->conn,my_instance->channel); + + amqp_exchange_delete(my_instance->conn,my_instance->channel,amqp_cstring_bytes(my_instance->exchange),0); + amqp_exchange_declare(my_instance->conn,my_instance->channel, + amqp_cstring_bytes(my_instance->exchange), + amqp_cstring_bytes(my_instance->exchange_type), + 0, 1, + amqp_empty_table); + reply = amqp_get_rpc_reply(my_instance->conn); + } + if(reply.reply_type != AMQP_RESPONSE_NORMAL){ + skygw_log_write(LOGFILE_ERROR, + "Error : Exchange redeclaration failed."); + goto cleanup; + } + } + + if(my_instance->queue){ + + + + amqp_queue_declare(my_instance->conn,my_instance->channel, + amqp_cstring_bytes(my_instance->queue), + 0, 1, 0, 0, + amqp_empty_table); + reply = amqp_get_rpc_reply(my_instance->conn); + if(reply.reply_type != AMQP_RESPONSE_NORMAL){ + skygw_log_write(LOGFILE_ERROR, + "Error : Queue declaration failed."); + goto cleanup; + } + + + amqp_queue_bind(my_instance->conn,my_instance->channel, + amqp_cstring_bytes(my_instance->queue), + amqp_cstring_bytes(my_instance->exchange), + amqp_cstring_bytes(my_instance->key), + amqp_empty_table); + reply = amqp_get_rpc_reply(my_instance->conn); + if(reply.reply_type != AMQP_RESPONSE_NORMAL){ + skygw_log_write(LOGFILE_ERROR, + "Error : Failed to bind queue to exchange."); + goto cleanup; + } + } + rval = 1; + + cleanup: + + return rval; + +} + +/** + * Parse the provided string into an array of strings. + * The caller is responsible for freeing all the allocated memory. + * If an error occurred no memory is allocated and the size of the array is set to zero. + * @param str String to parse + * @param tok Token string containing delimiting characters + * @param szstore Address where to store the size of the array after parsing + * @return The array containing the parsed string + */ +char** parse_optstr(char* str, char* tok, int* szstore) +{ + char* tk = str; + char** arr; + int i = 0, size = 1; + while((tk = strpbrk(tk + 1,tok))){ + size++; + } + + arr = malloc(sizeof(char*)*size); + + if(arr == NULL){ + skygw_log_write(LOGFILE_ERROR, + "Error : Cannot allocate enough memory."); + *szstore = 0; + return NULL; + } + + *szstore = size; + tk = strtok(str,tok); + while(tk && i < size){ + arr[i++] = strdup(tk); + tk = strtok(NULL,tok); + } + return arr; +} + +/** + * Create an instance of the filter for a particular service + * within MaxScale. + * + * @param options The options for this filter + * + * @return The instance data for this new instance + */ +static FILTER * +createInstance(char **options, FILTER_PARAMETER **params) +{ + MQ_INSTANCE *my_instance; + int paramcount = 0, parammax = 64, i = 0, x = 0, arrsize = 0; + FILTER_PARAMETER** paramlist; + char** arr; + + if ((my_instance = calloc(1, sizeof(MQ_INSTANCE)))&& + (my_instance->rconn_lock = malloc(sizeof(SPINLOCK)))) + { + spinlock_init(my_instance->rconn_lock); + uid_gen = 0; + paramlist = malloc(sizeof(FILTER_PARAMETER*)*64); + + if((my_instance->conn = amqp_new_connection()) == NULL){ + + + return NULL; + } + my_instance->channel = 1; + my_instance->last_rconn = time(NULL); + my_instance->conn_stat = AMQP_STATUS_OK; + my_instance->rconn_intv = 1; + my_instance->port = 5672; + my_instance->trgtype = TRG_ALL; + my_instance->log_all = false; + my_instance->strict_logging = true; + + for(i = 0;params[i];i++){ + if(!strcmp(params[i]->name,"hostname")){ + my_instance->hostname = strdup(params[i]->value); + }else if(!strcmp(params[i]->name,"username")){ + my_instance->username = strdup(params[i]->value); + }else if(!strcmp(params[i]->name,"password")){ + my_instance->password = strdup(params[i]->value); + }else if(!strcmp(params[i]->name,"vhost")){ + my_instance->vhost = strdup(params[i]->value); + }else if(!strcmp(params[i]->name,"port")){ + my_instance->port = atoi(params[i]->value); + }else if(!strcmp(params[i]->name,"exchange")){ + my_instance->exchange = strdup(params[i]->value); + }else if(!strcmp(params[i]->name,"key")){ + my_instance->key = strdup(params[i]->value); + }else if(!strcmp(params[i]->name,"queue")){ + my_instance->queue = strdup(params[i]->value); + } + else if(!strcmp(params[i]->name,"ssl_client_certificate")){ + + my_instance->ssl_client_cert = strdup(params[i]->value); + + }else if(!strcmp(params[i]->name,"ssl_client_key")){ + + my_instance->ssl_client_key = strdup(params[i]->value); + }else if(!strcmp(params[i]->name,"ssl_CA_cert")){ + + my_instance->ssl_CA_cert = strdup(params[i]->value); + + }else if(!strcmp(params[i]->name,"exchange_type")){ + + my_instance->exchange_type = strdup(params[i]->value); + + }else if(!strcmp(params[i]->name,"logging_trigger")){ + + arr = parse_optstr(params[i]->value,",",&arrsize); + + for(x = 0;xtrgtype |= TRG_SOURCE; + }else if(!strcmp(arr[x],"schema")){ + my_instance->trgtype |= TRG_SCHEMA; + }else if(!strcmp(arr[x],"object")){ + my_instance->trgtype |= TRG_OBJECT; + }else if(!strcmp(arr[x],"all")){ + my_instance->trgtype = TRG_ALL; + }else{ + skygw_log_write(LOGFILE_ERROR,"Error: Unknown option for 'logging_trigger':%s.",arr[x]); + } + } + + if(arrsize > 0){ + free(arr); + } + arrsize = 0; + + + + }else if(strstr(params[i]->name,"logging_")){ + + if(paramcount < parammax){ + paramlist[paramcount] = malloc(sizeof(FILTER_PARAMETER)); + paramlist[paramcount]->name = strdup(params[i]->name); + paramlist[paramcount]->value = strdup(params[i]->value); + paramcount++; + } + + } + + } + + if(my_instance->trgtype & TRG_SOURCE){ + + my_instance->src_trg = (SRC_TRIG*)malloc(sizeof(SRC_TRIG)); + my_instance->src_trg->user = NULL; + my_instance->src_trg->host = NULL; + my_instance->src_trg->usize = 0; + my_instance->src_trg->hsize = 0; + + } + + if(my_instance->trgtype & TRG_SCHEMA){ + + my_instance->shm_trg = (SHM_TRIG*)malloc(sizeof(SHM_TRIG)); + my_instance->shm_trg->objects = NULL; + my_instance->shm_trg->size = 0; + + } + + if(my_instance->trgtype & TRG_OBJECT){ + + my_instance->obj_trg = (OBJ_TRIG*)malloc(sizeof(OBJ_TRIG)); + my_instance->obj_trg->objects = NULL; + my_instance->obj_trg->size = 0; + + } + + for(i = 0;iname,"logging_source_user")){ + + if(my_instance->src_trg){ + my_instance->src_trg->user = parse_optstr(paramlist[i]->value,",",&arrsize); + my_instance->src_trg->usize = arrsize; + arrsize = 0; + } + + }else if(!strcmp(paramlist[i]->name,"logging_source_host")){ + + if(my_instance->src_trg){ + my_instance->src_trg->host = parse_optstr(paramlist[i]->value,",",&arrsize); + my_instance->src_trg->hsize = arrsize; + arrsize = 0; + } + + }else if(!strcmp(paramlist[i]->name,"logging_schema")){ + + if(my_instance->shm_trg){ + my_instance->shm_trg->objects = parse_optstr(paramlist[i]->value,",",&arrsize); + my_instance->shm_trg->size = arrsize; + arrsize = 0; + } + + }else if(!strcmp(paramlist[i]->name,"logging_object")){ + + if(my_instance->obj_trg){ + my_instance->obj_trg->objects = parse_optstr(paramlist[i]->value,",",&arrsize); + my_instance->obj_trg->size = arrsize; + arrsize = 0; + } + + }else if(!strcmp(paramlist[i]->name,"logging_log_all")){ + if(!strcmp(paramlist[i]->value,"true")){ + my_instance->log_all = true; + } + }else if(!strcmp(paramlist[i]->name,"logging_strict")){ + if(strcmp(paramlist[i]->value,"false") == 0){ + my_instance->strict_logging = false; + } + } + free(paramlist[i]->name); + free(paramlist[i]->value); + free(paramlist[i]); + } + + free(paramlist); + + if(my_instance->hostname == NULL){ + my_instance->hostname = strdup("localhost"); + } + if(my_instance->username == NULL){ + my_instance->username = strdup("guest"); + } + if(my_instance->password == NULL){ + my_instance->password = strdup("guest"); + } + if(my_instance->vhost == NULL){ + my_instance->vhost = strdup("/"); + } + if(my_instance->exchange == NULL){ + my_instance->exchange = strdup("default_exchange"); + } + if(my_instance->key == NULL){ + my_instance->key = strdup("key"); + } + if(my_instance->exchange_type == NULL){ + my_instance->exchange_type = strdup("direct"); + } + + if(my_instance->ssl_client_cert != NULL && + my_instance->ssl_client_key != NULL && + my_instance->ssl_CA_cert != NULL){ + my_instance->use_ssl = true; + }else{ + my_instance->use_ssl = false; + } + + if(my_instance->use_ssl){ + amqp_set_initialize_ssl_library(0);/**Assume the underlying SSL library is already initialized*/ + } + + /**Connect to the server*/ + init_conn(my_instance); + + + + } + return (FILTER *)my_instance; +} + + + +/** + * Declares a persistent, non-exclusive and non-passive queue that + * auto-deletes after all the messages have been consumed. + * @param my_session MQ_SESSION instance used to declare the queue + * @param qname Name of the queue to be declared + * @return Returns 0 if an error occurred, 1 if successful + */ +int declareQueue(MQ_INSTANCE *my_instance, MQ_SESSION* my_session, char* qname) +{ + int success = 1; + amqp_rpc_reply_t reply; + + spinlock_acquire(my_instance->rconn_lock); + + amqp_queue_declare(my_instance->conn,my_instance->channel, + amqp_cstring_bytes(qname), + 0, 1, 0, 1, + amqp_empty_table); + reply = amqp_get_rpc_reply(my_instance->conn); + if(reply.reply_type != AMQP_RESPONSE_NORMAL){ + success = 0; + skygw_log_write(LOGFILE_ERROR, + "Error : Queue declaration failed."); + + } + + + amqp_queue_bind(my_instance->conn,my_instance->channel, + amqp_cstring_bytes(qname), + amqp_cstring_bytes(my_instance->exchange), + amqp_cstring_bytes(my_session->uid), + amqp_empty_table); + reply = amqp_get_rpc_reply(my_instance->conn); + if(reply.reply_type != AMQP_RESPONSE_NORMAL){ + success = 0; + skygw_log_write(LOGFILE_ERROR, + "Error : Failed to bind queue to exchange."); + + } + spinlock_release(my_instance->rconn_lock); + return success; +} + +/** + * Broadcasts a message on the message stack to the RabbitMQ server + * and frees the allocated memory if successful. + * @return AMQP_STATUS_OK if the broadcasting was successful + */ +int sendMessage(MQ_INSTANCE *instance) +{ + int err_code; + mqmessage *tmp; + + if(instance->conn_stat != AMQP_STATUS_OK){ + + if(difftime(time(NULL),instance->last_rconn) > instance->rconn_intv){ + + instance->last_rconn = time(NULL); + + if(init_conn(instance)){ + instance->rconn_intv = 1.0; + instance->conn_stat = AMQP_STATUS_OK; + + }else{ + instance->rconn_intv += 5.0; + skygw_log_write(LOGFILE_ERROR, + "Error : Failed to reconnect to the MQRabbit server "); + } + } + } + + if(instance->messages){ + instance->conn_stat = amqp_basic_publish(instance->conn,instance->channel, + amqp_cstring_bytes(instance->exchange), + amqp_cstring_bytes(instance->key), + 0,0,instance->messages->prop,amqp_cstring_bytes(instance->messages->msg)); + + + /**Message was sent successfully*/ + if(instance->conn_stat == AMQP_STATUS_OK){ + tmp = instance->messages; + instance->messages = instance->messages->next; + free(tmp->prop); + free(tmp->msg); + free(tmp); + } + + } + + err_code = instance->conn_stat; + + return err_code; +} + + +/** + * Push a new message on the stack to be broadcasted later. + * The message assumes ownership of the memory allocated to the message content and properties. + * @param prop Message properties + * @param msg Message content + */ +void pushMessage(MQ_INSTANCE *instance, amqp_basic_properties_t* prop, char* msg) +{ + spinlock_acquire(instance->rconn_lock); + + mqmessage* newmsg = malloc(sizeof(mqmessage)); + if(newmsg){ + newmsg->msg = msg; + newmsg->prop = prop; + newmsg->next = NULL; + + if(instance->messages){ + newmsg->next = instance->messages; + } + + instance->messages = newmsg; + + }else{ + skygw_log_write(LOGFILE_ERROR, + "Error : Cannot allocate enough memory."); + free(prop); + free(msg); + } + + while(instance->messages){ + if(sendMessage(instance) != AMQP_STATUS_OK){ + break; + } + } + + spinlock_release(instance->rconn_lock); +} + + + +/** + * Associate a new session with this instance of the filter and opens + * a connection to the server and prepares the exchange and the queue for use. + * + * + * @param instance The filter instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(FILTER *instance, SESSION *session) +{ + MQ_SESSION *my_session; + MYSQL_session* sessauth; + + if ((my_session = calloc(1, sizeof(MQ_SESSION))) != NULL){ + + my_session->was_query = false; + my_session->uid = NULL; + my_session->session = session; + sessauth = my_session->session->data; + if(sessauth->db && strnlen(sessauth->db,128)>0){ + my_session->db = strdup(sessauth->db); + }else{ + my_session->db = NULL; + } + + } + + return my_session; +} + + + +/** + * Close a session with the filter, this is the mechanism + * by which a filter may cleanup data structure etc. + * In the case of the MQ filter we do nothing. + * + * @param instance The filter instance data + * @param session The session being closed + */ +static void +closeSession(FILTER *instance, void *session) +{ +} + +/** + * Free the memory associated with the session + * + * @param instance The filter instance + * @param session The filter session + */ +static void +freeSession(FILTER *instance, void *session) +{ + MQ_SESSION *my_session = (MQ_SESSION *)session; + free(my_session->uid); + free(my_session->db); + free(my_session); + return; +} + +/** + * Set the downstream filter or router to which queries will be + * passed from this filter. + * + * @param instance The filter instance data + * @param session The filter session + * @param downstream The downstream filter or router. + */ +static void +setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream) +{ + MQ_SESSION *my_session = (MQ_SESSION *)session; + my_session->down = *downstream; +} + +static void setUpstream(FILTER *instance, void *session, UPSTREAM *upstream) +{ + MQ_SESSION *my_session = (MQ_SESSION *)session; + my_session->up = *upstream; +} + + +/** + * Generates a unique key using a number of unique unsigned integers. + * @param array The array that is used + * @param size Size of the array + */ +void genkey(char* array, int size) +{ + int i = 0; + for(i = 0;istart + 4)) == 0x02){ + if(my_session->db){ + free(my_session->db); + } + plen = pktlen(queue->start); + my_session->db = calloc(plen,sizeof(char)); + memcpy(my_session->db,queue->start + 5,plen - 1); + } + + if(modutil_is_SQL(queue)){ + + /**Parse the query*/ + + if (!query_is_parsed(queue)){ + success = parse_query(queue); + } + + if(!success){ + skygw_log_write(LOGFILE_ERROR,"Error: Parsing query failed."); + goto send_downstream; + } + + if(!my_instance->log_all){ + if(!skygw_is_real_query(queue)){ + goto send_downstream; + } + } + + if(my_instance->trgtype & TRG_SOURCE && my_instance->src_trg){ + + if(session_isvalid(my_session->session)){ + + sessusr = session_getUser(my_session->session); + sesshost = session_get_remote(my_session->session); + + /**Username was configured*/ + if(my_instance->src_trg->usize > 0){ + for(i = 0;isrc_trg->usize;i++){ + + if(strcmp(my_instance->src_trg->user[i],sessusr) == 0) + { + skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SOURCE: user: %s = %s",my_instance->src_trg->user[i],sessusr); + src_ok = true; + break; + } + + } + + + } + + /**If username was not matched, try to match hostname*/ + + if(!src_ok && my_instance->src_trg->hsize > 0){ + + for(i = 0;isrc_trg->hsize;i++){ + + if(strcmp(my_instance->src_trg->host[i],sesshost) == 0) + { + skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SOURCE: host: %s = %s",my_instance->src_trg->host[i],sesshost); + src_ok = true; + break; + } + + } + + } + + } + + if(src_ok && !my_instance->strict_logging){ + schema_ok = true; + obj_ok = true; + goto validate_triggers; + } + + }else{ + src_ok = true; + } + + + + if(my_instance->trgtype & TRG_SCHEMA && my_instance->shm_trg){ + int tbsz = 0,z; + char** tblnames = skygw_get_table_names(queue,&tbsz,true); + char* tmp; + bool all_remotes = true; + + for(z = 0;zshm_trg->size; i++){ + + if(strcmp(tmp,my_instance->shm_trg->objects[i]) == 0){ + + skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SCHEMA: %s = %s",tmp,my_instance->shm_trg->objects[i]); + + schema_ok = true; + break; + } + } + }else{ + all_remotes = false; + } + free(tblnames[z]); + } + free(tblnames); + + if(!schema_ok && !all_remotes && my_session->db && strlen(my_session->db)>0){ + + for(i = 0; ishm_trg->size; i++){ + + if(strcmp(my_session->db,my_instance->shm_trg->objects[i]) == 0){ + + skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_SCHEMA: %s = %s",my_session->db,my_instance->shm_trg->objects[i]); + + schema_ok = true; + break; + } + } + } + + if(schema_ok && !my_instance->strict_logging){ + src_ok = true; + obj_ok = true; + goto validate_triggers; + } + + }else{ + schema_ok = true; + } + + + if(my_instance->trgtype & TRG_OBJECT && my_instance->obj_trg){ + + sesstbls = skygw_get_table_names(queue,&dbcount,false); + + for(j = 0; jobj_trg->size; i++){ + + + if(!strcmp(tbnm,my_instance->obj_trg->objects[i])){ + obj_ok = true; + skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_OBJECT: %s = %s",my_instance->obj_trg->objects[i],sesstbls[j]); + break; + } + + } + + } + if(dbcount > 0){ + for(j = 0; jstrict_logging){ + src_ok = true; + schema_ok = true; + goto validate_triggers; + } + + }else{ + obj_ok = true; + } + + if(my_instance->trgtype == TRG_ALL){ + skygw_log_write_flush(LOGFILE_TRACE,"Trigger is TRG_ALL"); + } + + validate_triggers: + + if(src_ok&&schema_ok&&obj_ok){ + + /** + * Something matched the trigger, log the query + */ + + skygw_log_write_flush(LOGFILE_TRACE,"Routing message to: %s:%d %s as %s/%s, exchange: %s<%s> key:%s queue:%s", + my_instance->hostname,my_instance->port, + my_instance->vhost,my_instance->username, + my_instance->password,my_instance->exchange, + my_instance->exchange_type,my_instance->key, + my_instance->queue); + + if(my_session->uid == NULL){ + + my_session->uid = calloc(33,sizeof(char)); + + if(!my_session->uid){ + skygw_log_write(LOGFILE_ERROR,"Error : Out of memory."); + }else{ + genkey(my_session->uid,32); + } + + } + + + if(modutil_extract_SQL(queue, &ptr, &length)){ + + my_session->was_query = true; + + if((prop = malloc(sizeof(amqp_basic_properties_t)))){ + prop->_flags = AMQP_BASIC_CONTENT_TYPE_FLAG | + AMQP_BASIC_DELIVERY_MODE_FLAG | + AMQP_BASIC_MESSAGE_ID_FLAG | + AMQP_BASIC_CORRELATION_ID_FLAG; + prop->content_type = amqp_cstring_bytes("text/plain"); + prop->delivery_mode = AMQP_DELIVERY_PERSISTENT; + prop->correlation_id = amqp_cstring_bytes(my_session->uid); + prop->message_id = amqp_cstring_bytes("query"); + } + + + + if(success){ + + /**Try to convert to a canonical form and use the plain query if unsuccessful*/ + if((canon_q = skygw_get_canonical(queue)) == NULL){ + skygw_log_write_flush(LOGFILE_ERROR, + "Error: Cannot form canonical query."); + } + + } + + memset(t_buf,0,128); + sprintf(t_buf, "%lu|",(unsigned long)time(NULL)); + + int qlen = strnlen(canon_q,length) + strnlen(t_buf,128); + if((combined = malloc((qlen+1)*sizeof(char))) == NULL){ + skygw_log_write_flush(LOGFILE_ERROR, + "Error: Out of memory"); + } + strcpy(combined,t_buf); + strncat(combined,canon_q,length); + + pushMessage(my_instance,prop,combined); + free(canon_q); + } + + } + + /** Pass the query downstream */ + } + send_downstream: + return my_session->down.routeQuery(my_session->down.instance, + my_session->down.session, queue); +} + +/** + * Converts a length-encoded integer to an unsigned integer as defined by the + * MySQL manual. + * @param c Pointer to the first byte of a length-encoded integer + * @return The value converted to a standard unsigned integer + */ +unsigned int leitoi(unsigned char* c) +{ + unsigned char* ptr = c; + unsigned int sz = *ptr; + if(*ptr < 0xfb) return sz; + if(*ptr == 0xfc){ + sz = *++ptr; + sz += (*++ptr << 8); + }else if(*ptr == 0xfd){ + sz = *++ptr; + sz += (*++ptr << 8); + sz += (*++ptr << 8); + }else{ + sz = *++ptr; + sz += (*++ptr << 8); + sz += (*++ptr << 8); + sz += (*++ptr << 8); + sz += (*++ptr << 8); + sz += (*++ptr << 8); + sz += (*++ptr << 8); + sz += (*++ptr << 8); + sz += (*++ptr << 8); + } + return sz; +} + +/** + * Converts a length-encoded integer into a standard unsigned integer + * and advances the pointer to the next unrelated byte. + * + * @param c Pointer to the first byte of a length-encoded integer + */ +unsigned int consume_leitoi(unsigned char** c) +{ + unsigned int rval = leitoi(*c); + if(**c == 0xfc){ + *c += 3; + }else if(**c == 0xfd){ + *c += 4; + }else if(**c == 0xfe){ + *c += 9; + }else{ + *c += 1; + } + return rval; +} + +/** + * Converts length-encoded strings to character strings and advanced the pointer to the next unrelated byte. + * The caller is responsible for freeing the allocated memory. + * @param c Pointer to the first byte of a valid packet. + * @return The newly allocated string or NULL of an error occurred + */ +char* consume_lestr(unsigned char** c) +{ + unsigned int slen = consume_leitoi(c); + char *str = calloc((slen + 1), sizeof(char)); + if(str){ + memcpy(str,*c,slen); + *c += slen; + } + return str; +} + +/** + *Checks whether the packet is an EOF packet. + * @param p Pointer to the first byte of a packet + * @return 1 if the packet is an EOF packet and 0 if it is not + */ +unsigned int is_eof(void* p) +{ + unsigned char* ptr = (unsigned char*) p; + return *(ptr) == 0x05 && *(ptr + 1) == 0x00 && *(ptr + 2) == 0x00 && *(ptr + 4) == 0xfe; +} + + +/** + * The clientReply entry point. This is passed the response buffer + * to which the filter should be applied. Once processed the + * query is passed to the upstream component + * (filter or router) in the filter chain. + * + * The function tries to extract a SQL query response out of the response buffer, + * adds a timestamp to it and publishes the resulting string on the exchange. + * The message is tagged with the same identifier that the query was. + * + * @param instance The filter instance data + * @param session The filter session + * @param reply The response data + */ +static int clientReply(FILTER* instance, void *session, GWBUF *reply) +{ + MQ_SESSION *my_session = (MQ_SESSION *)session; + MQ_INSTANCE *my_instance = (MQ_INSTANCE *)instance; + char t_buf[128],*combined; + unsigned int pkt_len = pktlen(reply->sbuf->data), offset = 0; + amqp_basic_properties_t *prop; + + if (my_session->was_query){ + + int packet_ok = 0, was_last = 0; + + my_session->was_query = false; + + if(pkt_len > 0){ + if((prop = malloc(sizeof(amqp_basic_properties_t)))){ + prop->_flags = AMQP_BASIC_CONTENT_TYPE_FLAG | + AMQP_BASIC_DELIVERY_MODE_FLAG | + AMQP_BASIC_MESSAGE_ID_FLAG | + AMQP_BASIC_CORRELATION_ID_FLAG; + prop->content_type = amqp_cstring_bytes("text/plain"); + prop->delivery_mode = AMQP_DELIVERY_PERSISTENT; + prop->correlation_id = amqp_cstring_bytes(my_session->uid); + prop->message_id = amqp_cstring_bytes("reply"); + } + if(!(combined = calloc(GWBUF_LENGTH(reply) + 256,sizeof(char)))){ + skygw_log_write_flush(LOGFILE_ERROR, + "Error : Out of memory"); + } + + memset(t_buf,0,128); + sprintf(t_buf,"%lu|",(unsigned long)time(NULL)); + + + memcpy(combined + offset,t_buf,strnlen(t_buf,40)); + offset += strnlen(t_buf,40); + + if(*(reply->sbuf->data + 4) == 0x00){ /**OK packet*/ + unsigned int aff_rows = 0, l_id = 0, s_flg = 0, wrn = 0; + unsigned char *ptr = (unsigned char*)(reply->sbuf->data + 5); + pkt_len = pktlen(reply->sbuf->data); + aff_rows = consume_leitoi(&ptr); + l_id = consume_leitoi(&ptr); + s_flg |= *ptr++; + s_flg |= (*ptr++ << 8); + wrn |= *ptr++; + wrn |= (*ptr++ << 8); + sprintf(combined + offset,"OK - affected_rows: %d " + " last_insert_id: %d " + " status_flags: %#0x " + " warnings: %d ", + aff_rows,l_id,s_flg,wrn); + offset += strnlen(combined,GWBUF_LENGTH(reply) + 256) - offset; + + if(pkt_len > 7){ + int plen = consume_leitoi(&ptr); + if(plen > 0){ + sprintf(combined + offset," message: %.*s\n",plen,ptr); + } + } + + packet_ok = 1; + was_last = 1; + + }else if(*(reply->sbuf->data + 4) == 0xff){ /**ERR packet*/ + + sprintf(combined + offset,"ERROR - message: %.*s", + (int)(reply->end - ((void*)(reply->sbuf->data + 13))), + (char *)reply->sbuf->data + 13); + packet_ok = 1; + was_last = 1; + + }else if(*(reply->sbuf->data + 4) == 0xfb){ /**LOCAL_INFILE request packet*/ + + unsigned char *rset = (unsigned char*)reply->sbuf->data; + strcpy(combined + offset,"LOCAL_INFILE: "); + strncat(combined + offset,(const char*)rset+5,pktlen(rset)); + packet_ok = 1; + was_last = 1; + + }else{ /**Result set*/ + + unsigned char *rset = (unsigned char*)(reply->sbuf->data + 4); + char *tmp; + unsigned int col_cnt = consume_leitoi(&rset); + + tmp = calloc(256,sizeof(char)); + sprintf(tmp,"Columns: %d",col_cnt); + memcpy(combined + offset,tmp,strnlen(tmp,256)); + offset += strnlen(tmp,256); + memcpy(combined + offset,"\n",1); + offset++; + free(tmp); + + packet_ok = 1; + was_last = 1; + + } + if(packet_ok){ + + pushMessage(my_instance,prop,combined); + + if(was_last){ + + /**Successful reply received and sent, releasing uid*/ + + free(my_session->uid); + my_session->uid = NULL; + + } + } + } + + } + + return my_session->up.clientReply(my_session->up.instance, + my_session->up.session, reply); +} + +/** + * Diagnostics routine + * + * Prints the connection details and the names of the exchange, + * queue and the routing key. + * + * @param instance The filter instance + * @param fsession Filter session, may be NULL + * @param dcb The DCB for diagnostic output + */ +static void +diagnostic(FILTER *instance, void *fsession, DCB *dcb) +{ + MQ_INSTANCE *my_instance = (MQ_INSTANCE *)instance; + + if (my_instance) + { + dcb_printf(dcb, "\t\tConnecting to %s:%d as %s/%s.\nVhost: %s\tExchange: %s\tKey: %s\tQueue: %s\n", + my_instance->hostname,my_instance->port, + my_instance->username,my_instance->password, + my_instance->vhost, my_instance->exchange, + my_instance->key, my_instance->queue + ); + } +} +