blob: 9190e5d353bb89659b9c021e987de660faf5a630 [file] [log] [blame]
/*
<:copyright-BRCM:2016:DUAL/GPL:standard
Broadcom Proprietary and Confidential.(c) 2016 Broadcom
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
/*******************************************************************
* bcmcli.c
*
* CLI engine
*
*******************************************************************/
#include <bcmos_system.h>
#define BCMCLI_INTERNAL
#include <bcmcli.h>
#include <bcmos_types.h>
#define BCMCLI_INBUF_LEN 2048
#define BCMCLI_MAX_QUAL_NAME_LENGTH 256
#define BCMCLI_MAX_PARMS 128
#define BCMCLI_UP_STR ".."
#define BCMCLI_ROOT_STR "/"
#define BCMCLI_COMMENT_CHAR '#'
#define BCMCLI_HELP_CHAR '?'
#define BCMCLI_ARRAY_DELIM_CHAR ','
#define BCMCLI_ROOT_HELP "root directory"
#define BCMCLI_MAX_PARM_VAL_LEN 256
#define BCMCLI_ENUM_MASK_DEL_CHAR '+'
#define BCMCLI_HELP_BUFFER_SIZE 16384
#define BCMCLI_EQUAL_CHAR '='
typedef enum { BCMCLI_ENTRY_DIR, BCMCLI_ENTRY_CMD } bcmcli_entry_selector;
/* External table - boolean values */
bcmcli_enum_val bcmcli_enum_bool_table[] = {
{ .name="true", .val = 1 },
{ .name="yes", .val = 1 },
{ .name="on", .val = 1 },
{ .name="false", .val = 0 },
{ .name="no", .val = 0 },
{ .name="off", .val = 0 },
BCMCLI_ENUM_LAST
};
/* Monitor token structure */
struct bcmcli_entry
{
struct bcmcli_entry *next;
char *name; /* Command/directory name */
char *help; /* Command/directory help */
bcmcli_entry_selector sel; /* Entry selector */
char *alias; /* Alias */
uint16_t alias_len; /* Alias length */
struct bcmcli_entry *parent; /* Parent directory */
bcmcli_access_right access_right;
union {
struct
{
struct bcmcli_entry *first; /* First entry in directory */
bcmcli_dir_extra_parm extras; /* Optional extras */
} dir;
struct
{
bcmcli_cmd_cb cmd_cb; /* Command callback */
bcmcli_cmd_parm *parms; /* Command parameters */
bcmcli_cmd_extra_parm extras; /* Optional extras */
uint16_t num_parms;
} cmd;
} u;
};
/* Token types */
typedef enum
{
BCMCLI_TOKEN_EMPTY,
BCMCLI_TOKEN_UP,
BCMCLI_TOKEN_ROOT,
BCMCLI_TOKEN_BREAK,
BCMCLI_TOKEN_HELP,
BCMCLI_TOKEN_NAME,
BCMCLI_TOKEN_VALUE,
} bcmcli_token_type;
/* Parameter value set descriptor */
typedef union bcmcli_parm_value_status
{
bcmos_bool value_set;
bcmos_bool *values_set;
} bcmcli_parm_value_status;
/* CLI session data */
typedef struct bcmcli_session_data
{
bcmcli_entry *curdir;
bcmcli_entry *curcmd;
bcmcli_cmd_parm cmd_parms[BCMCLI_MAX_PARMS];
bcmcli_parm_value_status value_status[BCMCLI_MAX_PARMS];
bcmcli_session *session;
uint16_t num_parms;
char *p_inbuf;
int stop_monitor;
char inbuf[BCMCLI_INBUF_LEN];
} bcmcli_session_extras;
/* Name, value pairs */
typedef struct bcmcli_name_value
{
bcmcli_token_type type;
const char *name;
const char *value;
} bcmcli_name_value;
static bcmcli_entry *_bcmcli_root_dir;
static bcmcli_session_extras *_bcmcli_root_session;
static bcmcli_log_mode _bcmcli_log_mode;
static bcmcli_session *_bcmcli_log_session;
#define BCMCLI_MIN_NAME_LENGTH_FOR_ALIAS 3
#define BCMCLI_ROOT_NAME "/"
/* Internal functions */
static void _bcmcli_alloc_root(const bcmcli_session_parm *parm);
static void _bcmcli_display_dir(bcmcli_session_extras *mon_session, bcmcli_entry *p_dir );
static bcmcli_token_type _bcmcli_get_word(bcmcli_session_extras *session, char **inbuf, char **p_word);
static bcmcli_token_type _bcmcli_analyze_token( const char *name );
static int _bcmcli_parse_parms( bcmcli_session_extras *mon_session, bcmcli_entry *p_token,
bcmcli_name_value *pairs, int npairs);
static int _bcmcli_extend_parms( bcmcli_session_extras *mon_session, bcmcli_name_value *pairs,
int npairs, bcmos_bool last_is_space, char *insert_str, uint32_t insert_size);
static bcmcli_entry *_bcmcli_search_token( bcmcli_entry *p_dir, const char *name );
static void _bcmcli_help_dir( bcmcli_session_extras *mon_session, bcmcli_entry *p_dir );
static void _bcmcli_help_entry(bcmcli_session_extras *mon_session, bcmcli_entry *p_token,
bcmcli_name_value *pairs, int npairs, bcmos_bool suppress_err_print);
static void _bcmcli_help_populated_cmd(bcmcli_session_extras *mon_session, bcmcli_entry *p_token,
const char *partial_match, bcmos_bool suppress_assigned);
static void _bcmcli_choose_alias( bcmcli_entry *p_dir, bcmcli_entry *p_new_token );
static bcmcli_cmd_parm *_bcmcli_find_named_parm(bcmcli_session_extras *mon_session, const char *name);
static char *_bcmcli_strlwr( char *s );
static int _bcmcli_stricmp( const char *s1, const char *s2, int len );
static bcmos_errno _bcmcli_dft_scan_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_val);
static const char *_bcmcli_get_type_name(const bcmcli_cmd_parm *parm);
static void _bcmcli_dft_format_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value value, char *buffer, int size);
static bcmos_errno _bcmcli_enum_scan_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_val);
static void _bcmcli_enum_format_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value value, char *buffer, int size);
static bcmos_errno _bcmcli_enum_mask_scan_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_val);
static void _bcmcli_enum_mask_format_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value value, char *buffer, int size);
static bcmos_errno _bcmcli_buffer_scan_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_val);
static const char *_bcmcli_qualified_name( bcmcli_entry *token, char *buffer, int size);
static bcmos_errno _bcmcli_split(bcmcli_session_extras *mon_session, bcmcli_name_value **pairs, int *npairs);
static void _bcmcli_assign_callbacks(bcmcli_cmd_parm *parm);
static void _bcmcli_log_cmd(const char *cmd);
static void _bcmcli_log_rc(bcmos_errno rc);
static void _bcmcli_free_session_value_status(bcmcli_session_extras *mon_session);
static inline bcmcli_session_extras *_bcmcli_session_data(bcmcli_session *session)
{
if (!session)
return _bcmcli_root_session;
return bcmcli_session_data(session);
}
/** Add subdirectory to the parent directory
*
* \param[in] parent Parent directory handle. NULL=root
* \param[in] name Directory name
* \param[in] help Help string
* \param[in] access_right Access rights
* \param[in] extras Optional directory descriptor. Mustn't be allocated on the stack.
* \return new directory handle or NULL in case of failure
*/
bcmcli_entry *bcmcli_dir_add(bcmcli_entry *parent, const char *name,
const char *help, bcmcli_access_right access_right,
const bcmcli_dir_extra_parm *extras)
{
bcmcli_entry *p_dir;
bcmcli_entry **p_e;
assert(name);
assert(help);
if (!name || !help)
return NULL;
if (!_bcmcli_root_dir)
{
_bcmcli_alloc_root(NULL);
if (!_bcmcli_root_dir)
return NULL;
}
if (!parent)
parent = _bcmcli_root_dir;
p_dir=(bcmcli_entry *)bcmos_calloc( sizeof(bcmcli_entry) + strlen(name) + strlen(help) + 2 );
if ( !p_dir )
return NULL;
p_dir->name = (char *)(p_dir + 1);
strcpy( p_dir->name, name);
p_dir->help = p_dir->name + strlen(name) + 1;
strcpy(p_dir->help, help);
p_dir->sel = BCMCLI_ENTRY_DIR;
_bcmcli_choose_alias( parent, p_dir );
p_dir->access_right = access_right;
if (extras)
p_dir->u.dir.extras = *extras;
/* Add new directory to the parent's list */
p_dir->parent = parent;
p_e = &(parent->u.dir.first);
while (*p_e)
p_e = &((*p_e)->next);
*p_e = p_dir;
return p_dir;
}
static bcmcli_entry * find_entry_in_dir( bcmcli_entry *dir, const char *name,
bcmcli_entry_selector type, uint16_t recursive_search)
{
bcmcli_entry *p1, *p;
if ( !dir )
{
dir = _bcmcli_root_dir;
if (!dir)
return NULL;
}
p = dir->u.dir.first;
while (p)
{
if ( !_bcmcli_stricmp(p->name, name, -1) && type == p->sel )
return p;
if ( recursive_search && p->sel == BCMCLI_ENTRY_DIR )
{
p1 = find_entry_in_dir(p, name , type, 1 );
if ( p1 )
return p1;
}
p = p->next;
}
return NULL;
}
/* Scan directory tree and look for directory with name starts from
* root directory with name root_name
*/
bcmcli_entry *bcmcli_dir_find(bcmcli_entry *parent, const char *name)
{
if ( !parent )
parent = _bcmcli_root_dir;
return find_entry_in_dir(parent, name, BCMCLI_ENTRY_DIR, 0 );
}
/* Scan directory tree and look for command named "name". */
bcmcli_entry *bcmcli_cmd_find(bcmcli_entry *parent, const char *name )
{
if ( !parent )
parent = _bcmcli_root_dir;
return find_entry_in_dir(parent, name, BCMCLI_ENTRY_CMD, 0 );
}
/** Add CLI command
*
* \param[in] dir Handle of directory to add command to. NULL=root
* \param[in] name Command name
* \param[in] cmd_cb Command handler
* \param[in] help Help string
* \param[in] access_right Access rights
* \param[in] extras Optional extras
* \param[in] parms Optional parameters array. Must not be allocated on the stack!
* If parms!=NULL, the last parameter in the array must have name==NULL.
* \return
* 0 =OK\n
* <0 =error code
*/
bcmos_errno bcmcli_cmd_add(bcmcli_entry *dir, const char *name, bcmcli_cmd_cb cmd_cb,
const char *help, bcmcli_access_right access_right,
const bcmcli_cmd_extra_parm *extras, bcmcli_cmd_parm parms[])
{
bcmcli_entry *p_token;
bcmcli_entry **p_e;
uint16_t i;
bcmcli_cmd_parm *parm = parms;
assert(name);
assert(help);
assert(cmd_cb);
if (!name || !cmd_cb || !help)
return BCM_ERR_PARM;
if (!_bcmcli_root_dir)
{
_bcmcli_alloc_root(NULL);
if (!_bcmcli_root_dir)
return BCM_ERR_NOMEM;
}
if (!dir)
dir = _bcmcli_root_dir;
p_token=(bcmcli_entry *)bcmos_calloc( sizeof(bcmcli_entry) + strlen(name) + strlen(help) + 2 );
if ( !p_token )
return BCM_ERR_NOMEM;
/* Copy name */
p_token->name = (char *)(p_token + 1);
strcpy( p_token->name, name );
p_token->help = p_token->name + strlen(name) + 1;
strcpy(p_token->help, help);
p_token->sel = BCMCLI_ENTRY_CMD;
p_token->u.cmd.cmd_cb = cmd_cb;
p_token->u.cmd.parms = parms;
if (extras)
p_token->u.cmd.extras = *extras;
p_token->access_right = access_right;
/* Convert name to lower case and choose alias */
_bcmcli_choose_alias(dir, p_token );
/* Check parameters */
for (i = 0; i < BCMCLI_MAX_PARMS && parms && parms[i].name; i++)
{
parm = &parms[i];
/* User-defined parameter must have a scan_cb callback for text->value conversion */
if ((parm->type==BCMCLI_PARM_USERDEF) && !parm->scan_cb)
{
bcmos_printf("MON: %s> scan_cb callback must be set for user-defined parameter %s\n", name, parm->name);
goto cmd_add_error;
}
if (parm->type==BCMCLI_PARM_ENUM || parm->type==BCMCLI_PARM_ENUM_MASK)
{
if (!parm->enum_table)
{
bcmos_printf("MON: %s> value table must be set in low_val for enum parameter %s\n", name, parm->name);
goto cmd_add_error;
}
/* Check default value if any */
if ((parm->flags & BCMCLI_PARM_FLAG_DEFVAL))
{
if (_bcmcli_enum_mask_scan_cb(parm, &parm->value, parm->value.string) < 0)
{
bcmos_printf("MON: %s> default value %s doesn't match any value of enum parameter %s\n", name, parm->value.string, parm->name);
goto cmd_add_error;
}
}
else if ((parm->flags & BCMCLI_PARM_FLAG_OPTIONAL))
{
/* Optional enum parameters are initialized by their 1st value by default.
* All other parameters are initialized to 0.
*/
bcmcli_enum_val *values=parm->enum_table;
parm->value.enum_val = values[0].val;
}
/* All values of enum mask parameters mast be complementary bits */
if (parm->type==BCMCLI_PARM_ENUM_MASK)
{
long all_mask = 0;
bcmcli_enum_val *values;
for (values=parm->enum_table; values->name; ++values)
all_mask |= values->val;
for (values=parm->enum_table; values->name; ++values)
{
if ((all_mask & values->val) != values->val)
{
bcmos_printf("MON: %s> enum_table values of enum_mask parameters must be complementary bits\n", name, parm->name);
goto cmd_add_error;
}
all_mask &= ~values->val;
}
}
}
else if (parm->type==BCMCLI_PARM_BUFFER)
{
if (!parm->value.buffer.start || !parm->value.buffer.len)
{
bcmos_printf("MON: %s> value.buffer.start is not set for BUFFER parameter %s\n", name, parm->name);
goto cmd_add_error;
}
if (parm->max_array_size)
{
bcmos_printf("MON: %s> BUFFER arrays are not supported %s\n", name, parm->name);
goto cmd_add_error;
}
}
if (parm->max_array_size)
{
if (!parm->values)
{
bcmos_printf("MON: %s> parm->values must be set for parameter-array %s\n", name, parm->name);
goto cmd_add_error;
}
}
_bcmcli_assign_callbacks(parm);
}
if ((i == BCMCLI_MAX_PARMS) && parms[i].name[0])
{
bcmos_printf("MON: %s> too many parameters\n", name);
goto cmd_add_error;
}
p_token->u.cmd.num_parms = i;
/* Add token to the directory */
p_token->parent = dir;
p_e = &(dir->u.dir.first);
while (*p_e)
p_e = &((*p_e)->next);
*p_e = p_token;
return 0;
cmd_add_error:
bcmos_free( p_token );
return BCM_ERR_PARM;
}
/** Destroy token (command or directory)
* \param[in] token Directory or command token. NULL=root
*/
void bcmcli_token_destroy(bcmcli_entry *token)
{
if (!token)
{
if (!_bcmcli_root_dir)
return;
token = _bcmcli_root_dir;
}
/* Remove from parent's list */
if (token->parent)
{
bcmcli_entry **p_e;
p_e = &(token->parent->u.dir.first);
while (*p_e)
{
if (*p_e == token)
{
*p_e = token->next;
break;
}
p_e = &((*p_e)->next);
}
}
/* Remove all directory entries */
if (token->sel == BCMCLI_ENTRY_DIR)
{
bcmcli_entry *e = token->u.dir.first;
while ((e = token->u.dir.first))
bcmcli_token_destroy(e);
}
else if (token->u.cmd.extras.free_parms)
token->u.cmd.extras.free_parms(token->u.cmd.parms);
/* Release the token */
bcmos_free(token);
if (token == _bcmcli_root_dir)
{
_bcmcli_root_dir = NULL;
if (_bcmcli_root_session)
{
bcmcli_session_close(_bcmcli_root_session->session);
_bcmcli_root_session = NULL;
}
}
}
/** Open monitor session
*
* Monitor supports multiple simultaneous sessions with different
* access rights.
* Note that there already is a default session with full administrative rights,
* that takes input from stdin and outputs to stdout.
* \param[in] parm Session parameters. Must not be allocated on the stack.
* \param[out] p_session Session handle
* \return
* 0 =OK\n
* <0 =error code
*/
bcmos_errno bcmcli_session_open(const bcmcli_session_parm *parm, bcmcli_session **p_session)
{
bcmcli_session *session;
bcmcli_session_extras *mon_session;
bcmcli_session_parm session_parms;
int rc;
assert(p_session);
if (!p_session)
return BCM_ERR_PARM;
if (!_bcmcli_root_dir)
{
_bcmcli_alloc_root(parm);
if (!_bcmcli_root_dir)
return BCM_ERR_NOMEM;
}
if (parm)
session_parms = *parm;
else
{
memset(&session_parms, 0, sizeof(session_parms));
session_parms.name = "unnamed";
}
/* Open comm session */
session_parms.extra_size = sizeof(bcmcli_session_extras);
rc = bcmcli_session_open_user(&session_parms, &session);
if (rc)
return rc;
mon_session = _bcmcli_session_data(session);
mon_session->curdir = _bcmcli_root_dir;
mon_session->session = session;
*p_session = session;
return 0;
}
#define BCMCLI_PARSE_RETURN(ret) \
do { \
rc = ret; \
goto bcmcli_parse_out; \
} while (0)
/* Parse a single command. Stop on ';' or EOL */
static bcmos_errno bcmcli_parse_command(bcmcli_session *session)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
bcmcli_entry *p_token;
bcmcli_name_value *pairs = NULL;
int stop_parsing = 0;
int npairs;
int i;
char *cmd_line;
bcmos_errno rc = BCM_ERR_OK;
session = mon_session->session;
/* Make a copy of command line - for logging */
cmd_line = bcmos_alloc(strlen(mon_session->p_inbuf) + 1);
if (!cmd_line)
return BCM_ERR_NOMEM;
strcpy(cmd_line, mon_session->p_inbuf);
/* Split string to name/value pairs */
rc = _bcmcli_split(mon_session, &pairs, &npairs);
if (rc)
{
if (rc == BCM_ERR_NOENT)
rc = BCM_ERR_OK;
BCMCLI_PARSE_RETURN(rc);
}
/* Interpret empty string as "display directory" */
if ( !npairs )
{
_bcmcli_display_dir(mon_session, mon_session->curdir );
BCMCLI_PARSE_RETURN(BCM_ERR_OK);
}
/* Identify parameters */
for (i=0; i<npairs && !rc && !stop_parsing; i++)
{
switch (pairs[i].type)
{
case BCMCLI_TOKEN_NAME:
case BCMCLI_TOKEN_VALUE:
/* Identify command. The 1st pair can't contain name, only value */
if (pairs[i].name)
{
bcmcli_session_print(session, "**ERR: %s is unexpected\n", pairs[i].name);
BCMCLI_PARSE_RETURN(BCM_ERR_PARM);
}
p_token = _bcmcli_search_token(mon_session->curdir, pairs[i].value);
if (p_token == NULL)
{
bcmcli_session_print(session, "**ERR: %s is unexpected\n", pairs[i].value);
BCMCLI_PARSE_RETURN(BCM_ERR_PARM);
}
/* Directory or command ? */
if (p_token->sel == BCMCLI_ENTRY_DIR)
{
mon_session->curdir = p_token;
_bcmcli_display_dir(mon_session, mon_session->curdir );
}
else
{
/* Function token */
mon_session->curcmd = p_token;
if (_bcmcli_parse_parms(mon_session, p_token, &pairs[i+1], npairs-i-1) < 0)
{
_bcmcli_help_entry(mon_session, p_token, &pairs[i+1], npairs-i-1, BCMOS_TRUE);
rc = BCM_ERR_PARM;
}
else
{
_bcmcli_log_cmd(cmd_line);
rc = p_token->u.cmd.cmd_cb(session, mon_session->cmd_parms, npairs-i-1 );
if (rc)
{
char buffer[BCMCLI_MAX_QUAL_NAME_LENGTH];
bcmcli_session_print(session, "MON: %s> failed with error code %s(%d)\n",
_bcmcli_qualified_name(p_token, buffer, sizeof(buffer)),
bcmos_strerror(rc), rc);
}
_bcmcli_log_rc(rc);
_bcmcli_free_session_value_status(mon_session);
}
stop_parsing = 1;
}
break;
case BCMCLI_TOKEN_UP: /* Go to upper directory */
if (mon_session->curdir->parent)
mon_session->curdir = mon_session->curdir->parent;
_bcmcli_display_dir(mon_session, mon_session->curdir );
break;
case BCMCLI_TOKEN_ROOT: /* Go to the root directory */
mon_session->curdir = _bcmcli_root_dir;
_bcmcli_display_dir(mon_session, mon_session->curdir );
break;
case BCMCLI_TOKEN_HELP: /* Display help */
if (i < npairs-1 &&
((p_token = _bcmcli_search_token( mon_session->curdir, pairs[i+1].value)) != NULL ))
{
_bcmcli_help_entry(mon_session, p_token, &pairs[i+2], npairs-i-2, BCMOS_FALSE);
}
else
{
_bcmcli_help_dir(mon_session, mon_session->curdir);
}
stop_parsing = 1;
break;
default:
stop_parsing = 1;
break;
}
}
bcmcli_parse_out:
if (pairs)
bcmos_free(pairs);
if (cmd_line)
bcmos_free(cmd_line);
return rc;
}
/** Context extension */
bcmos_errno bcmcli_extend(bcmcli_session *session, char *input_str, char *insert_str, uint32_t insert_size)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
bcmcli_entry *p_token;
bcmcli_name_value *pairs;
bcmos_bool last_is_space;
int npairs;
bcmos_errno rc = BCM_ERR_OK;
if (!mon_session || !mon_session->curdir || !input_str)
return BCM_ERR_PARM;
insert_str[0] = 0;
mon_session->p_inbuf = input_str;
last_is_space = strlen(input_str) && (input_str[strlen(input_str) - 1] == ' ');
/* Split string to name/value pairs */
rc = _bcmcli_split(mon_session, &pairs, &npairs);
if (rc)
return rc;
/* empty list - display list of commands */
if ( !npairs )
{
_bcmcli_display_dir(mon_session, mon_session->curdir );
BCMCLI_PARSE_RETURN(0);
}
/* Identify parameters */
switch (pairs[0].type)
{
case BCMCLI_TOKEN_NAME:
case BCMCLI_TOKEN_VALUE:
/* Identify command. The 1st pair can't contain name, only value */
if (pairs[0].name ||
!(p_token = _bcmcli_search_token(mon_session->curdir, pairs[0].value)))
{
_bcmcli_display_dir(mon_session, mon_session->curdir );
BCMCLI_PARSE_RETURN(BCM_ERR_PARM);
}
/* Directory or command ? */
if (p_token->sel != BCMCLI_ENTRY_CMD)
BCMCLI_PARSE_RETURN(BCM_ERR_OK);
/* Function token */
mon_session->curcmd = p_token;
rc = _bcmcli_extend_parms(mon_session, &pairs[1], npairs-1, last_is_space, insert_str, insert_size);
break;
default:
break;
}
bcmcli_parse_out:
bcmos_free(pairs);
return rc;
}
/** Parse and execute input string.
* input_string can contain multiple commands delimited by ';'
*
* \param[in] session Session handle
* \param[in] input_string String to be parsed. May consist of multiple ';'-delimited commands
* \return
* =0 - OK \n
* BCM_ERR_PARM - parsing error\n
* other - return code - as returned from command handler.
* It is recommended to return -EINTR to interrupt monitor loop.
*/
bcmos_errno bcmcli_parse(bcmcli_session *session, char* input_string)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
uint32_t input_len;
int rc = 0;
if (!mon_session || !mon_session->curdir || !input_string)
return BCM_ERR_PARM;
input_len = strlen(input_string);
if (!input_len)
return 0;
/* cut CR, LF if any */
while (input_len && (input_string[input_len-1]=='\n' || input_string[input_len-1]=='\r'))
input_string[--input_len]=0;
mon_session->p_inbuf = input_string;
mon_session->stop_monitor = 0;
do {
rc = bcmcli_parse_command(session);
} while (mon_session->p_inbuf && mon_session->p_inbuf[0] && !mon_session->stop_monitor && !rc);
return rc;
}
/** Read input and parse iteratively until EOF or bcmcli_is_stopped()
*
* \param[in] session Session handle
* \return
* =0 - OK \n
*/
bcmos_errno bcmcli_driver(bcmcli_session *session)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
session = mon_session->session;
mon_session->stop_monitor = 0;
while (!bcmcli_is_stopped(session) &&
bcmcli_session_gets(session, mon_session->inbuf, sizeof(mon_session->inbuf)-1))
{
/* Session could've been stopped while in "gets". Check again and proceed if active */
if (!bcmcli_is_stopped(session))
bcmcli_parse(session, mon_session->inbuf);
}
return BCM_ERR_OK;
}
/* Stop monitor driver */
void bcmcli_stop( bcmcli_session *session )
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
assert(mon_session);
mon_session->stop_monitor = 1;
}
/** Returns 1 if monitor session is stopped
* \param[in] session Session handle
* \returns 1 if monitor session stopped by bcmcli_stop()\n
* 0 otherwise
*/
bcmos_bool bcmcli_is_stopped(bcmcli_session *session)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
return mon_session->stop_monitor;
}
/** Get parameter number given its name.
* The function is intended for use by command handlers
* \param[in] session Session handle
* \param[in,out] parm_name Parameter name
* \return
* >=0 - parameter number\n
* <0 - parameter with this name doesn't exist
*/
int bcmcli_parm_number(bcmcli_session *session, const char *parm_name)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
int i;
if (!parm_name || !mon_session || !mon_session->curcmd)
return BCM_ERR_PARM;
for(i=0;
mon_session->cmd_parms[i].name &&
_bcmcli_stricmp( parm_name, mon_session->cmd_parms[i].name, -1);
i++)
;
if (!mon_session->cmd_parms[i].name)
return BCM_ERR_PARM;
return i;
}
/** Get parameter by name
* The function is intended for use by command handlers
* \param[in] session Session handle
* \param[in] parm_name Parameter name
* \return
* parameter pointer or NULL if not found
*/
bcmcli_cmd_parm *bcmcli_parm_get(bcmcli_session *session, const char *parm_name)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
int nparm = bcmcli_parm_number(session, parm_name);
if (nparm < 0)
{
return NULL;
}
return &mon_session->cmd_parms[nparm];
}
/** Check if parameter is set
* \param[in] session Session handle
* \param[in] parm_number Parameter number
* \return
* 1 if parameter is set\n
* 0 if parameter is not set or parm_number is invalid
*/
bcmos_errno bcmcli_parm_check(bcmcli_session *session, int parm_number)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
if (parm_number < 0 || !mon_session || !mon_session->curcmd)
return BCM_ERR_PARM;
if (parm_number >= mon_session->num_parms)
return BCM_ERR_PARM;
if (!(mon_session->cmd_parms[parm_number].flags & BCMCLI_PARM_FLAG_ASSIGNED))
return BCM_ERR_NOENT;
return BCM_ERR_OK;
}
/** Get enum's string value given its internal value
* \param[in] session Session handle
* \param[in] parm_number Parameter number
* \param[in] value Internal value
* \return
* enum string value or NULL if parameter is not enum or
* internal value is invalid
*/
const char *bcmcli_enum_parm_stringval(bcmcli_session *session, int parm_number, long value)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
int i;
bcmcli_enum_val *values;
if (parm_number < 0 || !mon_session || !mon_session->curcmd)
return NULL;
for(i=0; i<parm_number && mon_session->cmd_parms[i].name; i++)
;
if (i < parm_number)
return NULL;
if (mon_session->cmd_parms[parm_number].type != BCMCLI_PARM_ENUM)
return NULL;
values = mon_session->cmd_parms[parm_number].enum_table;
return bcmcli_enum_stringval(values, value);
}
/* Get current directory handle */
bcmcli_entry *bcmcli_dir_get(bcmcli_session *session)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
if (!mon_session)
return NULL;
return mon_session->curdir;
}
/* Set current directory */
bcmos_errno bcmcli_dir_set(bcmcli_session *session, bcmcli_entry *dir)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
assert(mon_session);
if (!mon_session)
return BCM_ERR_PARM;
/* Check access rights */
if (!dir)
dir = _bcmcli_root_dir;
if (dir->access_right > bcmcli_session_access_right(mon_session->session))
return BCM_ERR_PERM;
mon_session->curdir = dir;
return 0;
}
/** Get token name
* \param[in] token Directory or command token
* \return directory token name
*/
const char *bcmcli_token_name(bcmcli_entry *token)
{
if (!token)
return NULL;
return token->name;
}
bcmcli_cmd_parm *bcmcli_find_named_parm(bcmcli_session *session, const char *name)
{
bcmcli_cmd_parm * cmd_parm;
if ( !session || !name || *name=='\0')
return NULL;
cmd_parm = _bcmcli_find_named_parm(_bcmcli_session_data(session), name);
if(cmd_parm && (cmd_parm->flags & BCMCLI_PARM_FLAG_ASSIGNED))
{
return cmd_parm;
}
return NULL;
}
/* Return TRUE if parameter value is set */
bcmos_bool bcmcli_parm_value_is_set(bcmcli_session *session, bcmcli_cmd_parm *parm, uint32_t value_index)
{
bcmcli_session_extras *mon_session=_bcmcli_session_data(session);
uint32_t parm_index = parm - mon_session->cmd_parms;
if (!mon_session)
{
bcmcli_print(NULL, "MON> Session %p is invalid\n", session);
return BCMOS_FALSE;
}
parm_index = parm - mon_session->cmd_parms;
if (parm_index >= BCMCLI_MAX_PARMS)
{
bcmcli_print(session, "MON> Parameter %p is invalid\n", parm);
return BCMOS_FALSE;
}
if (!parm->array_size)
return mon_session->value_status[parm_index].value_set;
if (value_index >= parm->array_size)
return BCMOS_FALSE;
return mon_session->value_status[parm_index].values_set[value_index];
}
bcmcli_cmd_parm *bcmcli_find_parm_by_prefix(bcmcli_session *session, const char *prefix)
{
bcmcli_cmd_parm *cmd_parm;
if (session == NULL || prefix == NULL)
{
return NULL;
}
cmd_parm = _bcmcli_session_data(session)->cmd_parms;
while (cmd_parm->name != NULL)
{
if ((_bcmcli_stricmp(prefix, cmd_parm->name, strlen(prefix)) == 0) &&
((cmd_parm->flags & BCMCLI_PARM_FLAG_ASSIGNED) != 0))
{
return cmd_parm;
}
++cmd_parm;
}
return NULL;
}
/** Print CLI parameter
* \param[in] session Session handle
* \param[in] parm Parameter
*/
void bcmcli_parm_print(bcmcli_session *session, const bcmcli_cmd_parm *parm)
{
char buf[BCMCLI_MAX_PARM_VAL_LEN] = "";
if (parm->type != BCMCLI_PARM_BUFFER)
{
parm->format_cb(parm, parm->value, buf, sizeof(buf));
bcmcli_print(session, "%s\n", buf);
}
else
{
if (parm->value.buffer.len == 0)
{
bcmcli_print(session, "-\n");
}
else
{
bcmcli_print(session, "\n");
bcmcli_session_hexdump(session, parm->value.buffer.start, 0,
bcmolt_buf_get_used(&parm->value.buffer), NULL);
}
}
}
/** Enable / disable CLI command logging
* \param[in] mode Logging flags
* \param[in] log Log session. Must be set if mode != BCMCLI_CMD_LOG_NONE
* \return 0=OK or error <0
*/
bcmos_errno bcmcli_log_set(bcmcli_log_mode mode, bcmcli_session *log)
{
if (mode != BCMCLI_LOG_NONE && log == NULL)
{
BCMOS_TRACE_ERR("log session must be set\n");
return BCM_ERR_PARM;
}
if (mode == BCMCLI_LOG_NONE)
{
_bcmcli_log_session = NULL;
}
else
{
_bcmcli_log_session = log;
}
_bcmcli_log_mode = mode;
return BCM_ERR_OK;
}
/** Write string to CLI log.
* The function is ignored if CLI logging is not enabled using bcmcli_log_set()
* \param[in] format printf-like format followed by arguments
*/
void bcmcli_log(const char *format, ...)
{
va_list ap;
if (!_bcmcli_log_session)
return;
va_start(ap, format);
bcmcli_session_vprint(_bcmcli_log_session, format, ap);
va_end(ap);
}
/*********************************************************/
/* Internal functions */
/*********************************************************/
static void _bcmcli_log_cmd(const char *cmd)
{
switch (_bcmcli_log_mode)
{
case BCMCLI_LOG_CLI:
bcmcli_log("%s\n", cmd);
break;
case BCMCLI_LOG_C_COMMENT:
bcmcli_log("/* %s */\n", cmd);
break;
default:
break;
}
}
static void _bcmcli_log_rc(bcmos_errno rc)
{
switch (_bcmcli_log_mode)
{
case BCMCLI_LOG_CLI:
bcmcli_log("# CLI command completed: %s (%d)\n", bcmos_strerror(rc), rc);
break;
case BCMCLI_LOG_C_COMMENT:
bcmcli_log("/* CLI command completed: %s (%d) */\n", bcmos_strerror(rc), rc);
break;
default:
break;
}
}
static bcmcli_session *_bcmcli_help_session_open(bcmcli_session *main_session)
{
bcmolt_string *help_scratch;
bcmcli_session *help_session;
bcmos_errno err;
bcmolt_string_create(&help_scratch, BCMCLI_HELP_BUFFER_SIZE);
err = bcmcli_session_open_string(&help_session, help_scratch);
if (err)
{
bcmcli_session_print(main_session, "CLI: can't create help session. Error %s\n", bcmos_strerror(err));
return NULL;
}
return help_session;
}
static void _bcmcli_help_session_print_and_close(bcmcli_session *main_session, bcmcli_session *help_session)
{
bcmolt_string *str = bcmcli_session_user_priv(help_session);
bcmcli_session_print(main_session, "%s", bcmolt_string_get(str));
bcmcli_session_close(help_session);
bcmolt_string_destroy(str);
}
#ifdef CONFIG_LINENOISE
static bcmos_errno _bcmcli_line_edit_cmd(bcmcli_session *session, const bcmcli_cmd_parm parm[], uint16_t n_parms)
{
if (n_parms > 0)
{
if ((parm[0].flags & BCMCLI_PARM_FLAG_ASSIGNED))
linenoiseSetDumbTerminal(session->ln_session, ! parm[0].value.number);
if ((parm[1].flags & BCMCLI_PARM_FLAG_ASSIGNED))
linenoiseSetMultiLine(session->ln_session, parm[1].value.number);
}
else
{
int dumb = linenoiseGetDumbTerminal(session->ln_session);
int multiline = linenoiseGetMultiLine(session->ln_session);
bcmcli_session_print(session, "Line editing: %s Multiline: %s\n",
dumb ? "off" : "on", multiline ? "on" : "off");
}
return BCM_ERR_OK;
}
#endif
/* Allocate root directory and default session */
static void _bcmcli_alloc_root(const bcmcli_session_parm *first_session_parm)
{
bcmcli_session_parm session_parms;
bcmcli_session *session;
int rc;
/* The very first call. Allocate root structure */
if ((_bcmcli_root_dir=(bcmcli_entry *)bcmos_calloc(sizeof(bcmcli_entry) + strlen(BCMCLI_ROOT_HELP) + 2 )) == NULL)
return;
_bcmcli_root_dir->name = (char *)(_bcmcli_root_dir + 1);
_bcmcli_root_dir->help = (char *)(_bcmcli_root_dir->name + 1);
strcpy(_bcmcli_root_dir->help, BCMCLI_ROOT_HELP);
_bcmcli_root_dir->sel = BCMCLI_ENTRY_DIR;
_bcmcli_root_dir->access_right = BCMCLI_ACCESS_GUEST;
memset(&session_parms, 0, sizeof(session_parms));
session_parms.access_right = BCMCLI_ACCESS_ADMIN;
session_parms.extra_size = sizeof(bcmcli_session_extras);
session_parms.name = "monroot";
if (first_session_parm)
{
session_parms.line_edit_mode = first_session_parm->line_edit_mode;
}
rc = bcmcli_session_open(&session_parms, &session);
if (rc)
{
bcmos_free(_bcmcli_root_dir);
_bcmcli_root_dir = NULL;
_bcmcli_root_session = NULL;
return;
}
_bcmcli_root_session = _bcmcli_session_data(session);
_bcmcli_root_session->session = session;
_bcmcli_root_session->curdir = _bcmcli_root_dir;
/* Add command to disable/enable line editing */
#ifdef CONFIG_LINENOISE
if (session_parms.line_edit_mode != BCMCLI_LINE_EDIT_DISABLE)
{
BCMCLI_MAKE_CMD(NULL, "~", "Enable/disable/query line editing", _bcmcli_line_edit_cmd,
BCMCLI_MAKE_PARM_ENUM("enable", "Enable line editing", bcmcli_enum_bool_table, BCMCLI_PARM_FLAG_OPTIONAL),
BCMCLI_MAKE_PARM_ENUM("multiline", "Enable multiline mode", bcmcli_enum_bool_table,
BCMCLI_PARM_FLAG_OPTIONAL));
}
#endif
}
/* Display directory */
static void _bcmcli_display_dir(bcmcli_session_extras *mon_session, bcmcli_entry *p_dir)
{
bcmcli_session *session = mon_session->session;
bcmcli_entry *p_token;
bcmcli_entry *prev=NULL;
bcmcli_session *help_session = _bcmcli_help_session_open(session);
if (!help_session)
return;
bcmcli_session_print(help_session, "%s%s> ", (p_dir==_bcmcli_root_dir)?"":".../", p_dir->name );
p_token = p_dir->u.dir.first;
while ( p_token )
{
if (p_token->access_right <= bcmcli_session_access_right(session))
{
if (prev)
bcmcli_session_print(help_session, ", ");
bcmcli_session_print(help_session, "%s", p_token->name );
if (p_token->sel == BCMCLI_ENTRY_DIR )
bcmcli_session_print(help_session, "/");
prev = p_token;
}
p_token = p_token->next;
}
bcmcli_session_print(help_session, "\n");
_bcmcli_help_session_print_and_close(session, help_session);
}
/* Is character that can be used in a single token ? */
static inline int _bcmcli_is_special_char(char c)
{
if (!c)
return 0;
return (c == BCMCLI_HELP_CHAR || c == BCMCLI_COMMENT_CHAR || c == BCMCLI_EQUAL_CHAR);
}
/* Make a preliminary analizis of <name> token.
* Returns a token type (Empty, Up, Root, Break, Name)
*/
static bcmcli_token_type _bcmcli_analyze_token( const char *name )
{
if (!name[0] || name[0]==';')
return BCMCLI_TOKEN_EMPTY;
if (*name == BCMCLI_COMMENT_CHAR)
return BCMCLI_TOKEN_BREAK;
if (!strcmp(name, BCMCLI_UP_STR))
return BCMCLI_TOKEN_UP;
if (!strcmp(name, BCMCLI_ROOT_STR))
return BCMCLI_TOKEN_ROOT;
if (*name == BCMCLI_HELP_CHAR)
return BCMCLI_TOKEN_HELP;
return BCMCLI_TOKEN_VALUE;
}
/* isspace wrapper */
static inline int _bcmcli_isspace(char c)
{
return isspace((int)c);
}
/* Cut the first word from <p_inbuf>.
* - Return pointer to start of the word in p_word
* - 0 terminator is inserted in the end of the word
* - session->p_inbuf is updated to point after the word
* Returns token type
*/
static bcmcli_token_type _bcmcli_get_word(bcmcli_session_extras *mon_session, char **buf, char **p_word)
{
bcmcli_token_type token_type;
char *p_inbuf = *buf;
char next_char = 0;
bcmos_bool quoted_string = BCMOS_FALSE;
/* Skip leading blanks */
while (*p_inbuf && (_bcmcli_isspace(*p_inbuf) || (*p_inbuf==',')))
++p_inbuf;
*buf = p_inbuf;
if (! *p_inbuf)
return BCMCLI_TOKEN_EMPTY;
if (*p_inbuf == ';')
{
*p_inbuf = 0;
*buf = ++p_inbuf;
return BCMCLI_TOKEN_EMPTY;
}
/* Quoted string ? */
if (*p_inbuf == '"')
{
quoted_string = BCMOS_TRUE;
*p_word = ++p_inbuf;
while ( *p_inbuf && *p_inbuf!='"' )
++p_inbuf;
if (*p_inbuf != '"')
{
bcmcli_session_print(mon_session->session, "MON: unterminated string %s\n", *p_word);
return BCMCLI_TOKEN_EMPTY;
}
if (*p_inbuf)
*(p_inbuf++) = 0;
}
else
{
*p_word = p_inbuf;
if (!_bcmcli_is_special_char(*p_inbuf))
{
do ++p_inbuf;
while (*p_inbuf && !_bcmcli_isspace(*p_inbuf) && *p_inbuf!=';' && !_bcmcli_is_special_char(*p_inbuf));
/* Skip trailing spaces */
while (*p_inbuf && _bcmcli_isspace(*p_inbuf))
*(p_inbuf++) = 0;
next_char = *p_inbuf;
if (next_char == BCMCLI_EQUAL_CHAR)
*(p_inbuf++) = 0;
}
else
{
++p_inbuf;
}
}
*buf = p_inbuf;
token_type = _bcmcli_analyze_token( *p_word );
if (token_type == BCMCLI_TOKEN_VALUE && next_char == BCMCLI_EQUAL_CHAR)
token_type = BCMCLI_TOKEN_NAME;
if ((token_type == BCMCLI_TOKEN_EMPTY) && quoted_string)
token_type = BCMCLI_TOKEN_VALUE;
return token_type;
}
/* Split string to [name=]value pairs */
static bcmos_errno _bcmcli_split(bcmcli_session_extras *mon_session, bcmcli_name_value **p_pairs, int *p_npairs)
{
bcmcli_name_value *pairs;
char *tmp_buf, *tmp_buf_org;
char *word;
bcmcli_token_type token_type, prev_type=BCMCLI_TOKEN_EMPTY;
int n = 0;
/* Make a copy of input buffer */
tmp_buf_org = tmp_buf = bcmos_alloc(strlen(mon_session->p_inbuf) + 1);
if (!tmp_buf)
return BCM_ERR_NOMEM;
strcpy(tmp_buf, mon_session->p_inbuf);
/* Calculate number of pairs first */
token_type = _bcmcli_get_word(mon_session, &tmp_buf, &word);
while (token_type != BCMCLI_TOKEN_EMPTY && token_type != BCMCLI_TOKEN_BREAK)
{
/* Skip =value */
if (!(prev_type == BCMCLI_TOKEN_NAME && token_type == BCMCLI_TOKEN_VALUE))
++n;
prev_type = token_type;
token_type = _bcmcli_get_word(mon_session, &tmp_buf, &word);
}
bcmos_free(tmp_buf_org);
*p_npairs = n;
if (!n)
{
*p_pairs = NULL;
/* Cut input string in order to prevent infinite loop in the parser if the string
* is not empty (e.g., contains spaces) */
*mon_session->p_inbuf = 0;
if (token_type == BCMCLI_TOKEN_BREAK)
{
return BCM_ERR_NOENT;
}
return 0;
}
*p_pairs = pairs = bcmos_calloc(n * sizeof(bcmcli_name_value));
if (! pairs)
return BCM_ERR_NOMEM;
/* Now scan the original string and set names and values */
token_type = _bcmcli_get_word(mon_session, &mon_session->p_inbuf, &word);
prev_type=BCMCLI_TOKEN_EMPTY;
--pairs; /* it is going to be pre-incremented */
while (token_type != BCMCLI_TOKEN_EMPTY && token_type != BCMCLI_TOKEN_BREAK)
{
if (!(prev_type == BCMCLI_TOKEN_NAME && token_type == BCMCLI_TOKEN_VALUE))
++pairs;
pairs->type = token_type;
if (token_type == BCMCLI_TOKEN_NAME)
{
pairs->name = word;
}
else
{
pairs->value = word;
}
prev_type = token_type;
token_type = _bcmcli_get_word(mon_session, &mon_session->p_inbuf, &word);
}
return 0;
}
/* Find parameter by name */
static bcmcli_cmd_parm *_bcmcli_find_named_parm(bcmcli_session_extras *mon_session, const char *name)
{
bcmcli_cmd_parm *cmd_parm = mon_session->cmd_parms;
while (cmd_parm->name)
{
if (!_bcmcli_stricmp(name, cmd_parm->name, -1))
break;
++cmd_parm;
}
if (!cmd_parm->name)
return NULL;
return cmd_parm;
}
/* Extend session parameter table based on selector value */
static bcmos_errno _bcmcli_extend_parm_table(bcmcli_session_extras *mon_session,
bcmcli_cmd_parm *selector, const char *value)
{
bcmcli_enum_val *values=selector->enum_table;
bcmcli_cmd_parm *parms;
bcmcli_cmd_parm *session_parm;
int nsel = selector - mon_session->cmd_parms;
bcmcli_parm_value_status *val_status;
int nparms;
while (values->name)
{
if (!_bcmcli_stricmp(values->name, value, -1))
break;
++values;
}
if (!values->name)
return BCM_ERR_INTERNAL;
/* Calculate number of parameters in selected table */
parms = values->parms;
while (parms && parms->name)
{
++parms;
}
nparms = parms - values->parms;
if (mon_session->num_parms + nparms >= BCMCLI_MAX_PARMS)
{
bcmcli_session_print(mon_session->session, "MON: %s> Can's process selector %s. Too many parameters\n",
mon_session->curcmd->name, selector->name);
return BCM_ERR_OVERFLOW;
}
/* Shift session parameters making room for the new table */
if (selector != &mon_session->cmd_parms[mon_session->num_parms-1])
{
memmove(selector + nparms + 1, selector + 1,
(&mon_session->cmd_parms[mon_session->num_parms-1] - selector) * sizeof(bcmcli_cmd_parm));
memmove(&mon_session->value_status[nsel + nparms + 1], &mon_session->value_status[nsel + 1],
(mon_session->num_parms - nsel) * sizeof(bcmcli_parm_value_status));
}
/* Finally insert selector's table */
parms = values->parms;
session_parm = selector+1;
val_status = &mon_session->value_status[nsel + 1];
while (parms && parms->name)
{
*session_parm = *parms;
_bcmcli_assign_callbacks(session_parm);
if (parms->max_array_size)
{
val_status->values_set = bcmos_calloc(sizeof(bcmos_bool) * parms->max_array_size);
if (!val_status->values_set)
{
bcmcli_session_print(mon_session->session, "MON: > Couldn't allocate value status array for %s\n",
parms->name);
return BCM_ERR_NOMEM;
}
}
else
{
val_status->value_set = BCMOS_FALSE;
}
++parms;
++session_parm;
++val_status;
}
mon_session->num_parms += nparms;
return BCM_ERR_OK;
}
/* Parse a single parameter value (scalar value or array element) */
static bcmos_errno _bcmcli_parse_1value(bcmcli_session_extras *mon_session, bcmcli_entry *cmd,
bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_value,
int val_len, bcmos_bool suppress_err_print)
{
bcmos_errno rc;
if (val_len >= 0)
{
/* We are dealing with array element. string_value is comma rather than
* 0-terminated. Copy it aside.
*/
char val_copy[val_len + 1];
strncpy(val_copy, string_value, val_len);
val_copy[val_len] = 0;
rc = parm->scan_cb(parm, value, val_copy);
}
else
{
rc = parm->scan_cb(parm, value, string_value);
}
if (rc)
{
if (!suppress_err_print)
{
bcmcli_session_print(mon_session->session, "MON: %s> <%s>: value %s is invalid\n",
cmd->name, parm->name, string_value);
}
}
return rc;
}
/* Parse parameter value, including array value (comma-delimited list of element values) */
static bcmos_errno _bcmcli_parse_value(bcmcli_session_extras *mon_session, bcmcli_entry *cmd,
bcmcli_cmd_parm *parm, const char *string_value, bcmos_bool suppress_err_print)
{
bcmos_errno rc = BCM_ERR_OK;
uint32_t parm_index = parm - mon_session->cmd_parms;
BUG_ON(parm_index >= BCMCLI_MAX_PARMS);
if (parm->max_array_size)
{
uint32_t i = 0;
/* Empty array? */
if (_bcmcli_stricmp(string_value, BCMCLI_ARRAY_EMPTY, -1))
{
/* array element values are comma-delimited */
for (i = 0; i < parm->max_array_size && string_value && *string_value && !rc; i++)
{
const char *pcomma;
int val_len;
pcomma = strchr(string_value, BCMCLI_ARRAY_DELIM_CHAR);
if (pcomma)
{
val_len = pcomma - string_value;
}
else
{
val_len = -1; /* to the end of string */
}
/* No value ? */
if (_bcmcli_stricmp(string_value, BCMCLI_PARM_NO_VALUE, val_len))
{
rc = _bcmcli_parse_1value(mon_session, cmd,
parm, &parm->values[i], string_value, val_len, suppress_err_print);
mon_session->value_status[parm_index].values_set[i] = (rc == BCM_ERR_OK);
}
string_value = pcomma ? pcomma + 1 : NULL;
}
/* If all parsed values were ok, but we have more values than array size - it is an error */
if (string_value && *string_value && !rc)
{
rc = BCM_ERR_TOO_MANY;
if (!suppress_err_print)
{
bcmcli_session_print(mon_session->session, "MON: %s> <%s>: too many values. %s is invalid\n",
cmd->name, parm->name, string_value);
}
}
}
parm->array_size = i;
}
else
{
if (_bcmcli_stricmp(string_value, BCMCLI_PARM_NO_VALUE, strlen(string_value)))
{
rc = _bcmcli_parse_1value(mon_session, cmd,
parm, &parm->value, string_value, -1, suppress_err_print);
mon_session->value_status[parm_index].value_set = (rc == BCM_ERR_OK);
}
}
return rc;
}
/* Release value status arrays */
static void _bcmcli_free_session_value_status(bcmcli_session_extras *mon_session)
{
bcmcli_cmd_parm *parms=mon_session->cmd_parms;
int i;
for (i = 0; i < BCMCLI_MAX_PARMS; i++)
{
if (parms[i].max_array_size)
{
if (mon_session->value_status[i].values_set != NULL)
{
bcmos_free(mon_session->value_status[i].values_set);
mon_session->value_status[i].values_set = NULL;
}
}
else
{
mon_session->value_status[i].value_set = BCMOS_FALSE;
}
}
}
/* Populate session parameters. Apply selectors */
static bcmos_errno _bcmcli_populate_parms(bcmcli_session_extras *mon_session, bcmcli_entry *cmd,
bcmcli_name_value *pairs, int npairs, bcmos_bool suppress_err_print, int *last)
{
const char *parm_value;
int positional=1;
bcmcli_cmd_parm *parms=mon_session->cmd_parms;
bcmcli_cmd_parm *cur_parm;
int rc;
int i;
/* Mark all parameters as don't having an explicit value */
memset(&parms[0], 0, sizeof(mon_session->cmd_parms));
memcpy(&parms[0], cmd->u.cmd.parms, sizeof(bcmcli_cmd_parm)*cmd->u.cmd.num_parms);
/* Clear array buffers */
for (i = 0; i < cmd->u.cmd.num_parms; i++)
{
if (parms[i].max_array_size)
{
BUG_ON(!parms[i].values);
memset(parms[i].values, 0, sizeof(bcmcli_parm_value) * parms[i].max_array_size);
mon_session->value_status[i].values_set = bcmos_calloc(sizeof(bcmos_bool) * parms[i].max_array_size);
if (!mon_session->value_status[i].values_set)
{
bcmcli_session_print(mon_session->session, "MON: %s> Couldn't allocate value status array for %s\n",
cmd->name, pairs[i].name);
return BCM_ERR_NOMEM;
}
}
else
{
mon_session->value_status[i].value_set = BCMOS_FALSE;
}
}
mon_session->curcmd = cmd;
mon_session->num_parms = cmd->u.cmd.num_parms;
if (last)
*last = 0;
/* Build a format string */
for (i=0; i<npairs && pairs[i].type != BCMCLI_TOKEN_BREAK; i++)
{
parm_value = pairs[i].value;
if (last)
*last = i;
cur_parm = NULL;
/* Named parameter ? */
if (pairs[i].name)
{
if ( (cmd->u.cmd.extras.flags & BCMCLI_CMD_FLAG_NO_NAME_PARMS) )
{
if (!suppress_err_print)
{
bcmcli_session_print(mon_session->session, "MON: %s> Doesn't support named parameters. %s is unexpected\n",
cmd->name, pairs[i].name);
}
return BCM_ERR_PARM;
}
positional = 0; /* No more positional parameters */
/* Check name */
cur_parm = _bcmcli_find_named_parm(mon_session, pairs[i].name);
if (!cur_parm)
{
if (!suppress_err_print)
{
bcmcli_session_print(mon_session->session, "MON: %s> parameter <%s> doesn't exist\n",
cmd->name, pairs[i].name);
}
return BCM_ERR_PARM;
}
if (!parm_value)
{
if (!suppress_err_print)
{
bcmcli_session_print(mon_session->session, "MON: %s> <%s>: value is missing\n",
cmd->name, cur_parm->name);
}
return BCM_ERR_PARM;
}
}
else
{
/* it can still be named ENUM parameter (without =value). In this case the 1st
* enum value is assumed. Check it
*/
if (parm_value && (cur_parm = _bcmcli_find_named_parm(mon_session, parm_value)) &&
(cur_parm->type == BCMCLI_PARM_ENUM))
{
pairs[i].name = parm_value;
pairs[i].value = parm_value = cur_parm->enum_table->name;
positional = 0; /* No more positional parameters */
}
else
{
if (!positional)
{
if (!suppress_err_print)
bcmcli_session_print(mon_session->session, "MON: %s> Expected named parameter. Got %s\n", cmd->name, parm_value);
return BCM_ERR_PARM;
}
cur_parm = &parms[i];
}
if (!cur_parm->name)
{
if (!suppress_err_print)
bcmcli_session_print(mon_session->session, "MON: %s> Too many parameters. %s is unexpected\n", cmd->name, parm_value);
return BCM_ERR_PARM;
}
}
if (cur_parm->flags & BCMCLI_PARM_FLAG_ASSIGNED)
{
if (!suppress_err_print)
{
bcmcli_session_print(mon_session->session, "MON: %s> Attempt to assign parameter %s more than once\n",
cmd->name, cur_parm->name);
}
return BCM_ERR_PARM;
}
if (parm_value)
{
if (cur_parm->type == BCMCLI_PARM_STRING)
cur_parm->value.string = parm_value;
else
{
rc = _bcmcli_parse_value(mon_session, cmd, cur_parm, parm_value, suppress_err_print);
if (rc)
return rc;
/* For parameter-selector extend list of parameters accordingly */
if (cur_parm->flags & BCMCLI_PARM_FLAG_SELECTOR)
{
rc = _bcmcli_extend_parm_table(mon_session, cur_parm, parm_value);
if (rc)
return rc;
}
}
cur_parm->flags |= BCMCLI_PARM_FLAG_ASSIGNED;
}
}
return BCM_ERR_OK;
}
/* Parse p_inbuf string based on parameter descriptions in <p_token>.
* Fill parameter values in <p_token>.
* Returns the number of parameters filled or BCM_ERR_PARM
* To Do: add a option of one-by-one user input of missing parameters.
*/
static int _bcmcli_parse_parms( bcmcli_session_extras *mon_session, bcmcli_entry *cmd, bcmcli_name_value *pairs, int npairs)
{
bcmcli_cmd_parm *parms=mon_session->cmd_parms;
int rc;
int i;
/* Populate parameter table */
rc = _bcmcli_populate_parms(mon_session, cmd, pairs, npairs, BCMOS_FALSE, NULL);
if (rc)
goto err_return;
rc = BCM_ERR_PARM;
/* Make sure that parameters are OK. Check range, process default values */
for (i=0; i<mon_session->num_parms; i++)
{
bcmcli_cmd_parm *cur_parm = &parms[i];
if (!(cur_parm->flags & BCMCLI_PARM_FLAG_ASSIGNED))
{
if ((cur_parm->flags & BCMCLI_PARM_FLAG_DEFVAL))
{
cur_parm->flags |= BCMCLI_PARM_FLAG_ASSIGNED;
}
else if (!(cur_parm->flags & BCMCLI_PARM_FLAG_OPTIONAL) )
{
/* Mandatory parameter missing */
bcmcli_session_print(mon_session->session, "MON: %s> Mandatory parameter <%s> is missing\n", cmd->name, parms[i].name);
goto err_return;
}
}
/* Check value */
if ((cur_parm->flags & BCMCLI_PARM_FLAG_RANGE))
{
if ((cur_parm->flags & BCMCLI_PARM_FLAG_ASSIGNED))
{
if (cur_parm->array_size)
{
uint32_t j;
for (j = 0; j < cur_parm->array_size; j++)
{
if (((cur_parm->values[j].number < cur_parm->low_val) ||
(cur_parm->values[j].number > cur_parm->hi_val)))
{
bcmcli_session_print(mon_session->session, "MON: %s> <%s>: %ld out of range (%ld, %ld)\n",
cmd->name, cur_parm->name, cur_parm->values[j].number, cur_parm->low_val, cur_parm->hi_val);
goto err_return;
}
}
}
else if (((cur_parm->value.number < cur_parm->low_val) ||
(cur_parm->value.number > cur_parm->hi_val)))
{
bcmcli_session_print(mon_session->session, "MON: %s> <%s>: %ld out of range (%ld, %ld)\n",
cmd->name, cur_parm->name, cur_parm->value.number, cur_parm->low_val, cur_parm->hi_val);
goto err_return;
}
}
}
}
return BCM_ERR_OK;
err_return:
_bcmcli_free_session_value_status(mon_session);
return rc;
}
/* insert value skipping partial match trhat is already present */
static void _bcmcli_insert(const char *partial_match, const char *insert_val1,
const char *insert_val2, char *insert_str, uint32_t insert_size)
{
if (partial_match)
insert_val1 += strlen(partial_match);
bcmcli_strncpy(insert_str, insert_val1, insert_size);
if (insert_val2)
bcmcli_strncat(insert_str, insert_val2, insert_size);
}
static void _bcmcli_update_longest_match(char *longest_match, const char *name)
{
uint32_t nlen = strlen(name);
uint32_t lmlen = strlen(longest_match);
if (nlen < lmlen)
{
lmlen = nlen;
}
while (lmlen && memcmp(longest_match, name, lmlen))
{
--lmlen;
}
longest_match[lmlen] = 0;
}
/* extend value.
* If !enum - do nothing
* If more than 1 matching value - display them
* If no matching value - do nothing
* If 1 matching value - insert
*/
static void _bcmcli_extend_value(bcmcli_session_extras *mon_session, bcmcli_cmd_parm *parm,
const char *partial_value, char *insert_str, uint32_t insert_size)
{
int nmatch = 0;
bcmcli_enum_val *vals = parm->enum_table;
char longest_match[BCMCLI_MAX_SEARCH_SUBSTR_LENGTH]="";
if ((parm->type != BCMCLI_PARM_ENUM && parm->type != BCMCLI_PARM_ENUM_MASK) || !vals)
return;
/* If enum mask, partial value can be a sum of values. Skip past the last '+' sign */
if (parm->type == BCMCLI_PARM_ENUM_MASK && partial_value)
{
char *pdel = strrchr(partial_value, BCMCLI_ENUM_MASK_DEL_CHAR);
if (pdel)
partial_value = pdel + 1;
}
while (vals->name)
{
if (!partial_value || !strncmp(vals->name, partial_value, strlen(partial_value)))
{
if (!nmatch)
{
bcmcli_strncpy(longest_match, vals->name, sizeof(longest_match));
}
else
{
_bcmcli_update_longest_match(longest_match, vals->name);
}
++nmatch;
}
++vals;
}
if (!nmatch)
return;
if (nmatch == 1)
{
_bcmcli_insert(partial_value, longest_match, " ", insert_str, insert_size);
return;
}
/* display all matching values */
_bcmcli_insert(partial_value, longest_match, "", insert_str, insert_size);
bcmcli_session_print(mon_session->session, "\n");
vals = parm->enum_table;
while (vals->name)
{
if (!partial_value || !strncmp(vals->name, partial_value, strlen(partial_value)))
bcmcli_session_print(mon_session->session, " %s", vals->name);
++vals;
}
bcmcli_session_print(mon_session->session, "\n");
}
/* calculate number of matching parameter names */
static int _bcmcli_num_matching_names(bcmcli_session_extras *mon_session, const char *partial_value, int *first_match)
{
int i;
int nmatch = 0;
*first_match = -1;
for (i = 0; i < mon_session->num_parms; i++)
{
uint32_t flags = mon_session->cmd_parms[i].flags;
if ((flags & BCMCLI_PARM_FLAG_ASSIGNED))
continue;
if (partial_value && strncmp(mon_session->cmd_parms[i].name, partial_value, strlen(partial_value)))
continue;
if (*first_match == -1)
*first_match = i;
++nmatch;
}
return nmatch;
}
/* calculate longest matching string.
* returns number of matching parameters
*/
static int _bcmcli_longest_match(bcmcli_session_extras *mon_session, const char *partial_value,
char *longest_match, uint32_t longest_match_size, int *first_match)
{
int nmatch0 = _bcmcli_num_matching_names(mon_session, partial_value, first_match);
int nmatch;
const char *match_name;
if (!nmatch0)
return nmatch0;
match_name = mon_session->cmd_parms[*first_match].name;
if (nmatch0 == 1)
{
bcmcli_strncpy(longest_match, match_name, longest_match_size);
return nmatch0;
}
bcmcli_strncpy(longest_match, match_name, longest_match_size);
nmatch = _bcmcli_num_matching_names(mon_session, longest_match, first_match);
while (nmatch != nmatch0)
{
longest_match[strlen(longest_match)-1] = 0;
nmatch = _bcmcli_num_matching_names(mon_session, longest_match, first_match);
}
return nmatch0;
}
/* display/insert unset matching names
* If more than 1 matching value - display them
* If no matching value - do nothing
* If 1 matching value - insert
*/
static void _bcmcli_extend_name(bcmcli_session_extras *mon_session, const char *partial_value,
char *insert_str, uint32_t insert_size)
{
char longest_match[BCMCLI_MAX_SEARCH_SUBSTR_LENGTH]="";
int first_match;
int nmatch = _bcmcli_longest_match(mon_session, partial_value, longest_match,
sizeof(longest_match), &first_match);
if (!nmatch)
return;
if (!partial_value || strcmp(partial_value, longest_match))
_bcmcli_insert(partial_value, longest_match, (nmatch == 1) ? "=" : "", insert_str, insert_size);
else
_bcmcli_help_populated_cmd(mon_session, mon_session->curcmd, partial_value, BCMOS_TRUE);
}
static int _bcmcli_extend_parms( bcmcli_session_extras *mon_session, bcmcli_name_value *pairs,
int npairs, bcmos_bool last_is_space, char *insert_str, uint32_t insert_size)
{
bcmos_errno rc;
int last = 0;
bcmcli_cmd_parm *help_parm = NULL;
int i;
rc = _bcmcli_populate_parms(mon_session, mon_session->curcmd, pairs, npairs, BCMOS_TRUE, &last);
if (!rc)
{
/* So far so good */
/* If there is unset mandatory parameter - insert its name.
* Otherwise, display list of unset parameters
*/
/* Find mandatory parameter that is still unassigned */
for (i = 0; i < mon_session->num_parms; i++)
{
uint32_t flags = mon_session->cmd_parms[i].flags;
if (!(flags & (BCMCLI_PARM_FLAG_OPTIONAL | BCMCLI_PARM_FLAG_DEFVAL | BCMCLI_PARM_FLAG_ASSIGNED)))
{
help_parm = &mon_session->cmd_parms[i];
break;
}
}
if (help_parm)
{
if (!last_is_space)
bcmcli_strncpy(insert_str, " ", insert_size);
bcmcli_strncat(insert_str, help_parm->name, insert_size);
bcmcli_strncat(insert_str, "=", insert_size);
}
else if (last < mon_session->num_parms)
_bcmcli_help_populated_cmd(mon_session, mon_session->curcmd, NULL, BCMOS_TRUE);
}
else
{
/* Parsing failed. See what stopped at */
if (last < mon_session->num_parms)
{
bcmcli_name_value *last_pair;
last_pair = &pairs[last];
if (last_pair->name)
{
/* Try to identify by name */
help_parm = _bcmcli_find_named_parm(mon_session, last_pair->name ? last_pair->name : last_pair->value);
}
if (help_parm)
{
/* Looking for values */
_bcmcli_extend_value(mon_session, help_parm, last_pair->value, insert_str, insert_size);
}
else
{
/* Looking for partial name */
_bcmcli_extend_name(mon_session, last_pair->name ? last_pair->name : last_pair->value,
insert_str, insert_size);
}
}
}
_bcmcli_free_session_value_status(mon_session);
return BCM_ERR_OK;
}
/* Identify token in the given directory */
static bcmcli_entry *_bcmcli_search_token1( bcmcli_entry *p_dir, const char **p_name, int name_len )
{
bcmcli_entry *p_token = NULL;
const char *name = *p_name;
bcmcli_token_type type=_bcmcli_analyze_token(name);
/* Name can be qualified */
if (type == BCMCLI_TOKEN_VALUE && !strncmp(name, BCMCLI_UP_STR, name_len))
type = BCMCLI_TOKEN_UP;
switch(type)
{
case BCMCLI_TOKEN_ROOT:
p_token = _bcmcli_root_dir;
*p_name = name + strlen(BCMCLI_ROOT_STR);
break;
case BCMCLI_TOKEN_UP:
if (p_dir->parent)
p_token = p_dir->parent;
else
p_token = p_dir;
*p_name = name + strlen(BCMCLI_UP_STR) + 1;
break;
case BCMCLI_TOKEN_NAME:
case BCMCLI_TOKEN_VALUE:
/* Check alias */
p_token = p_dir->u.dir.first;
while ( p_token )
{
if (p_token->alias &&
(name_len == p_token->alias_len) &&
!_bcmcli_stricmp(p_token->alias, name, p_token->alias_len))
break;
p_token = p_token->next;
}
if (!p_token)
{
bcmcli_entry *partial_match = NULL;
/* Check name */
p_token = p_dir->u.dir.first;
while( p_token )
{
if (!_bcmcli_stricmp(p_token->name, name, name_len))
{
if (name_len == strlen(p_token->name))
break;
if (!partial_match)
partial_match = p_token;
}
p_token = p_token->next;
}
if (!p_token)
p_token = partial_match;
}
*p_name = name + name_len + 1;
break;
default:
break;
}
return p_token;
}
/* Search a token by name in the current directory.
* The name can be qualified (contain path)
*/
static bcmcli_entry *_bcmcli_search_token( bcmcli_entry *p_dir, const char *name )
{
bcmcli_entry *p_token;
const char *name0 = name;
const char *p_slash;
if (!name[0])
return p_dir;
/* Check if name is qualified */
do
{
p_slash = strchr(name, '/');
if (p_slash)
{
if (p_slash == name0)
{
p_dir = p_token = _bcmcli_root_dir;
name = p_slash + 1;
}
else
{
p_token = _bcmcli_search_token1(p_dir, &name, p_slash - name);
if (p_token && (p_token->sel == BCMCLI_ENTRY_DIR))
p_dir = p_token;
}
}
else
{
p_token = _bcmcli_search_token1(p_dir, &name, strlen(name));
}
} while (p_slash && p_token && *name);
return p_token;
}
/* Display help for each entry in the current directory */
static void _bcmcli_help_dir(bcmcli_session_extras *mon_session, bcmcli_entry *p_dir)
{
bcmcli_session *help_session = _bcmcli_help_session_open(mon_session->session);
bcmcli_entry *p_token;
char buffer[BCMCLI_MAX_QUAL_NAME_LENGTH];
_bcmcli_qualified_name(p_dir, buffer, sizeof(buffer));
bcmcli_session_print(help_session, "Directory %s/ - %s\n", buffer, p_dir->help);
bcmcli_session_print(help_session, "Commands:\n");
p_token = p_dir->u.dir.first;
while ( p_token )
{
if (bcmcli_session_access_right(help_session) >= p_token->access_right)
{
if (p_token->sel == BCMCLI_ENTRY_DIR)
bcmcli_session_print(help_session, "\t%s/: %s directory\n", p_token->name, p_token->help );
else
{
char *peol = strchr(p_token->help, '\n');
int help_len = peol ? peol - p_token->help : (int)strlen(p_token->help);
bcmcli_session_print(help_session, "\t%s(%d parms): %.*s\n",
p_token->name, p_token->u.cmd.num_parms, help_len, p_token->help );
}
}
p_token = p_token->next;
}
bcmcli_session_print(help_session, "Type ? <name> for command help, \"/\"-root, \"..\"-upper\n" );
_bcmcli_help_session_print_and_close(mon_session->session, help_session);
}
/* Display help a token */
static void _bcmcli_help_populated_cmd(bcmcli_session_extras *mon_session, bcmcli_entry *p_token,
const char *partial_match, bcmos_bool suppress_assigned)
{
char tmp[80];
char bra, ket;
uint16_t i;
if (suppress_assigned)
bcmcli_session_print(mon_session->session, "\n");
for ( i=0; i<mon_session->num_parms; i++ )
{
bcmcli_cmd_parm *cur_parm = &mon_session->cmd_parms[i];
if (suppress_assigned && (cur_parm->flags & BCMCLI_PARM_FLAG_ASSIGNED))
continue;
if (partial_match && memcmp(partial_match, cur_parm->name, strlen(partial_match)))
continue;
if ((cur_parm->flags & BCMCLI_PARM_FLAG_OPTIONAL))
{
bra = '[';
ket=']';
}
else
{
bra = '<';
ket='>';
}
bcmcli_session_print(mon_session->session, "\t%c%s(%s)", bra, cur_parm->name, _bcmcli_get_type_name(cur_parm) );
if (cur_parm->max_array_size || cur_parm->type == BCMCLI_PARM_BUFFER)
{
uint32_t num_entries = (cur_parm->type == BCMCLI_PARM_BUFFER) ? cur_parm->value.buffer.len : cur_parm->max_array_size;
bcmcli_session_print(mon_session->session, "[%u]", num_entries);
}
if (cur_parm->type == BCMCLI_PARM_ENUM || cur_parm->type == BCMCLI_PARM_ENUM_MASK)
{
bcmcli_enum_val *values=cur_parm->enum_table;
bcmcli_session_print(mon_session->session, " {");
while (values->name)
{
if (values!=cur_parm->enum_table)
bcmcli_session_print(mon_session->session, ", ");
bcmcli_session_print(mon_session->session, "%s", values->name);
++values;
}
bcmcli_session_print(mon_session->session, "}");
}
if ((cur_parm->flags & BCMCLI_PARM_FLAG_DEFVAL))
{
bcmcli_session_print(mon_session->session, "=");
cur_parm->format_cb(cur_parm, cur_parm->value, tmp, sizeof(tmp));
bcmcli_session_print(mon_session->session, "%s", tmp);
}
if ((cur_parm->flags & BCMCLI_PARM_FLAG_RANGE))
{
bcmcli_parm_value low_val = { .number = cur_parm->low_val };
bcmcli_parm_value hi_val = { .number = cur_parm->hi_val };
bcmcli_session_print(mon_session->session, " (");
cur_parm->format_cb(cur_parm, low_val, tmp, sizeof(tmp));
bcmcli_session_print(mon_session->session, "%s..", tmp);
cur_parm->format_cb(cur_parm, hi_val, tmp, sizeof(tmp));
bcmcli_session_print(mon_session->session, "%s)", tmp);
}
bcmcli_session_print(mon_session->session, "%c ", ket);
bcmcli_session_print(mon_session->session, "- %s\n", cur_parm->description);
}
/* Print extra help if command has unresolved selector */
if (mon_session->num_parms &&
(mon_session->cmd_parms[mon_session->num_parms-1].flags & BCMCLI_PARM_FLAG_SELECTOR) &&
!(mon_session->cmd_parms[mon_session->num_parms-1].flags & BCMCLI_PARM_FLAG_ASSIGNED))
{
const char *sel_name = mon_session->cmd_parms[mon_session->num_parms-1].name;
bcmcli_session_print(mon_session->session, "Add %s=%s_value to see %s-specific parameters\n",
sel_name, sel_name, sel_name);
}
bcmcli_session_print(mon_session->session, "\n");
}
/* Display help a token */
static void _bcmcli_help_entry(bcmcli_session_extras *mon_session, bcmcli_entry *p_token,
bcmcli_name_value *pairs, int npairs, bcmos_bool suppress_err_print)
{
char buffer[BCMCLI_MAX_QUAL_NAME_LENGTH];
if (p_token->sel == BCMCLI_ENTRY_DIR)
{
_bcmcli_help_dir(mon_session, p_token);
return;
}
/* Populate parameter table */
_bcmcli_populate_parms(mon_session, p_token, pairs, npairs, suppress_err_print, NULL);
_bcmcli_qualified_name(p_token, buffer, sizeof(buffer));
bcmcli_session_print(mon_session->session, "%s: \t%s\n", buffer, p_token->help );
if (p_token->u.cmd.num_parms)
bcmcli_session_print(mon_session->session, "Parameters:\n");
_bcmcli_help_populated_cmd(mon_session, p_token, NULL, BCMOS_FALSE);
_bcmcli_free_session_value_status(mon_session);
}
/* Choose unique alias for <name> in <p_dir> */
/* Currently only single-character aliases are supported */
static void __bcmcli_chooseAlias(bcmcli_entry *p_dir, bcmcli_entry *p_new_token, int from)
{
bcmcli_entry *p_token;
int i;
char c;
_bcmcli_strlwr( p_new_token->name );
i = from;
while ( p_new_token->name[i] )
{
c = p_new_token->name[i];
p_token = p_dir->u.dir.first;
while ( p_token )
{
if (p_token->alias &&
(tolower( *p_token->alias ) == c) )
break;
if (strlen(p_token->name)<=2 && tolower(p_token->name[0])==c)
break;
p_token = p_token->next;
}
if (p_token)
++i;
else
{
p_new_token->name[i] = toupper( c );
p_new_token->alias = &p_new_token->name[i];
p_new_token->alias_len = 1;
break;
}
}
}
/* isupper wrapper */
static inline int _bcmcli_isupper(char c)
{
return isupper((int)c);
}
static void _bcmcli_choose_alias(bcmcli_entry *p_dir, bcmcli_entry *p_new_token)
{
int i=0;
p_new_token->alias_len = 0;
p_new_token->alias = NULL;
/* Don't try to alias something short */
if (strlen(p_new_token->name) < BCMCLI_MIN_NAME_LENGTH_FOR_ALIAS)
return;
/* Try pre-set alias 1st */
while ( p_new_token->name[i] )
{
if (_bcmcli_isupper(p_new_token->name[i]))
break;
i++;
}
if (p_new_token->name[i])
__bcmcli_chooseAlias(p_dir, p_new_token, i);
if (p_new_token->alias != &p_new_token->name[i])
__bcmcli_chooseAlias(p_dir, p_new_token, 0);
}
/* Convert string s to lower case. Return pointer to s */
static char * _bcmcli_strlwr( char *s )
{
char *s0=s;
while ( *s )
{
*s = tolower( *s );
++s;
}
return s0;
}
/* Compare strings case incensitive */
static int _bcmcli_stricmp(const char *s1, const char *s2, int len)
{
int i;
for ( i=0; (i<len || len<0); i++ )
{
if (tolower( s1[i]) != tolower( s2[i] ))
return 1;
if (!s1[i])
break;
}
return 0;
}
static const char *_bcmcli_get_type_name(const bcmcli_cmd_parm *parm)
{
bcmcli_parm_type type = parm->type;
static const char *type_name[] = {
[BCMCLI_PARM_DECIMAL] = "decimal",
[BCMCLI_PARM_DECIMAL64] = "decimal64",
[BCMCLI_PARM_UDECIMAL] = "udecimal",
[BCMCLI_PARM_UDECIMAL64] = "udecimal64",
[BCMCLI_PARM_HEX] = "hex",
[BCMCLI_PARM_HEX64] = "hex64",
[BCMCLI_PARM_NUMBER] = "number",
[BCMCLI_PARM_NUMBER64] = "number64",
[BCMCLI_PARM_UNUMBER] = "unumber",
[BCMCLI_PARM_UNUMBER64] = "unumber64",
[BCMCLI_PARM_FLOAT] = "float",
[BCMCLI_PARM_DOUBLE] = "double",
[BCMCLI_PARM_ENUM] = "enum",
[BCMCLI_PARM_ENUM_MASK] = "enum_mask",
[BCMCLI_PARM_STRING] = "string",
[BCMCLI_PARM_IP] = "IP",
[BCMCLI_PARM_IPV6] = "IPv6",
[BCMCLI_PARM_MAC] = "MAC",
[BCMCLI_PARM_BUFFER] = "buffer",
[BCMCLI_PARM_USERDEF] = "userdef",
};
static const char *undefined = "undefined";
static const char *selector = "selector";
if (type > BCMCLI_PARM_USERDEF || !type_name[type])
return undefined;
if (type == BCMCLI_PARM_ENUM && (parm->flags & BCMCLI_PARM_FLAG_SELECTOR))
return selector;
return type_name[type];
}
/* Assign default callbacks */
static void _bcmcli_assign_callbacks(bcmcli_cmd_parm *parm)
{
if (parm->type == BCMCLI_PARM_ENUM)
{
parm->scan_cb = _bcmcli_enum_scan_cb;
parm->format_cb = _bcmcli_enum_format_cb;
}
else if (parm->type == BCMCLI_PARM_ENUM_MASK)
{
parm->scan_cb = _bcmcli_enum_mask_scan_cb;
parm->format_cb = _bcmcli_enum_mask_format_cb;
}
else if (parm->type == BCMCLI_PARM_BUFFER)
{
if (!parm->scan_cb)
parm->scan_cb = _bcmcli_buffer_scan_cb;
if (!parm->format_cb)
parm->format_cb = _bcmcli_dft_format_cb;
}
else
{
if (!parm->scan_cb)
parm->scan_cb = _bcmcli_dft_scan_cb;
if (!parm->format_cb)
parm->format_cb = _bcmcli_dft_format_cb;
}
}
/* Convert hex-string to binary data.
* Returns: converted length >=0 or error < 0
*/
static int _bcmcli_strhex(const char *src, uint8_t *dst, uint16_t dst_len)
{
uint16_t src_len = (uint16_t)strlen( src );
uint16_t i = src_len, j, shift = 0;
if ( !dst || !dst_len || (src_len > 2*dst_len) || (src_len%2) )
{
return BCM_ERR_PARM;
}
/* Calculate hex buffer length and fill it up from right-to-left
* in order to start the process from LS nibble
*/
dst_len = src_len / 2;
memset(dst, 0, dst_len);
j = dst_len-1;
do
{
int c = src[--i];
if ( (c>='0') && (c<='9') )
{
c = c - '0';
}
else if ( (c>='a') && (c<='f') )
{
c = 0xA + c - 'a';
}
else if ( (c>='A') && (c<='F') )
{
c = 0xA + c - 'A';
}
else
{
return BCM_ERR_PARM;
}
dst[j] |= (uint8_t)(c<<shift); /* shift can have 1 of 2 values: 0 and 4 */
j -= shift>>2; /* move to the next byte if we've just filled the ms nibble */
shift ^= 4; /* alternate nibbles */
} while ( i );
return dst_len;
}
/* Default function for string->value conversion.
* Returns 0 if OK
*/
static bcmos_errno _bcmcli_dft_scan_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_val)
{
char *p_end = NULL;
int n;
if (parm->type == BCMCLI_PARM_UDECIMAL ||
parm->type == BCMCLI_PARM_UDECIMAL64 ||
parm->type == BCMCLI_PARM_UNUMBER ||
parm->type == BCMCLI_PARM_UNUMBER64)
{
/* strtoul returns OK even when parsing a negative number */
if (string_val[0] == '-')
{
return BCM_ERR_PARM;
}
}
switch(parm->type)
{
case BCMCLI_PARM_DECIMAL:
value->number = strtol(string_val, &p_end, 10);
break;
case BCMCLI_PARM_UDECIMAL:
value->unumber = strtoul(string_val, &p_end, 10);
break;
case BCMCLI_PARM_DECIMAL64:
value->number64 = strtoll(string_val, &p_end, 10);
break;
case BCMCLI_PARM_UDECIMAL64:
value->unumber64 = strtoull(string_val, &p_end, 10);
break;
case BCMCLI_PARM_HEX:
value->unumber = strtoul(string_val, &p_end, 16);
break;
case BCMCLI_PARM_HEX64:
value->unumber64 = strtoull(string_val, &p_end, 16);
break;
case BCMCLI_PARM_NUMBER:
value->number = strtol(string_val, &p_end, 0);
break;
case BCMCLI_PARM_UNUMBER:
value->unumber = strtoul(string_val, &p_end, 0);
break;
case BCMCLI_PARM_NUMBER64:
value->number64 = strtoll(string_val, &p_end, 0);
break;
case BCMCLI_PARM_UNUMBER64:
value->unumber64 = strtoull(string_val, &p_end, 0);
break;
case BCMCLI_PARM_FLOAT:
case BCMCLI_PARM_DOUBLE:
value->d = strtod(string_val, &p_end);
break;
case BCMCLI_PARM_MAC:
{
unsigned m0, m1, m2, m3, m4, m5;
n = sscanf(string_val, "%02x:%02x:%02x:%02x:%02x:%02x",
&m0, &m1, &m2, &m3, &m4, &m5);
if (n != 6)
{
n = sscanf(string_val, "%02x%02x%02x%02x%02x%02x",
&m0, &m1, &m2, &m3, &m4, &m5);
}
if (n != 6)
return BCM_ERR_PARM;
if (m0 > 255 || m1 > 255 || m2 > 255 || m3 > 255 || m4 > 255 || m5 > 255)
return BCM_ERR_PARM;
value->mac.u8[0] = m0;
value->mac.u8[1] = m1;
value->mac.u8[2] = m2;
value->mac.u8[3] = m3;
value->mac.u8[4] = m4;
value->mac.u8[5] = m5;
break;
}
case BCMCLI_PARM_IP:
{
int n1, n2, n3, n4;
n = sscanf(string_val, "%d.%d.%d.%d", &n1, &n2, &n3, &n4);
if (n != 4)
return BCM_ERR_PARM;
if ((unsigned)n1 > 255 || (unsigned)n2 > 255 || (unsigned)n3 > 255 || (unsigned)n4 > 255)
return BCM_ERR_PARM;
value->unumber = (n1 << 24) | (n2 << 16) | (n3 << 8) | n4;
break;
}
default:
return BCM_ERR_PARM;
}
if (p_end && *p_end)
return BCM_ERR_PARM;
return BCM_ERR_OK;
}
static void _bcmcli_dft_format_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value value, char *buffer, int size)
{
switch(parm->type)
{
case BCMCLI_PARM_DECIMAL:
snprintf(buffer, size, "%ld", value.number);
break;
case BCMCLI_PARM_UDECIMAL:
snprintf(buffer, size, "%lu", value.unumber);
break;
case BCMCLI_PARM_DECIMAL64:
snprintf(buffer, size, "%lld", value.number64);
break;
case BCMCLI_PARM_UDECIMAL64:
snprintf(buffer, size, "%llu", value.unumber64);
break;
case BCMCLI_PARM_HEX:
snprintf(buffer, size, "0x%lx", value.unumber);
break;
case BCMCLI_PARM_HEX64:
snprintf(buffer, size, "0x%llx", value.unumber64);
break;
case BCMCLI_PARM_NUMBER:
snprintf(buffer, size, "%ld", value.number);
break;
case BCMCLI_PARM_NUMBER64:
snprintf(buffer, size, "%lld", value.number64);
break;
case BCMCLI_PARM_UNUMBER:
snprintf(buffer, size, "%lu", value.unumber);
break;
case BCMCLI_PARM_UNUMBER64:
snprintf(buffer, size, "%llu", value.unumber64);
break;
case BCMCLI_PARM_FLOAT:
case BCMCLI_PARM_DOUBLE:
snprintf(buffer, size, "%f", value.d);
break;
case BCMCLI_PARM_STRING:
snprintf(buffer, size, "%s", value.string);
break;
case BCMCLI_PARM_MAC:
snprintf(buffer, size, "%02x:%02x:%02x:%02x:%02x:%02x",
parm->value.mac.u8[0], parm->value.mac.u8[1], parm->value.mac.u8[2],
parm->value.mac.u8[3], parm->value.mac.u8[4], parm->value.mac.u8[5]);
break;
case BCMCLI_PARM_IP:
snprintf(buffer, size, "%d.%d.%d.%d",
(int)((parm->value.unumber >> 24) & 0xff), (int)((parm->value.unumber >> 16) & 0xff),
(int)((parm->value.unumber >> 8) & 0xff), (int)(parm->value.unumber & 0xff));
break;
default:
bcmcli_strncpy(buffer, "*unknown*", size);
}
}
static bcmos_errno _bcmcli_enum_scan_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_val)
{
bcmcli_enum_val *values=parm->enum_table;
while (values->name)
{
if (!_bcmcli_stricmp(values->name, string_val, -1))
{
value->enum_val = values->val;
return BCM_ERR_OK;
}
++values;
}
return BCM_ERR_PARM;
}
static void _bcmcli_enum_format_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value value, char *buffer, int size)
{
bcmcli_enum_val *values=parm->enum_table;
while (values->name)
{
if (values->val == value.enum_val)
break;
++values;
}
if (values->name)
strncpy(buffer, values->name, size);
else
strncpy(buffer, "*invalid*", size);
}
static bcmos_errno _bcmcli_enum_mask_scan_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_val)
{
bcmcli_parm_value val1;
char *del;
bcmos_errno err;
value->number = 0;
/* string_val is a combination of enum values separated by BCMCLI_ENUM_MASK_DEL_STR */
del = strchr(string_val, BCMCLI_ENUM_MASK_DEL_CHAR);
while (del)
{
char single_val[64];
if (del - string_val >= sizeof(single_val))
return BCM_ERR_OVERFLOW;
memcpy(single_val, string_val, del - string_val);
single_val[del - string_val] = 0;
err = _bcmcli_enum_scan_cb(parm, &val1, single_val);
if (err)
return err;
value->enum_val |= val1.enum_val;
string_val = del+1;
del = strchr(string_val, BCMCLI_ENUM_MASK_DEL_CHAR);
}
err = _bcmcli_enum_scan_cb(parm, &val1, string_val);
if (err)
return err;
value->number |= val1.enum_val;
return BCM_ERR_OK;
}
static void _bcmcli_enum_mask_format_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value value, char *buffer, int size)
{
bcmcli_enum_val *values=parm->enum_table;
const char *none = NULL;
*buffer = 0;
while (values->name)
{
if (values->val == 0)
{
none = values->name;
}
if ((values->val & value.enum_val) != 0)
{
if (*buffer)
strncat(buffer, BCMCLI_ENUM_MASK_DEL_STR, size - strlen(buffer));
strncat(buffer, values->name, size - strlen(buffer));
}
++values;
}
if (! *buffer)
strncpy(buffer, NULL != none ? none : "0", size);
}
static bcmos_errno _bcmcli_buffer_scan_cb(const bcmcli_cmd_parm *parm, bcmcli_parm_value *value, const char *string_val)
{
int n;
if (!value->buffer.start)
return BCM_ERR_PARM;
value->buffer.curr = value->buffer.start;
if (strcmp(string_val, "-") == 0)
{
return BCM_ERR_OK;
}
n = _bcmcli_strhex(string_val, value->buffer.start, value->buffer.len);
if (n < 0)
return n;
bcmolt_buf_skip(&value->buffer, n);
return BCM_ERR_OK;
}
static const char *_bcmcli_qualified_name(bcmcli_entry *token, char *buffer, int size )
{
bcmcli_entry *parent = token->parent;
char qual_name[BCMCLI_MAX_QUAL_NAME_LENGTH];
*buffer=0;
while (parent)
{
bcmcli_strncpy(qual_name, parent->name, sizeof(qual_name));
if (parent->parent)
bcmcli_strncat(qual_name, "/", sizeof(qual_name));
bcmcli_strncat(qual_name, buffer, sizeof(qual_name));
bcmcli_strncpy(buffer, qual_name, size);
parent = parent->parent;
}
size -= strlen(buffer);
bcmcli_strncat(buffer, token->name, size);
return buffer;
}
/*
* Exports
*/
EXPORT_SYMBOL(bcmcli_dir_add);
EXPORT_SYMBOL(bcmcli_dir_find);
EXPORT_SYMBOL(bcmcli_token_name);
EXPORT_SYMBOL(bcmcli_cmd_add);
EXPORT_SYMBOL(bcmcli_session_open);
EXPORT_SYMBOL(bcmcli_session_close);
EXPORT_SYMBOL(bcmcli_parse);
EXPORT_SYMBOL(bcmcli_stop);
EXPORT_SYMBOL(bcmcli_is_stopped);
EXPORT_SYMBOL(bcmcli_dir_get);
EXPORT_SYMBOL(bcmcli_dir_set);
EXPORT_SYMBOL(bcmcli_parm_number);
EXPORT_SYMBOL(bcmcli_parm_is_set);
EXPORT_SYMBOL(bcmcli_enum_parm_stringval);
EXPORT_SYMBOL(bcmcli_token_destroy);
EXPORT_SYMBOL(bcmcli_enum_bool_table);