diff --git a/server/core/dcb.c b/server/core/dcb.c index 563ea6f8f..52a9bbfc9 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -1606,3 +1606,31 @@ DCB_CALLBACK *cb, *nextcb; } spinlock_release(&dcb->cb_lock); } + +/** + * Check the passed DCB to ensure it is in the list of allDCBS + * + * @param DCB The DCB to check + * @return 1 if the DCB is in the list, otherwise 0 + */ +int +dcb_isvalid(DCB *dcb) +{ +DCB *ptr; +int rval = 0; + + spinlock_acquire(&dcbspin); + ptr = allDCBs; + while (ptr) + { + if (ptr == dcb) + { + rval = 1; + break; + } + ptr = ptr->next; + } + spinlock_release(&dcbspin); + + return rval; +} diff --git a/server/core/monitor.c b/server/core/monitor.c index 6c028cad6..cee2f2d9e 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -197,3 +197,26 @@ MONITOR *ptr; } spinlock_release(&monLock); } + +/** + * Find a monitor by name + * + * @param name The name of the monitor + * @return Pointer to the monitor or NULL + */ +MONITOR * +monitor_find(char *name) +{ +MONITOR *ptr; + + spinlock_acquire(&monLock); + ptr = allMonitors; + while (ptr) + { + if (!strcmp(ptr->name, name)) + break; + ptr = ptr->next; + } + spinlock_release(&monLock); + return ptr; +} diff --git a/server/core/service.c b/server/core/service.c index e44b9c697..93db01c83 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -29,6 +29,7 @@ * 25/02/14 Massimiliano Pinto Added: service refresh limit feature * 28/02/14 Massimiliano Pinto users_alloc moved from service_alloc to serviceStartPort (generic hashable for services) * 07/05/14 Massimiliano Pinto Added: version_string initialized to NULL + * 23/05/14 Mark Riddoch Addition of service validation call * * @endverbatim */ @@ -114,6 +115,33 @@ SERVICE *service; return service; } +/** + * Check to see if a service pointer is valid + * + * @param service The poitner to check + * @return 1 if the service is in the list of all services + */ +int +service_isvalid(SERVICE *service) +{ +SERVICE *ptr; +int rval = 0; + + spinlock_acquire(&service_spin); + ptr = allServices; + while (ptr) + { + if (ptr == service) + { + rval = 1; + break; + } + ptr = ptr->next; + } + spinlock_release(&service_spin); + return rval; +} + /** * Start an individual port/protocol pair * diff --git a/server/core/session.c b/server/core/session.c index 1e0d49771..bb039edf5 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -269,6 +269,34 @@ return_succp : return succp; } +/** + * Check to see if a session is valid, i.e. in the list of all sessions + * + * @param session Session to check + * @return 1 if the session is valid otherwise 0 + */ +int +session_isvalid(SESSION *session) +{ +SESSION *ptr; +int rval = 0; + + spinlock_acquire(&session_spin); + ptr = allSessions; + while (ptr) + { + if (ptr == session) + { + rval = 1; + break; + } + ptr = ptr->next; + } + spinlock_release(&session_spin); + + return rval; +} + /** * Print details of an individual session * diff --git a/server/include/dcb.h b/server/include/dcb.h index 625ae6026..6b9ef093d 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -273,6 +273,7 @@ int dcb_add_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON, void void *); int dcb_remove_callback(DCB *, DCB_REASON, int (*)(struct dcb *, DCB_REASON), void *); +int dcb_isvalid(DCB *); /* Check the DCB is in the linked list */ bool dcb_set_state( DCB* dcb, diff --git a/server/include/monitor.h b/server/include/monitor.h index 6aa0f2430..6444fecd5 100644 --- a/server/include/monitor.h +++ b/server/include/monitor.h @@ -29,6 +29,7 @@ * Date Who Description * 07/07/13 Mark Riddoch Initial implementation * 25/07/13 Mark Riddoch Addition of diagnotics + * 23/05/14 Mark Riddoch Addition of routine to find monitors by name * * @endverbatim */ @@ -79,6 +80,7 @@ typedef struct monitor { extern MONITOR *monitor_alloc(char *, char *); extern void monitor_free(MONITOR *); +extern MONITOR *monitor_find(char *); extern void monitorAddServer(MONITOR *, SERVER *); extern void monitorAddUser(MONITOR *, char *, char *); extern void monitorStop(MONITOR *); diff --git a/server/include/service.h b/server/include/service.h index 85fe3972d..c7b9126f0 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -129,6 +129,7 @@ typedef struct service { extern SERVICE *service_alloc(char *, char *); extern int service_free(SERVICE *); extern SERVICE *service_find(char *); +extern int service_isvalid(SERVICE *); extern int serviceAddProtocol(SERVICE *, char *, char *, unsigned short); extern int serviceHasProtocol(SERVICE *, char *, unsigned short); extern void serviceAddBackend(SERVICE *, SERVER *); diff --git a/server/include/session.h b/server/include/session.h index 6cafa160d..c9f9f673c 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -87,6 +87,7 @@ typedef struct session { SESSION *session_alloc(struct service *, struct dcb *); bool session_free(SESSION *); +int session_isvalid(SESSION *); void printAllSessions(); void printSession(SESSION *); void dprintAllSessions(struct dcb *); diff --git a/server/modules/include/debugcli.h b/server/modules/include/debugcli.h index 0e7afcfe2..b373cae6b 100644 --- a/server/modules/include/debugcli.h +++ b/server/modules/include/debugcli.h @@ -41,6 +41,7 @@ struct cli_session; typedef struct cli_instance { SPINLOCK lock; /*< The instance spinlock */ SERVICE *service; /*< The debug cli service */ + int mode; /*< CLI interface mode */ struct cli_session *sessions; /*< Linked list of sessions within this instance */ struct cli_instance @@ -53,8 +54,13 @@ typedef struct cli_instance { */ typedef struct cli_session { char cmdbuf[80]; /*< The command buffer used to build up user commands */ + int mode; /*< The CLI Mode for this session */ SESSION *session; /*< The gateway session */ struct cli_session *next; /*< The next pointer for the list of sessions */ } CLI_SESSION; + +/* Command line interface modes */ +#define CLIM_USER 1 +#define CLIM_DEVELOPER 2 #endif diff --git a/server/modules/routing/debugcli.c b/server/modules/routing/debugcli.c index 66c02c98b..d6becdfda 100644 --- a/server/modules/routing/debugcli.c +++ b/server/modules/routing/debugcli.c @@ -45,7 +45,7 @@ extern int lm_enabled_logfiles_bitmask; -static char *version_str = "V1.0.1"; +static char *version_str = "V1.1.0"; /* The router entry points */ static ROUTER *createInstance(SERVICE *service, char **options); @@ -127,6 +127,7 @@ static ROUTER * createInstance(SERVICE *service, char **options) { CLI_INSTANCE *inst; +int i; if ((inst = malloc(sizeof(CLI_INSTANCE))) == NULL) return NULL; @@ -134,7 +135,29 @@ CLI_INSTANCE *inst; inst->service = service; spinlock_init(&inst->lock); inst->sessions = NULL; + inst->mode = CLIM_USER; + if (options) + { + for (i = 0; options[i]; i++) + { + if (!strcasecmp(options[i], "developer")) + { + inst->mode = CLIM_DEVELOPER; + } + else if (!strcasecmp(options[i], "user")) + { + inst->mode = CLIM_USER; + } + else + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Unknown option for CLI '%s'\n", + options[i]))); + } + } + } /* * We have completed the creation of the instance data, so now @@ -176,11 +199,15 @@ CLI_SESSION *client; spinlock_release(&inst->lock); session->state = SESSION_STATE_READY; + client->mode = inst->mode; dcb_printf(session->client, "Welcome the SkySQL MaxScale Debug Interface (%s).\n", version_str); - dcb_printf(session->client, "WARNING: This interface is meant for developer usage,\n"); - dcb_printf(session->client, "passing incorrect addresses to commands can endanger your MaxScale server.\n\n"); + if (client->mode == CLIM_DEVELOPER) + { + dcb_printf(session->client, "WARNING: This interface is meant for developer usage,\n"); + dcb_printf(session->client, "passing incorrect addresses to commands can endanger your MaxScale server.\n\n"); + } dcb_printf(session->client, "Type help for a list of available commands.\n\n"); return (void *)client; @@ -281,4 +308,4 @@ static uint8_t getCapabilities( void* router_session) { return 0; -} \ No newline at end of file +} diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index a4fda3bb5..b78620b09 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -39,6 +39,7 @@ * 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 * * @endverbatim */ @@ -73,6 +74,9 @@ #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 /** * The subcommand structure * @@ -83,6 +87,7 @@ struct subcommand { int n_args; void (*fn)(); char *help; + char *devhelp; int arg_types[3]; }; @@ -91,33 +96,59 @@ static void telnetdShowUsers(DCB *); * The subcommands of the show command */ struct subcommand showoptions[] = { - { "dcbs", 0, dprintAllDCBs, "Show all descriptor control blocks (network connections)", + { "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", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "dbusers", 1, dcb_usersPrint, "Show statistics and user names for a service's user table.\n\t\tExample : show dbusers |", + { "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 ", + "Show statistics and user names for a service's user table.\n\t\tExample : show dbusers |", {ARG_TYPE_DBUSERS, 0, 0} }, - { "epoll", 0, dprintPollStats, "Show the poll statistics", + { "epoll", 0, dprintPollStats, + "Show the poll statistics", + "Show the poll statistics", {0, 0, 0} }, - { "modules", 0, dprintAllModules, "Show all currently loaded modules", + { "modules", 0, dprintAllModules, + "Show all currently loaded modules", + "Show all currently loaded modules", {0, 0, 0} }, - { "monitors", 0, monitorShowAll, "Show the monitors that are configured", + { "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 server, e.g. show server 0x485390. The address may also be repalced with the server name form the configuration file", + { "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", + { "servers", 0, dprintAllServers, + "Show all configured servers", + "Show all configured servers", {0, 0, 0} }, - { "services", 0, dprintAllServices, "Show all configured services in MaxScale", + { "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 or address of a service object", + { "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", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "sessions", 0, dprintAllSessions, "Show all active sessions in MaxScale", + { "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} }, - { "users", 0, telnetdShowUsers, "Show statistics and user names for the debug interface", - {ARG_TYPE_ADDRESS, 0, 0} }, - { NULL, 0, NULL, NULL, + { "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} } }; @@ -133,7 +164,7 @@ struct subcommand shutdownoptions[] = { 0, shutdown_server, "Shutdown MaxScale", - + "Shutdown MaxScale", {0, 0, 0} }, { @@ -141,12 +172,14 @@ struct subcommand shutdownoptions[] = { 1, shutdown_monitor, "Shutdown a monitor, e.g. shutdown monitor 0x48381e0", - {ARG_TYPE_ADDRESS, 0, 0} + "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} }, @@ -155,6 +188,7 @@ struct subcommand shutdownoptions[] = { 0, NULL, NULL, + NULL, {0, 0, 0} } }; @@ -166,11 +200,15 @@ 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", - {ARG_TYPE_ADDRESS, 0, 0} }, - { "service", 1, restart_service, "Restart a service, e.g. restart service 0x4838320", + { "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, NULL, NULL, NULL, {0, 0, 0} } }; @@ -179,9 +217,11 @@ static void set_server(DCB *dcb, SERVER *server, char *bit); * The subcommands of the set command */ struct subcommand setoptions[] = { - { "server", 2, set_server, "Set the status of a server. E.g. set server 0x4838320 master", + { "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} }, - { NULL, 0, NULL, NULL, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -190,9 +230,11 @@ 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 0x4838320 master", + { "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, NULL, NULL, NULL, {0, 0, 0} } }; @@ -203,11 +245,15 @@ 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.", + { "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 0x849420", + { "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_DBUSERS, 0, 0} }, - { NULL, 0, NULL, NULL, + { NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -224,6 +270,8 @@ struct subcommand enableoptions[] = { 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} }, { @@ -231,6 +279,7 @@ struct subcommand enableoptions[] = { 0, NULL, NULL, + NULL, {0, 0, 0} } }; @@ -246,6 +295,8 @@ struct subcommand disableoptions[] = { 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} }, { @@ -253,6 +304,7 @@ struct subcommand disableoptions[] = { 0, NULL, NULL, + NULL, {0, 0, 0} } }; @@ -271,6 +323,7 @@ struct subcommand failoptions[] = { 0, fail_backendfd, "Fail backend socket for next operation.", + "Fail backend socket for next operation.", {ARG_TYPE_STRING, 0, 0} }, { @@ -278,6 +331,7 @@ struct subcommand failoptions[] = { 0, fail_clientfd, "Fail client socket for next operation.", + "Fail client socket for next operation.", {ARG_TYPE_STRING, 0, 0} }, { @@ -285,6 +339,7 @@ struct subcommand failoptions[] = { 2, fail_accept, "Fail to accept next client connection.", + "Fail to accept next client connection.", {ARG_TYPE_STRING, ARG_TYPE_STRING, 0} }, { @@ -292,6 +347,7 @@ struct subcommand failoptions[] = { 0, NULL, NULL, + NULL, {0, 0, 0} } }; @@ -302,9 +358,11 @@ 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", + { "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, NULL, NULL, NULL, {0, 0, 0} } }; @@ -319,10 +377,11 @@ struct subcommand removeoptions[] = { 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, {0, 0, 0} + NULL, 0, NULL, NULL, NULL, {0, 0, 0} } }; @@ -355,12 +414,13 @@ static struct { * 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(char *arg, int arg_type) +convert_arg(int mode, char *arg, int arg_type) { unsigned long rval; SERVICE *service; @@ -372,15 +432,15 @@ SERVICE *service; case ARG_TYPE_STRING: return (unsigned long)arg; case ARG_TYPE_SERVICE: - if ((rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + 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 ((rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + 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 ((rval = (unsigned long)strtol(arg, NULL, 0)) == 0) + if (mode == CLIM_USER || (rval = (unsigned long)strtol(arg, NULL, 0)) == 0) { service = service_find(arg); if (service) @@ -389,6 +449,20 @@ SERVICE *service; 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; } return 0; } @@ -486,11 +560,23 @@ char *ptr, *lptr; dcb_printf(dcb, "Available commands:\n"); for (i = 0; cmds[i].cmd; i++) { - for (j = 0; cmds[i].options[j].arg1; j++) + 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 %s\n", cmds[i].cmd, cmds[i].options[j].arg1); + 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 { @@ -544,7 +630,7 @@ char *ptr, *lptr; cmds[i].options[j].fn(dcb); break; case 1: - arg1 = convert_arg(args[2],cmds[i].options[j].arg_types[0]); + arg1 = convert_arg(cli->mode, args[2],cmds[i].options[j].arg_types[0]); if (arg1) cmds[i].options[j].fn(dcb, arg1); else @@ -552,8 +638,8 @@ char *ptr, *lptr; args[2]); break; case 2: - arg1 = convert_arg(args[2],cmds[i].options[j].arg_types[0]); - arg2 = convert_arg(args[3],cmds[i].options[j].arg_types[1]); + 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) @@ -564,9 +650,9 @@ char *ptr, *lptr; args[3]); break; case 3: - arg1 = convert_arg(args[2],cmds[i].options[j].arg_types[0]); - arg2 = convert_arg(args[3],cmds[i].options[j].arg_types[1]); - arg3 = convert_arg(args[4],cmds[i].options[j].arg_types[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]); + 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)