732 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			732 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2016 MariaDB Corporation Ab
 | |
|  *
 | |
|  * Use of this software is governed by the Business Source License included
 | |
|  * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 | |
|  *
 | |
|  * Change Date: 2020-01-01
 | |
|  *
 | |
|  * On the date above, in accordance with the Business Source License, use
 | |
|  * of this software will be governed by version 2 or later of the General
 | |
|  * Public License.
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @file maxinfo.c - A "routing module" that in fact merely gives access
 | |
|  * to a MaxScale information schema usign the MySQL protocol
 | |
|  *
 | |
|  * @verbatim
 | |
|  * Revision History
 | |
|  *
 | |
|  * Date       Who                 Description
 | |
|  * 16/02/15   Mark Riddoch        Initial implementation
 | |
|  * 27/02/15   Massimiliano Pinto  Added maxinfo_add_mysql_user
 | |
|  * 09/09/2015 Martin Brampton     Modify error handler
 | |
|  *
 | |
|  * @endverbatim
 | |
|  */
 | |
| 
 | |
| #include "maxinfo.h"
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <time.h>
 | |
| 
 | |
| #include <maxscale/alloc.h>
 | |
| #include <maxscale/service.h>
 | |
| #include <maxscale/server.h>
 | |
| #include <maxscale/router.h>
 | |
| #include <maxscale/modinfo.h>
 | |
| #include <maxscale/modutil.h>
 | |
| #include <maxscale/monitor.h>
 | |
| #include <maxscale/atomic.h>
 | |
| #include <maxscale/spinlock.h>
 | |
| #include <maxscale/dcb.h>
 | |
| #include <maxscale/maxscale.h>
 | |
| #include <maxscale/log_manager.h>
 | |
| #include <maxscale/resultset.h>
 | |
| #include <maxscale/version.h>
 | |
| #include <maxscale/resultset.h>
 | |
| #include <maxscale/secrets.h>
 | |
| #include <maxscale/users.h>
 | |
| #include <maxscale/protocol/mysql.h>
 | |
| 
 | |
| #include "../../../core/internal/modules.h"
 | |
| #include "../../../core/internal/monitor.h"
 | |
| #include "../../../core/internal/session.h"
 | |
| #include "../../../core/internal/poll.h"
 | |
| 
 | |
| extern char *create_hex_sha1_sha1_passwd(char *passwd);
 | |
| 
 | |
| static int maxinfo_statistics(INFO_INSTANCE *, INFO_SESSION *, GWBUF *);
 | |
| static int maxinfo_ping(INFO_INSTANCE *, INFO_SESSION *, GWBUF *);
 | |
| static int maxinfo_execute_query(INFO_INSTANCE *, INFO_SESSION *, char *);
 | |
| static int handle_url(INFO_INSTANCE *instance, INFO_SESSION *router_session, GWBUF *queue);
 | |
| static int maxinfo_send_ok(DCB *dcb);
 | |
| 
 | |
| /* The router entry points */
 | |
| static  MXS_ROUTER *createInstance(SERVICE *service, char **options);
 | |
| static  MXS_ROUTER_SESSION *newSession(MXS_ROUTER *instance, MXS_SESSION *session);
 | |
| static  void closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
 | |
| static  void freeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session);
 | |
| static  int execute(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session, GWBUF *queue);
 | |
| static  void diagnostics(MXS_ROUTER *instance, DCB *dcb);
 | |
| static  json_t* diagnostics_json(const MXS_ROUTER *instance);
 | |
| static  uint64_t getCapabilities(MXS_ROUTER* instance);
 | |
| static  void handleError(MXS_ROUTER     *instance,
 | |
|                          MXS_ROUTER_SESSION  *router_session,
 | |
|                          GWBUF          *errbuf,
 | |
|                          DCB            *backend_dcb,
 | |
|                          mxs_error_action_t action,
 | |
|                          bool           *succp);
 | |
| 
 | |
| static SPINLOCK     instlock;
 | |
| static INFO_INSTANCE    *instances;
 | |
| 
 | |
| /**
 | |
|  * 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
 | |
|  */
 | |
| MXS_MODULE* MXS_CREATE_MODULE()
 | |
| {
 | |
|     MXS_NOTICE("Initialise MaxInfo router module.");
 | |
|     spinlock_init(&instlock);
 | |
|     instances = NULL;
 | |
| 
 | |
|     static MXS_ROUTER_OBJECT MyObject =
 | |
|     {
 | |
|         createInstance,
 | |
|         newSession,
 | |
|         closeSession,
 | |
|         freeSession,
 | |
|         execute,
 | |
|         diagnostics,
 | |
|         diagnostics_json,
 | |
|         NULL,
 | |
|         handleError,
 | |
|         getCapabilities,
 | |
|         NULL
 | |
|     };
 | |
| 
 | |
|     static MXS_MODULE info =
 | |
|     {
 | |
|         MXS_MODULE_API_ROUTER,
 | |
|         MXS_MODULE_ALPHA_RELEASE,
 | |
|         MXS_ROUTER_VERSION,
 | |
|         "The MaxScale Information Schema",
 | |
|         "V1.0.0",
 | |
|         RCAP_TYPE_NO_AUTH,
 | |
|         &MyObject,
 | |
|         NULL, /* Process init. */
 | |
|         NULL, /* Process finish. */
 | |
|         NULL, /* Thread init. */
 | |
|         NULL, /* Thread finish. */
 | |
|         {
 | |
|             {MXS_END_MODULE_PARAMS}
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     return &info;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create an instance of the router for a particular service
 | |
|  * within the gateway.
 | |
|  *
 | |
|  * @param service   The service this router is being create for
 | |
|  * @param options   Any array of options for the query router
 | |
|  *
 | |
|  * @return The instance data for this new instance
 | |
|  */
 | |
| static  MXS_ROUTER  *
 | |
| createInstance(SERVICE *service, char **options)
 | |
| {
 | |
|     INFO_INSTANCE *inst;
 | |
|     int i;
 | |
| 
 | |
|     if ((inst = MXS_MALLOC(sizeof(INFO_INSTANCE))) == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     inst->sessions = NULL;
 | |
|     inst->service = service;
 | |
|     spinlock_init(&inst->lock);
 | |
| 
 | |
|     if (options)
 | |
|     {
 | |
|         for (i = 0; options[i]; i++)
 | |
|         {
 | |
|             MXS_ERROR("Unknown option for MaxInfo '%s'", options[i]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * We have completed the creation of the instance data, so now
 | |
|      * insert this router instance into the linked list of routers
 | |
|      * that have been created with this module.
 | |
|      */
 | |
|     spinlock_acquire(&instlock);
 | |
|     inst->next = instances;
 | |
|     instances = inst;
 | |
|     spinlock_release(&instlock);
 | |
| 
 | |
|     return (MXS_ROUTER *)inst;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Associate a new session with this instance of the router.
 | |
|  *
 | |
|  * @param instance  The router instance data
 | |
|  * @param session   The session itself
 | |
|  * @return Session specific data for this session
 | |
|  */
 | |
| static MXS_ROUTER_SESSION *
 | |
| newSession(MXS_ROUTER *instance, MXS_SESSION *session)
 | |
| {
 | |
|     INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
 | |
|     INFO_SESSION *client;
 | |
| 
 | |
|     if ((client = (INFO_SESSION *)MXS_MALLOC(sizeof(INFO_SESSION))) == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
|     client->session = session;
 | |
|     client->dcb = session->client_dcb;
 | |
|     client->queue = NULL;
 | |
| 
 | |
|     spinlock_acquire(&inst->lock);
 | |
|     client->next = inst->sessions;
 | |
|     inst->sessions = client;
 | |
|     spinlock_release(&inst->lock);
 | |
| 
 | |
|     session->state = SESSION_STATE_READY;
 | |
| 
 | |
|     return (void *)client;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Close a session with the router, this is the mechanism
 | |
|  * by which a router may cleanup data structure etc.
 | |
|  *
 | |
|  * @param instance      The router instance data
 | |
|  * @param router_session    The session being closed
 | |
|  */
 | |
| static  void
 | |
| closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session)
 | |
| {
 | |
|     INFO_INSTANCE *inst = (INFO_INSTANCE *)instance;
 | |
|     INFO_SESSION *session = (INFO_SESSION *)router_session;
 | |
| 
 | |
| 
 | |
|     spinlock_acquire(&inst->lock);
 | |
|     if (inst->sessions == session)
 | |
|     {
 | |
|         inst->sessions = session->next;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         INFO_SESSION *ptr = inst->sessions;
 | |
|         while (ptr && ptr->next != session)
 | |
|         {
 | |
|             ptr = ptr->next;
 | |
|         }
 | |
|         if (ptr)
 | |
|         {
 | |
|             ptr->next = session->next;
 | |
|         }
 | |
|     }
 | |
|     spinlock_release(&inst->lock);
 | |
|     /**
 | |
|      * Router session is freed in session.c:session_close, when session who
 | |
|      * owns it, is freed.
 | |
|      */
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Free a maxinfo session
 | |
|  *
 | |
|  * @param router_instance   The router session
 | |
|  * @param router_client_session The router session as returned from newSession
 | |
|  */
 | |
| static void freeSession(MXS_ROUTER* router_instance,
 | |
|                         MXS_ROUTER_SESSION* router_client_session)
 | |
| {
 | |
|     MXS_FREE(router_client_session);
 | |
|     return;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Error Handler routine
 | |
|  *
 | |
|  * The routine will handle errors that occurred in backend writes.
 | |
|  *
 | |
|  * @param instance        The router instance
 | |
|  * @param router_session  The router session
 | |
|  * @param message         The error message to reply
 | |
|  * @param backend_dcb     The backend DCB
 | |
|  * @param action          The action: ERRACT_NEW_CONNECTION or ERRACT_REPLY_CLIENT
 | |
|  * @param succp           Result of action: true iff router can continue
 | |
|  *
 | |
|  */
 | |
| static void handleError(MXS_ROUTER         *instance,
 | |
|                         MXS_ROUTER_SESSION *router_session,
 | |
|                         GWBUF              *errbuf,
 | |
|                         DCB                *backend_dcb,
 | |
|                         mxs_error_action_t action,
 | |
|                         bool               *succp)
 | |
| 
 | |
| {
 | |
|     ss_dassert(backend_dcb->dcb_role == DCB_ROLE_BACKEND_HANDLER);
 | |
|     DCB *client_dcb;
 | |
|     MXS_SESSION *session = backend_dcb->session;
 | |
| 
 | |
|     client_dcb = session->client_dcb;
 | |
| 
 | |
|     if (session->state == SESSION_STATE_ROUTER_READY)
 | |
|     {
 | |
|         CHK_DCB(client_dcb);
 | |
|         client_dcb->func.write(client_dcb, gwbuf_clone(errbuf));
 | |
|     }
 | |
| 
 | |
|     /** false because connection is not available anymore */
 | |
|     dcb_close(backend_dcb);
 | |
|     *succp = false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * We have data from the client, this is a SQL command, or other MySQL
 | |
|  * packet type.
 | |
|  *
 | |
|  * @param instance       The router instance
 | |
|  * @param router_session The router session returned from the newSession call
 | |
|  * @param queue          The queue of data buffers to route
 | |
|  * @return The number of bytes sent
 | |
|  */
 | |
| static int
 | |
| execute(MXS_ROUTER *rinstance, MXS_ROUTER_SESSION *router_session, GWBUF *queue)
 | |
| {
 | |
|     INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance;
 | |
|     INFO_SESSION *session = (INFO_SESSION *)router_session;
 | |
|     uint8_t *data;
 | |
|     int length, len, residual;
 | |
|     char *sql;
 | |
| 
 | |
|     if (GWBUF_TYPE(queue) == GWBUF_TYPE_HTTP)
 | |
|     {
 | |
|         return handle_url(instance, session, queue);
 | |
|     }
 | |
|     if (session->queue)
 | |
|     {
 | |
|         queue = gwbuf_append(session->queue, queue);
 | |
|         session->queue = NULL;
 | |
|         queue = gwbuf_make_contiguous(queue);
 | |
|     }
 | |
|     data = (uint8_t *)GWBUF_DATA(queue);
 | |
|     length = data[0] + (data[1] << 8) + (data[2] << 16);
 | |
|     if (length + 4 > GWBUF_LENGTH(queue))
 | |
|     {
 | |
|         // Incomplete packet, must be buffered
 | |
|         session->queue = queue;
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     int rc = 1;
 | |
|     // We have a complete request in a single buffer
 | |
|     if (modutil_MySQL_Query(queue, &sql, &len, &residual))
 | |
|     {
 | |
|         sql = strndup(sql, len);
 | |
|         rc = maxinfo_execute_query(instance, session, sql);
 | |
|         MXS_FREE(sql);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         switch (MYSQL_COMMAND(queue))
 | |
|         {
 | |
|         case MXS_COM_PING:
 | |
|             rc = maxinfo_send_ok(session->dcb);
 | |
|             break;
 | |
|         case MXS_COM_STATISTICS:
 | |
|             rc = maxinfo_statistics(instance, session, queue);
 | |
|             break;
 | |
|         case MXS_COM_QUIT:
 | |
|             break;
 | |
|         default:
 | |
|             MXS_ERROR("Unexpected MySQL command 0x%x",
 | |
|                       MYSQL_COMMAND(queue));
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     // MaxInfo doesn't route the data forward so it should be freed.
 | |
|     gwbuf_free(queue);
 | |
|     return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Display router diagnostics
 | |
|  *
 | |
|  * @param instance  Instance of the router
 | |
|  * @param dcb       DCB to send diagnostics to
 | |
|  */
 | |
| static  void
 | |
| diagnostics(MXS_ROUTER *instance, DCB *dcb)
 | |
| {
 | |
|     return; /* Nothing to do currently */
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Display router diagnostics
 | |
|  *
 | |
|  * @param instance  Instance of the router
 | |
|  * @param dcb       DCB to send diagnostics to
 | |
|  */
 | |
| static  json_t*
 | |
| diagnostics_json(const MXS_ROUTER *instance)
 | |
| {
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Capabilities interface for the rotuer
 | |
|  *
 | |
|  * Not used for the maxinfo router
 | |
|  */
 | |
| static uint64_t
 | |
| getCapabilities(MXS_ROUTER* instance)
 | |
| {
 | |
|     return RCAP_TYPE_NONE;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Return some basic statistics from the router in response to a COM_STATISTICS
 | |
|  * request.
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param session   The connection that requested the statistics
 | |
|  * @param queue     The statistics request
 | |
|  *
 | |
|  * @return non-zero on sucessful send
 | |
|  */
 | |
| static int
 | |
| maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue)
 | |
| {
 | |
|     char result[1000];
 | |
|     uint8_t *ptr;
 | |
|     GWBUF *ret;
 | |
|     int len;
 | |
| 
 | |
|     snprintf(result, 1000,
 | |
|              "Uptime: %u  Threads: %u  Sessions: %u ",
 | |
|              maxscale_uptime(),
 | |
|              config_threadcount(),
 | |
|              serviceSessionCountAll());
 | |
|     if ((ret = gwbuf_alloc(4 + strlen(result))) == NULL)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
|     len = strlen(result);
 | |
|     ptr = GWBUF_DATA(ret);
 | |
|     *ptr++ = len & 0xff;
 | |
|     *ptr++ = (len & 0xff00) >> 8;
 | |
|     *ptr++ = (len & 0xff0000) >> 16;
 | |
|     *ptr++ = 1;
 | |
|     memcpy(ptr, result, len);
 | |
| 
 | |
|     return session->dcb->func.write(session->dcb, ret);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Respond to a COM_PING command
 | |
|  *
 | |
|  * @param router    The router instance
 | |
|  * @param session   The connection that requested the ping
 | |
|  * @param queue     The ping request
 | |
|  */
 | |
| static int
 | |
| maxinfo_ping(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue)
 | |
| {
 | |
|     uint8_t *ptr;
 | |
|     GWBUF *ret;
 | |
|     int len;
 | |
| 
 | |
|     if ((ret = gwbuf_alloc(5)) == NULL)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
|     ptr = GWBUF_DATA(ret);
 | |
|     *ptr++ = 0x01;
 | |
|     *ptr++ = 0;
 | |
|     *ptr++ = 0;
 | |
|     *ptr++ = 1;
 | |
|     *ptr = 0;       // OK
 | |
| 
 | |
|     return session->dcb->func.write(session->dcb, ret);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Populate the version comment with the MaxScale version
 | |
|  *
 | |
|  * @param   result  The result set
 | |
|  * @param   data    Pointer to int which is row count
 | |
|  * @return  The populated row
 | |
|  */
 | |
| static RESULT_ROW *
 | |
| version_comment(RESULTSET *result, void *data)
 | |
| {
 | |
|     int *context = (int *)data;
 | |
|     RESULT_ROW *row;
 | |
| 
 | |
|     if (*context == 0)
 | |
|     {
 | |
|         (*context)++;
 | |
|         row = resultset_make_row(result);
 | |
|         resultset_row_set(row, 0, MAXSCALE_VERSION);
 | |
|         return row;
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The hardwired select @@vercom response
 | |
|  *
 | |
|  * @param dcb   The DCB of the client
 | |
|  */
 | |
| static void
 | |
| respond_vercom(DCB *dcb)
 | |
| {
 | |
|     RESULTSET *result;
 | |
|     int context = 0;
 | |
| 
 | |
|     if ((result = resultset_create(version_comment, &context)) == NULL)
 | |
|     {
 | |
|         maxinfo_send_error(dcb, 0, "No resources available");
 | |
|         return;
 | |
|     }
 | |
|     resultset_add_column(result, "@@version_comment", 40, COL_TYPE_VARCHAR);
 | |
|     resultset_stream_mysql(result, dcb);
 | |
|     resultset_free(result);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Populate the version comment with the MaxScale version
 | |
|  *
 | |
|  * @param   result  The result set
 | |
|  * @param   data    Pointer to int which is row count
 | |
|  * @return  The populated row
 | |
|  */
 | |
| static RESULT_ROW *
 | |
| starttime_row(RESULTSET *result, void *data)
 | |
| {
 | |
|     int *context = (int *)data;
 | |
|     RESULT_ROW *row;
 | |
|     struct tm tm;
 | |
|     static char buf[40];
 | |
| 
 | |
|     if (*context == 0)
 | |
|     {
 | |
|         (*context)++;
 | |
|         row = resultset_make_row(result);
 | |
|         sprintf(buf, "%u", (unsigned int)maxscale_started());
 | |
|         resultset_row_set(row, 0, buf);
 | |
|         return row;
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The hardwired select ... as starttime response
 | |
|  *
 | |
|  * @param dcb   The DCB of the client
 | |
|  */
 | |
| static void
 | |
| respond_starttime(DCB *dcb)
 | |
| {
 | |
|     RESULTSET *result;
 | |
|     int context = 0;
 | |
| 
 | |
|     if ((result = resultset_create(starttime_row, &context)) == NULL)
 | |
|     {
 | |
|         maxinfo_send_error(dcb, 0, "No resources available");
 | |
|         return;
 | |
|     }
 | |
|     resultset_add_column(result, "starttime", 40, COL_TYPE_VARCHAR);
 | |
|     resultset_stream_mysql(result, dcb);
 | |
|     resultset_free(result);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Send a MySQL OK packet to the DCB
 | |
|  *
 | |
|  * @param dcb   The DCB to send the OK packet to
 | |
|  * @return result of a write call, non-zero if write was successful
 | |
|  */
 | |
| static int
 | |
| maxinfo_send_ok(DCB *dcb)
 | |
| {
 | |
|     GWBUF *buf;
 | |
|     uint8_t *ptr;
 | |
| 
 | |
|     if ((buf = gwbuf_alloc(11)) == NULL)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
|     ptr = GWBUF_DATA(buf);
 | |
|     *ptr++ = 7; // Payload length
 | |
|     *ptr++ = 0;
 | |
|     *ptr++ = 0;
 | |
|     *ptr++ = 1; // Seqno
 | |
|     *ptr++ = 0; // ok
 | |
|     *ptr++ = 0;
 | |
|     *ptr++ = 0;
 | |
|     *ptr++ = 2;
 | |
|     *ptr++ = 0;
 | |
|     *ptr++ = 0;
 | |
|     *ptr++ = 0;
 | |
|     return dcb->func.write(dcb, buf);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Execute a SQL query against the MaxScale Information Schema
 | |
|  *
 | |
|  * @param instance  The instance strcture
 | |
|  * @param session   The session pointer
 | |
|  * @param sql       The SQL to execute
 | |
|  */
 | |
| static int
 | |
| maxinfo_execute_query(INFO_INSTANCE *instance, INFO_SESSION *session, char *sql)
 | |
| {
 | |
|     MAXINFO_TREE    *tree;
 | |
|     PARSE_ERROR err;
 | |
| 
 | |
|     MXS_INFO("SQL statement: '%s' for 0x%p.",
 | |
|              sql, session->dcb);
 | |
|     if (strcmp(sql, "select @@version_comment limit 1") == 0)
 | |
|     {
 | |
|         respond_vercom(session->dcb);
 | |
|         return 1;
 | |
|     }
 | |
|     /* Below is a kludge for MonYog, if we see
 | |
|      *  select unix_timestamp... as starttime
 | |
|      * just return the starttime of MaxScale
 | |
|      */
 | |
|     if (strncasecmp(sql, "select UNIX_TIMESTAMP",
 | |
|                     strlen("select UNIX_TIMESTAMP")) == 0
 | |
|         && (strstr(sql, "as starttime") != NULL || strstr(sql, "AS starttime") != NULL))
 | |
|     {
 | |
|         respond_starttime(session->dcb);
 | |
|         return 1;
 | |
|     }
 | |
|     if (strncasecmp(sql, "set names", 9) == 0)
 | |
|     {
 | |
|         return maxinfo_send_ok(session->dcb);
 | |
|     }
 | |
|     if (strncasecmp(sql, "set session", 11) == 0)
 | |
|     {
 | |
|         return maxinfo_send_ok(session->dcb);
 | |
|     }
 | |
|     if (strncasecmp(sql, "set @@session", 13) == 0)
 | |
|     {
 | |
|         return maxinfo_send_ok(session->dcb);
 | |
|     }
 | |
|     if (strncasecmp(sql, "set autocommit", 14) == 0)
 | |
|     {
 | |
|         return maxinfo_send_ok(session->dcb);
 | |
|     }
 | |
|     if (strncasecmp(sql, "SELECT `ENGINES`.`SUPPORT`", 26) == 0)
 | |
|     {
 | |
|         return maxinfo_send_ok(session->dcb);
 | |
|     }
 | |
|     if ((tree = maxinfo_parse(sql, &err)) == NULL)
 | |
|     {
 | |
|         maxinfo_send_parse_error(session->dcb, sql, err);
 | |
|         MXS_NOTICE("Failed to parse SQL statement: '%s'.", sql);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         maxinfo_execute(session->dcb, tree);
 | |
|         maxinfo_free_tree(tree);
 | |
|     }
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Session all result set
 | |
|  * @return A resultset for all sessions
 | |
|  */
 | |
| static RESULTSET *
 | |
| maxinfoSessionsAll()
 | |
| {
 | |
|     return sessionGetList(SESSION_LIST_ALL);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Client session result set
 | |
|  * @return A resultset for all sessions
 | |
|  */
 | |
| static RESULTSET *
 | |
| maxinfoClientSessions()
 | |
| {
 | |
|     return sessionGetList(SESSION_LIST_CONNECTION);
 | |
| }
 | |
| 
 | |
| typedef RESULTSET *(*RESULTSETFUNC)();
 | |
| 
 | |
| /**
 | |
|  * Table that maps a URI to a function to call to
 | |
|  * to obtain the result set related to that URI
 | |
|  */
 | |
| static struct uri_table
 | |
| {
 | |
|     char        *uri;
 | |
|     RESULTSETFUNC   func;
 | |
| } supported_uri[] =
 | |
| {
 | |
|     { "/services", serviceGetList },
 | |
|     { "/listeners", serviceGetListenerList },
 | |
|     { "/modules", moduleGetList },
 | |
|     { "/monitors", monitorGetList },
 | |
|     { "/sessions", maxinfoSessionsAll },
 | |
|     { "/clients", maxinfoClientSessions },
 | |
|     { "/servers", serverGetList },
 | |
|     { "/variables", maxinfo_variables },
 | |
|     { "/status", maxinfo_status },
 | |
|     { "/event/times", eventTimesGetList },
 | |
|     { NULL, NULL }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * We have data from the client, this is a HTTP URL
 | |
|  *
 | |
|  * @param instance  The router instance
 | |
|  * @param session   The router session returned from the newSession call
 | |
|  * @param queue     The queue of data buffers to route
 | |
|  * @return The number of bytes sent
 | |
|  */
 | |
| static  int
 | |
| handle_url(INFO_INSTANCE *instance, INFO_SESSION *session, GWBUF *queue)
 | |
| {
 | |
|     char *uri;
 | |
|     int i;
 | |
|     RESULTSET *set;
 | |
| 
 | |
|     uri = (char *)GWBUF_DATA(queue);
 | |
|     for (i = 0; supported_uri[i].uri; i++)
 | |
|     {
 | |
|         if (strcmp(uri, supported_uri[i].uri) == 0)
 | |
|         {
 | |
|             set = (*supported_uri[i].func)();
 | |
|             resultset_stream_json(set, session->dcb);
 | |
|             resultset_free(set);
 | |
|         }
 | |
|     }
 | |
|     gwbuf_free(queue);
 | |
|     return 1;
 | |
| }
 | 
