/*
 * This file is distributed as part of the MariaDB Corporation MaxScale.  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 MariaDB Corporation Ab 2013-2014
 */

/**
 * @file debugcmd.c  - The debug CLI command line interpreter
 *
 * The command interpreter for the dbug user interface. The command
 * structure is such that there are a numerb of commands, notably
 * show and a set of subcommands, the things to show in this case.
 *
 * Each subcommand has a handler function defined for it that is passeed
 * the DCB to use to print the output of the commands and up to 3 arguments
 * as numeric values.
 *
 * There are two "built in" commands, the help command and the quit
 * command.
 *
 * @verbatim
 * Revision History
 *
 * Date		Who			Description
 * 20/06/13	Mark Riddoch		Initial implementation
 * 17/07/13	Mark Riddoch		Additional commands
 * 09/08/2013	Massimiliano Pinto	Added enable/disable commands (now only for log)
 * 20/05/14	Mark Riddoch		Added ability to give server and service names rather
 *					than simply addresses
 * 23/05/14	Mark Riddoch		Added support for developer and user modes
 * 29/05/14	Mark Riddoch		Add Filter support
 * 16/10/14	Mark Riddoch		Add show eventq
 *
 * @endverbatim
 */
#include <my_config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <service.h>
#include <session.h>
#include <router.h>
#include <filter.h>
#include <modules.h>
#include <atomic.h>
#include <server.h>
#include <spinlock.h>
#include <dcb.h>
#include <poll.h>
#include <users.h>
#include <dbusers.h>
#include <config.h>
#include <telnetd.h>
#include <adminusers.h>
#include <monitor.h>
#include <debugcli.h>
#include <poll.h>
#include <housekeeper.h>

#include <skygw_utils.h>
#include <log_manager.h>

#define	MAXARGS	5

#define	ARG_TYPE_ADDRESS	1
#define	ARG_TYPE_STRING		2
#define	ARG_TYPE_SERVICE	3
#define	ARG_TYPE_SERVER		4
#define	ARG_TYPE_DBUSERS	5
#define	ARG_TYPE_SESSION	6
#define	ARG_TYPE_DCB		7
#define	ARG_TYPE_MONITOR	8
#define	ARG_TYPE_FILTER		9
#define	ARG_TYPE_NUMERIC	10	

/**
 * The subcommand structure
 *
 * These are the options that may be passed to a command
 */
struct subcommand {
	char	*arg1;
	int	n_args;
	void	(*fn)();
	char	*help;
	char	*devhelp;
	int	arg_types[3];
};

static	void	telnetdShowUsers(DCB *);
/**
 * The subcommands of the show command
 */
struct subcommand showoptions[] = {
        { "dcbs",	0, dprintAllDCBs,
		"Show all descriptor control blocks (network connections)",
		"Show all descriptor control blocks (network connections)",
				{0, 0, 0} },
	{ "dcb",	1, dprintDCB,	
		"Show a single descriptor control block e.g. show dcb 0x493340",
		"Show a single descriptor control block e.g. show dcb 0x493340",
				{ARG_TYPE_DCB, 0, 0} },
	{ "dbusers",	1, dcb_usersPrint,
			"Show statistics and user names for a service's user table.\n\t\tExample : show dbusers <service name>",
			"Show statistics and user names for a service's user table.\n\t\tExample : show dbusers <ptr of 'User's data' from services list>|<service name>",
				{ARG_TYPE_DBUSERS, 0, 0} },
	{ "epoll",	0, dprintPollStats,
			"Show the poll statistics",
			"Show the poll statistics",
				{0, 0, 0} },
	{ "eventq",	0, dShowEventQ,
			"Show the queue of events waiting to be processed",
			"Show the queue of events waiting to be processed",
				{0, 0, 0} },
	{ "eventstats",	0, dShowEventStats,
			"Show the event statistics",
			"Show the event statistics",
				{0, 0, 0} },
	{ "filter",	1, dprintFilter,
			"Show details of a filter, called with a filter name",
			"Show details of a filter, called with the address of a filter",
				{ARG_TYPE_FILTER, 0, 0} },
	{ "filters",	0, dprintAllFilters,
			"Show all filters",
			"Show all filters",
				{0, 0, 0} },
	{ "modules",	0, dprintAllModules,
			"Show all currently loaded modules",
			"Show all currently loaded modules",
				{0, 0, 0} },
	{ "monitor",	1, monitorShow,
			"Show the monitor details",
			"Show the monitor details",
				{ARG_TYPE_MONITOR, 0, 0} },
	{ "monitors",	0, monitorShowAll,
			"Show the monitors that are configured",
			"Show the monitors that are configured",
				{0, 0, 0} },
	{ "server",	1, dprintServer,
			"Show details for a named server, e.g. show server dbnode1",
			"Show details for a server, e.g. show server 0x485390. The address may also be repalced with the server name from the configuration file",
				{ARG_TYPE_SERVER, 0, 0} },
	{ "servers",	0, dprintAllServers,
			"Show all configured servers",
			"Show all configured servers",
				{0, 0, 0} },
	{ "services",	0, dprintAllServices,
			"Show all configured services in MaxScale",
			"Show all configured services in MaxScale",
				{0, 0, 0} },
	{ "service",	1, dprintService,
			"Show a single service in MaxScale, may be passed a service name",
			"Show a single service in MaxScale, may be passed a service name or address of a service object",
				{ARG_TYPE_SERVICE, 0, 0} },
	{ "session",	1, dprintSession,
		 	"Show a single session in MaxScale, e.g. show session 0x284830",
		 	"Show a single session in MaxScale, e.g. show session 0x284830",
				{ARG_TYPE_SESSION, 0, 0} },
	{ "sessions",	0, dprintAllSessions,
		 	"Show all active sessions in MaxScale",
		 	"Show all active sessions in MaxScale",
				{0, 0, 0} },
	{ "tasks",	0, hkshow_tasks,
		 	"Show all active housekeeper tasks in MaxScale",
		 	"Show all active housekeeper tasks in MaxScale",
				{0, 0, 0} },
	{ "threads",	0, dShowThreads,
		 	"Show the status of the polling threads in MaxScale",
		 	"Show the status of the polling threads in MaxScale",
				{0, 0, 0} },
	{ "users",	0, telnetdShowUsers,
			"Show statistics and user names for the debug interface",
			"Show statistics and user names for the debug interface",
				{0, 0, 0} },
	{ NULL,		0, NULL,		NULL,	NULL,
				{0, 0, 0} }
};

/**
 * The subcommands of the list command
 */
struct subcommand listoptions[] = {
        { "clients",	0, dListClients,
		"List all the client connections to MaxScale",
		"List all the client connections to MaxScale",
				{0, 0, 0} },
        { "dcbs",	0, dListDCBs,
		"List all the DCBs active within MaxScale",
		"List all the DCBs active within MaxScale",
				{0, 0, 0} },
        { "filters",	0, dListFilters,
		"List all the filters defined within MaxScale",
		"List all the filters defined within MaxScale",
				{0, 0, 0} },
        { "listeners",	0, dListListeners,
		"List all the listeners defined within MaxScale",
		"List all the listeners defined within MaxScale",
				{0, 0, 0} },
	{ "modules",	0, dprintAllModules,
			"List all currently loaded modules",
			"List all currently loaded modules",
				{0, 0, 0} },
	{ "monitors",	0, monitorList,
			"List all monitors",
			"List all monitors",
				{0, 0, 0} },
        { "services",	0, dListServices,
		"List all the services defined within MaxScale",
		"List all the services defined within MaxScale",
				{0, 0, 0} },
        { "servers",	0, dListServers,
		"List all the servers defined within MaxScale",
		"List all the servers defined within MaxScale",
				{0, 0, 0} },
        { "sessions",	0, dListSessions,
		"List all the active sessions within MaxScale",
		"List all the active sessions within MaxScale",
				{0, 0, 0} },
	{ "threads",	0, dShowThreads,
	 	"List the status of the polling threads in MaxScale",
	 	"List the status of the polling threads in MaxScale",
				{0, 0, 0} },
	{ NULL,		0, NULL,		NULL,	NULL,
				{0, 0, 0} }
};

extern void shutdown_server();
static void shutdown_service(DCB *dcb, SERVICE *service);
static void shutdown_monitor(DCB *dcb, MONITOR *monitor);

/**
 * The subcommands of the shutdown command
 */
struct subcommand shutdownoptions[] = {
	{ "maxscale",
          0,
          shutdown_server,
          "Shutdown MaxScale",
          "Shutdown MaxScale",
          {0, 0, 0}
        },
	{
            "monitor",
            1,
            shutdown_monitor,
            "Shutdown a monitor, e.g. shutdown monitor 0x48381e0",
            "Shutdown a monitor, e.g. shutdown monitor 0x48381e0",
            {ARG_TYPE_MONITOR, 0, 0}
        },
	{
            "service",
            1,
            shutdown_service,
            "Shutdown a service, e.g. shutdown service \"Sales Database\"",
            "Shutdown a service, e.g. shutdown service 0x4838320 or shutdown service \"Sales Database\"",
            {ARG_TYPE_SERVICE, 0, 0}
        },
	{
            NULL,
            0,
            NULL,
            NULL,
            NULL,
            {0, 0, 0}
        }
};


static void restart_service(DCB *dcb, SERVICE *service);
static void restart_monitor(DCB *dcb, MONITOR *monitor);
/**
 * The subcommands of the restart command
 */
struct subcommand restartoptions[] = {
	{ "monitor",	1, restart_monitor,
		"Restart a monitor, e.g. restart monitor 0x48181e0",
		"Restart a monitor, e.g. restart monitor 0x48181e0",
				{ARG_TYPE_MONITOR, 0, 0} },
	{ "service",	1, restart_service,
		"Restart a service, e.g. restart service \"Test Service\"",
		"Restart a service, e.g. restart service 0x4838320",
				{ARG_TYPE_SERVICE, 0, 0} },
	{ NULL,		0, NULL,		NULL, NULL,
				{0, 0, 0} }
};

static void set_server(DCB *dcb, SERVER *server, char *bit);
static void set_pollsleep(DCB *dcb, int);
static void set_nbpoll(DCB *dcb, int);
/**
 * The subcommands of the set command
 */
struct subcommand setoptions[] = {
	{ "server",	2, set_server,
		"Set the status of a server. E.g. set server dbnode4 master",
		"Set the status of a server. E.g. set server 0x4838320 master",
				{ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} },
	{ "pollsleep",	1, set_pollsleep,
		"Set the maximum poll sleep period in milliseconds",
		"Set the maximum poll sleep period in milliseconds",
				{ARG_TYPE_NUMERIC, 0, 0} },
	{ "nbpolls",	1, set_nbpoll,
		"Set the number of non-blocking polls",
		"Set the number of non-blocking polls",
				{ARG_TYPE_NUMERIC, 0, 0} },

	{ NULL,		0, NULL,		NULL,	NULL,
				{0, 0, 0} }
};

static void clear_server(DCB *dcb, SERVER *server, char *bit);
/**
 * The subcommands of the clear command
 */
struct subcommand clearoptions[] = {
	{ "server",	2, clear_server,
		"Clear the status of a server. E.g. clear server dbnode2 master",
		"Clear the status of a server. E.g. clear server 0x4838320 master",
				{ARG_TYPE_SERVER, ARG_TYPE_STRING, 0} },
	{ NULL,		0, NULL,		NULL,	NULL,
				{0, 0, 0} }
};

static void reload_dbusers(DCB *dcb, SERVICE *service);
static void reload_config(DCB *dcb);

/**
 * The subcommands of the reload command
 */
struct subcommand reloadoptions[] = {
	{ "config",	0, reload_config,
		"Reload the configuration data for MaxScale.",
		"Reload the configuration data for MaxScale.",
				{0, 0, 0} },
	{ "dbusers",	1, reload_dbusers,
		"Reload the dbuser data for a service. E.g. reload dbusers \"splitter service\"",
		"Reload the dbuser data for a service. E.g. reload dbusers 0x849420",
				{ARG_TYPE_SERVICE, 0, 0} },
	{ NULL,		0, NULL,		NULL,	NULL,
				{0, 0, 0} }
};

static void enable_log_action(DCB *, char *);
static void disable_log_action(DCB *, char *);
static void enable_sess_log_action(DCB *dcb, char *arg1, char *arg2);
static void disable_sess_log_action(DCB *dcb, char *arg1, char *arg2);
static void enable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor);
static void disable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor);
static void enable_service_root(DCB *dcb, SERVICE *service);
static void disable_service_root(DCB *dcb, SERVICE *service);

/**
 *  * The subcommands of the enable command
 *   */
struct subcommand enableoptions[] = {
        {
                "heartbeat",
                1,
                enable_monitor_replication_heartbeat,
                "Enable the monitor replication heartbeat, pass a monitor name as argument",
                "Enable the monitor replication heartbeat, pass a monitor name as argument",
                {ARG_TYPE_MONITOR, 0, 0}
        },
        {
                "log",
                1,
                enable_log_action,
                "Enable Log options for MaxScale, options trace | error | "
                "message E.g. enable log message.",
                "Enable Log options for MaxScale, options trace | error | "
                "message E.g. enable log message.",
                {ARG_TYPE_STRING, 0, 0}
        },
		{
                "sessionlog",
                2,
                enable_sess_log_action,
				"Enable Log options for a single session. Usage: enable sessionlog [trace | error | "
				"message | debug] <session id>\t E.g. enable sessionlog message 123.",
				"Enable Log options for a single session. Usage: enable sessionlog [trace | error | "
				"message | debug] <session id>\t E.g. enable sessionlog message 123.",
                {ARG_TYPE_STRING, ARG_TYPE_STRING, 0}
        },
        {
                "root",
                1,
                enable_service_root,
                "Enable root access to a service, pass a service name to enable root access",
                "Enable root access to a service, pass a service name to enable root access",
                {ARG_TYPE_SERVICE, 0, 0}
        },
        {
                NULL,
                0,
                NULL,
                NULL,
                NULL,
                {0, 0, 0}
        }
};



/**
 *  * The subcommands of the disable command
 *   */
struct subcommand disableoptions[] = {
        {
                "heartbeat",
                1,
                disable_monitor_replication_heartbeat,
                "Disable the monitor replication heartbeat",
                "Disable the monitor replication heartbeat",
                {ARG_TYPE_MONITOR, 0, 0}
        },
	{
		"log",
		1,
		disable_log_action,
		"Disable Log for MaxScale, Options: debug | trace | error | message "
		"E.g. disable log debug",
		"Disable Log for MaxScale, Options: debug | trace | error | message "
		"E.g. disable log debug",
		{ARG_TYPE_STRING, 0, 0}
    	},
		{
			"sessionlog",
			2,
			disable_sess_log_action,
			"Disable Log options for a single session. Usage: disable sessionlog [trace | error | "
			"message | debug] <session id>\t E.g. disable sessionlog message 123.",
			"Disable Log options for a single session. Usage: disable sessionlog [trace | error | "
			"message | debug] <session id>\t E.g. disable sessionlog message 123.",
			{ARG_TYPE_STRING, ARG_TYPE_STRING, 0}
        },
        {
                "root",
                1,
                disable_service_root,
                "Disable root access to a service",
                "Disable root access to a service",
                {ARG_TYPE_SERVICE, 0, 0}
        },
    	{
            NULL,
            0,
            NULL,
            NULL,
            NULL,
            {0, 0, 0}
    	}
};

#if defined(FAKE_CODE)

static void fail_backendfd(void);
static void fail_clientfd(void);
static void fail_accept(DCB* dcb, char* arg1, char* arg2);
/**
 *  * The subcommands of the fail command
 *   */
struct subcommand failoptions[] = {
    {
        "backendfd",
        0,
        fail_backendfd,
        "Fail backend socket for next operation.",
        "Fail backend socket for next operation.",
        {ARG_TYPE_STRING, 0, 0}
    },
    {
        "clientfd",
        0,
        fail_clientfd,
        "Fail client socket for next operation.",
        "Fail client socket for next operation.",
        {ARG_TYPE_STRING, 0, 0}
    },
    {
        "accept",
        2,
        fail_accept,
        "Fail to accept next client connection.",
        "Fail to accept next client connection.",
        {ARG_TYPE_STRING, ARG_TYPE_STRING, 0}
    },
    {
        NULL,
        0,
        NULL,
        NULL,
        NULL,
        {0, 0, 0}
    }
};
#endif /* FAKE_CODE */

static void telnetdAddUser(DCB *, char *, char *);
/**
 * The subcommands of the add command
 */
struct subcommand addoptions[] = {
	{ "user",	2, telnetdAddUser,
		"Add a new user for the debug interface. E.g. add user john today",
		"Add a new user for the debug interface. E.g. add user john today",
				{ARG_TYPE_STRING, ARG_TYPE_STRING, 0} },
	{ NULL,		0, NULL,		NULL,	NULL,
				{0, 0, 0} }
};


static void telnetdRemoveUser(DCB *, char *, char *);
/**
 * The subcommands of the remove command
 */
struct subcommand removeoptions[] = {
	{
            "user",
            2,
            telnetdRemoveUser,
            "Remove existing maxscale user. Example : remove user john johnpwd",
            "Remove existing maxscale user. Example : remove user john johnpwd",
            {ARG_TYPE_STRING, ARG_TYPE_STRING, 0}
        },
	{
            NULL, 0, NULL, NULL, NULL, {0, 0, 0}
        }
};

/**
 * User command to flush a single logfile
 *
 * @param pdcb		The stream to write output to
 * @param logname	The name of the log
 */
static void
flushlog(DCB *pdcb, char *logname)
{
	if (logname == NULL)
	{
	}
	else if (!strcasecmp(logname, "error"))
	{
		skygw_log_rotate(LOGFILE_ERROR);
	}
	else if (!strcasecmp(logname, "message"))
	{
		skygw_log_rotate(LOGFILE_MESSAGE);
	}
	else if (!strcasecmp(logname, "trace"))
	{
		skygw_log_rotate(LOGFILE_TRACE);
	}
	else if (!strcasecmp(logname, "debug"))
	{
		skygw_log_rotate(LOGFILE_DEBUG);
	}
	else
	{
		dcb_printf(pdcb, "Unexpected logfile name, expected "
			"error, message, trace oe debug.\n");
	}
}

/**
 * User command to flush all logfiles
 *
 * @param pdcb		The stream to write output to
 */
static void
flushlogs(DCB *pdcb)
{
	skygw_log_rotate(LOGFILE_ERROR);
	skygw_log_rotate(LOGFILE_MESSAGE);
	skygw_log_rotate(LOGFILE_TRACE);
	skygw_log_rotate(LOGFILE_DEBUG);
}


/**
 * The subcommands of the flush command
 */
struct subcommand flushoptions[] = {
	{
            "log",
            1,
            flushlog,
	    "Flush the content of a log file, close that log, rename it and open a new log file",
	    "Flush the content of a log file, close that log, rename it and open a new log file",
            {ARG_TYPE_STRING, 0, 0}
        },
	{
            "logs",
            0,
            flushlogs,
	    "Flush the content of all log files, close that logs, rename them and open a new log files",
	    "Flush the content of all log files, close that logs, rename them and open a new log files",
            {0, 0, 0}
        },
	{
            NULL, 0, NULL, NULL, NULL, {0, 0, 0}
        }
};


/**
 * The debug command table
 */
static struct {
	char			*cmd;
	struct	subcommand	*options;
} cmds[] = {
	{ "add",	addoptions },
	{ "clear",	clearoptions },
	{ "disable",    disableoptions },
	{ "enable",     enableoptions },
#if defined(FAKE_CODE)
        { "fail",       failoptions },
#endif /* FAKE_CODE */
	{ "flush",	flushoptions },
	{ "list",	listoptions },
	{ "reload",	reloadoptions },
        { "remove",     removeoptions },
	{ "restart",	restartoptions },
	{ "set",	setoptions },
	{ "show",	showoptions },
	{ "shutdown",	shutdownoptions },
	{ NULL,		NULL	}
};


/**
 * Convert a string argument to a numeric, observing prefixes
 * for number bases, e.g. 0x for hex, 0 for octal
 *
 * @param mode		The CLI mode
 * @param arg		The string representation of the argument
 * @param arg_type	The target type for the argument
 * @return The argument as a long integer
 */
static unsigned long
convert_arg(int mode, char *arg, int arg_type)
{
unsigned long	rval;
SERVICE		*service;

	switch (arg_type)
	{
	case ARG_TYPE_ADDRESS:
		return (unsigned long)strtol(arg, NULL, 0);
	case ARG_TYPE_STRING:
		return (unsigned long)arg;
	case ARG_TYPE_SERVICE:
		if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0)
			rval = (unsigned long)service_find(arg);
		return rval;
	case ARG_TYPE_SERVER:
		if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0)
			rval = (unsigned long)server_find_by_unique_name(arg);
		return rval;
	case ARG_TYPE_DBUSERS:
		if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0)
		{
			service = service_find(arg);
			if (service)
				return (unsigned long)(service->users);
			else
				return 0;
		}
		return rval;
	case ARG_TYPE_DCB:
		rval = (unsigned long)strtol(arg, NULL, 0);
		if (mode == CLIM_USER && dcb_isvalid((DCB *)rval) == 0)
			rval = 0;
		return rval;
	case ARG_TYPE_SESSION:
		rval = (unsigned long)strtol(arg, NULL, 0);
		if (mode == CLIM_USER && session_isvalid((SESSION *)rval) == 0)
			rval = 0;
		return rval;
	case ARG_TYPE_MONITOR:
		if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0)
			rval = (unsigned long)monitor_find(arg);
		return rval;
	case ARG_TYPE_FILTER:
		if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0)
			rval = (unsigned long)filter_find(arg);
		return rval;
	case ARG_TYPE_NUMERIC:
		{
			int i;
			for (i = 0; arg[i]; i++)
			{
				if (arg[i] < '0' || arg[i] > '9')
					return 0;
			}
			return atoi(arg);
		}
	}
	return 0;
}

/**
 * We have a complete line from the user, lookup the commands and execute them
 *
 * Commands are tokenised based on white space and then the first
 * word is checked againts the cmds table. If a match is found the
 * second word is compared to the different options for that command.
 *
 * Commands may also take up to 3 additional arguments, these are all
 * assumed to the numeric values and will be converted before being passed
 * to the handler function for the command.
 *
 * @param cli		The CLI_SESSION
 * @return	Returns 0 if the interpreter should exit
 */
int
execute_cmd(CLI_SESSION *cli)
{
DCB		*dcb = cli->session->client;
int		argc, i, j, found = 0;
char		*args[MAXARGS + 1];
unsigned long	arg1, arg2, arg3;
int		in_quotes = 0, escape_next = 0;
char		*ptr, *lptr;
bool in_space = false;

	args[0] = cli->cmdbuf;
	ptr = args[0];
	lptr = ptr;
	i = 0;
	/*
	 * Break the command line into a number of words. Whitespace is used
	 * to delimit words and may be escaped by use of the \ character or
	 * the use of double quotes.
	 * The array args contains the broken down words, one per index.
	 */

	while (*ptr)
	{
		if (escape_next)
		{
			*lptr++ = *ptr++;
			escape_next = 0;
		}
		else if (*ptr == '\\')
		{
			escape_next = 1;
			ptr++;
		}
		else if (in_quotes == 0 && ((in_space = *ptr == ' ') || *ptr == '\t' || *ptr == '\r' || *ptr == '\n'))
		{

			*lptr = 0;

			if(!in_space){
				break;
			}

			if (args[i] == ptr)
				args[i] = ptr + 1;
			else
			{
				i++;
				if (i >= MAXARGS-1)
					break;
				args[i] = ptr + 1;
			}
			ptr++;
			lptr++;
		}
		else if (*ptr == '\"' && in_quotes == 0)
		{
			in_quotes = 1;
			ptr++;
		}
		else if (*ptr == '\"' && in_quotes == 1)
		{
			in_quotes = 0;
			ptr++;
		}
		else
		{
			*lptr++ = *ptr++;
		}
	}
	*lptr = 0;
	args[i+1] = NULL;

	if (args[0] == NULL || *args[0] == 0)
		return 1;
	for (i = 0; args[i] && *args[i]; i++)
		;
	argc = i - 2;	/* The number of extra arguments to commands */
	

	if (!strcasecmp(args[0], "help"))
	{
		if (args[1] == NULL || *args[1] == 0)
		{
			found = 1;
			dcb_printf(dcb, "Available commands:\n");
			for (i = 0; cmds[i].cmd; i++)
			{
				if (cmds[i].options[1].arg1 == NULL)
					dcb_printf(dcb, "    %s %s\n", cmds[i].cmd, cmds[i].options[0].arg1);
				else
				{
					dcb_printf(dcb, "    %s [", cmds[i].cmd);
					for (j = 0; cmds[i].options[j].arg1; j++)
					{
						dcb_printf(dcb, "%s%s", cmds[i].options[j].arg1,
							cmds[i].options[j+1].arg1 ? "|" : "");
					}
					dcb_printf(dcb, "]\n");
				}
			}
			dcb_printf(dcb, "\nType help command to see details of each command.\n");
			dcb_printf(dcb, "Where commands require names as arguments and these names contain\n");
			dcb_printf(dcb, "whitespace either the \\ character may be used to escape the whitespace\n");
			dcb_printf(dcb, "or the name may be enclosed in double quotes \".\n\n");
		}
		else
		{
			for (i = 0; cmds[i].cmd; i++)
			{
				if (!strcasecmp(args[1], cmds[i].cmd))
				{
					found = 1;
					dcb_printf(dcb, "Available options to the %s command:\n", args[1]);
					for (j = 0; cmds[i].options[j].arg1; j++)
					{
						dcb_printf(dcb, "    %-10s %s\n", cmds[i].options[j].arg1,
										cmds[i].options[j].help);
					}
				}
			}
			if (found == 0)
			{
				dcb_printf(dcb, "No command %s to offer help with\n", args[1]);
			}
		}
		found = 1;
	}
	else if (!strcasecmp(args[0], "quit"))
	{
		return 0;
	}
	else if (argc >= 0)
	{
		for (i = 0; cmds[i].cmd; i++)
		{
			if (strcasecmp(args[0], cmds[i].cmd) == 0)
			{
				for (j = 0; cmds[i].options[j].arg1; j++)
				{
					if (strcasecmp(args[1], cmds[i].options[j].arg1) == 0)
					{
                                        	found = 1; /**< command and sub-command match */
						if (argc != cmds[i].options[j].n_args)
						{
							dcb_printf(dcb, "Incorrect number of arguments: %s %s expects %d arguments\n",
								cmds[i].cmd, cmds[i].options[j].arg1,
								cmds[i].options[j].n_args);
							
						}
						else
						{
							switch (cmds[i].options[j].n_args)
							{
							case 0:
								cmds[i].options[j].fn(dcb);
								break;
							case 1:
								arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]);
								if (arg1)
									cmds[i].options[j].fn(dcb, arg1);
								else
									dcb_printf(dcb, "Invalid argument: %s\n",
										args[2]);
								break;
							case 2:
								arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]);
								arg2 = convert_arg(cli->mode, args[3],cmds[i].options[j].arg_types[1]);
								if (arg1 && arg2)
									cmds[i].options[j].fn(dcb, arg1, arg2);
								else if (arg1 == 0)
									dcb_printf(dcb, "Invalid argument: %s\n",
										args[2]);
								else
									dcb_printf(dcb, "Invalid argument: %s\n",
										args[3]);
								break;
							case 3:
								arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]);
								arg2 = convert_arg(cli->mode, args[3],cmds[i].options[j].arg_types[1]);
								arg3 = convert_arg(cli->mode, args[4],cmds[i].options[j].arg_types[2]);
								if (arg1 && arg2 && arg3)
									cmds[i].options[j].fn(dcb, arg1, arg2, arg3);
								else if (arg1 == 0)
									dcb_printf(dcb, "Invalid argument: %s\n",
										args[2]);
								else if (arg2 == 0)
									dcb_printf(dcb, "Invalid argument: %s\n",
										args[3]);
								else if (arg3 == 0)
									dcb_printf(dcb, "Invalid argument: %s\n",
										args[4]);
							}
						}
					}
				}
				if (!found)
				{
					dcb_printf(dcb,
						"Unknown or missing option for the %s command. Valid sub-commands are:\n",
							cmds[i].cmd);
					for (j = 0; cmds[i].options[j].arg1; j++)
					{
						dcb_printf(dcb, "    %-10s %s\n", cmds[i].options[j].arg1,
										cmds[i].options[j].help);
					}
					found = 1;
				}
			}
		}
	}
	else if (argc == -1)
	{
		dcb_printf(dcb,
			"Commands must consist of at least two words. Type help for a list of commands\n");
		found = 1;
	}
	if (!found)
		dcb_printf(dcb,
			"Command '%s' not known, type help for a list of available commands\n", args[0]);
	memset(cli->cmdbuf, 0, 80);

	return 1;
}

/**
 * Debug command to stop a service
 *
 * @param dcb		The DCB to print any output to
 * @param service	The service to shutdown
 */
static void
shutdown_service(DCB *dcb, SERVICE *service)
{
	serviceStop(service);
}

/**
 * Debug command to restart a stopped service
 *
 * @param dcb		The DCB to print any output to
 * @param service	The service to restart
 */
static void
restart_service(DCB *dcb, SERVICE *service)
{
	serviceRestart(service);
}

static struct {
	char		*str;
	unsigned int	bit;
} ServerBits[] = {
	{ "running", 		SERVER_RUNNING },
	{ "master",		SERVER_MASTER },
	{ "slave",		SERVER_SLAVE },
	{ "synced",		SERVER_JOINED },
	{ "ndb",		SERVER_NDB },
	{ "maintenance",	SERVER_MAINT },
	{ "maint",		SERVER_MAINT },
	{ NULL,			0 }
};
/**
 * Map the server status bit
 *
 * @param str	String representation
 * @return bit value or 0 on error
 */
static unsigned int
server_map_status(char *str)
{
int i;

	for (i = 0; ServerBits[i].str; i++)
		if (!strcasecmp(str, ServerBits[i].str))
			return ServerBits[i].bit;
	return 0;
}

/**
 * Set the status bit of a server
 *
 * @param dcb		DCB to send output to
 * @param server	The server to set the status of
 * @param bit		String representation of the status bit
 */
static void
set_server(DCB *dcb, SERVER *server, char *bit)
{
unsigned int bitvalue;

	if ((bitvalue = server_map_status(bit)) != 0)
		server_set_status(server, bitvalue);
	else
		dcb_printf(dcb, "Unknown status bit %s\n", bit);
}


/**
 * Clear the status bit of a server
 *
 * @param dcb		DCB to send output to
 * @param server	The server to set the status of
 * @param bit		String representation of the status bit
 */
static void
clear_server(DCB *dcb, SERVER *server, char *bit)
{
unsigned int bitvalue;

	if ((bitvalue = server_map_status(bit)) != 0)
		server_clear_status(server, bitvalue);
	else
		dcb_printf(dcb, "Unknown status bit %s\n", bit);
}

/**
 * Reload the authenticaton data from the backend database of a service.
 *
 * @param dcb		DCB to send output
 * @param service	The service to update
 */
static void
reload_dbusers(DCB *dcb, SERVICE *service)
{
	dcb_printf(dcb, "Loaded %d database users for service %s.\n",
			reload_mysql_users(service), service->name);
}

/**
 * Relaod the configuration data from the config file
 *
 * @param dcb		DCB to use to send output
 */
static void
reload_config(DCB *dcb)
{
	dcb_printf(dcb, "Reloading configuration from file.\n");
	config_reload();
}

/**
 * Add a new maxscale admin user
 *
 * @param dcb		The DCB for messages
 * @param user		The user name
 * @param passwd	The Password of the user
 */
static void
telnetdAddUser(DCB *dcb, char *user, char *passwd)
{
char	*err;

	if (admin_search_user(user))
	{
		dcb_printf(dcb, "User %s already exists.\n", user);
		return;
	}
	if ((err = admin_add_user(user, passwd)) == NULL)
		dcb_printf(dcb, "User %s has been successfully added.\n", user);
	else
		dcb_printf(dcb, "Failed to add new user. %s\n", err);
}


/**
 * Remove a maxscale admin user
 *
 * @param dcb		The DCB for messages
 * @param user		The user name
 * @param passwd	The Password of the user
 */
static void telnetdRemoveUser(
        DCB*  dcb,
        char* user,
        char* passwd)
{
        char* err;

	if (!admin_search_user(user))
        {
            dcb_printf(dcb, "User %s doesn't exist.\n", user);
            return;
        }
        
	if ((err = admin_remove_user(user, passwd)) == NULL)
        {
            dcb_printf(dcb, "User %s has been successfully removed.\n", user);
        }
        else
        {
            dcb_printf(dcb, "Failed to remove user %s. %s\n", user, err);
        }
}



/**
 * Print the adminsitration users
 *
 * @param dcb	The DCB to print the user data to
 */
static void
telnetdShowUsers(DCB *dcb)
{
	dcb_printf(dcb, "Administration interface users:\n");
	dcb_PrintAdminUsers(dcb);
}

/**
 * Command to shutdown a running monitor
 *
 * @param dcb	The DCB to use to print messages
 * @param monitor	The monitor to shutdown
 */
static void
shutdown_monitor(DCB *dcb, MONITOR *monitor)
{
	monitorStop(monitor);
}

/**
 * Command to restart a stopped monitor
 *
 * @param dcb	The DCB to use to print messages
 * @param monitor	The monitor to restart
 */
static void
restart_monitor(DCB *dcb, MONITOR *monitor)
{
	monitorStart(monitor);
}

/**
 * Enable replication heartbeat for a monitor
 *
 * @param dcb		Connection to user interface
 * @param monitor	The monitor
 */
static void
enable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor)
{
	monitorSetReplicationHeartbeat(monitor, 1);
}

/**
 * Disable replication heartbeat for a monitor
 *
 * @param dcb		Connection to user interface
 * @param monitor	The monitor
 */
static void
disable_monitor_replication_heartbeat(DCB *dcb, MONITOR *monitor)
{
	monitorSetReplicationHeartbeat(monitor, 0);
}

/**
 * Enable root access to a service
 *
 * @param dcb		Connection to user interface
 * @param service	The service
 */
static void
enable_service_root(DCB *dcb, SERVICE *service)
{
	serviceEnableRootUser(service, 1);
}

/**
 * Disable root access to a service
 *
 * @param dcb		Connection to user interface
 * @param service	The service
 */
static void
disable_service_root(DCB *dcb, SERVICE *service)
{
	serviceEnableRootUser(service, 0);
}

/**
 * Enables a log for a single session
 * @param session The session in question
 * @param dcb Client DCB
 * @param type Which log to enable
 */
static void enable_sess_log_action(DCB *dcb, char *arg1, char *arg2)
{
	logfile_id_t type;
	size_t id = 0;
	int max_len = strlen("message");
	SESSION* session = get_all_sessions();

	ss_dassert(arg1 != NULL && arg2 != NULL && session != NULL);

	if (strncmp(arg1, "debug", max_len) == 0) {
		type = LOGFILE_DEBUG;
	} else if (strncmp(arg1, "trace", max_len) == 0) {
		type = LOGFILE_TRACE;
	} else if (strncmp(arg1, "error", max_len) == 0) {
		type = LOGFILE_ERROR;
	} else if (strncmp(arg1, "message", max_len) == 0) {
		type = LOGFILE_MESSAGE;
	} else {
		dcb_printf(dcb, "%s is not supported for enable log\n", arg1);
		return ;
	}
   
	id = (size_t)strtol(arg2,0,0);

	while(session)
		{
			if(session->ses_id == id)
				{
					session_enable_log(session,type);
					return;
				}
			session = session->next;
		}

	dcb_printf(dcb, "Session not found: %s\n", arg2);
}

/**
 * Disables a log for a single session
 * @param session The session in question
 * @param dcb Client DCB
 * @param type Which log to disable
 */
static void disable_sess_log_action(DCB *dcb, char *arg1, char *arg2)
{
	logfile_id_t type;
	int id = 0;
	int max_len = strlen("message");
	SESSION* session = get_all_sessions();

	ss_dassert(arg1 != NULL && arg2 != NULL && session != NULL);

	if (strncmp(arg1, "debug", max_len) == 0) {
		type = LOGFILE_DEBUG;
	} else if (strncmp(arg1, "trace", max_len) == 0) {
		type = LOGFILE_TRACE;
	} else if (strncmp(arg1, "error", max_len) == 0) {
		type = LOGFILE_ERROR;
	} else if (strncmp(arg1, "message", max_len) == 0) {
		type = LOGFILE_MESSAGE;
	} else {
		dcb_printf(dcb, "%s is not supported for disable log\n", arg1);
		return ;
        }
      
	id = (size_t)strtol(arg2,0,0);

	while(session)
		{
			if(session->ses_id == id)
				{
					session_disable_log(session,type);
					return;
				}
			session = session->next;
		}

	dcb_printf(dcb, "Session not found: %s\n", arg2);
}

/**
 * The log enable action
 */

static void enable_log_action(DCB *dcb, char *arg1) {
        logfile_id_t type;
        int max_len = strlen("message");

        if (strncmp(arg1, "debug", max_len) == 0) {
                type = LOGFILE_DEBUG;
        } else if (strncmp(arg1, "trace", max_len) == 0) {
                type = LOGFILE_TRACE;
        } else if (strncmp(arg1, "error", max_len) == 0) {
                type = LOGFILE_ERROR;
        } else if (strncmp(arg1, "message", max_len) == 0) {
                type = LOGFILE_MESSAGE;
        } else {
                dcb_printf(dcb, "%s is not supported for enable log\n", arg1);
                return ;
        }
		
        skygw_log_enable(type);
}

/**
 * The log disable action
 */

static void disable_log_action(DCB *dcb, char *arg1) {
        logfile_id_t type;
        int max_len = strlen("message");

        if (strncmp(arg1, "debug", max_len) == 0) {
                type = LOGFILE_DEBUG;
        } else if (strncmp(arg1, "trace", max_len) == 0) {
                type = LOGFILE_TRACE;
        } else if (strncmp(arg1, "error", max_len) == 0) {
                type = LOGFILE_ERROR;
        } else if (strncmp(arg1, "message", max_len) == 0) {
                type = LOGFILE_MESSAGE;
        } else {
                dcb_printf(dcb, "%s is not supported for disable log\n", arg1);
                return ;
        }

        skygw_log_disable(type);
}

/**
 * Set the duration of the sleep passed to the poll wait
 *
 * @param	dcb		DCB for output
 * @param	sleeptime	Sleep time in milliseconds
 */
static void
set_pollsleep(DCB *dcb, int sleeptime)
{
	poll_set_maxwait(sleeptime);
}

/**
 * Set the number of non-blockign spins to make
 *
 * @param	dcb		DCB for output
 * @param	nb		Number of spins
 */
static void
set_nbpoll(DCB *dcb, int nb)
{
	poll_set_nonblocking_polls(nb);
}

#if defined(FAKE_CODE)
static void fail_backendfd(void)
{ 
        fail_next_backend_fd = true;
}

static void fail_clientfd(void)
{ 
        fail_next_client_fd = true;
}

static void fail_accept(
        DCB*  dcb,
        char* arg1,
        char* arg2)
{
        int failcount = MIN(atoi(arg2), 100);
        fail_accept_errno = atoi(arg1);


        switch(fail_accept_errno) {
                case EAGAIN:
//                case EWOULDBLOCK:
                case EBADF:
                case EINTR:
                case EINVAL:
                case EMFILE:
                case ENFILE:
                case ENOTSOCK:
                case EOPNOTSUPP:
                case ENOBUFS:
                case ENOMEM:
                case EPROTO:
                        fail_next_accept = failcount;
        break;

                default:
                        dcb_printf(dcb,
                                   "[%d, %s] is not valid errno for accept.\n",
                                   fail_accept_errno,
                                   strerror(fail_accept_errno));
                return ;
        }
}
#endif /* FAKE_CODE */
